View Javadoc

1   /*
2    * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
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  
17  package org.opensaml.xml.security.x509;
18  
19  import java.security.GeneralSecurityException;
20  import java.security.cert.CRL;
21  import java.security.cert.CertPathBuilder;
22  import java.security.cert.CertPathBuilderException;
23  import java.security.cert.CertStore;
24  import java.security.cert.CertStoreException;
25  import java.security.cert.Certificate;
26  import java.security.cert.CollectionCertStoreParameters;
27  import java.security.cert.PKIXBuilderParameters;
28  import java.security.cert.PKIXCertPathBuilderResult;
29  import java.security.cert.TrustAnchor;
30  import java.security.cert.X509CRL;
31  import java.security.cert.X509CertSelector;
32  import java.security.cert.X509Certificate;
33  import java.util.ArrayList;
34  import java.util.Collection;
35  import java.util.Date;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Set;
39  
40  import org.opensaml.xml.security.SecurityException;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * An implementation of {@link PKIXTrustEvaluator} that is based on the Java CertPath API.
46   */
47  public class CertPathPKIXTrustEvaluator implements PKIXTrustEvaluator {
48      
49      /** Default verify depth. */
50      public static final Integer DEFAULT_VERIFY_DEPTH = 1;
51  
52      /** Class logger. */
53      private final Logger log = LoggerFactory.getLogger(CertPathPKIXTrustEvaluator.class);
54      
55      /** Responsible for parsing and serializing X.500 names to/from {@link X500Principal} instances. */
56      private X500DNHandler x500DNHandler;
57  
58      /** Constructor. */
59      public CertPathPKIXTrustEvaluator() {
60          x500DNHandler = new InternalX500DNHandler();
61      }
62  
63      /**
64       * Get the handler which process X.500 distinguished names.
65       * 
66       * Defaults to {@link InternalX500DNHandler}.
67       * 
68       * @return returns the X500DNHandler instance
69       */
70      public X500DNHandler getX500DNHandler() {
71          return x500DNHandler;
72      }
73  
74      /**
75       * Set the handler which process X.500 distinguished names.
76       * 
77       * Defaults to {@link InternalX500DNHandler}.
78       * 
79       * @param handler the new X500DNHandler instance
80       */
81      public void setX500DNHandler(X500DNHandler handler) {
82          if (handler == null) {
83              throw new IllegalArgumentException("X500DNHandler may not be null");
84          }
85          x500DNHandler = handler;
86      }
87  
88      /** {@inheritDoc} */
89      public boolean validate(PKIXValidationInformation validationInfo, X509Credential untrustedCredential)
90              throws SecurityException {
91          
92          if (log.isDebugEnabled()) {
93              log.debug("Attempting PKIX path validation on untrusted credential: {}",
94                      X509Util.getIdentifiersToken(untrustedCredential, x500DNHandler));
95          }        
96          
97          try {
98              PKIXBuilderParameters params = getPKIXBuilderParameters(validationInfo, untrustedCredential);
99  
100             log.trace("Building certificate validation path");
101 
102             CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
103             PKIXCertPathBuilderResult buildResult = (PKIXCertPathBuilderResult) builder.build(params);
104             if (log.isDebugEnabled()) {
105                 logCertPathDebug(buildResult, untrustedCredential.getEntityCertificate());
106                 log.debug("PKIX validation succeeded for untrusted credential: {}",
107                         X509Util.getIdentifiersToken(untrustedCredential, x500DNHandler));
108             }            
109             return true;
110 
111         } catch (CertPathBuilderException e) {
112             if (log.isTraceEnabled()) {
113                 log.trace("PKIX path construction failed for untrusted credential: " 
114                         + X509Util.getIdentifiersToken(untrustedCredential, x500DNHandler), e);
115             } else {
116                 log.error("PKIX path construction failed for untrusted credential: " 
117                         + X509Util.getIdentifiersToken(untrustedCredential, x500DNHandler) + ": " + e.getMessage());
118             }
119             return false;
120         } catch (GeneralSecurityException e) {
121             log.error("PKIX validation failure", e);
122             throw new SecurityException("PKIX validation failure", e);
123         }
124     }
125 
126     /**
127      * Creates the set of PKIX builder parameters to use when building the cert path builder.
128      * 
129      * @param validationInfo PKIX validation information
130      * @param untrustedCredential credential to be validated
131      * 
132      * @return PKIX builder params
133      * 
134      * @throws GeneralSecurityException thrown if the parameters can not be created
135      */
136     protected PKIXBuilderParameters getPKIXBuilderParameters(PKIXValidationInformation validationInfo,
137             X509Credential untrustedCredential) throws GeneralSecurityException {
138         Set<TrustAnchor> trustAnchors = getTrustAnchors(validationInfo);
139         if (trustAnchors == null || trustAnchors.isEmpty()) {
140             throw new GeneralSecurityException(
141                     "Unable to validate X509 certificate, no trust anchors found in the PKIX validation information");
142         }
143 
144         X509CertSelector selector = new X509CertSelector();
145         selector.setCertificate(untrustedCredential.getEntityCertificate());
146 
147         log.trace("Adding trust anchors to PKIX validator parameters");
148         PKIXBuilderParameters params = new PKIXBuilderParameters(trustAnchors, selector);
149 
150         Integer effectiveVerifyDepth = getEffectiveVerificationDepth(validationInfo);
151         log.trace("Setting max verification depth to: {} ", effectiveVerifyDepth);
152         params.setMaxPathLength(effectiveVerifyDepth);
153 
154         CertStore certStore = buildCertStore(validationInfo, untrustedCredential);
155         params.addCertStore(certStore);
156 
157         if (storeContainsCRLs(certStore)) {
158             log.trace("At least one CRL was present in cert store, enabling revocation checking");
159             params.setRevocationEnabled(true);
160         } else {
161             log.trace("No CRLs present in cert store, disabling revocation checking");
162             params.setRevocationEnabled(false);
163         }
164 
165         return params;
166     }
167 
168     /**
169      * Determine whether there are any CRL's in the {@link CertStore} that is to be used.
170      * 
171      * @param certStore the cert store that will be used for validation
172      * @return true if the store contains at least 1 CRL instance, false otherwise
173      */
174     protected boolean storeContainsCRLs(CertStore certStore) {
175         Collection<? extends CRL> crls = null;
176         try {
177             //Save some cycles and memory: Collection cert store allows null as specifier to return all.
178             //crls = certStore.getCRLs( new X509CRLSelector() );
179             crls = certStore.getCRLs(null);
180         } catch (CertStoreException e) {
181             log.error("Error examining cert store for CRL's, treating as if no CRL's present", e);
182             return false;
183         }
184         if (crls != null && !crls.isEmpty()) {
185             return true;
186         }
187         return false;
188     }
189 
190     /**
191      * Get the effective maximum path depth to use when constructing PKIX cert path builder parameters.
192      * 
193      * @param validationInfo PKIX validation information
194      * @return the effective max verification depth to use
195      */
196     protected Integer getEffectiveVerificationDepth(PKIXValidationInformation validationInfo) {
197         Integer effectiveVerifyDepth = validationInfo.getVerificationDepth();
198         if (effectiveVerifyDepth == null) {
199             effectiveVerifyDepth = DEFAULT_VERIFY_DEPTH;
200         }
201         return effectiveVerifyDepth;
202     }
203 
204     /**
205      * Creates the collection of trust anchors to use during validation.
206      * 
207      * @param validationInfo PKIX validation information
208      * 
209      * @return trust anchors to use during validation
210      */
211     protected Set<TrustAnchor> getTrustAnchors(PKIXValidationInformation validationInfo) {
212         Collection<X509Certificate> validationCertificates = validationInfo.getCertificates();
213 
214         log.trace("Constructing trust anchors for PKIX validation");
215         Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
216         for (X509Certificate cert : validationCertificates) {
217             trustAnchors.add(buildTrustAnchor(cert));
218         }
219 
220         if (log.isTraceEnabled()) {
221             for (TrustAnchor anchor : trustAnchors) {
222                 log.trace("TrustAnchor: {}", anchor.toString());
223             }
224         }
225 
226         return trustAnchors;
227     }
228 
229     /**
230      * Build a trust anchor from the given X509 certificate.
231      * 
232      * This could for example be extended by subclasses to add custom name constraints, if desired.
233      * 
234      * @param cert the certificate which serves as the trust anchor
235      * @return the newly constructed TrustAnchor
236      */
237     protected TrustAnchor buildTrustAnchor(X509Certificate cert) {
238         return new TrustAnchor(cert, null);
239     }
240 
241     /**
242      * Creates the certificate store that will be used during validation.
243      * 
244      * @param validationInfo PKIX validation information
245      * @param untrustedCredential credential to be validated
246      * 
247      * @return certificate store used during validation
248      * 
249      * @throws GeneralSecurityException thrown if the certificate store can not be created from the cert and CRL
250      *             material
251      */
252     protected CertStore buildCertStore(PKIXValidationInformation validationInfo, X509Credential untrustedCredential)
253             throws GeneralSecurityException {
254 
255         log.trace("Creating cert store to use during path validation");
256 
257         log.trace("Adding entity certificate chain to cert store");
258         List<Object> storeMaterial = new ArrayList<Object>(untrustedCredential.getEntityCertificateChain());
259         if (log.isTraceEnabled()) {
260             for (X509Certificate cert : untrustedCredential.getEntityCertificateChain()) {
261                 log.trace(String.format("Added X509Certificate from entity cert chain to cert store "
262                         + "with subject name '%s' issued by '%s' with serial number '%s'",
263                         x500DNHandler.getName(cert.getSubjectX500Principal()),
264                         x500DNHandler.getName(cert.getIssuerX500Principal()),
265                         cert.getSerialNumber().toString()));
266             }
267         }
268         
269         Date now = new Date();
270         for (X509CRL crl : validationInfo.getCRLs()) {
271             if (crl.getRevokedCertificates() != null && !crl.getRevokedCertificates().isEmpty()) {
272                 storeMaterial.add(crl);
273                 if (log.isTraceEnabled()) {
274                     log.trace("Added X509CRL to cert store from issuer {} dated {}",
275                             x500DNHandler.getName(crl.getIssuerX500Principal()), crl.getThisUpdate());
276                 }
277                 if (crl.getNextUpdate().before(now)) {
278                     log.warn("Using X509CRL from issuer {} with a nextUpdate in the past: {}",
279                             x500DNHandler.getName(crl.getIssuerX500Principal()), crl.getNextUpdate());
280                 }
281             } else {
282                 if (log.isTraceEnabled()) {
283                     log.trace("Empty X509CRL not added to cert store, from issuer {} dated {}",
284                             x500DNHandler.getName(crl.getIssuerX500Principal()), crl.getThisUpdate());
285                 }
286             }
287         }
288 
289         return CertStore.getInstance("Collection", new CollectionCertStoreParameters(storeMaterial));
290     }
291 
292     /**
293      * Log information from the constructed cert path at level debug.
294      * 
295      * @param buildResult the PKIX cert path builder result containing the cert path and trust anchor
296      * @param targetCert the cert untrusted certificate that was being evaluated
297      */
298     private void logCertPathDebug(PKIXCertPathBuilderResult buildResult, X509Certificate targetCert) {
299         log.debug("Built valid PKIX cert path");
300         log.debug("Target certificate: {}", x500DNHandler.getName(targetCert.getSubjectX500Principal()));
301         for (Certificate cert : buildResult.getCertPath().getCertificates()) {
302             log.debug("CertPath certificate: {}", x500DNHandler.getName(((X509Certificate) cert)
303                     .getSubjectX500Principal()));
304         }
305         TrustAnchor ta = buildResult.getTrustAnchor();
306         if (ta.getTrustedCert() != null) {
307             log.debug("TrustAnchor: {}", x500DNHandler.getName(ta.getTrustedCert().getSubjectX500Principal()));
308         } else if (ta.getCA() != null) {
309             log.debug("TrustAnchor: {}", x500DNHandler.getName(ta.getCA()));
310         } else {
311             log.debug("TrustAnchor: {}", ta.getCAName());
312         }
313     }
314 
315 }