1 /* PKIXCertPathValidatorImpl.java -- PKIX certificate path validator.
2 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu
.java
.security
.provider
;
41 import gnu
.java
.security
.OID
;
42 import gnu
.java
.security
.x509
.GnuPKIExtension
;
43 import gnu
.java
.security
.x509
.PolicyNodeImpl
;
44 import gnu
.java
.security
.x509
.X509CRLSelectorImpl
;
45 import gnu
.java
.security
.x509
.X509CertSelectorImpl
;
46 import gnu
.java
.security
.x509
.ext
.BasicConstraints
;
47 import gnu
.java
.security
.x509
.ext
.CertificatePolicies
;
48 import gnu
.java
.security
.x509
.ext
.Extension
;
49 import gnu
.java
.security
.x509
.ext
.KeyUsage
;
50 import gnu
.java
.security
.x509
.ext
.PolicyConstraint
;
52 import java
.io
.IOException
;
53 import java
.security
.InvalidAlgorithmParameterException
;
54 import java
.security
.InvalidKeyException
;
55 import java
.security
.PublicKey
;
56 import java
.security
.cert
.CRL
;
57 import java
.security
.cert
.CertPath
;
58 import java
.security
.cert
.CertPathParameters
;
59 import java
.security
.cert
.CertPathValidatorException
;
60 import java
.security
.cert
.CertPathValidatorResult
;
61 import java
.security
.cert
.CertPathValidatorSpi
;
62 import java
.security
.cert
.CertStore
;
63 import java
.security
.cert
.CertStoreException
;
64 import java
.security
.cert
.CertificateException
;
65 import java
.security
.cert
.PKIXCertPathChecker
;
66 import java
.security
.cert
.PKIXCertPathValidatorResult
;
67 import java
.security
.cert
.PKIXParameters
;
68 import java
.security
.cert
.TrustAnchor
;
69 import java
.security
.cert
.X509CRL
;
70 import java
.security
.cert
.X509Certificate
;
71 import java
.security
.interfaces
.DSAParams
;
72 import java
.security
.interfaces
.DSAPublicKey
;
73 import java
.util
.Arrays
;
74 import java
.util
.Collection
;
75 import java
.util
.Collections
;
76 import java
.util
.Date
;
77 import java
.util
.HashSet
;
78 import java
.util
.Iterator
;
79 import java
.util
.LinkedList
;
80 import java
.util
.List
;
84 * An implementation of the Public Key Infrastructure's X.509
85 * certificate path validation algorithm.
87 * <p>See <a href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280:
88 * Internet X.509 Public Key Infrastructure Certificate and
89 * Certificate Revocation List (CRL) Profile</a>.
91 * @author Casey Marshall (rsdio@metastatic.org)
93 public class PKIXCertPathValidatorImpl
extends CertPathValidatorSpi
97 // -------------------------------------------------------------------------
99 private static final boolean DEBUG
= false;
100 private static void debug (String msg
)
102 System
.err
.print (">> PKIXCertPathValidatorImpl: ");
103 System
.err
.println (msg
);
106 public static final String ANY_POLICY
= "2.5.29.32.0";
109 // -------------------------------------------------------------------------
111 public PKIXCertPathValidatorImpl()
117 // -------------------------------------------------------------------------
119 public CertPathValidatorResult
engineValidate(CertPath path
,
120 CertPathParameters params
)
121 throws CertPathValidatorException
, InvalidAlgorithmParameterException
123 if (!(params
instanceof PKIXParameters
))
124 throw new InvalidAlgorithmParameterException("not a PKIXParameters object");
126 // First check if the certificate path is valid.
130 // (a) for all x in {1, ..., n-1}, the subject of certificate x is
131 // the issuer of certificate x+1;
133 // (b) for all x in {1, ..., n}, the certificate was valid at the
136 // Because this is the X.509 algorithm, we also check if all
137 // cerificates are of type X509Certificate.
139 PolicyNodeImpl rootNode
= new PolicyNodeImpl();
140 Set initPolicies
= ((PKIXParameters
) params
).getInitialPolicies();
141 rootNode
.setValidPolicy(ANY_POLICY
);
142 rootNode
.setCritical(false);
143 rootNode
.setDepth(0);
144 if (initPolicies
!= null)
145 rootNode
.addAllExpectedPolicies(initPolicies
);
147 rootNode
.addExpectedPolicy(ANY_POLICY
);
148 List checks
= ((PKIXParameters
) params
).getCertPathCheckers();
149 List l
= path
.getCertificates();
150 if (l
== null || l
.size() == 0)
151 throw new CertPathValidatorException();
152 X509Certificate
[] p
= null;
155 p
= (X509Certificate
[]) l
.toArray(new X509Certificate
[l
.size()]);
157 catch (ClassCastException cce
)
159 throw new CertPathValidatorException("invalid certificate path");
162 String sigProvider
= ((PKIXParameters
) params
).getSigProvider();
163 PublicKey prevKey
= null;
164 Date now
= ((PKIXParameters
) params
).getDate();
167 LinkedList policyConstraints
= new LinkedList();
168 for (int i
= p
.length
- 1; i
>= 0; i
--)
172 p
[i
].checkValidity(now
);
174 catch (CertificateException ce
)
176 throw new CertPathValidatorException(ce
.toString());
178 Set uce
= getCritExts(p
[i
]);
179 for (Iterator check
= checks
.iterator(); check
.hasNext(); )
183 ((PKIXCertPathChecker
) check
.next()).check(p
[i
], uce
);
190 PolicyConstraint constr
= null;
191 if (p
[i
] instanceof GnuPKIExtension
)
194 ((GnuPKIExtension
) p
[i
]).getExtension (PolicyConstraint
.ID
);
196 constr
= (PolicyConstraint
) pcx
.getValue();
200 byte[] pcx
= p
[i
].getExtensionValue (PolicyConstraint
.ID
.toString());
205 constr
= new PolicyConstraint (pcx
);
212 if (constr
!= null && constr
.getRequireExplicitPolicy() >= 0)
214 policyConstraints
.add (new int[]
215 { p
.length
-i
, constr
.getRequireExplicitPolicy() });
218 updatePolicyTree(p
[i
], rootNode
, p
.length
-i
, (PKIXParameters
) params
,
219 checkExplicitPolicy (p
.length
-i
, policyConstraints
));
221 // The rest of the tests involve this cert's relationship with the
222 // next in the path. If this cert is the end entity, we can stop.
227 PublicKey pubKey
= null;
230 pubKey
= p
[i
].getPublicKey();
231 if (pubKey
instanceof DSAPublicKey
)
233 DSAParams dsa
= ((DSAPublicKey
) pubKey
).getParams();
234 // If the DSA public key is missing its parameters, use those
235 // from the previous cert's key.
236 if (dsa
== null || dsa
.getP() == null || dsa
.getG() == null
237 || dsa
.getQ() == null)
240 throw new InvalidKeyException("DSA keys not chainable");
241 if (!(prevKey
instanceof DSAPublicKey
))
242 throw new InvalidKeyException("DSA keys not chainable");
243 dsa
= ((DSAPublicKey
) prevKey
).getParams();
244 pubKey
= new GnuDSAPublicKey(((DSAPublicKey
) pubKey
).getY(),
245 dsa
.getP(), dsa
.getQ(), dsa
.getG());
248 if (sigProvider
== null)
249 p
[i
-1].verify(pubKey
);
251 p
[i
-1].verify(pubKey
, sigProvider
);
256 throw new CertPathValidatorException(e
.toString());
258 if (!p
[i
].getSubjectDN().equals(p
[i
-1].getIssuerDN()))
259 throw new CertPathValidatorException("issuer DN mismatch");
260 boolean[] issuerUid
= p
[i
-1].getIssuerUniqueID();
261 boolean[] subjectUid
= p
[i
].getSubjectUniqueID();
262 if (issuerUid
!= null && subjectUid
!= null)
263 if (!Arrays
.equals(issuerUid
, subjectUid
))
264 throw new CertPathValidatorException("UID mismatch");
266 // Check the certificate against the revocation lists.
267 if (((PKIXParameters
) params
).isRevocationEnabled())
269 X509CRLSelectorImpl selector
= new X509CRLSelectorImpl();
272 selector
.addIssuerName(p
[i
].getSubjectDN());
274 catch (IOException ioe
)
276 throw new CertPathValidatorException("error selecting CRLs");
278 List certStores
= ((PKIXParameters
) params
).getCertStores();
279 List crls
= new LinkedList();
280 for (Iterator it
= certStores
.iterator(); it
.hasNext(); )
282 CertStore cs
= (CertStore
) it
.next();
285 Collection c
= cs
.getCRLs(selector
);
288 catch (CertStoreException cse
)
293 throw new CertPathValidatorException("no CRLs for issuer");
294 boolean certOk
= false;
295 for (Iterator it
= crls
.iterator(); it
.hasNext(); )
297 CRL crl
= (CRL
) it
.next();
298 if (!(crl
instanceof X509CRL
))
300 X509CRL xcrl
= (X509CRL
) crl
;
301 if (!checkCRL(xcrl
, p
, now
, p
[i
], pubKey
, certStores
))
303 if (xcrl
.isRevoked(p
[i
-1]))
304 throw new CertPathValidatorException("certificate is revoked");
309 throw new CertPathValidatorException("certificate's validity could not be determined");
312 rootNode
.setReadOnly();
314 // Now ensure that the first certificate in the chain was issued
315 // by a trust anchor.
316 Exception cause
= null;
317 Set anchors
= ((PKIXParameters
) params
).getTrustAnchors();
318 for (Iterator i
= anchors
.iterator(); i
.hasNext(); )
320 TrustAnchor anchor
= (TrustAnchor
) i
.next();
321 X509Certificate anchorCert
= null;
322 PublicKey anchorKey
= null;
323 if (anchor
.getTrustedCert() != null)
325 anchorCert
= anchor
.getTrustedCert();
326 anchorKey
= anchorCert
.getPublicKey();
329 anchorKey
= anchor
.getCAPublicKey();
330 if (anchorKey
== null)
334 if (anchorCert
!= null)
335 anchorCert
.checkValidity(now
);
336 p
[p
.length
-1].verify(anchorKey
);
337 if (anchorCert
!= null && anchorCert
.getBasicConstraints() >= 0
338 && anchorCert
.getBasicConstraints() < p
.length
)
341 if (((PKIXParameters
) params
).isRevocationEnabled())
343 X509CRLSelectorImpl selector
= new X509CRLSelectorImpl();
344 if (anchorCert
!= null)
347 selector
.addIssuerName(anchorCert
.getSubjectDN());
349 catch (IOException ioe
)
353 selector
.addIssuerName(anchor
.getCAName());
354 List certStores
= ((PKIXParameters
) params
).getCertStores();
355 List crls
= new LinkedList();
356 for (Iterator it
= certStores
.iterator(); it
.hasNext(); )
358 CertStore cs
= (CertStore
) it
.next();
361 Collection c
= cs
.getCRLs(selector
);
364 catch (CertStoreException cse
)
370 for (Iterator it
= crls
.iterator(); it
.hasNext(); )
372 CRL crl
= (CRL
) it
.next();
373 if (!(crl
instanceof X509CRL
))
375 X509CRL xcrl
= (X509CRL
) crl
;
378 xcrl
.verify(anchorKey
);
384 Date nextUpdate
= xcrl
.getNextUpdate();
385 if (nextUpdate
!= null && nextUpdate
.compareTo(now
) < 0)
387 if (xcrl
.isRevoked(p
[p
.length
-1]))
388 throw new CertPathValidatorException("certificate is revoked");
392 // The chain is valid; return the result.
393 return new PKIXCertPathValidatorResult(anchor
, rootNode
,
394 p
[0].getPublicKey());
396 catch (Exception ignored
)
403 // The path is not valid.
404 CertPathValidatorException cpve
=
405 new CertPathValidatorException("path validation failed");
407 cpve
.initCause (cause
);
412 // -------------------------------------------------------------------------
415 * Check if a given CRL is acceptable for checking the revocation status
416 * of certificates in the path being checked.
418 * <p>The CRL is accepted iff:</p>
421 * <li>The <i>nextUpdate</i> field (if present) is in the future.</li>
422 * <li>The CRL does not contain any unsupported critical extensions.</li>
423 * <li>The CRL is signed by one of the certificates in the path, or,</li>
424 * <li>The CRL is signed by the given public key and was issued by the
425 * public key's subject, or,</li>
426 * <li>The CRL is signed by a certificate in the given cert stores, and
427 * that cert is signed by one of the certificates in the path.</li>
430 * @param crl The CRL being checked.
431 * @param path The path this CRL is being checked against.
432 * @param now The value to use as 'now'.
433 * @param pubKeySubject The subject of the public key.
434 * @param pubKey The public key to check.
435 * @return True if the CRL is acceptable.
437 private static boolean checkCRL(X509CRL crl
, X509Certificate
[] path
, Date now
,
438 X509Certificate pubKeyCert
, PublicKey pubKey
,
441 Date nextUpdate
= crl
.getNextUpdate();
442 if (nextUpdate
!= null && nextUpdate
.compareTo(now
) < 0)
444 if (crl
.hasUnsupportedCriticalExtension())
446 for (int i
= 0; i
< path
.length
; i
++)
448 if (!path
[i
].getSubjectDN().equals(crl
.getIssuerDN()))
450 boolean[] keyUsage
= path
[i
].getKeyUsage();
451 if (keyUsage
!= null)
453 if (!keyUsage
[KeyUsage
.CRL_SIGN
])
458 crl
.verify(path
[i
].getPublicKey());
465 if (crl
.getIssuerDN().equals(pubKeyCert
.getSubjectDN()))
469 boolean[] keyUsage
= pubKeyCert
.getKeyUsage();
470 if (keyUsage
!= null)
472 if (!keyUsage
[KeyUsage
.CRL_SIGN
])
473 throw new Exception();
484 X509CertSelectorImpl select
= new X509CertSelectorImpl();
485 select
.addSubjectName(crl
.getIssuerDN());
486 List certs
= new LinkedList();
487 for (Iterator it
= certStores
.iterator(); it
.hasNext(); )
489 CertStore cs
= (CertStore
) it
.next();
492 certs
.addAll(cs
.getCertificates(select
));
494 catch (CertStoreException cse
)
498 for (Iterator it
= certs
.iterator(); it
.hasNext(); )
500 X509Certificate c
= (X509Certificate
) it
.next();
501 for (int i
= 0; i
< path
.length
; i
++)
503 if (!c
.getIssuerDN().equals(path
[i
].getSubjectDN()))
505 boolean[] keyUsage
= c
.getKeyUsage();
506 if (keyUsage
!= null)
508 if (!keyUsage
[KeyUsage
.CRL_SIGN
])
513 c
.verify(path
[i
].getPublicKey());
514 crl
.verify(c
.getPublicKey());
521 if (c
.getIssuerDN().equals(pubKeyCert
.getSubjectDN()))
524 crl
.verify(c
.getPublicKey());
534 private static Set
getCritExts(X509Certificate cert
)
536 HashSet s
= new HashSet();
537 if (cert
instanceof GnuPKIExtension
)
539 Collection exts
= ((GnuPKIExtension
) cert
).getExtensions();
540 for (Iterator it
= exts
.iterator(); it
.hasNext(); )
542 Extension ext
= (Extension
) it
.next();
543 if (ext
.isCritical() && !ext
.isSupported())
544 s
.add(ext
.getOid().toString());
548 s
.addAll(cert
.getCriticalExtensionOIDs());
553 * Perform a basic sanity check on the CA certificate at <code>index</code>.
555 private static void basicSanity(X509Certificate
[] path
, int index
)
556 throws CertPathValidatorException
558 X509Certificate cert
= path
[index
];
560 for (int i
= index
- 1; i
> 0; i
--)
562 if (!path
[i
].getIssuerDN().equals(path
[i
].getSubjectDN()))
566 if (cert
instanceof GnuPKIExtension
)
568 e
= ((GnuPKIExtension
) cert
).getExtension(BasicConstraints
.ID
);
574 e
= new Extension(cert
.getExtensionValue(BasicConstraints
.ID
.toString()));
581 throw new CertPathValidatorException("no basicConstraints");
582 BasicConstraints bc
= (BasicConstraints
) e
.getValue();
584 throw new CertPathValidatorException("certificate cannot be used to verify signatures");
585 if (bc
.getPathLengthConstraint() >= 0 && bc
.getPathLengthConstraint() < pathLen
)
586 throw new CertPathValidatorException("path is too long");
588 boolean[] keyUsage
= cert
.getKeyUsage();
589 if (keyUsage
!= null)
591 if (!keyUsage
[KeyUsage
.KEY_CERT_SIGN
])
592 throw new CertPathValidatorException("certificate cannot be used to sign certificates");
596 private static void updatePolicyTree(X509Certificate cert
, PolicyNodeImpl root
,
597 int depth
, PKIXParameters params
,
598 boolean explicitPolicy
)
599 throws CertPathValidatorException
601 if (DEBUG
) debug("updatePolicyTree depth == " + depth
);
602 Set nodes
= new HashSet();
603 LinkedList stack
= new LinkedList();
604 Iterator current
= null;
605 stack
.addLast(Collections
.singleton(root
).iterator());
608 current
= (Iterator
) stack
.removeLast();
609 while (current
.hasNext())
611 PolicyNodeImpl p
= (PolicyNodeImpl
) current
.next();
612 if (DEBUG
) debug("visiting node == " + p
);
613 if (p
.getDepth() == depth
- 1)
615 if (DEBUG
) debug("added node");
620 if (DEBUG
) debug("skipped node");
621 stack
.addLast(current
);
622 current
= p
.getChildren();
626 while (!stack
.isEmpty());
629 CertificatePolicies policies
= null;
630 List qualifierInfos
= null;
631 if (cert
instanceof GnuPKIExtension
)
633 e
= ((GnuPKIExtension
) cert
).getExtension(CertificatePolicies
.ID
);
635 policies
= (CertificatePolicies
) e
.getValue();
639 if (policies
!= null)
640 cp
= policies
.getPolicies();
642 cp
= Collections
.EMPTY_LIST
;
643 boolean match
= false;
644 if (DEBUG
) debug("nodes are == " + nodes
);
645 if (DEBUG
) debug("cert policies are == " + cp
);
646 for (Iterator it
= nodes
.iterator(); it
.hasNext(); )
648 PolicyNodeImpl parent
= (PolicyNodeImpl
) it
.next();
649 if (DEBUG
) debug("adding policies to " + parent
);
650 for (Iterator it2
= cp
.iterator(); it2
.hasNext(); )
652 OID policy
= (OID
) it2
.next();
653 if (DEBUG
) debug("trying to add policy == " + policy
);
654 if (policy
.toString().equals(ANY_POLICY
) &&
655 params
.isAnyPolicyInhibited())
657 PolicyNodeImpl child
= new PolicyNodeImpl();
658 child
.setValidPolicy(policy
.toString());
659 child
.addExpectedPolicy(policy
.toString());
660 if (parent
.getExpectedPolicies().contains(policy
.toString()))
662 parent
.addChild(child
);
665 else if (parent
.getExpectedPolicies().contains(ANY_POLICY
))
667 parent
.addChild(child
);
670 else if (ANY_POLICY
.equals (policy
.toString()))
672 parent
.addChild (child
);
675 if (match
&& policies
!= null)
677 List qualifiers
= policies
.getPolicyQualifierInfos (policy
);
678 if (qualifiers
!= null)
679 child
.addAllPolicyQualifiers (qualifiers
);
683 if (!match
&& (params
.isExplicitPolicyRequired() || explicitPolicy
))
684 throw new CertPathValidatorException("policy tree building failed");
687 private boolean checkExplicitPolicy (int depth
, List explicitPolicies
)
689 if (DEBUG
) debug ("checkExplicitPolicy depth=" + depth
);
690 for (Iterator it
= explicitPolicies
.iterator(); it
.hasNext(); )
692 int[] i
= (int[]) it
.next();
695 if (DEBUG
) debug (" caDepth=" + caDepth
+ " limit=" + limit
);
696 if (depth
- caDepth
>= limit
)