1 /* PKIXCertPathValidatorImpl.java -- PKIX certificate path validator.
2 Copyright (C) 2004, 2005, 2006, 2010 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.Configuration;
42 import gnu.java.security.OID;
43 import gnu.java.security.Registry;
44 import gnu.java.security.key.dss.DSSPublicKey;
45 import gnu.java.security.x509.GnuPKIExtension;
46 import gnu.java.security.x509.PolicyNodeImpl;
47 import gnu.java.security.x509.X509CRLSelectorImpl;
48 import gnu.java.security.x509.X509CertSelectorImpl;
49 import gnu.java.security.x509.ext.BasicConstraints;
50 import gnu.java.security.x509.ext.CertificatePolicies;
51 import gnu.java.security.x509.ext.Extension;
52 import gnu.java.security.x509.ext.KeyUsage;
53 import gnu.java.security.x509.ext.PolicyConstraint;
55 import java.io.IOException;
56 import java.security.InvalidAlgorithmParameterException;
57 import java.security.InvalidKeyException;
58 import java.security.PublicKey;
59 import java.security.cert.CRL;
60 import java.security.cert.CertPath;
61 import java.security.cert.CertPathParameters;
62 import java.security.cert.CertPathValidatorException;
63 import java.security.cert.CertPathValidatorResult;
64 import java.security.cert.CertPathValidatorSpi;
65 import java.security.cert.CertStore;
66 import java.security.cert.CertStoreException;
67 import java.security.cert.CertificateException;
68 import java.security.cert.PKIXCertPathChecker;
69 import java.security.cert.PKIXCertPathValidatorResult;
70 import java.security.cert.PKIXParameters;
71 import java.security.cert.TrustAnchor;
72 import java.security.cert.X509CRL;
73 import java.security.cert.X509Certificate;
74 import java.security.interfaces.DSAParams;
75 import java.security.interfaces.DSAPublicKey;
76 import java.util.Arrays;
77 import java.util.Collection;
78 import java.util.Collections;
79 import java.util.Date;
80 import java.util.HashSet;
81 import java.util.Iterator;
82 import java.util.LinkedList;
83 import java.util.List;
85 import java.util.logging.Logger;
88 * An implementation of the Public Key Infrastructure's X.509 certificate path
89 * validation algorithm.
91 * See <a href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280: Internet X.509
92 * Public Key Infrastructure Certificate and Certificate Revocation List (CRL)
95 * @author Casey Marshall (rsdio@metastatic.org)
97 public class PKIXCertPathValidatorImpl
98 extends CertPathValidatorSpi
100 private static final Logger log = Configuration.DEBUG ?
101 Logger.getLogger(PKIXCertPathValidatorImpl.class.getName()) : null;
103 public static final String ANY_POLICY = "2.5.29.32.0";
105 public PKIXCertPathValidatorImpl()
110 public CertPathValidatorResult engineValidate(CertPath path,
111 CertPathParameters params)
112 throws CertPathValidatorException, InvalidAlgorithmParameterException
114 if (! (params instanceof PKIXParameters))
115 throw new InvalidAlgorithmParameterException("not a PKIXParameters object");
116 // First check if the certificate path is valid.
120 // (a) for all x in {1, ..., n-1}, the subject of certificate x is
121 // the issuer of certificate x+1;
123 // (b) for all x in {1, ..., n}, the certificate was valid at the
126 // Because this is the X.509 algorithm, we also check if all
127 // cerificates are of type X509Certificate.
128 PolicyNodeImpl rootNode = new PolicyNodeImpl();
129 Set initPolicies = ((PKIXParameters) params).getInitialPolicies();
130 rootNode.setValidPolicy(ANY_POLICY);
131 rootNode.setCritical(false);
132 rootNode.setDepth(0);
133 if (initPolicies != null)
134 rootNode.addAllExpectedPolicies(initPolicies);
136 rootNode.addExpectedPolicy(ANY_POLICY);
137 List checks = ((PKIXParameters) params).getCertPathCheckers();
138 List l = path.getCertificates();
139 if (l == null || l.size() == 0)
140 throw new CertPathValidatorException();
141 X509Certificate[] p = null;
144 p = (X509Certificate[]) l.toArray(new X509Certificate[l.size()]);
146 catch (ClassCastException cce)
148 throw new CertPathValidatorException("invalid certificate path");
150 String sigProvider = ((PKIXParameters) params).getSigProvider();
151 PublicKey prevKey = null;
152 Date now = ((PKIXParameters) params).getDate();
155 LinkedList policyConstraints = new LinkedList();
156 for (int i = p.length - 1; i >= 0; i--)
160 p[i].checkValidity(now);
162 catch (CertificateException ce)
164 throw new CertPathValidatorException(ce.toString());
166 Set uce = getCritExts(p[i]);
167 for (Iterator check = checks.iterator(); check.hasNext();)
171 ((PKIXCertPathChecker) check.next()).check(p[i], uce);
177 PolicyConstraint constr = null;
178 if (p[i] instanceof GnuPKIExtension)
180 Extension pcx = ((GnuPKIExtension) p[i]).getExtension(PolicyConstraint.ID);
182 constr = (PolicyConstraint) pcx.getValue();
186 byte[] pcx = p[i].getExtensionValue(PolicyConstraint.ID.toString());
191 constr = new PolicyConstraint(pcx);
198 if (constr != null && constr.getRequireExplicitPolicy() >= 0)
199 policyConstraints.add(new int[] { p.length - i,
200 constr.getRequireExplicitPolicy() });
201 updatePolicyTree(p[i], rootNode, p.length - i, (PKIXParameters) params,
202 checkExplicitPolicy(p.length - i, policyConstraints));
203 // The rest of the tests involve this cert's relationship with the
204 // next in the path. If this cert is the end entity, we can stop.
209 PublicKey pubKey = null;
212 pubKey = p[i].getPublicKey();
213 if (pubKey instanceof DSAPublicKey)
215 DSAParams dsa = ((DSAPublicKey) pubKey).getParams();
216 // If the DSA public key is missing its parameters, use those
217 // from the previous cert's key.
218 if (dsa == null || dsa.getP() == null || dsa.getG() == null
219 || dsa.getQ() == null)
222 throw new InvalidKeyException("DSA keys not chainable");
223 if (! (prevKey instanceof DSAPublicKey))
224 throw new InvalidKeyException("DSA keys not chainable");
225 dsa = ((DSAPublicKey) prevKey).getParams();
226 pubKey = new DSSPublicKey(Registry.X509_ENCODING_ID,
227 dsa.getP(), dsa.getQ(),
229 ((DSAPublicKey) pubKey).getY());
232 if (sigProvider == null)
233 p[i - 1].verify(pubKey);
235 p[i - 1].verify(pubKey, sigProvider);
240 throw new CertPathValidatorException(e.toString());
242 if (! p[i].getSubjectDN().equals(p[i - 1].getIssuerDN()))
243 throw new CertPathValidatorException("issuer DN mismatch");
244 boolean[] issuerUid = p[i - 1].getIssuerUniqueID();
245 boolean[] subjectUid = p[i].getSubjectUniqueID();
246 if (issuerUid != null && subjectUid != null)
247 if (! Arrays.equals(issuerUid, subjectUid))
248 throw new CertPathValidatorException("UID mismatch");
250 // Check the certificate against the revocation lists.
251 if (((PKIXParameters) params).isRevocationEnabled())
253 X509CRLSelectorImpl selector = new X509CRLSelectorImpl();
256 selector.addIssuerName(p[i].getSubjectDN());
258 catch (IOException ioe)
260 throw new CertPathValidatorException("error selecting CRLs");
262 List certStores = ((PKIXParameters) params).getCertStores();
263 List crls = new LinkedList();
264 for (Iterator it = certStores.iterator(); it.hasNext();)
266 CertStore cs = (CertStore) it.next();
269 Collection c = cs.getCRLs(selector);
272 catch (CertStoreException cse)
277 throw new CertPathValidatorException("no CRLs for issuer");
278 boolean certOk = false;
279 for (Iterator it = crls.iterator(); it.hasNext();)
281 CRL crl = (CRL) it.next();
282 if (! (crl instanceof X509CRL))
284 X509CRL xcrl = (X509CRL) crl;
285 if (! checkCRL(xcrl, p, now, p[i], pubKey, certStores))
287 if (xcrl.isRevoked(p[i - 1]))
288 throw new CertPathValidatorException("certificate is revoked");
293 throw new CertPathValidatorException(
294 "certificate's validity could not be determined");
297 rootNode.setReadOnly();
298 // Now ensure that the first certificate in the chain was issued
299 // by a trust anchor.
300 Exception cause = null;
301 Set anchors = ((PKIXParameters) params).getTrustAnchors();
302 for (Iterator i = anchors.iterator(); i.hasNext();)
304 TrustAnchor anchor = (TrustAnchor) i.next();
305 X509Certificate anchorCert = null;
306 PublicKey anchorKey = null;
307 if (anchor.getTrustedCert() != null)
309 anchorCert = anchor.getTrustedCert();
310 anchorKey = anchorCert.getPublicKey();
313 anchorKey = anchor.getCAPublicKey();
314 if (anchorKey == null)
318 if (anchorCert != null)
319 anchorCert.checkValidity(now);
320 p[p.length - 1].verify(anchorKey);
321 if (anchorCert != null && anchorCert.getBasicConstraints() >= 0
322 && anchorCert.getBasicConstraints() < p.length)
325 if (((PKIXParameters) params).isRevocationEnabled())
327 X509CRLSelectorImpl selector = new X509CRLSelectorImpl();
328 if (anchorCert != null)
331 selector.addIssuerName(anchorCert.getSubjectDN());
333 catch (IOException ioe)
337 selector.addIssuerName(anchor.getCAName());
338 List certStores = ((PKIXParameters) params).getCertStores();
339 List crls = new LinkedList();
340 for (Iterator it = certStores.iterator(); it.hasNext();)
342 CertStore cs = (CertStore) it.next();
345 Collection c = cs.getCRLs(selector);
348 catch (CertStoreException cse)
354 for (Iterator it = crls.iterator(); it.hasNext();)
356 CRL crl = (CRL) it.next();
357 if (! (crl instanceof X509CRL))
359 X509CRL xcrl = (X509CRL) crl;
362 xcrl.verify(anchorKey);
368 Date nextUpdate = xcrl.getNextUpdate();
369 if (nextUpdate != null && nextUpdate.compareTo(now) < 0)
371 if (xcrl.isRevoked(p[p.length - 1]))
372 throw new CertPathValidatorException("certificate is revoked");
375 // The chain is valid; return the result.
376 return new PKIXCertPathValidatorResult(anchor, rootNode,
377 p[0].getPublicKey());
379 catch (Exception ignored)
385 // The path is not valid.
386 CertPathValidatorException cpve =
387 new CertPathValidatorException("path validation failed");
389 cpve.initCause(cause);
394 * Check if a given CRL is acceptable for checking the revocation status of
395 * certificates in the path being checked.
397 * The CRL is accepted iff:
399 * <li>The <i>nextUpdate</i> field (if present) is in the future.</li>
400 * <li>The CRL does not contain any unsupported critical extensions.</li>
401 * <li>The CRL is signed by one of the certificates in the path, or,</li>
402 * <li>The CRL is signed by the given public key and was issued by the public
403 * key's subject, or,</li>
404 * <li>The CRL is signed by a certificate in the given cert stores, and that
405 * cert is signed by one of the certificates in the path.</li>
408 * @param crl The CRL being checked.
409 * @param path The path this CRL is being checked against.
410 * @param now The value to use as 'now'.
411 * @param pubKeyCert The certificate authenticating the public key.
412 * @param pubKey The public key to check.
413 * @return True if the CRL is acceptable.
415 private static boolean checkCRL(X509CRL crl, X509Certificate[] path,
416 Date now, X509Certificate pubKeyCert,
417 PublicKey pubKey, List certStores)
419 Date nextUpdate = crl.getNextUpdate();
420 if (nextUpdate != null && nextUpdate.compareTo(now) < 0)
422 if (crl.hasUnsupportedCriticalExtension())
424 for (int i = 0; i < path.length; i++)
426 if (! path[i].getSubjectDN().equals(crl.getIssuerDN()))
428 boolean[] keyUsage = path[i].getKeyUsage();
429 if (keyUsage != null)
431 if (! keyUsage[KeyUsage.CRL_SIGN])
436 crl.verify(path[i].getPublicKey());
443 if (crl.getIssuerDN().equals(pubKeyCert.getSubjectDN()))
447 boolean[] keyUsage = pubKeyCert.getKeyUsage();
448 if (keyUsage != null)
450 if (! keyUsage[KeyUsage.CRL_SIGN])
451 throw new Exception();
462 X509CertSelectorImpl select = new X509CertSelectorImpl();
463 select.addSubjectName(crl.getIssuerDN());
464 List certs = new LinkedList();
465 for (Iterator it = certStores.iterator(); it.hasNext();)
467 CertStore cs = (CertStore) it.next();
470 certs.addAll(cs.getCertificates(select));
472 catch (CertStoreException cse)
476 for (Iterator it = certs.iterator(); it.hasNext();)
478 X509Certificate c = (X509Certificate) it.next();
479 for (int i = 0; i < path.length; i++)
481 if (! c.getIssuerDN().equals(path[i].getSubjectDN()))
483 boolean[] keyUsage = c.getKeyUsage();
484 if (keyUsage != null)
486 if (! keyUsage[KeyUsage.CRL_SIGN])
491 c.verify(path[i].getPublicKey());
492 crl.verify(c.getPublicKey());
499 if (c.getIssuerDN().equals(pubKeyCert.getSubjectDN()))
502 crl.verify(c.getPublicKey());
512 private static Set getCritExts(X509Certificate cert)
514 HashSet s = new HashSet();
515 if (cert instanceof GnuPKIExtension)
517 Collection exts = ((GnuPKIExtension) cert).getExtensions();
518 for (Iterator it = exts.iterator(); it.hasNext();)
520 Extension ext = (Extension) it.next();
521 if (ext.isCritical() && ! ext.isSupported())
522 s.add(ext.getOid().toString());
526 s.addAll(cert.getCriticalExtensionOIDs());
531 * Perform a basic sanity check on the CA certificate at <code>index</code>.
533 private static void basicSanity(X509Certificate[] path, int index)
534 throws CertPathValidatorException
536 X509Certificate cert = path[index];
538 for (int i = index - 1; i > 0; i--)
540 if (! path[i].getIssuerDN().equals(path[i].getSubjectDN()))
544 if (cert instanceof GnuPKIExtension)
546 e = ((GnuPKIExtension) cert).getExtension(BasicConstraints.ID);
552 e = new Extension(cert.getExtensionValue(BasicConstraints.ID.toString()));
559 throw new CertPathValidatorException("no basicConstraints");
560 BasicConstraints bc = (BasicConstraints) e.getValue();
562 throw new CertPathValidatorException(
563 "certificate cannot be used to verify signatures");
564 if (bc.getPathLengthConstraint() >= 0
565 && bc.getPathLengthConstraint() < pathLen)
566 throw new CertPathValidatorException("path is too long");
568 boolean[] keyUsage = cert.getKeyUsage();
569 if (keyUsage != null)
571 if (! keyUsage[KeyUsage.KEY_CERT_SIGN])
572 throw new CertPathValidatorException(
573 "certificate cannot be used to sign certificates");
577 private static void updatePolicyTree(X509Certificate cert,
578 PolicyNodeImpl root, int depth,
579 PKIXParameters params,
580 boolean explicitPolicy)
581 throws CertPathValidatorException
583 if (Configuration.DEBUG)
584 log.fine("updatePolicyTree depth == " + depth);
585 Set nodes = new HashSet();
586 LinkedList stack = new LinkedList();
587 Iterator current = null;
588 stack.addLast(Collections.singleton(root).iterator());
591 current = (Iterator) stack.removeLast();
592 while (current.hasNext())
594 PolicyNodeImpl p = (PolicyNodeImpl) current.next();
595 if (Configuration.DEBUG)
596 log.fine("visiting node == " + p);
597 if (p.getDepth() == depth - 1)
599 if (Configuration.DEBUG)
600 log.fine("added node");
605 if (Configuration.DEBUG)
606 log.fine("skipped node");
607 stack.addLast(current);
608 current = p.getChildren();
612 while (! stack.isEmpty());
615 CertificatePolicies policies = null;
616 List qualifierInfos = null;
617 if (cert instanceof GnuPKIExtension)
619 e = ((GnuPKIExtension) cert).getExtension(CertificatePolicies.ID);
621 policies = (CertificatePolicies) e.getValue();
625 if (policies != null)
626 cp = policies.getPolicies();
628 cp = Collections.EMPTY_LIST;
629 boolean match = false;
630 if (Configuration.DEBUG)
632 log.fine("nodes are == " + nodes);
633 log.fine("cert policies are == " + cp);
635 for (Iterator it = nodes.iterator(); it.hasNext();)
637 PolicyNodeImpl parent = (PolicyNodeImpl) it.next();
638 if (Configuration.DEBUG)
639 log.fine("adding policies to " + parent);
640 for (Iterator it2 = cp.iterator(); it2.hasNext();)
642 OID policy = (OID) it2.next();
643 if (Configuration.DEBUG)
644 log.fine("trying to add policy == " + policy);
645 if (policy.toString().equals(ANY_POLICY)
646 && params.isAnyPolicyInhibited())
648 PolicyNodeImpl child = new PolicyNodeImpl();
649 child.setValidPolicy(policy.toString());
650 child.addExpectedPolicy(policy.toString());
651 if (parent.getExpectedPolicies().contains(policy.toString()))
653 parent.addChild(child);
656 else if (parent.getExpectedPolicies().contains(ANY_POLICY))
658 parent.addChild(child);
661 else if (ANY_POLICY.equals(policy.toString()))
663 parent.addChild(child);
666 if (match && policies != null)
668 List qualifiers = policies.getPolicyQualifierInfos(policy);
669 if (qualifiers != null)
670 child.addAllPolicyQualifiers(qualifiers);
674 if (! match && (params.isExplicitPolicyRequired() || explicitPolicy))
675 throw new CertPathValidatorException("policy tree building failed");
678 private boolean checkExplicitPolicy(int depth, List explicitPolicies)
680 if (Configuration.DEBUG)
681 log.fine("checkExplicitPolicy depth=" + depth);
682 for (Iterator it = explicitPolicies.iterator(); it.hasNext();)
684 int[] i = (int[]) it.next();
687 if (Configuration.DEBUG)
688 log.fine(" caDepth=" + caDepth + " limit=" + limit);
689 if (depth - caDepth >= limit)