View Javadoc
1   /*
2    * Copyright 2013 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.juddi.v3.client.cryptor;
17  
18  import java.io.ByteArrayInputStream;
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.StringWriter;
24  import java.net.MalformedURLException;
25  import java.net.URI;
26  import java.net.URL;
27  import java.security.InvalidAlgorithmParameterException;
28  import java.security.KeyStore;
29  import java.security.NoSuchAlgorithmException;
30  import java.security.PrivateKey;
31  import java.security.PublicKey;
32  import java.security.Security;
33  import java.security.cert.CRLException;
34  import java.security.cert.CertPath;
35  import java.security.cert.CertPathValidator;
36  import java.security.cert.CertPathValidatorException;
37  import java.security.cert.CertPathValidatorResult;
38  import java.security.cert.Certificate;
39  import java.security.cert.CertificateException;
40  import java.security.cert.CertificateFactory;
41  import java.security.cert.PKIXCertPathValidatorResult;
42  import java.security.cert.PKIXParameters;
43  import java.security.cert.TrustAnchor;
44  import java.security.cert.X509CRL;
45  import java.security.cert.X509CertSelector;
46  import java.security.cert.X509Certificate;
47  import java.util.ArrayList;
48  import java.util.Arrays;
49  import java.util.Collections;
50  import java.util.Enumeration;
51  import java.util.Iterator;
52  import java.util.List;
53  import java.util.Properties;
54  import java.util.concurrent.atomic.AtomicReference;
55  import javax.security.auth.x500.X500Principal;
56  import javax.xml.bind.JAXB;
57  import javax.xml.crypto.dsig.CanonicalizationMethod;
58  import javax.xml.crypto.dsig.DigestMethod;
59  import javax.xml.crypto.dsig.Reference;
60  import javax.xml.crypto.dsig.SignatureMethod;
61  import javax.xml.crypto.dsig.SignedInfo;
62  import javax.xml.crypto.dsig.Transform;
63  import javax.xml.crypto.dsig.XMLSignature;
64  import javax.xml.crypto.dsig.XMLSignatureFactory;
65  import javax.xml.crypto.dsig.dom.DOMSignContext;
66  import javax.xml.crypto.dsig.dom.DOMValidateContext;
67  import javax.xml.crypto.dsig.keyinfo.KeyInfo;
68  import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
69  import javax.xml.crypto.dsig.keyinfo.X509Data;
70  import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
71  import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
72  import javax.xml.crypto.dsig.spec.TransformParameterSpec;
73  import javax.xml.transform.dom.DOMResult;
74  import javax.xml.transform.dom.DOMSource;
75  import org.apache.commons.logging.Log;
76  import org.apache.commons.logging.LogFactory;
77  import org.w3c.dom.Document;
78  import org.w3c.dom.Element;
79  import org.w3c.dom.Node;
80  import org.w3c.dom.NodeList;
81  import sun.security.provider.certpath.CertId;
82  import sun.security.provider.certpath.OCSP;
83  import sun.security.provider.certpath.OCSP.RevocationStatus;
84  import static sun.security.provider.certpath.OCSP.getResponderURI;
85  import sun.security.x509.X509CertImpl;
86  
87  
88  /**
89   * A utility class for signing and verifying JAXB Objects, such as UDDI
90   * entities.
91   *
92   * Notes: This class only supports elements that are signed once. Multiple
93   * signature are not currently supported.
94   *
95   * @author <a href="mailto:alexoree@apache.org">Alex O'Ree </a>
96   */
97  public class DigSigUtil {
98          public final static String SIGNATURE_KEYSTORE_KEY_PASSWORD_PROVIDER="signatureKeystoreKeyPassENCProvider";
99          public final static String SIGNATURE_KEYSTORE_KEY_PASSWORD_WAS_ENC="signatureKeystoreKeyPassENC";
100         public final static String SIGNATURE_KEYSTORE_KEY_PASSWORD_CIPHER="signatureKeyStoreCipherPass";
101         
102         public final static String SIGNATURE_KEYSTORE_FILE_PASSWORD_WASENC="signatureKeystoreFilePassENC";
103         public final static String SIGNATURE_KEYSTORE_FILE_PASSWORD_PROVIDER="signatureKeystoreFileENCProvider";
104         public final static String TRUSTSTORE_FILE_PASSWORD_WASENC="truststoreFilePassENC";
105         public final static String TRUSTSTORE_FILE_PASSWORD_PROVIDER="truststoreFilePassENCProvider";
106         public final static String SIGNATURE_KEYSTORE_FILE_PASSWORD_CIPHER="signatureKeystoreFileKeyPass";
107         public final static String TRUSTSTORE_FILE_PASSWORD_CIPHER="truststoreFilePass";
108 
109         /**
110          * Expects a properties object containing the desired configuration
111          *
112          * @param config
113          * @throws CertificateException
114          */
115         public DigSigUtil(Properties config) throws CertificateException {
116                 cf = CertificateFactory.getInstance("X.509");
117                 this.map = config;
118         }
119 
120         /**
121          * Creates a new instance of the digital signature utility with no configuration options set.
122          * @throws CertificateException 
123          */
124         public DigSigUtil() throws CertificateException {
125                 cf = CertificateFactory.getInstance("X.509");
126         }
127         private Log logger = LogFactory.getLog(this.getClass());
128 
129         public void put(String key, String value) {
130                 map.put(key, value);
131         }
132 
133         /**
134          * clears the configuration for reuse
135          */
136         public void clear() {
137                 map.clear();
138         }
139         private Properties map = new Properties();
140         
141         
142         
143         /**
144          * This is the location of the keystore
145          *
146          * If referencing a Windows certificate store, use WINDOWS-MY as a value
147          * with a null password
148          */
149         public final static String SIGNATURE_KEYSTORE_FILE = "keyStorePath";
150         /**
151          * The type of file, such as JKS for most Java applications, or
152          * WINDOWS-MY to use the Windows certificate store of the current user
153          * or KeychainStore for MacOS
154          */
155         public final static String SIGNATURE_KEYSTORE_FILETYPE = "keyStoreType";
156         public final static String SIGNATURE_KEYSTORE_FILE_PASSWORD = "filePassword";
157         public final static String SIGNATURE_KEYSTORE_KEY_PASSWORD = "keyPassword";
158         public final static String SIGNATURE_KEYSTORE_KEY_ALIAS = "keyAlias";
159         /**
160          *
161          * trust loaded as follows
162          * system property via file
163          * programmatically specified map via file
164          * programmatically specified map thread classloader lookup
165          * programmatically specified map this class's classloader lookup
166          * windows trust store
167          * JDK provided trust store
168          */
169         public final static String TRUSTSTORE_FILE = "trustStorePath";
170         /**
171          *
172          * trust loaded as follows
173          * system property via file
174          * programmatically specified map via file
175          * programmatically specified map thread classloader lookup
176          * programmatically specified map this class's classloader lookup
177          * windows trust store
178          * JDK provided trust store
179          */
180         public final static String TRUSTSTORE_FILETYPE = "trustStoreType";
181         /**
182          *
183          * trust loaded as follows
184          * system property via file
185          * programmatically specified map via file
186          * programmatically specified map thread classloader lookup
187          * programmatically specified map this class's classloader lookup
188          * windows trust store
189          * JDK provided trust store
190          */
191         public final static String TRUSTSTORE_FILE_PASSWORD = "trustStorePassword";
192         /**
193          * default is CanonicalizationMethod.EXCLUSIVE
194          * http://www.w3.org/2001/10/xml-exc-c14n#
195          *
196          * @see CanonicalizationMethod
197          */
198         public final static String CANONICALIZATIONMETHOD = "CanonicalizationMethod";
199         /**
200          * default is http://www.w3.org/2000/09/xmldsig#rsa-sha1
201          *
202          * @see SignatureMethod
203          */
204         public final static String SIGNATURE_METHOD = "SignatureMethod";
205         /**
206          * Defines whether or not a certificate is included with the
207          * signature<Br>
208          * Values - Include whole X509 Public Key in the signature (recommended)
209          * (default) * Example
210          * <pre>
211          * Map map = new HashMap();
212          * map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_BASE64, "true");</pre>
213          * any value can be used.
214          */
215         public final static String SIGNATURE_OPTION_CERT_INCLUSION_BASE64 = "BASE64";
216 
217         /**
218          * Include the signer's serial of the public key and the issuer's
219          * subject name
220          *
221          * Clients will not be able to validate the signature unless they have a
222          * copy of the signer's public key in a trust store or the full
223          * certificate is included out of band
224          *
225          * Example
226          * <pre>
227          * Map map = new HashMap();
228          * map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_SERIAL, "true");</pre>
229          * any value can be used.
230          * see {@link #SIGNATURE_OPTION_CERT_INCLUSION_BASE64 SIGNATURE_OPTION_CERT_INCLUSION_BASE64} 
231          */
232         public final static String SIGNATURE_OPTION_CERT_INCLUSION_SERIAL = "SERIAL";
233         /**
234          * Include the signer's Subject DN of the public key.
235          *
236          * Clients will not be able to validate the signature unless they have a
237          * copy of the signer's public key in a trust store or the full
238          * certificate is included out of band
239          *
240          * Example
241          * <pre>
242          * Map map = new HashMap();
243          * map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_SUBJECTDN, "true");</pre>
244          * any value can be used.
245          *
246          * see {@link #SIGNATURE_OPTION_CERT_INCLUSION_BASE64 SIGNATURE_OPTION_CERT_INCLUSION_BASE64} 
247          */
248         public final static String SIGNATURE_OPTION_CERT_INCLUSION_SUBJECTDN = "SUBJECTDN";
249         /*
250          * Include the signer's X500 Prinicple of the public key.
251          *
252          * Clients will not be able to validate the signature unless they have a
253          * copy of the signer's public key in a trust store or the full certificate
254          * is included out of band
255          *
256          * Example
257          * <pre>
258          * Map map = new HashMap();
259          * map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_X500_PRINICPAL, "true");</pre>
260          * any value can be used.
261          *
262          * @see SIGNATURE_OPTION_CERT_INCLUSION_BASE64
263          */
264         //public final static String SIGNATURE_OPTION_CERT_INCLUSION_X500_PRINICPAL = "X500";
265 
266         /**
267          * This is the namespace of the digital signature.
268          */
269         public final static String XML_DIGSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
270         /**
271          * Default value DigestMethod.SHA1 =
272          * "http://www.w3.org/2000/09/xmldsig#sha1"
273          *
274          * @see javax.xml.crypto.dsig.DigestMethod
275          */
276         public final static String SIGNATURE_OPTION_DIGEST_METHOD = "digestMethod";
277         /**
278          * When validating a signature, include this field will validate that
279          * the signature is still valid with regards to timestamps NotBefore and
280          * OnOrAfter
281          *
282          * Example
283          * <pre>
284          * Map map = new HashMap();
285          * map.put(DigSigUtil.CHECK_TIMESTAMPS, true);</pre> any value can be
286          * used.
287          */
288         public final static String CHECK_TIMESTAMPS = "checkTimestamps";
289         private CertificateFactory cf = null;
290         public final static String CHECK_REVOCATION_STATUS_OCSP = "checkRevocationOCSP";
291         public final static String CHECK_REVOCATION_STATUS_CRL = "checkRevocationCRL";
292         public final static String CHECK_TRUST_CHAIN = "checkTrust";
293 
294         /**
295          * Digital signs a UDDI entity, such as a business, service, tmodel or
296          * binding template using the map to provide certificate key stores and
297          * credentials<br><br> The UDDI entity MUST support XML Digital
298          * Signatures (tModel, Business, Service, Binding Template)
299          *
300          * @param <T> Any UDDI entity that supports digital signatures
301          * @param jaxbObj
302          * @return an enveloped signed UDDI element, do not modify this object
303          * after signing
304          */
305         public <T> T signUddiEntity(T jaxbObj) {
306                 DOMResult domResult = new DOMResult();
307                 JAXB.marshal(jaxbObj, domResult);
308                 Document doc = ((Document) domResult.getNode());
309                 Element docElement = doc.getDocumentElement();
310 
311                 try {
312                         KeyStore ks = KeyStore.getInstance(map.getProperty(SIGNATURE_KEYSTORE_FILETYPE));
313                         URL url = Thread.currentThread().getContextClassLoader().getResource(map.getProperty(SIGNATURE_KEYSTORE_FILE));
314                         if (url == null) {
315                                 try {
316                                         url = new File(map.getProperty(SIGNATURE_KEYSTORE_FILE)).toURI().toURL();
317                                 } catch (Exception x) {
318                                 }
319                         }
320                         if (url == null) {
321                                 try {
322                                         url = this.getClass().getClassLoader().getResource(map.getProperty(SIGNATURE_KEYSTORE_FILE));
323                                 } catch (Exception x) {
324                                 }
325                         }
326                         KeyStore.PrivateKeyEntry keyEntry = null;
327                         if (!map.getProperty(SIGNATURE_KEYSTORE_FILETYPE).equalsIgnoreCase("WINDOWS-MY")) {
328                                 ks.load(url.openStream(), (map.getProperty(SIGNATURE_KEYSTORE_FILE_PASSWORD)).toCharArray());
329                                 if (map.getProperty(SIGNATURE_KEYSTORE_KEY_PASSWORD) == null) {
330                                         keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS),
331                                                 new KeyStore.PasswordProtection(map.getProperty(SIGNATURE_KEYSTORE_FILE_PASSWORD).toCharArray()));
332                                 } else {
333                                         keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS),
334                                                 new KeyStore.PasswordProtection(map.getProperty(SIGNATURE_KEYSTORE_KEY_PASSWORD).toCharArray()));
335                                 }
336                         } else {
337                                 //Windows only
338                                 ks.load(null, null);
339                                 keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS),
340                                         null);
341                         }
342 
343                         PrivateKey privateKey = keyEntry.getPrivateKey();
344                         Certificate origCert = keyEntry.getCertificate();
345                         //PublicKey validatingKey = origCert.getPublicKey();
346                         this.signDOM(docElement, privateKey, origCert);
347 
348                         DOMSource domSource = new DOMSource(doc);
349                         T result = (T) JAXB.unmarshal(domSource, jaxbObj.getClass());
350                         return result;
351                 } catch (Exception e) {
352                         throw new RuntimeException("Signature failure due to: " + e.getMessage(), e);
353                 }
354         }
355 
356         /**
357          * Digitally signs a UDDI entity, such as a business, service, tmodel or
358          * binding template, provided you've already done the legwork to provide
359          * the signing keys <br><br> The UDDI entity MUST support XML Digital
360          * Signatures (tModel, Business, Service, Binding Template)
361          *
362          * @param <T>
363          * @param jaxbObj
364          * @param publicKey
365          * @param privateKey
366          * @return a signed entity
367          */
368         public <T> T signUddiEntity(T jaxbObj, Certificate publicKey, PrivateKey privateKey) {
369                 DOMResult domResult = new DOMResult();
370                 JAXB.marshal(jaxbObj, domResult);
371                 Document doc = ((Document) domResult.getNode());
372                 Element docElement = doc.getDocumentElement();
373                 try {
374 
375                         //PublicKey validatingKey = origCert.getPublicKey();
376                         this.signDOM(docElement, privateKey, publicKey);
377                         DOMSource domSource = new DOMSource(doc);
378                         T result = (T) JAXB.unmarshal(domSource, jaxbObj.getClass());
379                         return result;
380                 } catch (Exception e) {
381                         throw new RuntimeException("Signature failure due to: " + e.getMessage(), e);
382                 }
383         }
384 
385         /**
386          * Serializes a JAXB object and prints to stdout
387          *
388          * @param obj
389          */
390         public static void JAXB_ToStdOut(Object obj) {
391                 StringWriter sw = new StringWriter();
392                 JAXB.marshal(obj, sw);
393                 System.out.println(sw.toString());
394         }
395 
396         /**
397          * Serializes a JAXB object and prints to stdout
398          *
399          * @param obj
400          * @return serialized text
401          */
402         public static String JAXB_ToString(Object obj) {
403                 StringWriter sw = new StringWriter();
404                 JAXB.marshal(obj, sw);
405                 return (sw.toString());
406         }
407 
408         /**
409          *
410          * returns the public key of the signing certificate used for a signed
411          * JAXB object.
412          *
413          * @param obj
414          * @return null if the item is not signed or if it references a
415          * certificate that is not present in the current keystore
416          * @throws IllegalArgumentException for null input
417          * @throws java.security.cert.CertificateException
418          */
419         public X509Certificate getSigningCertificatePublicKey(Object obj) throws IllegalArgumentException, CertificateException {
420                 DOMResult domResult = new DOMResult();
421                 JAXB.marshal(obj, domResult);
422 
423                 Document doc = ((Document) domResult.getNode());
424                 Element docElement = doc.getDocumentElement();  //this is our signed node
425                 return getSigningCertificatePublicKey(docElement);
426         }
427 
428         /**
429          *
430          * returns the public key of the signing certificate used for a signed
431          * JAXB object.
432          *
433          * @param obj
434          * @return null if the item is not signed or if it references a
435          * certificate that is not present in the current keystore
436          * * @throws IllegalArgumentException for null input
437          */
438         private X509Certificate getSigningCertificatePublicKey(Element docElement) throws IllegalArgumentException, CertificateException {
439                 if (docElement == null) {
440                         throw new IllegalArgumentException();
441                 }
442 
443                 NodeList childNodes = docElement.getChildNodes();   //children, one of these SHOULD be our signature element
444                 // X509Certificate signingcert = null;
445                 for (int i = 0; i < childNodes.getLength(); i++) {
446                         //System.out.println(childNodes.item(i).getNamespaceURI() + " " + childNodes.item(i).getNodeName());
447                         if (childNodes.item(i).getNamespaceURI().equalsIgnoreCase(XML_DIGSIG_NS) && childNodes.item(i).getLocalName().equalsIgnoreCase("Signature")) {
448                                 Node sig = childNodes.item(i);
449                                 for (int k = 0; k < sig.getChildNodes().getLength(); k++) {
450                                         //      System.out.println(sig.getChildNodes().item(k).getNamespaceURI() + " " + sig.getChildNodes().item(k).getNodeName());
451                                         if ("KeyInfo".equalsIgnoreCase(sig.getChildNodes().item(k).getLocalName())) {
452                                                 //TODO figure out how to reference Subject DN, serial, thumbprint, etc
453                                                 for (int j = 0; j < sig.getChildNodes().item(k).getChildNodes().getLength(); j++) {
454                                                         if ("X509Data".equalsIgnoreCase(sig.getChildNodes().item(k).getChildNodes().item(j).getLocalName())) {
455                                                                 Node X509Data = sig.getChildNodes().item(k).getChildNodes().item(j);
456                                                                 for (int x = 0; x < X509Data.getChildNodes().getLength(); x++) {
457                                                                         if ("X509Certificate".equalsIgnoreCase(X509Data.getChildNodes().item(x).getLocalName())) {
458                                         //yay found it!
459 
460                                                                                 String c
461                                                                                         = "-----BEGIN CERTIFICATE-----\n"
462                                                                                         + X509Data.getChildNodes().item(x).getTextContent()
463                                                                                         + "\n-----END CERTIFICATE-----";
464                                                                                 //System.out.println("X509 Public key: " + c);
465                                                                                 InputStream is = new ByteArrayInputStream(c.getBytes());
466                                                                                 X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
467 
468                                                                                 logger.info("embedded certificate found, X509 public key " + cert.getSubjectDN().toString());
469                                                                                 return cert;
470 
471                                                                         }
472 
473                                     //if we have a 
474                                                                         //TODO other parsing items, lots of other potentials here
475                                                                 }
476                                                                 X509Certificate cert = FindCert(X509Data.getChildNodes());
477                                                                 if (cert != null) {
478                                                                         logger.info("certificate loaded from local trust store, X509 public key " + cert.getSubjectDN().toString());
479                                                                         return cert;
480                                                                 }
481                                                         }
482 
483                                                 }
484                                                 break;
485                                         }
486 
487                                 }
488 
489                                 break;
490                         }
491                 }
492                 return null;
493         }
494         
495         /**
496           * wrapper to overcome JDK differences between oracle vs openjdk
497           */
498           public static RevocationStatus check(X509Certificate cert,
499               X509Certificate issuerCert)
500               throws IOException, CertPathValidatorException, CertificateException {
501               CertId certId = null;
502               URI responderURI = null;
503               
504                   X509CertImpl certImpl = X509CertImpl.toImpl(cert);
505                   responderURI = getResponderURI(certImpl);
506                   if (responderURI == null) {
507                       throw new CertPathValidatorException
508                           ("No OCSP Responder URI in certificate");
509                   }
510                   return OCSP.check(cert, issuerCert, responderURI, cert, null);
511          }
512 
513         /**
514          * Verifies the signature on an enveloped digital signature on a UDDI
515          * entity, such as a business, service, tmodel or binding template.
516          * <br><Br>
517          * It is expected that either the public key of the signing certificate
518          * is included within the signature keyinfo section OR that sufficient
519          * information is provided in the signature to reference a public key
520          * located within the Trust Store provided<br><Br> Optionally, this
521          * function also validate the signing certificate using the options
522          * provided to the configuration map.
523          *
524          * @param obj an enveloped signed JAXB object
525          * @param OutErrorMessage a human readable error message explaining the
526          * reason for failure
527          * @return true if the validation passes the signature validation test,
528          * and optionally any certificate validation or trust chain validation
529          * @throws IllegalArgumentException for null input
530          */
531         public boolean verifySignedUddiEntity(Object obj, AtomicReference<String> OutErrorMessage) throws IllegalArgumentException {
532                 if (OutErrorMessage == null) {
533                         OutErrorMessage = new AtomicReference<String>();
534                         OutErrorMessage.set("");
535                 }
536                 if (obj == null) {
537                         throw new IllegalArgumentException("obj");
538                 }
539                 try {
540                         DOMResult domResult = new DOMResult();
541                         JAXB.marshal(obj, domResult);
542 
543                         Document doc = ((Document) domResult.getNode());
544                         Element docElement = doc.getDocumentElement();  //this is our signed node
545 
546                         X509Certificate signingcert = getSigningCertificatePublicKey(docElement);
547 
548                         if (signingcert != null) {
549                                 logger.info("verifying signature based on X509 public key " + signingcert.getSubjectDN().toString());
550                                 if (map.containsKey(CHECK_TIMESTAMPS) && Boolean.parseBoolean(map.getProperty(CHECK_TIMESTAMPS))) {
551                                         signingcert.checkValidity();
552                                 }
553                                 if (map.containsKey(CHECK_REVOCATION_STATUS_OCSP)
554                                         && Boolean.parseBoolean(map.getProperty(CHECK_REVOCATION_STATUS_OCSP))) {
555                                         logger.info("verifying revocation status via OSCP for X509 public key " + signingcert.getSubjectDN().toString());
556                                         X500Principal issuerX500Principal = signingcert.getIssuerX500Principal();
557                                         logger.info("certificate " + signingcert.getSubjectDN().toString() + " was issued by " + issuerX500Principal.getName() + ", attempting to retrieve certificate");
558                                         Security.setProperty("ocsp.enable", "false");
559                                         X509Certificate issuer = FindCertByDN(issuerX500Principal);
560                                         if (issuer == null) {
561                                                 OutErrorMessage.set("Unable to verify certificate status from OCSP because the issuer of the certificate is not in the trust store. " + OutErrorMessage.get());
562                                         } else {
563                                                 RevocationStatus check = check(signingcert, issuer);
564                                                 logger.info("certificate " + signingcert.getSubjectDN().toString() + " revocation status is " + check.getCertStatus().toString() + " reason " + check.getRevocationReason().toString());
565                                                 if (check.getCertStatus() != RevocationStatus.CertStatus.GOOD) {
566                                                         OutErrorMessage.set("Certificate status is " + check.getCertStatus().toString() + " reason " + check.getRevocationReason().toString() + "." + OutErrorMessage.get());
567                                                 }
568                                                
569                                         }
570                                 }
571                                 if (map.containsKey(CHECK_REVOCATION_STATUS_CRL) && Boolean.parseBoolean(map.getProperty(CHECK_REVOCATION_STATUS_CRL))) {
572                                         logger.info("verifying revokation status via CRL for X509 public key " + signingcert.getSubjectDN().toString());
573 
574                                         Security.setProperty("ocsp.enable", "false");
575                                         System.setProperty("com.sun.security.enableCRLDP", "true");
576 
577                                         X509CertSelector targetConstraints = new X509CertSelector();
578                                         targetConstraints.setCertificate(signingcert);
579                                         PKIXParameters params = new PKIXParameters(GetTrustStore());
580                                         params.setRevocationEnabled(true);
581                                         CertPath certPath = cf.generateCertPath(Arrays.asList(signingcert));
582 
583                                         CertPathValidator certPathValidator = CertPathValidator.getInstance(CertPathValidator.getDefaultType());
584                                         CertPathValidatorResult result = certPathValidator.validate(certPath, params);
585                                         try {
586                                                 PKIXCertPathValidatorResult pkixResult = (PKIXCertPathValidatorResult) result;
587                                                 logger.info("revokation status via CRL PASSED for X509 public key " + signingcert.getSubjectDN().toString() + " " + pkixResult.toString());
588                                         } catch (Exception ex) {
589                                                 OutErrorMessage.set("Certificate status is via CRL Failed: " + ex.getMessage() + "." + OutErrorMessage.get());
590                                         }
591                                 }
592                                 if (map.containsKey(CHECK_TRUST_CHAIN) && Boolean.parseBoolean(map.getProperty(CHECK_TRUST_CHAIN))) {
593                                         logger.info("verifying trust chain X509 public key " + signingcert.getSubjectDN().toString());
594                                         try {
595                                                 PKIXParameters params = new PKIXParameters(GetTrustStore());
596                                                 params.setRevocationEnabled(false);
597                                                 CertPath certPath = cf.generateCertPath(Arrays.asList(signingcert));
598 
599                                                 CertPathValidator certPathValidator = CertPathValidator.getInstance(CertPathValidator.getDefaultType());
600                                                 CertPathValidatorResult result = certPathValidator.validate(certPath, params);
601 
602                                                 PKIXCertPathValidatorResult pkixResult = (PKIXCertPathValidatorResult) result;
603 
604                                                 TrustAnchor ta = pkixResult.getTrustAnchor();
605                                                 X509Certificate cert = ta.getTrustedCert();
606 
607                                                 logger.info("trust chain validated X509 public key " + signingcert.getSubjectDN().toString() + " issued by " + cert.getPublicKey().toString());
608                                         } catch (Exception ex) {
609                                                 OutErrorMessage.set("Certificate status Trust validation failed: " + ex.getMessage() + "." + OutErrorMessage.get());
610                                         }
611                                 }
612                                 boolean b = verifySignature(docElement, signingcert.getPublicKey(), OutErrorMessage);
613                                 if ((OutErrorMessage.get() == null || OutErrorMessage.get().length() == 0) && b) {
614                                         //no error message and its cryptographically valid
615                                         return true;
616                                 }
617                                 return false;
618                         }
619 
620                         //last chance validation
621                         logger.info("signature did not have an embedded X509 public key. reverting to user specified certificate");
622                         //cert wasn't included in the signature, revert to some other means
623                         KeyStore ks = KeyStore.getInstance(map.getProperty(SIGNATURE_KEYSTORE_FILETYPE));
624                         URL url = Thread.currentThread().getContextClassLoader().getResource(map.getProperty(SIGNATURE_KEYSTORE_FILE));
625                         if (url == null) {
626                                 try {
627                                         url = new File(map.getProperty(SIGNATURE_KEYSTORE_FILE)).toURI().toURL();
628                                 } catch (Exception x) {
629                                 }
630                         }
631                         if (url == null) {
632                                 try {
633                                         url = this.getClass().getClassLoader().getResource(map.getProperty(SIGNATURE_KEYSTORE_FILE));
634                                 } catch (Exception x) {
635                                 }
636                         }
637                         if (url == null) {
638                                 logger.error("");
639                                 OutErrorMessage.set("The signed entity is signed but does not have a certificate attached and"
640                                         + "you didn't specify a keystore for me to look it up in. " + OutErrorMessage.get());
641                                 return false;
642                         }
643                         KeyStore.PrivateKeyEntry keyEntry = null;
644 
645                         ks.load(url.openStream(), map.getProperty(SIGNATURE_KEYSTORE_FILE_PASSWORD).toCharArray());
646 
647                         if (map.getProperty(SIGNATURE_KEYSTORE_KEY_PASSWORD) == null) {
648                                 keyEntry
649                                         = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS),
650                                                 new KeyStore.PasswordProtection(map.getProperty(SIGNATURE_KEYSTORE_FILE_PASSWORD).toCharArray()));
651                         } else {
652                                 keyEntry
653                                         = (KeyStore.PrivateKeyEntry) ks.getEntry(map.getProperty(SIGNATURE_KEYSTORE_KEY_ALIAS),
654                                                 new KeyStore.PasswordProtection(map.getProperty(SIGNATURE_KEYSTORE_KEY_PASSWORD).toCharArray()));
655                         }
656 
657                         Certificate origCert = keyEntry.getCertificate();
658                         if (map.containsKey(CHECK_TIMESTAMPS)) {
659                                 if (origCert.getPublicKey() instanceof X509Certificate) {
660                                         X509Certificate x = (X509Certificate) origCert.getPublicKey();
661                                         x.checkValidity();
662                                 }
663                         }
664                         PublicKey validatingKey = origCert.getPublicKey();
665                         return verifySignature(docElement, validatingKey, OutErrorMessage);
666                 } catch (Exception e) {
667                         //throw new RuntimeException(e);
668                         logger.error("Error caught validating signature", e);
669                         OutErrorMessage.set(e.getMessage());
670                         return false;
671                 }
672         }
673 
674         /**
675          * trust loaded as follows
676          * system property via file
677          * programmatically specified map via file
678          * programmatically specified map thread classloader lookup
679          * programmatically specified map this class's classloader lookup
680          * windows trust store
681          * JDK provided trust store
682          * @return
683          * @throws Exception 
684          */
685         private KeyStore GetTrustStore() throws Exception {
686                 String type = map.getProperty(TRUSTSTORE_FILETYPE);
687                 if (type == null) {
688                         type = "JKS";
689                 }
690                 KeyStore ks = KeyStore.getInstance(type);
691                 boolean ksLoaded = false;
692 
693                 if (!ksLoaded) {
694                         String truststore = System.getProperty("javax.net.ssl.keyStore");
695                         try {
696                                 
697                                 String pwd = System.getProperty("javax.net.ssl.keyStorePassword");
698                                 if (truststore != null && pwd != null) {
699                                         ks.load(new File(truststore).toURI().toURL().openStream(), pwd.toCharArray());
700                                         ksLoaded = true;
701                                         logger.info("trust store loaded from sysprop " + truststore);
702                                 }
703                         } catch (Exception ex) {
704                                 logger.warn("unable to load truststore from sysprop " + truststore + " "  + ex.getMessage());
705                                 logger.debug("unable to load truststore from sysprop " + ex.getMessage(),ex);
706                         }
707                 }
708                 
709                 File f=new File(map.getProperty(TRUSTSTORE_FILE));
710                  //load as a file
711                 if (!ksLoaded) {
712                         try {
713                                 if (f.exists()){
714                                 URL url = f.toURI().toURL();
715                                 ks.load(url.openStream(), (map.getProperty(TRUSTSTORE_FILE_PASSWORD)).toCharArray());
716                                 ksLoaded = true;
717                                 logger.info("trust store loaded from file " + map.getProperty(TRUSTSTORE_FILE));
718                                 }
719                         } catch (Exception x) {
720                                 logger.warn("unable to load truststore from file "+map.getProperty(TRUSTSTORE_FILE)+" "+ x.getMessage());
721                                 logger.debug("unable to load truststore from file "+ x.getMessage(), x);
722                                 
723                         }
724                 }
725                 
726                 if (!ksLoaded) {
727                         FileInputStream fis=null;
728                         try {
729                                 //File f = new File(map.getProperty(TRUSTSTORE_FILE));
730                                 if (f.exists())
731                                 {
732                                         fis = new FileInputStream(f);
733                                         ks.load(fis, (map.getProperty(TRUSTSTORE_FILE_PASSWORD)).toCharArray());
734                                         fis.close();
735                                         ksLoaded = true;
736                                 logger.info("trust store loaded from file " + map.getProperty(TRUSTSTORE_FILE));
737                                 }
738                         } catch (Exception x) {
739                                 logger.warn("unable to load truststore from file "+map.getProperty(TRUSTSTORE_FILE)+" "+ x.getMessage());
740                                 logger.debug("unable to load truststore from file "+ x.getMessage(), x);
741                         }
742                         finally {
743                                 if (fis!=null)
744                                         fis.close();
745                         }
746                 }
747 
748                 
749                 
750                 
751 
752                 //load from thread classloader
753                 if (!ksLoaded) {
754                         try {
755                                 URL url = Thread.currentThread().getContextClassLoader().getResource(map.getProperty(TRUSTSTORE_FILE));
756                                 ks.load(url.openStream(), (map.getProperty(TRUSTSTORE_FILE_PASSWORD)).toCharArray());
757                                 ksLoaded = true;
758                                 logger.info("trust store loaded from classpath(1) " + map.getProperty(TRUSTSTORE_FILE));
759                         } catch (Exception x) {
760                                 logger.warn("unable to load truststore from classpath" + map.getProperty(TRUSTSTORE_FILE) + " " +x.getMessage());
761                                 logger.debug("unable to load truststore from classpath", x);
762                         }
763                 }
764 
765                 //load from this classloader
766                 if (!ksLoaded) {
767                         try {
768                                 URL url = this.getClass().getClassLoader().getResource(map.getProperty(TRUSTSTORE_FILE));
769                                 ks.load(url.openStream(), (map.getProperty(TRUSTSTORE_FILE_PASSWORD)).toCharArray());
770                                 ksLoaded = true;
771                                 logger.info("trust store loaded from classpath(2) " + map.getProperty(TRUSTSTORE_FILE));
772                         } catch (Exception x) {
773                                 logger.warn("unable to load truststore from classpath "+ map.getProperty(TRUSTSTORE_FILE) + " " +x.getMessage());
774                                 logger.debug("unable to load truststore from classpath", x);
775                         }
776                 }
777                
778 
779                 if (!ksLoaded) {
780                         try {
781                                 URL cacerts = new File(System.getenv("JAVA_HOME") + File.separator + "lib" + File.separator + "security" + File.separator + "cacerts").toURI().toURL();
782                                 ks.load(cacerts.openStream(), "changeit".toCharArray());
783                                 logger.info("trust store loaded from JRE " + cacerts.toExternalForm());
784                                 ksLoaded = true;
785                         } catch (Exception c) {
786                                 logger.warn("unable to load default JDK truststore "+ c.getMessage());
787                                 logger.debug("unable to load default JDK truststore",c);
788                         }
789                 }
790                 
791                 //try windows trust store first
792                 try {
793                         if (map.getProperty(TRUSTSTORE_FILETYPE).equalsIgnoreCase("WINDOWS-ROOT")) {
794                                 ks.load(null, null);
795                                 ksLoaded = true;
796                                 logger.info("trust store loaded from windows");
797                         }
798                 } catch (Exception ex) {
799                         logger.warn("unable to load truststore from windows " +ex.getMessage());
800                         logger.debug("unable to load truststore from windows", ex);
801                 }
802                 
803                 if (!ksLoaded) {
804                         try {
805                                 URL cacerts = new File(System.getenv("JAVA_HOME") + File.separator + "jre" + File.separator + "lib" + File.separator + "security" + File.separator + "cacerts").toURI().toURL();
806                                 ks.load(cacerts.openStream(), "changeit".toCharArray());
807                                 logger.info("trust store loaded from JRE " + cacerts.toExternalForm());
808                                 ksLoaded = true;
809                         } catch (Exception c) {
810                                 logger.warn("unable to load default jdk/jre truststore " +c.getMessage());
811                                 logger.debug("unable to load default jdk/jre truststore", c);
812                         }
813                 }
814                 if (!ksLoaded) {
815                         logger.warn("unable to load trust store!");
816                 }
817 
818                 return ks;
819         }
820 
821         private XMLSignatureFactory initXMLSigFactory() {
822                 XMLSignatureFactory fac = XMLSignatureFactory.getInstance();
823                 return fac;
824         }
825 
826         private Reference initReference(XMLSignatureFactory fac) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
827                 List transformers = new ArrayList();
828                 transformers.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null));
829 
830                 String dm = map.getProperty(SIGNATURE_OPTION_DIGEST_METHOD);
831                 if (dm == null) {
832                         dm = DigestMethod.SHA1;
833                 }
834                 Reference ref = fac.newReference("", fac.newDigestMethod(dm, null), transformers, null, null);
835                 return ref;
836         }
837 
838         private SignedInfo initSignedInfo(XMLSignatureFactory fac) throws Exception {
839                 Reference ref = initReference(fac);
840                 String cm = null;
841                 cm = map.getProperty(CANONICALIZATIONMETHOD);
842                 String sigmethod = null;
843                 sigmethod = map.getProperty(SIGNATURE_METHOD);
844                 if (sigmethod == null) {
845                         sigmethod = SignatureMethod.RSA_SHA1;
846                 }
847                 if (cm == null) {
848                         cm = CanonicalizationMethod.EXCLUSIVE;
849                 }
850                 SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(
851                         cm,
852                         (C14NMethodParameterSpec) null),
853                         fac.newSignatureMethod(sigmethod,
854                                 null), Collections.singletonList(ref));
855                 return si;
856         }
857 
858         private boolean verifySignature(Element element, PublicKey validatingKey, AtomicReference<String> OutReadableErrorMessage) {
859                 if (OutReadableErrorMessage == null) {
860                         OutReadableErrorMessage = new AtomicReference<String>();
861                 }
862                 XMLSignatureFactory fac = initXMLSigFactory();
863                 NodeList nl = element.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
864                 if (nl.getLength() == 0) {
865                         throw new RuntimeException("Cannot find Signature element");
866                 }
867                 DOMValidateContext valContext = new DOMValidateContext(validatingKey, nl.item(0));
868                 try {
869                         valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
870                         XMLSignature signature = fac.unmarshalXMLSignature(valContext);
871                         boolean coreValidity = signature.validate(valContext);
872                         // Check core validation status.
873                         if (coreValidity == false) {
874                                 logger.warn("Signature failed core validation");
875                                 boolean sv = signature.getSignatureValue().validate(valContext);
876                                 logger.debug("signature validation status: " + sv);
877                                 OutReadableErrorMessage.set("signature validation failed: " + sv + "." + OutReadableErrorMessage.get());
878                                 // Check the validation status of each Reference.
879                                 @SuppressWarnings("unchecked")
880                                 Iterator<Reference> i = signature.getSignedInfo().getReferences().iterator();
881                                 //System.out.println("---------------------------------------------");
882                                 for (int j = 0; i.hasNext(); j++) {
883                                         Reference ref = (Reference) i.next();
884                                         boolean refValid = ref.validate(valContext);
885                                         logger.debug(j);
886                                         logger.debug("ref[" + j + "] validity status: " + refValid);
887                                         if (!refValid) {
888                                                 OutReadableErrorMessage.set("signature reference " + j + " invalid. " + OutReadableErrorMessage.get());
889                                         }
890                                         logger.debug("Ref type: " + ref.getType() + ", URI: " + ref.getURI());
891                                         for (Object xform : ref.getTransforms()) {
892                                                 logger.debug("Transform: " + xform);
893                                         }
894                                         String calcDigValStr = digestToString(ref.getCalculatedDigestValue());
895                                         String expectedDigValStr = digestToString(ref.getDigestValue());
896                                         logger.warn("    Calc Digest: " + calcDigValStr);
897                                         logger.warn("Expected Digest: " + expectedDigValStr);
898                                         if (!calcDigValStr.equalsIgnoreCase(expectedDigValStr)) {
899                                                 OutReadableErrorMessage.set("digest mismatch for signature ref " + j + "." + OutReadableErrorMessage.get());
900                                         }
901                                 }
902                         } else {
903                                 logger.info("Signature passed core validation");
904                         }
905                         return coreValidity;
906                 } catch (Exception e) {
907                         OutReadableErrorMessage.set("signature validation failed: " + e.getMessage() + OutReadableErrorMessage.get());
908                         logger.fatal(e);
909                         return false;
910                 }
911         }
912 
913         private String digestToString(byte[] digest) {
914                 StringBuilder sb = new StringBuilder();
915                 for (byte b : digest) {
916                         String hex = Integer.toHexString(0xFF & b);
917                         if (hex.length() == 1) {
918                                 sb.append('0');
919                         }
920                         sb.append(hex);
921                 }
922                 return sb.toString();
923         }
924 
925         private void signDOM(Node node, PrivateKey privateKey, Certificate origCert) {
926                 XMLSignatureFactory fac = initXMLSigFactory();
927                 X509Certificate cert = (X509Certificate) origCert;
928                 // Create the KeyInfo containing the X509Data.
929 
930                 KeyInfoFactory kif = fac.getKeyInfoFactory();
931 
932                 List<Object> x509Content = null;//new ArrayList<Object>();
933                 List<X509Data> data = new ArrayList<X509Data>();
934                 if (map.containsKey(SIGNATURE_OPTION_CERT_INCLUSION_SUBJECTDN)) {
935                         x509Content = new ArrayList<Object>();
936 
937                         x509Content.add(cert.getSubjectDN().getName());
938             //  x509Content.add(cert);
939                         //x509Content.add(cert.getSubjectDN().getName());
940                         X509Data xd = kif.newX509Data(x509Content);
941                         data.add(xd);
942                 }
943 
944         //  if (map.containsKey(SIGNATURE_OPTION_CERT_INCLUSION_X500_PRINICPAL)) {
945                 // }
946                 if (map.containsKey(SIGNATURE_OPTION_CERT_INCLUSION_BASE64)) {
947                         x509Content = new ArrayList<Object>();
948                         x509Content.add(cert);
949                         //x509Content.add(cert.getSubjectX500Principal().getName());
950                         X509Data xd = kif.newX509Data(x509Content);
951                         data.add(xd);
952                 }
953                 if (map.containsKey(SIGNATURE_OPTION_CERT_INCLUSION_SERIAL)) {
954                         x509Content = new ArrayList<Object>();
955 
956                         X509IssuerSerial issuer = kif.newX509IssuerSerial(cert.getIssuerX500Principal().getName(), cert.getSerialNumber());
957 
958                         x509Content.add(issuer);
959                         X509Data xd = kif.newX509Data(x509Content);
960                         data.add(xd);
961                 }
962 
963         //  
964                 //x509Content.add(cert);
965                 KeyInfo ki = kif.newKeyInfo(data);
966 
967         // Create a DOMSignContext and specify the RSA PrivateKey and
968                 // location of the resulting XMLSignature's parent element.
969                 DOMSignContext dsc = new DOMSignContext(privateKey, node);
970                 dsc.putNamespacePrefix(XML_DIGSIG_NS, "ns2");
971 
972                 // Create the XMLSignature, but don't sign it yet.
973                 try {
974                         SignedInfo si = initSignedInfo(fac);
975                         XMLSignature signature = fac.newXMLSignature(si, ki);
976 
977                         // Marshal, generate, and sign the enveloped signature.
978                         signature.sign(dsc);
979                 } catch (Exception e) {
980                         throw new RuntimeException(e);
981                 }
982         }
983 
984         /**
985          * searches local keystores for a referenced signing certificate
986          *
987          * @param childNodes
988          * @return null or the public key of a signing certificate
989          */
990         private X509Certificate FindCert(NodeList childNodes) {
991                 try {
992                         for (int x = 0; x < childNodes.getLength(); x++) {
993                                 if (childNodes.item(x).getLocalName().equalsIgnoreCase("X509SubjectName")) {
994 
995                                         String dn = childNodes.item(x).getTextContent().trim();
996                                         return FindCertByDN(new X500Principal(dn));
997 
998                                 }
999                                 if (childNodes.item(x).getLocalName().equalsIgnoreCase("X509IssuerSerial")) {
1000                                         String X509IssuerName = null;
1001                                         String X509SerialNumber = null;
1002                                         for (int k = 0; k < childNodes.item(x).getChildNodes().getLength(); k++) {
1003                                                 if (childNodes.item(x).getChildNodes().item(x).getLocalName().equalsIgnoreCase("X509IssuerName")) {
1004                                                         X509IssuerName = childNodes.item(x).getTextContent().trim();
1005                                                 }
1006                                                 if (childNodes.item(x).getChildNodes().item(x).getLocalName().equalsIgnoreCase("X509SerialNumber")) {
1007                                                         X509SerialNumber = childNodes.item(x).getTextContent().trim();
1008                                                 }
1009 
1010                                         }
1011                                         if (X509IssuerName != null && X509SerialNumber != null) {
1012                                                 return FindCertByIssuer(X509IssuerName, X509SerialNumber);
1013                                         }
1014 
1015                                 }
1016                         }
1017                 } catch (Exception ex) {
1018                         logger.warn("error caught searching for a certificate", ex);
1019                 }
1020                 return null;
1021         }
1022 
1023         private X509Certificate FindCertByDN(X500Principal name) throws Exception {
1024                 KeyStore ks = GetTrustStore();
1025                 if (ks == null) {
1026                         return null;
1027                 }
1028                 Enumeration<String> aliases = ks.aliases();
1029                 while (aliases.hasMoreElements()) {
1030                         String nextElement = aliases.nextElement();
1031                         Certificate certificate = ks.getCertificate(nextElement);
1032                         X509Certificate x = (X509Certificate) certificate;
1033                         if (x.getSubjectX500Principal().equals(name)) {
1034                                 return x;
1035                         }
1036                 }
1037                 return null;
1038         }
1039 
1040         /**
1041          * Downloads a CRL from given HTTP/HTTPS/FTP URL, e.g.
1042          * http://crl.infonotary.com/crl/identity-ca.crl
1043          */
1044         private X509CRL downloadCRLFromWeb(String crlURL)
1045                 throws MalformedURLException, IOException, CertificateException,
1046                 CRLException {
1047                 URL url = new URL(crlURL);
1048                 InputStream crlStream = url.openStream();
1049                 try {
1050                         //	CertificateFactory cf = CertificateFactory.getInstance("X.509");
1051                         X509CRL crl = (X509CRL) cf.generateCRL(crlStream);
1052                         return crl;
1053                 } finally {
1054                         crlStream.close();
1055                 }
1056         }
1057 
1058         private X509Certificate FindCertByIssuer(String X509IssuerName, String X509SerialNumber) throws Exception {
1059                 KeyStore ks = GetTrustStore();
1060                 if (ks == null) {
1061                         return null;
1062                 }
1063                 Enumeration<String> aliases = ks.aliases();
1064                 while (aliases.hasMoreElements()) {
1065                         String nextElement = aliases.nextElement();
1066                         Certificate certificate = ks.getCertificate(nextElement);
1067                         X509Certificate x = (X509Certificate) certificate;
1068                         if (x.getIssuerDN().getName().equals(X509IssuerName)
1069                                 && x.getSerialNumber().toString().equalsIgnoreCase(X509SerialNumber)) {
1070                                 return x;
1071                         }
1072                 }
1073                 return null;
1074         }
1075 }