blob: 848d0518702bae15477cac4ecec3160ffb299f6e [file] [log] [blame]
/*
* 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;
}
}