org.apache.juddi.v3.client.cryptor.DigSigUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.juddi.v3.client.cryptor.DigSigUtil.java

Source

/*
 * 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.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.MalformedURLException;
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.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.OCSP;
import sun.security.provider.certpath.OCSP.RevocationStatus;

/**
 * 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 {

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

    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";
    public final static String TRUSTSTORE_FILE = "trustStorePath";
    public final static String TRUSTSTORE_FILETYPE = "trustStoreType";
    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 (sig.getChildNodes().item(k).getLocalName().equalsIgnoreCase("KeyInfo")) {
                        //TODO figure out how to reference Subject DN, serial, thumbprint, etc
                        for (int j = 0; j < sig.getChildNodes().item(k).getChildNodes().getLength(); j++) {
                            if (sig.getChildNodes().item(k).getChildNodes().item(j).getLocalName()
                                    .equalsIgnoreCase("X509Data")) {
                                Node X509Data = sig.getChildNodes().item(k).getChildNodes().item(j);
                                for (int x = 0; x < X509Data.getChildNodes().getLength(); x++) {
                                    if (X509Data.getChildNodes().item(x).getLocalName()
                                            .equalsIgnoreCase("X509Certificate")) {
                                        //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;
    }

    /**
     * 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());
                        //throw new CertificateException("unable to locate the issuers certificate in the trust store");
                    } else {
                        RevocationStatus check = OCSP.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());

                            //throw new CertificateException("Certificate status is " + check.getCertStatus().toString() + " reason " + check.getRevocationReason().toString());
                        }
                    }
                }
                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());
                    } 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());
                    } 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;
        }
    }

    private KeyStore GetTrustStore() throws Exception {
        String type = map.getProperty(TRUSTSTORE_FILETYPE);
        if (type == null) {
            type = "JKS";
        }
        KeyStore ks = KeyStore.getInstance(type);
        boolean ksLoaded = false;

        //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.debug("unable to load truststore from windows", ex);
        }

        //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.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.debug("unable to load truststore from classpath", x);
            }
        }
        //load as a file
        if (!ksLoaded) {
            try {
                URL url = new File(map.getProperty(TRUSTSTORE_FILE)).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.debug("unable to load truststore from file", x);
            }
        }

        //    logger.error("Unable to load user specified trust store! attempting to load the default", ex);
        //load from system property
        if (!ksLoaded) {
            try {
                String truststore = System.getProperty("javax.net.ssl.keyStore");
                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.debug("unable to load truststore from sysprop", ex);
            }
        }

        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.debug("unable to load default JDK truststore", c);
            }
        }
        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.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;
    }
}