/*
* Copyright 2013 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.juddi.v3.client.cryptor;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CRLException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorResult;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXCertPathValidatorResult;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CRL;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import javax.security.auth.x500.X500Principal;
import javax.xml.bind.JAXB;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import sun.security.provider.certpath.CertId;
import sun.security.provider.certpath.OCSP;
import sun.security.provider.certpath.OCSP.RevocationStatus;
import static sun.security.provider.certpath.OCSP.getResponderURI;
import sun.security.x509.X509CertImpl;
/**
* A utility class for signing and verifying JAXB Objects, such as UDDI
* entities.
*
* Notes: This class only supports elements that are signed once. Multiple
* signature are not currently supported.
*
* @author <a href="mailto:alexoree@apache.org">Alex O'Ree </a>
*/
public class DigSigUtil {
public final static String SIGNATURE_KEYSTORE_KEY_PASSWORD_PROVIDER="signatureKeystoreKeyPassENCProvider";
public final static String SIGNATURE_KEYSTORE_KEY_PASSWORD_WAS_ENC="signatureKeystoreKeyPassENC";
public final static String SIGNATURE_KEYSTORE_KEY_PASSWORD_CIPHER="signatureKeyStoreCipherPass";
public final static String SIGNATURE_KEYSTORE_FILE_PASSWORD_WASENC="signatureKeystoreFilePassENC";
public final static String SIGNATURE_KEYSTORE_FILE_PASSWORD_PROVIDER="signatureKeystoreFileENCProvider";
public final static String TRUSTSTORE_FILE_PASSWORD_WASENC="truststoreFilePassENC";
public final static String TRUSTSTORE_FILE_PASSWORD_PROVIDER="truststoreFilePassENCProvider";
public final static String SIGNATURE_KEYSTORE_FILE_PASSWORD_CIPHER="signatureKeystoreFileKeyPass";
public final static String TRUSTSTORE_FILE_PASSWORD_CIPHER="truststoreFilePass";
/**
* Expects a properties object containing the desired configuration
*
* @param config
* @throws CertificateException
*/
public DigSigUtil(Properties config) throws CertificateException { cf = CertificateFactory.getInstance("X.509"); this.map = config; }
/**
* Creates a new instance of the digital signature utility with no configuration options set.
* @throws CertificateException
*/
public DigSigUtil() throws CertificateException { cf = CertificateFactory.getInstance("X.509"); } private Log logger = LogFactory.getLog(this.getClass());
public void put(String key, String value) {
map.put(key, value); }
/**
* clears the configuration for reuse
*/
public void clear() {
map.clear(); } private Properties map = new Properties();
/**
* This is the location of the keystore
*
* If referencing a Windows certificate store, use WINDOWS-MY as a value
* with a null password
*/
public final static String SIGNATURE_KEYSTORE_FILE = "keyStorePath";
/**
* The type of file, such as JKS for most Java applications, or
* WINDOWS-MY to use the Windows certificate store of the current user
* or KeychainStore for MacOS
*/
public final static String SIGNATURE_KEYSTORE_FILETYPE = "keyStoreType";
public final static String SIGNATURE_KEYSTORE_FILE_PASSWORD = "filePassword";
public final static String SIGNATURE_KEYSTORE_KEY_PASSWORD = "keyPassword";
public final static String SIGNATURE_KEYSTORE_KEY_ALIAS = "keyAlias";
/**
*
* trust loaded as follows
* system property via file
* programmatically specified map via file
* programmatically specified map thread classloader lookup
* programmatically specified map this class's classloader lookup
* windows trust store
* JDK provided trust store
*/
public final static String TRUSTSTORE_FILE = "trustStorePath";
/**
*
* trust loaded as follows
* system property via file
* programmatically specified map via file
* programmatically specified map thread classloader lookup
* programmatically specified map this class's classloader lookup
* windows trust store
* JDK provided trust store
*/
public final static String TRUSTSTORE_FILETYPE = "trustStoreType";
/**
*
* trust loaded as follows
* system property via file
* programmatically specified map via file
* programmatically specified map thread classloader lookup
* programmatically specified map this class's classloader lookup
* windows trust store
* JDK provided trust store
*/
public final static String TRUSTSTORE_FILE_PASSWORD = "trustStorePassword";
/**
* default is CanonicalizationMethod.EXCLUSIVE
* http://www.w3.org/2001/10/xml-exc-c14n#
*
* @see CanonicalizationMethod
*/
public final static String CANONICALIZATIONMETHOD = "CanonicalizationMethod";
/**
* default is http://www.w3.org/2000/09/xmldsig#rsa-sha1
*
* @see SignatureMethod
*/
public final static String SIGNATURE_METHOD = "SignatureMethod";
/**
* Defines whether or not a certificate is included with the
* signature<Br>
* Values - Include whole X509 Public Key in the signature (recommended)
* (default) * Example
* <pre>
* Map map = new HashMap();
* map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_BASE64, "true");</pre>
* any value can be used.
*/
public final static String SIGNATURE_OPTION_CERT_INCLUSION_BASE64 = "BASE64";
/**
* Include the signer's serial of the public key and the issuer's
* subject name
*
* Clients will not be able to validate the signature unless they have a
* copy of the signer's public key in a trust store or the full
* certificate is included out of band
*
* Example
* <pre>
* Map map = new HashMap();
* map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_SERIAL, "true");</pre>
* any value can be used.
* see {@link #SIGNATURE_OPTION_CERT_INCLUSION_BASE64 SIGNATURE_OPTION_CERT_INCLUSION_BASE64}
*/
public final static String SIGNATURE_OPTION_CERT_INCLUSION_SERIAL = "SERIAL";
/**
* Include the signer's Subject DN of the public key.
*
* Clients will not be able to validate the signature unless they have a
* copy of the signer's public key in a trust store or the full
* certificate is included out of band
*
* Example
* <pre>
* Map map = new HashMap();
* map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_SUBJECTDN, "true");</pre>
* any value can be used.
*
* see {@link #SIGNATURE_OPTION_CERT_INCLUSION_BASE64 SIGNATURE_OPTION_CERT_INCLUSION_BASE64}
*/
public final static String SIGNATURE_OPTION_CERT_INCLUSION_SUBJECTDN = "SUBJECTDN";
/*
* Include the signer's X500 Prinicple of the public key.
*
* Clients will not be able to validate the signature unless they have a
* copy of the signer's public key in a trust store or the full certificate
* is included out of band
*
* Example
* <pre>
* Map map = new HashMap();
* map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_X500_PRINICPAL, "true");</pre>
* any value can be used.
*
* @see SIGNATURE_OPTION_CERT_INCLUSION_BASE64
*/
//public final static String SIGNATURE_OPTION_CERT_INCLUSION_X500_PRINICPAL = "X500";
/**
* This is the namespace of the digital signature.
*/
public final static String XML_DIGSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
/**
* Default value DigestMethod.SHA1 =
* "http://www.w3.org/2000/09/xmldsig#sha1"
*
* @see javax.xml.crypto.dsig.DigestMethod
*/
public final static String SIGNATURE_OPTION_DIGEST_METHOD = "digestMethod";
/**
* When validating a signature, include this field will validate that
* the signature is still valid with regards to timestamps NotBefore and
* OnOrAfter
*
* Example
* <pre>
* Map map = new HashMap();
* map.put(DigSigUtil.CHECK_TIMESTAMPS, true);</pre> any value can be
* used.
*/
public final static String CHECK_TIMESTAMPS = "checkTimestamps";
private CertificateFactory cf = null;
public final static String CHECK_REVOCATION_STATUS_OCSP = "checkRevocationOCSP";
public final static String CHECK_REVOCATION_STATUS_CRL = "checkRevocationCRL";
public final static String CHECK_TRUST_CHAIN = "checkTrust";
/**
* Digital signs a UDDI entity, such as a business, service, tmodel or
* binding template using the map to provide certificate key stores and
* credentials<br><br> The UDDI entity MUST support XML Digital
* Signatures (tModel, Business, Service, Binding Template)
*
* @param <T> Any UDDI entity that supports digital signatures
* @param jaxbObj
* @return an enveloped signed UDDI element, do not modify this object
* after signing
*/
public <T> T signUddiEntity(T jaxbObj) {
DOMResult domResult = new DOMResult(); JAXB.marshal(jaxbObj, domResult); Document doc = ((Document) domResult.getNode()); Element docElement = doc.getDocumentElement();
try {
KeyStore ks = KeyStore.getInstance(map.getProperty(SIGNATURE_KEYSTORE_FILETYPE)); URL url = Thread.currentThread().getContextClassLoader().getResource(map.getProperty(SIGNATURE_KEYSTORE_FILE)); if (url == null) {
try {
url = new File(map.getProperty(SIGNATURE_KEYSTORE_FILE)).toURI().toURL(); } catch (Exception x) { }
}
if (url == null) {
try {
url = this.getClass().getClassLoader().getResource(map.getProperty(SIGNATURE_KEYSTORE_FILE)); } catch (Exception x) { }
}
KeyStore.PrivateKeyEntry keyEntry = null; if (!map.getProperty(SIGNATURE_KEYSTORE_FILETYPE).equalsIgnoreCase("WINDOWS-MY")) { ks.load(url.openStream(), (map.getProperty(SIGNATURE_KEYSTORE_FILE_PASSWORD)).toCharArray()); if (map.getProperty(SIGNATURE_KEYSTORE_KEY_PASSWORD) == null) { keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS), new KeyStore.PasswordProtection(map.getProperty(SIGNATURE_KEYSTORE_FILE_PASSWORD).toCharArray()));
} else {
keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS), new KeyStore.PasswordProtection(map.getProperty(SIGNATURE_KEYSTORE_KEY_PASSWORD).toCharArray()));
}
} else {
//Windows only
ks.load(null, null); keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS),
null);
}
PrivateKey privateKey = keyEntry.getPrivateKey(); Certificate origCert = keyEntry.getCertificate();
//PublicKey validatingKey = origCert.getPublicKey();
this.signDOM(docElement, privateKey, origCert); DOMSource domSource = new DOMSource(doc); T result = (T) JAXB.unmarshal(domSource, jaxbObj.getClass()); return result; } catch (Exception e) { throw new RuntimeException("Signature failure due to: " + e.getMessage(), e);
}
}
/**
* Digitally signs a UDDI entity, such as a business, service, tmodel or
* binding template, provided you've already done the legwork to provide
* the signing keys <br><br> The UDDI entity MUST support XML Digital
* Signatures (tModel, Business, Service, Binding Template)
*
* @param <T>
* @param jaxbObj
* @param publicKey
* @param privateKey
* @return a signed entity
*/
public <T> T signUddiEntity(T jaxbObj, Certificate publicKey, PrivateKey privateKey) {
DOMResult domResult = new DOMResult(); JAXB.marshal(jaxbObj, domResult); Document doc = ((Document) domResult.getNode()); Element docElement = doc.getDocumentElement();
try {
//PublicKey validatingKey = origCert.getPublicKey();
this.signDOM(docElement, privateKey, publicKey); DOMSource domSource = new DOMSource(doc); T result = (T) JAXB.unmarshal(domSource, jaxbObj.getClass()); return result; } catch (Exception e) { throw new RuntimeException("Signature failure due to: " + e.getMessage(), e);
}
}
/**
* Serializes a JAXB object and prints to stdout
*
* @param obj
*/
public static void JAXB_ToStdOut(Object obj) {
StringWriter sw = new StringWriter(); JAXB.marshal(obj, sw); System.out.println(sw.toString()); }
/**
* Serializes a JAXB object and prints to stdout
*
* @param obj
* @return serialized text
*/
public static String JAXB_ToString(Object obj) {
StringWriter sw = new StringWriter(); JAXB.marshal(obj, sw); return (sw.toString());
}
/**
*
* returns the public key of the signing certificate used for a signed
* JAXB object.
*
* @param obj
* @return null if the item is not signed or if it references a
* certificate that is not present in the current keystore
* @throws IllegalArgumentException for null input
* @throws java.security.cert.CertificateException
*/
public X509Certificate getSigningCertificatePublicKey(Object obj) throws IllegalArgumentException, CertificateException {
DOMResult domResult = new DOMResult(); JAXB.marshal(obj, domResult); Document doc = ((Document) domResult.getNode()); Element docElement = doc.getDocumentElement(); //this is our signed node return getSigningCertificatePublicKey(docElement);
}
/**
*
* returns the public key of the signing certificate used for a signed
* JAXB object.
*
* @param obj
* @return null if the item is not signed or if it references a
* certificate that is not present in the current keystore
* * @throws IllegalArgumentException for null input
*/
private X509Certificate getSigningCertificatePublicKey(Element docElement) throws IllegalArgumentException, CertificateException {
if (docElement == null) { throw new IllegalArgumentException();
}
NodeList childNodes = docElement.getChildNodes(); //children, one of these SHOULD be our signature element
// X509Certificate signingcert = null;
for (int i = 0; i < childNodes.getLength(); i++) {
//System.out.println(childNodes.item(i).getNamespaceURI() + " " + childNodes.item(i).getNodeName());
if (childNodes.item(i).getNamespaceURI().equalsIgnoreCase(XML_DIGSIG_NS) && childNodes.item(i).getLocalName().equalsIgnoreCase("Signature")) { Node sig = childNodes.item(i); for (int k = 0; k < sig.getChildNodes().getLength(); k++) {
// System.out.println(sig.getChildNodes().item(k).getNamespaceURI() + " " + sig.getChildNodes().item(k).getNodeName());
if ("KeyInfo".equalsIgnoreCase(sig.getChildNodes().item(k).getLocalName())) {
//TODO figure out how to reference Subject DN, serial, thumbprint, etc
for (int j = 0; j < sig.getChildNodes().item(k).getChildNodes().getLength(); j++) { if ("X509Data".equalsIgnoreCase(sig.getChildNodes().item(k).getChildNodes().item(j).getLocalName())) { Node X509Data = sig.getChildNodes().item(k).getChildNodes().item(j); for (int x = 0; x < X509Data.getChildNodes().getLength(); x++) { if ("X509Certificate".equalsIgnoreCase(X509Data.getChildNodes().item(x).getLocalName())) {
//yay found it!
String c
= "-----BEGIN CERTIFICATE-----\n"
+ X509Data.getChildNodes().item(x).getTextContent()
+ "\n-----END CERTIFICATE-----";
//System.out.println("X509 Public key: " + c);
InputStream is = new ByteArrayInputStream(c.getBytes()); X509Certificate cert = (X509Certificate) cf.generateCertificate(is); logger.info("embedded certificate found, X509 public key " + cert.getSubjectDN().toString()); return cert;
}
//if we have a
//TODO other parsing items, lots of other potentials here
}
X509Certificate cert = FindCert(X509Data.getChildNodes()); if (cert != null) { logger.info("certificate loaded from local trust store, X509 public key " + cert.getSubjectDN().toString()); return cert;
}
}
}
break;
}
}
break;
}
}
return null;
}
/**
* wrapper to overcome JDK differences between oracle vs openjdk
*/
public static RevocationStatus check(X509Certificate cert,
X509Certificate issuerCert)
throws IOException, CertPathValidatorException, CertificateException {
CertId certId = null; URI responderURI = null; X509CertImpl certImpl = X509CertImpl.toImpl(cert); responderURI = getResponderURI(certImpl); if (responderURI == null) { throw new CertPathValidatorException
("No OCSP Responder URI in certificate");
}
return OCSP.check(cert, issuerCert, responderURI, cert, null);
}
/**
* Verifies the signature on an enveloped digital signature on a UDDI
* entity, such as a business, service, tmodel or binding template.
* <br><Br>
* It is expected that either the public key of the signing certificate
* is included within the signature keyinfo section OR that sufficient
* information is provided in the signature to reference a public key
* located within the Trust Store provided<br><Br> Optionally, this
* function also validate the signing certificate using the options
* provided to the configuration map.
*
* @param obj an enveloped signed JAXB object
* @param OutErrorMessage a human readable error message explaining the
* reason for failure
* @return true if the validation passes the signature validation test,
* and optionally any certificate validation or trust chain validation
* @throws IllegalArgumentException for null input
*/
public boolean verifySignedUddiEntity(Object obj, AtomicReference<String> OutErrorMessage) throws IllegalArgumentException {
if (OutErrorMessage == null) { OutErrorMessage = new AtomicReference<String>(); OutErrorMessage.set("");
}
if (obj == null) { throw new IllegalArgumentException("obj");
}
try {
DOMResult domResult = new DOMResult(); JAXB.marshal(obj, domResult); Document doc = ((Document) domResult.getNode()); Element docElement = doc.getDocumentElement(); //this is our signed node X509Certificate signingcert = getSigningCertificatePublicKey(docElement); if (signingcert != null) { logger.info("verifying signature based on X509 public key " + signingcert.getSubjectDN().toString()); if (map.containsKey(CHECK_TIMESTAMPS) && Boolean.parseBoolean(map.getProperty(CHECK_TIMESTAMPS))) { signingcert.checkValidity();
}
if (map.containsKey(CHECK_REVOCATION_STATUS_OCSP) && Boolean.parseBoolean(map.getProperty(CHECK_REVOCATION_STATUS_OCSP))) { logger.info("verifying revocation status via OSCP for X509 public key " + signingcert.getSubjectDN().toString()); X500Principal issuerX500Principal = signingcert.getIssuerX500Principal(); logger.info("certificate " + signingcert.getSubjectDN().toString() + " was issued by " + issuerX500Principal.getName() + ", attempting to retrieve certificate"); Security.setProperty("ocsp.enable", "false"); X509Certificate issuer = FindCertByDN(issuerX500Principal); if (issuer == null) { OutErrorMessage.set("Unable to verify certificate status from OCSP because the issuer of the certificate is not in the trust store. " + OutErrorMessage.get());
} else {
RevocationStatus check = check(signingcert, issuer); logger.info("certificate " + signingcert.getSubjectDN().toString() + " revocation status is " + check.getCertStatus().toString() + " reason " + check.getRevocationReason().toString()); if (check.getCertStatus() != RevocationStatus.CertStatus.GOOD) { OutErrorMessage.set("Certificate status is " + check.getCertStatus().toString() + " reason " + check.getRevocationReason().toString() + "." + OutErrorMessage.get());
}
}
}
if (map.containsKey(CHECK_REVOCATION_STATUS_CRL) && Boolean.parseBoolean(map.getProperty(CHECK_REVOCATION_STATUS_CRL))) { logger.info("verifying revokation status via CRL for X509 public key " + signingcert.getSubjectDN().toString()); Security.setProperty("ocsp.enable", "false"); System.setProperty("com.sun.security.enableCRLDP", "true"); X509CertSelector targetConstraints = new X509CertSelector(); targetConstraints.setCertificate(signingcert); PKIXParameters params = new PKIXParameters(GetTrustStore()); params.setRevocationEnabled(true); CertPath certPath = cf.generateCertPath(Arrays.asList(signingcert)); CertPathValidator certPathValidator = CertPathValidator.getInstance(CertPathValidator.getDefaultType()); CertPathValidatorResult result = certPathValidator.validate(certPath, params);
try {
PKIXCertPathValidatorResult pkixResult = (PKIXCertPathValidatorResult) result; logger.info("revokation status via CRL PASSED for X509 public key " + signingcert.getSubjectDN().toString() + " " + pkixResult.toString()); } catch (Exception ex) { OutErrorMessage.set("Certificate status is via CRL Failed: " + ex.getMessage() + "." + OutErrorMessage.get()); }
}
if (map.containsKey(CHECK_TRUST_CHAIN) && Boolean.parseBoolean(map.getProperty(CHECK_TRUST_CHAIN))) { logger.info("verifying trust chain X509 public key " + signingcert.getSubjectDN().toString());
try {
PKIXParameters params = new PKIXParameters(GetTrustStore()); params.setRevocationEnabled(false); CertPath certPath = cf.generateCertPath(Arrays.asList(signingcert)); CertPathValidator certPathValidator = CertPathValidator.getInstance(CertPathValidator.getDefaultType()); CertPathValidatorResult result = certPathValidator.validate(certPath, params); PKIXCertPathValidatorResult pkixResult = (PKIXCertPathValidatorResult) result; TrustAnchor ta = pkixResult.getTrustAnchor(); X509Certificate cert = ta.getTrustedCert(); logger.info("trust chain validated X509 public key " + signingcert.getSubjectDN().toString() + " issued by " + cert.getPublicKey().toString()); } catch (Exception ex) { OutErrorMessage.set("Certificate status Trust validation failed: " + ex.getMessage() + "." + OutErrorMessage.get()); }
}
boolean b = verifySignature(docElement, signingcert.getPublicKey(), OutErrorMessage); if ((OutErrorMessage.get() == null || OutErrorMessage.get().length() == 0) && b) {
//no error message and its cryptographically valid
return true;
}
return false;
}
//last chance validation
logger.info("signature did not have an embedded X509 public key. reverting to user specified certificate");
//cert wasn't included in the signature, revert to some other means
KeyStore ks = KeyStore.getInstance(map.getProperty(SIGNATURE_KEYSTORE_FILETYPE)); URL url = Thread.currentThread().getContextClassLoader().getResource(map.getProperty(SIGNATURE_KEYSTORE_FILE)); if (url == null) {
try {
url = new File(map.getProperty(SIGNATURE_KEYSTORE_FILE)).toURI().toURL(); } catch (Exception x) { }
}
if (url == null) {
try {
url = this.getClass().getClassLoader().getResource(map.getProperty(SIGNATURE_KEYSTORE_FILE)); } catch (Exception x) { }
}
if (url == null) { logger.error(""); OutErrorMessage.set("The signed entity is signed but does not have a certificate attached and" + "you didn't specify a keystore for me to look it up in. " + OutErrorMessage.get()); return false;
}
KeyStore.PrivateKeyEntry keyEntry = null; ks.load(url.openStream(), map.getProperty(SIGNATURE_KEYSTORE_FILE_PASSWORD).toCharArray()); if (map.getProperty(SIGNATURE_KEYSTORE_KEY_PASSWORD) == null) { keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS), new KeyStore.PasswordProtection(map.getProperty(SIGNATURE_KEYSTORE_FILE_PASSWORD).toCharArray()));
} else {
keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS), new KeyStore.PasswordProtection(map.getProperty(SIGNATURE_KEYSTORE_KEY_PASSWORD).toCharArray()));
}
Certificate origCert = keyEntry.getCertificate(); if (map.containsKey(CHECK_TIMESTAMPS)) { if (origCert.getPublicKey() instanceof X509Certificate) { X509Certificate x = (X509Certificate) origCert.getPublicKey(); x.checkValidity();
}
}
PublicKey validatingKey = origCert.getPublicKey(); return verifySignature(docElement, validatingKey, OutErrorMessage); } catch (Exception e) {
//throw new RuntimeException(e);
logger.error("Error caught validating signature", e); OutErrorMessage.set(e.getMessage()); return false;
}
}
/**
* trust loaded as follows
* system property via file
* programmatically specified map via file
* programmatically specified map thread classloader lookup
* programmatically specified map this class's classloader lookup
* windows trust store
* JDK provided trust store
* @return
* @throws Exception
*/
private KeyStore GetTrustStore() throws Exception {
String type = map.getProperty(TRUSTSTORE_FILETYPE); if (type == null) { type = "JKS";
}
KeyStore ks = KeyStore.getInstance(type); boolean ksLoaded = false; if (!ksLoaded) { String truststore = System.getProperty("javax.net.ssl.keyStore");
try {
String pwd = System.getProperty("javax.net.ssl.keyStorePassword"); if (truststore != null && pwd != null) { ks.load(new File(truststore).toURI().toURL().openStream(), pwd.toCharArray()); ksLoaded = true; logger.info("trust store loaded from sysprop " + truststore);
}
} catch (Exception ex) { logger.warn("unable to load truststore from sysprop " + truststore + " " + ex.getMessage()); logger.debug("unable to load truststore from sysprop " + ex.getMessage(),ex); }
}
File f=new File(map.getProperty(TRUSTSTORE_FILE));
//load as a file
if (!ksLoaded) {
try {
if (f.exists()){ URL url = f.toURI().toURL(); ks.load(url.openStream(), (map.getProperty(TRUSTSTORE_FILE_PASSWORD)).toCharArray()); ksLoaded = true; logger.info("trust store loaded from file " + map.getProperty(TRUSTSTORE_FILE));
}
} catch (Exception x) { logger.warn("unable to load truststore from file "+map.getProperty(TRUSTSTORE_FILE)+" "+ x.getMessage()); logger.debug("unable to load truststore from file "+ x.getMessage(), x); }
}
if (!ksLoaded) { FileInputStream fis=null;
try {
//File f = new File(map.getProperty(TRUSTSTORE_FILE));
if (f.exists())
{
fis = new FileInputStream(f); ks.load(fis, (map.getProperty(TRUSTSTORE_FILE_PASSWORD)).toCharArray()); fis.close(); ksLoaded = true; logger.info("trust store loaded from file " + map.getProperty(TRUSTSTORE_FILE));
}
} catch (Exception x) { logger.warn("unable to load truststore from file "+map.getProperty(TRUSTSTORE_FILE)+" "+ x.getMessage()); logger.debug("unable to load truststore from file "+ x.getMessage(), x);
}
finally {
if (fis!=null) fis.close();
}
}
//load from thread classloader
if (!ksLoaded) {
try {
URL url = Thread.currentThread().getContextClassLoader().getResource(map.getProperty(TRUSTSTORE_FILE)); ks.load(url.openStream(), (map.getProperty(TRUSTSTORE_FILE_PASSWORD)).toCharArray()); ksLoaded = true; logger.info("trust store loaded from classpath(1) " + map.getProperty(TRUSTSTORE_FILE)); } catch (Exception x) { logger.warn("unable to load truststore from classpath" + map.getProperty(TRUSTSTORE_FILE) + " " +x.getMessage()); logger.debug("unable to load truststore from classpath", x); }
}
//load from this classloader
if (!ksLoaded) {
try {
URL url = this.getClass().getClassLoader().getResource(map.getProperty(TRUSTSTORE_FILE)); ks.load(url.openStream(), (map.getProperty(TRUSTSTORE_FILE_PASSWORD)).toCharArray()); ksLoaded = true; logger.info("trust store loaded from classpath(2) " + map.getProperty(TRUSTSTORE_FILE)); } catch (Exception x) { logger.warn("unable to load truststore from classpath "+ map.getProperty(TRUSTSTORE_FILE) + " " +x.getMessage()); logger.debug("unable to load truststore from classpath", x); }
}
if (!ksLoaded) {
try {
URL cacerts = new File(System.getenv("JAVA_HOME") + File.separator + "lib" + File.separator + "security" + File.separator + "cacerts").toURI().toURL(); ks.load(cacerts.openStream(), "changeit".toCharArray()); logger.info("trust store loaded from JRE " + cacerts.toExternalForm()); ksLoaded = true; } catch (Exception c) { logger.warn("unable to load default JDK truststore "+ c.getMessage()); logger.debug("unable to load default JDK truststore",c); }
}
//try windows trust store first
try {
if (map.getProperty(TRUSTSTORE_FILETYPE).equalsIgnoreCase("WINDOWS-ROOT")) { ks.load(null, null); ksLoaded = true; logger.info("trust store loaded from windows");
}
} catch (Exception ex) { logger.warn("unable to load truststore from windows " +ex.getMessage()); logger.debug("unable to load truststore from windows", ex); } if (!ksLoaded) {
try {
URL cacerts = new File(System.getenv("JAVA_HOME") + File.separator + "jre" + File.separator + "lib" + File.separator + "security" + File.separator + "cacerts").toURI().toURL(); ks.load(cacerts.openStream(), "changeit".toCharArray()); logger.info("trust store loaded from JRE " + cacerts.toExternalForm()); ksLoaded = true; } catch (Exception c) { logger.warn("unable to load default jdk/jre truststore " +c.getMessage()); logger.debug("unable to load default jdk/jre truststore", c); }
}
if (!ksLoaded) { logger.warn("unable to load trust store!");
}
return ks;
}
private XMLSignatureFactory initXMLSigFactory() {
XMLSignatureFactory fac = XMLSignatureFactory.getInstance(); return fac;
}
private Reference initReference(XMLSignatureFactory fac) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
List transformers = new ArrayList(); transformers.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)); String dm = map.getProperty(SIGNATURE_OPTION_DIGEST_METHOD); if (dm == null) { dm = DigestMethod.SHA1;
}
Reference ref = fac.newReference("", fac.newDigestMethod(dm, null), transformers, null, null); return ref;
}
private SignedInfo initSignedInfo(XMLSignatureFactory fac) throws Exception {
Reference ref = initReference(fac); String cm = null; cm = map.getProperty(CANONICALIZATIONMETHOD); String sigmethod = null; sigmethod = map.getProperty(SIGNATURE_METHOD); if (sigmethod == null) { sigmethod = SignatureMethod.RSA_SHA1;
}
if (cm == null) { cm = CanonicalizationMethod.EXCLUSIVE;
}
SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(
cm,
(C14NMethodParameterSpec) null),
fac.newSignatureMethod(sigmethod, null), Collections.singletonList(ref)); return si;
}
private boolean verifySignature(Element element, PublicKey validatingKey, AtomicReference<String> OutReadableErrorMessage) {
if (OutReadableErrorMessage == null) { OutReadableErrorMessage = new AtomicReference<String>();
}
XMLSignatureFactory fac = initXMLSigFactory(); NodeList nl = element.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); if (nl.getLength() == 0) { throw new RuntimeException("Cannot find Signature element");
}
DOMValidateContext valContext = new DOMValidateContext(validatingKey, nl.item(0));
try {
valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE); XMLSignature signature = fac.unmarshalXMLSignature(valContext); boolean coreValidity = signature.validate(valContext);
// Check core validation status.
if (coreValidity == false) { logger.warn("Signature failed core validation"); boolean sv = signature.getSignatureValue().validate(valContext); logger.debug("signature validation status: " + sv); OutReadableErrorMessage.set("signature validation failed: " + sv + "." + OutReadableErrorMessage.get());
// Check the validation status of each Reference.
@SuppressWarnings("unchecked")
Iterator<Reference> i = signature.getSignedInfo().getReferences().iterator();
//System.out.println("---------------------------------------------");
for (int j = 0; i.hasNext(); j++) { Reference ref = (Reference) i.next(); boolean refValid = ref.validate(valContext); logger.debug(j); logger.debug("ref[" + j + "] validity status: " + refValid); if (!refValid) { OutReadableErrorMessage.set("signature reference " + j + " invalid. " + OutReadableErrorMessage.get());
}
logger.debug("Ref type: " + ref.getType() + ", URI: " + ref.getURI()); for (Object xform : ref.getTransforms()) { logger.debug("Transform: " + xform); } String calcDigValStr = digestToString(ref.getCalculatedDigestValue()); String expectedDigValStr = digestToString(ref.getDigestValue()); logger.warn(" Calc Digest: " + calcDigValStr); logger.warn("Expected Digest: " + expectedDigValStr); if (!calcDigValStr.equalsIgnoreCase(expectedDigValStr)) { OutReadableErrorMessage.set("digest mismatch for signature ref " + j + "." + OutReadableErrorMessage.get());
}
}
} else { logger.info("Signature passed core validation");
}
return coreValidity; } catch (Exception e) { OutReadableErrorMessage.set("signature validation failed: " + e.getMessage() + OutReadableErrorMessage.get()); logger.fatal(e); return false;
}
}
private String digestToString(byte[] digest) {
StringBuilder sb = new StringBuilder(); for (byte b : digest) { String hex = Integer.toHexString(0xFF & b); if (hex.length() == 1) { sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
private void signDOM(Node node, PrivateKey privateKey, Certificate origCert) {
XMLSignatureFactory fac = initXMLSigFactory(); X509Certificate cert = (X509Certificate) origCert;
// Create the KeyInfo containing the X509Data.
KeyInfoFactory kif = fac.getKeyInfoFactory(); List<Object> x509Content = null;//new ArrayList<Object>(); List<X509Data> data = new ArrayList<X509Data>(); if (map.containsKey(SIGNATURE_OPTION_CERT_INCLUSION_SUBJECTDN)) { x509Content = new ArrayList<Object>(); x509Content.add(cert.getSubjectDN().getName());
// x509Content.add(cert);
//x509Content.add(cert.getSubjectDN().getName());
X509Data xd = kif.newX509Data(x509Content); data.add(xd);
}
// if (map.containsKey(SIGNATURE_OPTION_CERT_INCLUSION_X500_PRINICPAL)) {
// }
if (map.containsKey(SIGNATURE_OPTION_CERT_INCLUSION_BASE64)) { x509Content = new ArrayList<Object>(); x509Content.add(cert);
//x509Content.add(cert.getSubjectX500Principal().getName());
X509Data xd = kif.newX509Data(x509Content); data.add(xd);
}
if (map.containsKey(SIGNATURE_OPTION_CERT_INCLUSION_SERIAL)) { x509Content = new ArrayList<Object>(); X509IssuerSerial issuer = kif.newX509IssuerSerial(cert.getIssuerX500Principal().getName(), cert.getSerialNumber()); x509Content.add(issuer); X509Data xd = kif.newX509Data(x509Content); data.add(xd);
}
//
//x509Content.add(cert);
KeyInfo ki = kif.newKeyInfo(data);
// Create a DOMSignContext and specify the RSA PrivateKey and
// location of the resulting XMLSignature's parent element.
DOMSignContext dsc = new DOMSignContext(privateKey, node); dsc.putNamespacePrefix(XML_DIGSIG_NS, "ns2");
// Create the XMLSignature, but don't sign it yet.
try {
SignedInfo si = initSignedInfo(fac); XMLSignature signature = fac.newXMLSignature(si, ki);
// Marshal, generate, and sign the enveloped signature.
signature.sign(dsc); } catch (Exception e) { throw new RuntimeException(e); } }
/**
* searches local keystores for a referenced signing certificate
*
* @param childNodes
* @return null or the public key of a signing certificate
*/
private X509Certificate FindCert(NodeList childNodes) {
try {
for (int x = 0; x < childNodes.getLength(); x++) { if (childNodes.item(x).getLocalName().equalsIgnoreCase("X509SubjectName")) { String dn = childNodes.item(x).getTextContent().trim(); return FindCertByDN(new X500Principal(dn));
}
if (childNodes.item(x).getLocalName().equalsIgnoreCase("X509IssuerSerial")) { String X509IssuerName = null; String X509SerialNumber = null; for (int k = 0; k < childNodes.item(x).getChildNodes().getLength(); k++) { if (childNodes.item(x).getChildNodes().item(x).getLocalName().equalsIgnoreCase("X509IssuerName")) { X509IssuerName = childNodes.item(x).getTextContent().trim();
}
if (childNodes.item(x).getChildNodes().item(x).getLocalName().equalsIgnoreCase("X509SerialNumber")) { X509SerialNumber = childNodes.item(x).getTextContent().trim();
}
}
if (X509IssuerName != null && X509SerialNumber != null) { return FindCertByIssuer(X509IssuerName, X509SerialNumber);
}
}
}
} catch (Exception ex) { logger.warn("error caught searching for a certificate", ex); } return null;
}
private X509Certificate FindCertByDN(X500Principal name) throws Exception {
KeyStore ks = GetTrustStore(); if (ks == null) { return null;
}
Enumeration<String> aliases = ks.aliases(); while (aliases.hasMoreElements()) { String nextElement = aliases.nextElement(); Certificate certificate = ks.getCertificate(nextElement); X509Certificate x = (X509Certificate) certificate; if (x.getSubjectX500Principal().equals(name)) { return x;
}
} return null;
}
/**
* Downloads a CRL from given HTTP/HTTPS/FTP URL, e.g.
* http://crl.infonotary.com/crl/identity-ca.crl
*/
private X509CRL downloadCRLFromWeb(String crlURL)
throws MalformedURLException, IOException, CertificateException,
CRLException {
URL url = new URL(crlURL); InputStream crlStream = url.openStream();
try {
// CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509CRL crl = (X509CRL) cf.generateCRL(crlStream); return crl;
} finally {
crlStream.close();
}
}
private X509Certificate FindCertByIssuer(String X509IssuerName, String X509SerialNumber) throws Exception {
KeyStore ks = GetTrustStore(); if (ks == null) { return null;
}
Enumeration<String> aliases = ks.aliases(); while (aliases.hasMoreElements()) { String nextElement = aliases.nextElement(); Certificate certificate = ks.getCertificate(nextElement); X509Certificate x = (X509Certificate) certificate; if (x.getIssuerDN().getName().equals(X509IssuerName) && x.getSerialNumber().toString().equalsIgnoreCase(X509SerialNumber)) { return x;
}
} return null;
}
}