| /* |
| * 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; |
| } |
| } |