1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
46
47 public class CertPathPKIXTrustEvaluator implements PKIXTrustEvaluator {
48
49
50 private final Logger log = LoggerFactory.getLogger(CertPathPKIXTrustEvaluator.class);
51
52
53 private X500DNHandler x500DNHandler;
54
55
56 private PKIXValidationOptions options;
57
58
59 public CertPathPKIXTrustEvaluator() {
60 options = new PKIXValidationOptions();
61 x500DNHandler = new InternalX500DNHandler();
62 }
63
64
65
66
67
68
69 public CertPathPKIXTrustEvaluator(PKIXValidationOptions newOptions) {
70 if (newOptions == null) {
71 throw new IllegalArgumentException("PKIXValidationOptions may not be null");
72 }
73 options = newOptions;
74 x500DNHandler = new InternalX500DNHandler();
75 }
76
77
78 public PKIXValidationOptions getPKIXValidationOptions() {
79 return options;
80 }
81
82
83
84
85
86
87 public void setPKIXValidationOptions(PKIXValidationOptions newOptions) {
88 if (newOptions == null) {
89 throw new IllegalArgumentException("PKIXValidationOptions may not be null");
90 }
91 options = newOptions;
92 }
93
94
95
96
97
98
99
100
101 public X500DNHandler getX500DNHandler() {
102 return x500DNHandler;
103 }
104
105
106
107
108
109
110
111
112 public void setX500DNHandler(X500DNHandler handler) {
113 if (handler == null) {
114 throw new IllegalArgumentException("X500DNHandler may not be null");
115 }
116 x500DNHandler = handler;
117 }
118
119
120 public boolean validate(PKIXValidationInformation validationInfo, X509Credential untrustedCredential)
121 throws SecurityException {
122
123 if (log.isDebugEnabled()) {
124 log.debug("Attempting PKIX path validation on untrusted credential: {}",
125 X509Util.getIdentifiersToken(untrustedCredential, x500DNHandler));
126 }
127
128 try {
129 PKIXBuilderParameters params = getPKIXBuilderParameters(validationInfo, untrustedCredential);
130
131 log.trace("Building certificate validation path");
132
133 CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
134 PKIXCertPathBuilderResult buildResult = (PKIXCertPathBuilderResult) builder.build(params);
135 if (log.isDebugEnabled()) {
136 logCertPathDebug(buildResult, untrustedCredential.getEntityCertificate());
137 log.debug("PKIX validation succeeded for untrusted credential: {}",
138 X509Util.getIdentifiersToken(untrustedCredential, x500DNHandler));
139 }
140 return true;
141
142 } catch (CertPathBuilderException e) {
143 if (log.isTraceEnabled()) {
144 log.trace("PKIX path construction failed for untrusted credential: "
145 + X509Util.getIdentifiersToken(untrustedCredential, x500DNHandler), e);
146 } else {
147 log.error("PKIX path construction failed for untrusted credential: "
148 + X509Util.getIdentifiersToken(untrustedCredential, x500DNHandler) + ": " + e.getMessage());
149 }
150 return false;
151 } catch (GeneralSecurityException e) {
152 log.error("PKIX validation failure", e);
153 throw new SecurityException("PKIX validation failure", e);
154 }
155 }
156
157
158
159
160
161
162
163
164
165
166
167 protected PKIXBuilderParameters getPKIXBuilderParameters(PKIXValidationInformation validationInfo,
168 X509Credential untrustedCredential) throws GeneralSecurityException {
169 Set<TrustAnchor> trustAnchors = getTrustAnchors(validationInfo);
170 if (trustAnchors == null || trustAnchors.isEmpty()) {
171 throw new GeneralSecurityException(
172 "Unable to validate X509 certificate, no trust anchors found in the PKIX validation information");
173 }
174
175 X509CertSelector selector = new X509CertSelector();
176 selector.setCertificate(untrustedCredential.getEntityCertificate());
177
178 log.trace("Adding trust anchors to PKIX validator parameters");
179 PKIXBuilderParameters params = new PKIXBuilderParameters(trustAnchors, selector);
180
181 Integer effectiveVerifyDepth = getEffectiveVerificationDepth(validationInfo);
182 log.trace("Setting max verification depth to: {} ", effectiveVerifyDepth);
183 params.setMaxPathLength(effectiveVerifyDepth);
184
185 CertStore certStore = buildCertStore(validationInfo, untrustedCredential);
186 params.addCertStore(certStore);
187
188 boolean isForceRevocationEnabled = false;
189 boolean forcedRevocation = false;
190 if (options instanceof CertPathPKIXValidationOptions) {
191 CertPathPKIXValidationOptions certpathOptions = (CertPathPKIXValidationOptions) options;
192 isForceRevocationEnabled = certpathOptions.isForceRevocationEnabled();
193 forcedRevocation = certpathOptions.isRevocationEnabled();
194 }
195
196 if (isForceRevocationEnabled) {
197 log.trace("PKIXBuilderParameters#setRevocationEnabled is being forced to: {}", forcedRevocation);
198 params.setRevocationEnabled(forcedRevocation);
199 } else {
200 if (storeContainsCRLs(certStore)) {
201 log.trace("At least one CRL was present in cert store, enabling revocation checking");
202 params.setRevocationEnabled(true);
203 } else {
204 log.trace("No CRLs present in cert store, disabling revocation checking");
205 params.setRevocationEnabled(false);
206 }
207 }
208
209 return params;
210 }
211
212
213
214
215
216
217
218 protected boolean storeContainsCRLs(CertStore certStore) {
219 Collection<? extends CRL> crls = null;
220 try {
221
222
223 crls = certStore.getCRLs(null);
224 } catch (CertStoreException e) {
225 log.error("Error examining cert store for CRL's, treating as if no CRL's present", e);
226 return false;
227 }
228 if (crls != null && !crls.isEmpty()) {
229 return true;
230 }
231 return false;
232 }
233
234
235
236
237
238
239
240 protected Integer getEffectiveVerificationDepth(PKIXValidationInformation validationInfo) {
241 Integer effectiveVerifyDepth = validationInfo.getVerificationDepth();
242 if (effectiveVerifyDepth == null) {
243 effectiveVerifyDepth = options.getDefaultVerificationDepth();
244 }
245 return effectiveVerifyDepth;
246 }
247
248
249
250
251
252
253
254
255 protected Set<TrustAnchor> getTrustAnchors(PKIXValidationInformation validationInfo) {
256 Collection<X509Certificate> validationCertificates = validationInfo.getCertificates();
257
258 log.trace("Constructing trust anchors for PKIX validation");
259 Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
260 for (X509Certificate cert : validationCertificates) {
261 trustAnchors.add(buildTrustAnchor(cert));
262 }
263
264 if (log.isTraceEnabled()) {
265 for (TrustAnchor anchor : trustAnchors) {
266 log.trace("TrustAnchor: {}", anchor.toString());
267 }
268 }
269
270 return trustAnchors;
271 }
272
273
274
275
276
277
278
279
280
281 protected TrustAnchor buildTrustAnchor(X509Certificate cert) {
282 return new TrustAnchor(cert, null);
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296 protected CertStore buildCertStore(PKIXValidationInformation validationInfo, X509Credential untrustedCredential)
297 throws GeneralSecurityException {
298
299 log.trace("Creating cert store to use during path validation");
300
301 log.trace("Adding entity certificate chain to cert store");
302 List<Object> storeMaterial = new ArrayList<Object>(untrustedCredential.getEntityCertificateChain());
303 if (log.isTraceEnabled()) {
304 for (X509Certificate cert : untrustedCredential.getEntityCertificateChain()) {
305 log.trace(String.format("Added X509Certificate from entity cert chain to cert store "
306 + "with subject name '%s' issued by '%s' with serial number '%s'",
307 x500DNHandler.getName(cert.getSubjectX500Principal()),
308 x500DNHandler.getName(cert.getIssuerX500Principal()),
309 cert.getSerialNumber().toString()));
310 }
311 }
312
313 Date now = new Date();
314
315 if (validationInfo.getCRLs() != null && !validationInfo.getCRLs().isEmpty()) {
316 log.trace("Processing CRL's from PKIX info set");
317 addCRLsToStoreMaterial(storeMaterial, validationInfo.getCRLs(), now);
318 }
319
320 if (untrustedCredential.getCRLs() != null && !untrustedCredential.getCRLs().isEmpty()
321 && options.isProcessCredentialCRLs()) {
322 log.trace("Processing CRL's from untrusted credential");
323 addCRLsToStoreMaterial(storeMaterial, untrustedCredential.getCRLs(), now);
324 }
325
326 return CertStore.getInstance("Collection", new CollectionCertStoreParameters(storeMaterial));
327 }
328
329
330
331
332
333
334
335
336
337 protected void addCRLsToStoreMaterial(List<Object> storeMaterial, Collection<X509CRL> crls, Date now) {
338 for (X509CRL crl : crls) {
339 boolean isEmpty = crl.getRevokedCertificates() == null || crl.getRevokedCertificates().isEmpty();
340 boolean isExpired = crl.getNextUpdate().before(now);
341 if (!isEmpty || options.isProcessEmptyCRLs()) {
342 if (!isExpired || options.isProcessExpiredCRLs()) {
343 storeMaterial.add(crl);
344 if (log.isTraceEnabled()) {
345 log.trace("Added X509CRL to cert store from issuer {} dated {}",
346 x500DNHandler.getName(crl.getIssuerX500Principal()), crl.getThisUpdate());
347 if (isEmpty) {
348 log.trace("X509CRL added to cert store from issuer {} dated {} was empty",
349 x500DNHandler.getName(crl.getIssuerX500Principal()), crl.getThisUpdate());
350 }
351 }
352 if (isExpired) {
353 log.warn("Using X509CRL from issuer {} with a nextUpdate in the past: {}",
354 x500DNHandler.getName(crl.getIssuerX500Principal()), crl.getNextUpdate());
355 }
356 } else {
357 if (log.isTraceEnabled()) {
358 log.trace("Expired X509CRL not added to cert store, from issuer {} nextUpdate {}",
359 x500DNHandler.getName(crl.getIssuerX500Principal()), crl.getNextUpdate());
360 }
361 }
362 } else {
363 if (log.isTraceEnabled()) {
364 log.trace("Empty X509CRL not added to cert store, from issuer {} dated {}",
365 x500DNHandler.getName(crl.getIssuerX500Principal()), crl.getThisUpdate());
366 }
367 }
368 }
369 }
370
371
372
373
374
375
376
377 private void logCertPathDebug(PKIXCertPathBuilderResult buildResult, X509Certificate targetCert) {
378 log.debug("Built valid PKIX cert path");
379 log.debug("Target certificate: {}", x500DNHandler.getName(targetCert.getSubjectX500Principal()));
380 for (Certificate cert : buildResult.getCertPath().getCertificates()) {
381 log.debug("CertPath certificate: {}", x500DNHandler.getName(((X509Certificate) cert)
382 .getSubjectX500Principal()));
383 }
384 TrustAnchor ta = buildResult.getTrustAnchor();
385 if (ta.getTrustedCert() != null) {
386 log.debug("TrustAnchor: {}", x500DNHandler.getName(ta.getTrustedCert().getSubjectX500Principal()));
387 } else if (ta.getCA() != null) {
388 log.debug("TrustAnchor: {}", x500DNHandler.getName(ta.getCA()));
389 } else {
390 log.debug("TrustAnchor: {}", ta.getCAName());
391 }
392 }
393
394 }