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