1 /*
   2  * Copyright 2002-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 
  26 package sun.security.validator;
  27 
  28 import java.util.*;
  29 
  30 import java.security.*;
  31 import java.security.cert.*;
  32 
  33 import javax.security.auth.x500.X500Principal;
  34 
  35 /**
  36  * Validator implementation built on the PKIX CertPath API. This
  37  * implementation will be emphasized going forward.<p>
  38  *
  39  * Note that the validate() implementation tries to use a PKIX validator
  40  * if that appears possible and a PKIX builder otherwise. This increases
  41  * performance and currently also leads to better exception messages
  42  * in case of failures.
  43  *
  44  * @author Andreas Sterbenz
  45  */
  46 public final class PKIXValidator extends Validator {
  47 
  48     // enable use of the validator if possible
  49     private final static boolean TRY_VALIDATOR = true;
  50 
  51     private final Set<X509Certificate> trustedCerts;
  52     private final PKIXBuilderParameters parameterTemplate;
  53     private int certPathLength = -1;
  54 
  55     // needed only for the validator
  56     private Map<X500Principal, X509Certificate> trustedSubjects;
  57     private CertificateFactory factory;
  58 
  59     private boolean plugin = false;
  60 
  61     PKIXValidator(String variant, Collection<X509Certificate> trustedCerts) {
  62         super(TYPE_PKIX, variant);
  63         if (trustedCerts instanceof Set) {
  64             this.trustedCerts = (Set<X509Certificate>)trustedCerts;
  65         } else {
  66             this.trustedCerts = new HashSet<X509Certificate>(trustedCerts);
  67         }
  68         Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
  69         for (X509Certificate cert : trustedCerts) {
  70             trustAnchors.add(new TrustAnchor(cert, null));
  71         }
  72         try {
  73             parameterTemplate = new PKIXBuilderParameters(trustAnchors, null);
  74         } catch (InvalidAlgorithmParameterException e) {
  75             throw new RuntimeException("Unexpected error: " + e.toString(), e);
  76         }
  77         setDefaultParameters(variant);
  78         initCommon();
  79     }
  80 
  81     PKIXValidator(String variant, PKIXBuilderParameters params) {
  82         super(TYPE_PKIX, variant);
  83         trustedCerts = new HashSet<X509Certificate>();
  84         for (TrustAnchor anchor : params.getTrustAnchors()) {
  85             X509Certificate cert = anchor.getTrustedCert();
  86             if (cert != null) {
  87                 trustedCerts.add(cert);
  88             }
  89         }
  90         parameterTemplate = params;
  91         initCommon();
  92     }
  93 
  94     private void initCommon() {
  95         if (TRY_VALIDATOR == false) {
  96             return;
  97         }
  98         trustedSubjects = new HashMap<X500Principal, X509Certificate>();
  99         for (X509Certificate cert : trustedCerts) {
 100             trustedSubjects.put(cert.getSubjectX500Principal(), cert);
 101         }
 102         try {
 103             factory = CertificateFactory.getInstance("X.509");
 104         } catch (CertificateException e) {
 105             throw new RuntimeException("Internal error", e);
 106         }
 107         plugin = variant.equals(VAR_PLUGIN_CODE_SIGNING);
 108     }
 109 
 110     public Collection<X509Certificate> getTrustedCertificates() {
 111         return trustedCerts;
 112     }
 113 
 114     /**
 115      * Returns the length of the last certification path that is validated by
 116      * CertPathValidator. This is intended primarily as a callback mechanism
 117      * for PKIXCertPathCheckers to determine the length of the certification
 118      * path that is being validated. It is necessary since engineValidate()
 119      * may modify the length of the path.
 120      *
 121      * @return the length of the last certification path passed to
 122      *   CertPathValidator.validate, or -1 if it has not been invoked yet
 123      */
 124     public int getCertPathLength() {
 125         return certPathLength;
 126     }
 127 
 128     /**
 129      * Set J2SE global default PKIX parameters. Currently, hardcoded to disable
 130      * revocation checking. In the future, this should be configurable.
 131      */
 132     private void setDefaultParameters(String variant) {
 133         parameterTemplate.setRevocationEnabled(false);
 134     }
 135 
 136     /**
 137      * Return the PKIX parameters used by this instance. An application may
 138      * modify the parameters but must make sure not to perform any concurrent
 139      * validations.
 140      */
 141     public PKIXBuilderParameters getParameters() {
 142         return parameterTemplate;
 143     }
 144 
 145     X509Certificate[] engineValidate(X509Certificate[] chain,
 146             Collection<X509Certificate> otherCerts, Object parameter)
 147             throws CertificateException {
 148         if ((chain == null) || (chain.length == 0)) {
 149             throw new CertificateException
 150                 ("null or zero-length certificate chain");
 151         }
 152         if (TRY_VALIDATOR) {
 153             // check if chain contains trust anchor
 154             for (int i = 0; i < chain.length; i++) {
 155                 if (trustedCerts.contains(chain[i])) {
 156                     if (i == 0) {
 157                         return new X509Certificate[] {chain[0]};
 158                     }
 159                     // Remove and call validator
 160                     X509Certificate[] newChain = new X509Certificate[i];
 161                     System.arraycopy(chain, 0, newChain, 0, i);
 162                     return doValidate(newChain);
 163                 }
 164             }
 165 
 166             // not self issued and apparently issued by trust anchor?
 167             X509Certificate last = chain[chain.length - 1];
 168             X500Principal issuer = last.getIssuerX500Principal();
 169             X500Principal subject = last.getSubjectX500Principal();
 170             if (trustedSubjects.containsKey(issuer) && !issuer.equals(subject)
 171                 && isSignatureValid(trustedSubjects.get(issuer), last)) {
 172                 return doValidate(chain);
 173             }
 174 
 175             // don't fallback to builder if called from plugin/webstart
 176             if (plugin) {
 177                 // Validate chain even if no trust anchor is found. This
 178                 // allows plugin/webstart to make sure the chain is
 179                 // otherwise valid
 180                 if (chain.length > 1) {
 181                     X509Certificate[] newChain =
 182                         new X509Certificate[chain.length-1];
 183                     System.arraycopy(chain, 0, newChain, 0, newChain.length);
 184                     // temporarily set last cert as sole trust anchor
 185                     PKIXBuilderParameters params =
 186                         (PKIXBuilderParameters) parameterTemplate.clone();
 187                     try {
 188                         params.setTrustAnchors
 189                             (Collections.singleton(new TrustAnchor
 190                                 (chain[chain.length-1], null)));
 191                     } catch (InvalidAlgorithmParameterException iape) {
 192                         // should never occur, but ...
 193                         throw new CertificateException(iape);
 194                     }
 195                     doValidate(newChain, params);
 196                 }
 197                 // if the rest of the chain is valid, throw exception
 198                 // indicating no trust anchor was found
 199                 throw new ValidatorException
 200                     (ValidatorException.T_NO_TRUST_ANCHOR);
 201             }
 202             // otherwise, fall back to builder
 203         }
 204 
 205         return doBuild(chain, otherCerts);
 206     }
 207 
 208     private boolean isSignatureValid(X509Certificate iss, X509Certificate sub) {
 209         if (plugin) {
 210             try {
 211                 sub.verify(iss.getPublicKey());
 212             } catch (Exception ex) {
 213                 return false;
 214             }
 215             return true;
 216         }
 217         return true; // only check if PLUGIN is set
 218     }
 219 
 220     private static X509Certificate[] toArray(CertPath path, TrustAnchor anchor)
 221             throws CertificateException {
 222         List<? extends java.security.cert.Certificate> list =
 223                                                 path.getCertificates();
 224         X509Certificate[] chain = new X509Certificate[list.size() + 1];
 225         list.toArray(chain);
 226         X509Certificate trustedCert = anchor.getTrustedCert();
 227         if (trustedCert == null) {
 228             throw new ValidatorException
 229                 ("TrustAnchor must be specified as certificate");
 230         }
 231         chain[chain.length - 1] = trustedCert;
 232         return chain;
 233     }
 234 
 235     /**
 236      * Set the check date (for debugging).
 237      */
 238     private void setDate(PKIXBuilderParameters params) {
 239         Date date = validationDate;
 240         if (date != null) {
 241             params.setDate(date);
 242         }
 243     }
 244 
 245     private X509Certificate[] doValidate(X509Certificate[] chain)
 246             throws CertificateException {
 247         PKIXBuilderParameters params =
 248             (PKIXBuilderParameters)parameterTemplate.clone();
 249         return doValidate(chain, params);
 250     }
 251 
 252     private X509Certificate[] doValidate(X509Certificate[] chain,
 253             PKIXBuilderParameters params) throws CertificateException {
 254         try {
 255             setDate(params);
 256 
 257             // do the validation
 258             CertPathValidator validator = CertPathValidator.getInstance("PKIX");
 259             CertPath path = factory.generateCertPath(Arrays.asList(chain));
 260             certPathLength = chain.length;
 261             PKIXCertPathValidatorResult result =
 262                 (PKIXCertPathValidatorResult)validator.validate(path, params);
 263 
 264             return toArray(path, result.getTrustAnchor());
 265         } catch (GeneralSecurityException e) {
 266             throw new ValidatorException
 267                 ("PKIX path validation failed: " + e.toString(), e);
 268         }
 269     }
 270 
 271     private X509Certificate[] doBuild(X509Certificate[] chain,
 272         Collection<X509Certificate> otherCerts) throws CertificateException {
 273 
 274         try {
 275             PKIXBuilderParameters params =
 276                 (PKIXBuilderParameters)parameterTemplate.clone();
 277             setDate(params);
 278 
 279             // setup target constraints
 280             X509CertSelector selector = new X509CertSelector();
 281             selector.setCertificate(chain[0]);
 282             params.setTargetCertConstraints(selector);
 283 
 284             // setup CertStores
 285             Collection<X509Certificate> certs =
 286                                         new ArrayList<X509Certificate>();
 287             certs.addAll(Arrays.asList(chain));
 288             if (otherCerts != null) {
 289                 certs.addAll(otherCerts);
 290             }
 291             CertStore store = CertStore.getInstance("Collection",
 292                                 new CollectionCertStoreParameters(certs));
 293             params.addCertStore(store);
 294 
 295             // do the build
 296             CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
 297             PKIXCertPathBuilderResult result =
 298                 (PKIXCertPathBuilderResult)builder.build(params);
 299 
 300             return toArray(result.getCertPath(), result.getTrustAnchor());
 301         } catch (GeneralSecurityException e) {
 302             throw new ValidatorException
 303                 ("PKIX path building failed: " + e.toString(), e);
 304         }
 305     }
 306 
 307 }