blob: c3f2b678f2c71fde19a4dccdd33495d96d9c1e03 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.security.provider.certpath;
27
28import java.io.IOException;
29import java.security.GeneralSecurityException;
30import java.security.Principal;
31import java.security.cert.CertificateException;
32import java.security.cert.X509Certificate;
33import java.security.cert.CertPathValidatorException;
34import java.security.cert.CertStore;
35import java.security.cert.CertStoreException;
36import java.security.cert.PKIXBuilderParameters;
37import java.security.cert.PKIXCertPathChecker;
38import java.security.cert.PKIXParameters;
39import java.security.cert.TrustAnchor;
40import java.security.cert.X509CertSelector;
41import java.util.ArrayList;
42import java.util.Collection;
43import java.util.Collections;
44import java.util.Comparator;
45import java.util.HashSet;
46import java.util.Iterator;
47import java.util.List;
48import java.util.LinkedList;
49import java.util.Set;
50
51import javax.security.auth.x500.X500Principal;
52
53import sun.security.util.Debug;
54import sun.security.x509.Extension;
55import sun.security.x509.PKIXExtensions;
56import sun.security.x509.X500Name;
57import sun.security.x509.X509CertImpl;
58import sun.security.x509.PolicyMappingsExtension;
59
60/**
61 * This class represents a reverse builder, which is able to retrieve
62 * matching certificates from CertStores and verify a particular certificate
63 * against a ReverseState.
64 *
65 * @since 1.4
66 * @author Sean Mullan
67 * @author Yassir Elley
68 */
69
70class ReverseBuilder extends Builder {
71
72 private Debug debug = Debug.getInstance("certpath");
73
74 Set<String> initPolicies;
75
76 /**
77 * Initialize the builder with the input parameters.
78 *
79 * @param params the parameter set used to build a certification path
80 */
81 ReverseBuilder(PKIXBuilderParameters buildParams,
82 X500Principal targetSubjectDN) {
83
84 super(buildParams, targetSubjectDN);
85
86 Set<String> initialPolicies = buildParams.getInitialPolicies();
87 initPolicies = new HashSet<String>();
88 if (initialPolicies.isEmpty()) {
89 // if no initialPolicies are specified by user, set
90 // initPolicies to be anyPolicy by default
91 initPolicies.add(PolicyChecker.ANY_POLICY);
92 } else {
93 for (String policy : initialPolicies) {
94 initPolicies.add(policy);
95 }
96 }
97 }
98
99 /**
100 * Retrieves all certs from the specified CertStores that satisfy the
101 * requirements specified in the parameters and the current
102 * PKIX state (name constraints, policy constraints, etc).
103 *
104 * @param currentState the current state.
105 * Must be an instance of <code>ReverseState</code>
106 * @param certStores list of CertStores
107 */
108 Collection<X509Certificate> getMatchingCerts
109 (State currState, List<CertStore> certStores)
110 throws CertStoreException, CertificateException, IOException
111 {
112 ReverseState currentState = (ReverseState) currState;
113
114 if (debug != null)
115 debug.println("In ReverseBuilder.getMatchingCerts.");
116
117 /*
118 * The last certificate could be an EE or a CA certificate
119 * (we may be building a partial certification path or
120 * establishing trust in a CA).
121 *
122 * Try the EE certs before the CA certs. It will be more
123 * common to build a path to an end entity.
124 */
125 Collection<X509Certificate> certs =
126 getMatchingEECerts(currentState, certStores);
127 certs.addAll(getMatchingCACerts(currentState, certStores));
128
129 return certs;
130 }
131
132 /*
133 * Retrieves all end-entity certificates which satisfy constraints
134 * and requirements specified in the parameters and PKIX state.
135 */
136 private Collection<X509Certificate> getMatchingEECerts
137 (ReverseState currentState, List<CertStore> certStores)
138 throws CertStoreException, CertificateException, IOException {
139
140 /*
141 * Compose a CertSelector to filter out
142 * certs which do not satisfy requirements.
143 *
144 * First, retrieve clone of current target cert constraints,
145 * and then add more selection criteria based on current validation state.
146 */
147 X509CertSelector sel = (X509CertSelector) targetCertConstraints.clone();
148
149 /*
150 * Match on issuer (subject of previous cert)
151 */
152 sel.setIssuer(currentState.subjectDN);
153
154 /*
155 * Match on certificate validity date.
156 */
157 sel.setCertificateValid(date);
158
159 /*
160 * Policy processing optimizations
161 */
162 if (currentState.explicitPolicy == 0)
163 sel.setPolicy(getMatchingPolicies());
164
165 /*
166 * If previous cert has a subject key identifier extension,
167 * use it to match on authority key identifier extension.
168 */
169 /*if (currentState.subjKeyId != null) {
170 AuthorityKeyIdentifierExtension authKeyId = new AuthorityKeyIdentifierExtension(
171 (KeyIdentifier) currentState.subjKeyId.get(SubjectKeyIdentifierExtension.KEY_ID),
172 null, null);
173 sel.setAuthorityKeyIdentifier(authKeyId.getExtensionValue());
174 }*/
175
176 /*
177 * Require EE certs
178 */
179 sel.setBasicConstraints(-2);
180
181 /* Retrieve matching certs from CertStores */
182 HashSet<X509Certificate> eeCerts = new HashSet<X509Certificate>();
183 addMatchingCerts(sel, certStores, eeCerts, true);
184
185 if (debug != null) {
186 debug.println("ReverseBuilder.getMatchingEECerts got " + eeCerts.size()
187 + " certs.");
188 }
189 return eeCerts;
190 }
191
192 /*
193 * Retrieves all CA certificates which satisfy constraints
194 * and requirements specified in the parameters and PKIX state.
195 */
196 private Collection<X509Certificate> getMatchingCACerts
197 (ReverseState currentState, List<CertStore> certStores)
198 throws CertificateException, CertStoreException, IOException {
199
200 /*
201 * Compose a CertSelector to filter out
202 * certs which do not satisfy requirements.
203 */
204 X509CertSelector sel = new X509CertSelector();
205
206 /*
207 * Match on issuer (subject of previous cert)
208 */
209 sel.setIssuer(currentState.subjectDN);
210
211 /*
212 * Match on certificate validity date.
213 */
214 sel.setCertificateValid(date);
215
216 /*
217 * Match on target subject name (checks that current cert's
218 * name constraints permit it to certify target).
219 * (4 is the integer type for DIRECTORY name).
220 */
221 sel.addPathToName(4, targetCertConstraints.getSubjectAsBytes());
222
223 /*
224 * Policy processing optimizations
225 */
226 if (currentState.explicitPolicy == 0)
227 sel.setPolicy(getMatchingPolicies());
228
229 /*
230 * If previous cert has a subject key identifier extension,
231 * use it to match on authority key identifier extension.
232 */
233 /*if (currentState.subjKeyId != null) {
234 AuthorityKeyIdentifierExtension authKeyId = new AuthorityKeyIdentifierExtension(
235 (KeyIdentifier) currentState.subjKeyId.get(SubjectKeyIdentifierExtension.KEY_ID),
236 null, null);
237 sel.setAuthorityKeyIdentifier(authKeyId.getExtensionValue());
238 }*/
239
240 /*
241 * Require CA certs
242 */
243 sel.setBasicConstraints(0);
244
245 /* Retrieve matching certs from CertStores */
246 ArrayList<X509Certificate> reverseCerts =
247 new ArrayList<X509Certificate>();
248 addMatchingCerts(sel, certStores, reverseCerts, true);
249
250 /* Sort remaining certs using name constraints */
251 Collections.sort(reverseCerts, new PKIXCertComparator());
252
253 if (debug != null)
254 debug.println("ReverseBuilder.getMatchingCACerts got " +
255 reverseCerts.size() + " certs.");
256 return reverseCerts;
257 }
258
259 /*
260 * This inner class compares 2 PKIX certificates according to which
261 * should be tried first when building a path to the target. For
262 * now, the algorithm is to look at name constraints in each cert and those
263 * which constrain the path closer to the target should be
264 * ranked higher. Later, we may want to consider other components,
265 * such as key identifiers.
266 */
267 class PKIXCertComparator implements Comparator<X509Certificate> {
268
269 private Debug debug = Debug.getInstance("certpath");
270
271 public int compare(X509Certificate cert1, X509Certificate cert2) {
272
273 /*
274 * if either cert certifies the target, always
275 * put at head of list.
276 */
277 if (cert1.getSubjectX500Principal().equals(targetSubjectDN)) {
278 return -1;
279 }
280 if (cert2.getSubjectX500Principal().equals(targetSubjectDN)) {
281 return 1;
282 }
283
284 int targetDist1;
285 int targetDist2;
286 try {
287 X500Name targetSubjectName = X500Name.asX500Name(targetSubjectDN);
288 targetDist1 = Builder.targetDistance(
289 null, cert1, targetSubjectName);
290 targetDist2 = Builder.targetDistance(
291 null, cert2, targetSubjectName);
292 } catch (IOException e) {
293 if (debug != null) {
294 debug.println("IOException in call to Builder.targetDistance");
295 e.printStackTrace();
296 }
297 throw new ClassCastException
298 ("Invalid target subject distinguished name");
299 }
300
301 if (targetDist1 == targetDist2)
302 return 0;
303
304 if (targetDist1 == -1)
305 return 1;
306
307 if (targetDist1 < targetDist2)
308 return -1;
309
310 return 1;
311 }
312 }
313
314 /**
315 * Verifies a matching certificate.
316 *
317 * This method executes any of the validation steps in the PKIX path validation
318 * algorithm which were not satisfied via filtering out non-compliant
319 * certificates with certificate matching rules.
320 *
321 * If the last certificate is being verified (the one whose subject
322 * matches the target subject, then the steps in Section 6.1.4 of the
323 * Certification Path Validation algorithm are NOT executed,
324 * regardless of whether or not the last cert is an end-entity
325 * cert or not. This allows callers to certify CA certs as
326 * well as EE certs.
327 *
328 * @param cert the certificate to be verified
329 * @param currentState the current state against which the cert is verified
330 * @param certPathList the certPathList generated thus far
331 */
332 void verifyCert(X509Certificate cert, State currState,
333 List<X509Certificate> certPathList)
334 throws GeneralSecurityException
335 {
336 if (debug != null) {
337 debug.println("ReverseBuilder.verifyCert(SN: "
338 + Debug.toHexString(cert.getSerialNumber())
339 + "\n Subject: " + cert.getSubjectX500Principal() + ")");
340 }
341
342 ReverseState currentState = (ReverseState) currState;
343
344 /* we don't perform any validation of the trusted cert */
345 if (currentState.isInitial()) {
346 return;
347 }
348
349 /*
350 * check for looping - abort a loop if
351 * ((we encounter the same certificate twice) AND
352 * ((policyMappingInhibited = true) OR (no policy mapping
353 * extensions can be found between the occurences of the same
354 * certificate)))
355 * in order to facilitate the check to see if there are
356 * any policy mapping extensions found between the occurences
357 * of the same certificate, we reverse the certpathlist first
358 */
359 if ((certPathList != null) && (!certPathList.isEmpty())) {
360 List<X509Certificate> reverseCertList =
361 new ArrayList<X509Certificate>();
362 for (X509Certificate c : certPathList) {
363 reverseCertList.add(0, c);
364 }
365
366 boolean policyMappingFound = false;
367 for (X509Certificate cpListCert : reverseCertList) {
368 X509CertImpl cpListCertImpl = X509CertImpl.toImpl(cpListCert);
369 PolicyMappingsExtension policyMappingsExt =
370 cpListCertImpl.getPolicyMappingsExtension();
371 if (policyMappingsExt != null) {
372 policyMappingFound = true;
373 }
374 if (debug != null)
375 debug.println("policyMappingFound = " + policyMappingFound);
376 if (cert.equals(cpListCert)){
377 if ((buildParams.isPolicyMappingInhibited()) ||
378 (!policyMappingFound)){
379 if (debug != null)
380 debug.println("loop detected!!");
381 throw new CertPathValidatorException("loop detected");
382 }
383 }
384 }
385 }
386
387 /* check if target cert */
388 boolean finalCert = cert.getSubjectX500Principal().equals(targetSubjectDN);
389
390 /* check if CA cert */
391 boolean caCert = (cert.getBasicConstraints() != -1 ? true : false);
392
393 /* if there are more certs to follow, verify certain constraints */
394 if (!finalCert) {
395
396 /* check if CA cert */
397 if (!caCert)
398 throw new CertPathValidatorException("cert is NOT a CA cert");
399
400 /* If the certificate was not self-issued, verify that
401 * remainingCerts is greater than zero
402 */
403 if ((currentState.remainingCACerts <= 0) && !X509CertImpl.isSelfIssued(cert)) {
404 throw new CertPathValidatorException
405 ("pathLenConstraint violated, path too long");
406 }
407
408 /*
409 * Check keyUsage extension (only if CA cert and not final cert)
410 */
411 KeyChecker.verifyCAKeyUsage(cert);
412
413 } else {
414
415 /*
416 * If final cert, check that it satisfies specified target
417 * constraints
418 */
419 if (targetCertConstraints.match(cert) == false) {
420 throw new CertPathValidatorException("target certificate " +
421 "constraints check failed");
422 }
423 }
424
425 /*
426 * Check revocation.
427 */
428 if (buildParams.isRevocationEnabled()) {
429
430 currentState.crlChecker.check(cert,
431 currentState.pubKey,
432 currentState.crlSign);
433 }
434
435 /* Check name constraints if this is not a self-issued cert */
436 if (finalCert || !X509CertImpl.isSelfIssued(cert)){
437 if (currentState.nc != null){
438 try {
439 if (!currentState.nc.verify(cert)){
440 throw new CertPathValidatorException
441 ("name constraints check failed");
442 }
443 } catch (IOException ioe){
444 throw new CertPathValidatorException(ioe);
445 }
446 }
447 }
448
449 /*
450 * Check policy
451 */
452 X509CertImpl certImpl = X509CertImpl.toImpl(cert);
453 currentState.rootNode = PolicyChecker.processPolicies
454 (currentState.certIndex, initPolicies,
455 currentState.explicitPolicy, currentState.policyMapping,
456 currentState.inhibitAnyPolicy,
457 buildParams.getPolicyQualifiersRejected(), currentState.rootNode,
458 certImpl, finalCert);
459
460 /*
461 * Check CRITICAL private extensions
462 */
463 Set<String> unresolvedCritExts = cert.getCriticalExtensionOIDs();
464 if (unresolvedCritExts == null) {
465 unresolvedCritExts = Collections.<String>emptySet();
466 }
467 for (PKIXCertPathChecker checker : currentState.userCheckers) {
468 checker.check(cert, unresolvedCritExts);
469 }
470 /*
471 * Look at the remaining extensions and remove any ones we have
472 * already checked. If there are any left, throw an exception!
473 */
474 if (!unresolvedCritExts.isEmpty()) {
475 unresolvedCritExts.remove(PKIXExtensions.BasicConstraints_Id.toString());
476 unresolvedCritExts.remove(PKIXExtensions.NameConstraints_Id.toString());
477 unresolvedCritExts.remove(PKIXExtensions.CertificatePolicies_Id.toString());
478 unresolvedCritExts.remove(PKIXExtensions.PolicyMappings_Id.toString());
479 unresolvedCritExts.remove(PKIXExtensions.PolicyConstraints_Id.toString());
480 unresolvedCritExts.remove(PKIXExtensions.InhibitAnyPolicy_Id.toString());
481 unresolvedCritExts.remove(PKIXExtensions.SubjectAlternativeName_Id.toString());
482 unresolvedCritExts.remove(PKIXExtensions.KeyUsage_Id.toString());
483 unresolvedCritExts.remove(PKIXExtensions.ExtendedKeyUsage_Id.toString());
484
485 if (!unresolvedCritExts.isEmpty())
486 throw new CertificateException("Unrecognized critical extension(s)");
487 }
488
489 /*
490 * Check signature.
491 */
492 if (buildParams.getSigProvider() != null) {
493 cert.verify(currentState.pubKey, buildParams.getSigProvider());
494 } else {
495 cert.verify(currentState.pubKey);
496 }
497 }
498
499 /**
500 * Verifies whether the input certificate completes the path.
501 * This checks whether the cert is the target certificate.
502 *
503 * @param cert the certificate to test
504 * @return a boolean value indicating whether the cert completes the path.
505 */
506 boolean isPathCompleted(X509Certificate cert) {
507 return cert.getSubjectX500Principal().equals(targetSubjectDN);
508 }
509
510 /** Adds the certificate to the certPathList
511 *
512 * @param cert the certificate to be added
513 * @param certPathList the certification path list
514 */
515 void addCertToPath(X509Certificate cert,
516 LinkedList<X509Certificate> certPathList) {
517 certPathList.addLast(cert);
518 }
519
520 /** Removes final certificate from the certPathList
521 *
522 * @param certPathList the certification path list
523 */
524 void removeFinalCertFromPath(LinkedList<X509Certificate> certPathList) {
525 certPathList.removeLast();
526 }
527}