1 /*
   2  * Copyright 2002-2010 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                 X500Principal dn = chain[i].getSubjectX500Principal();
 156                 if (trustedSubjects.containsKey(dn)
 157                         && trustedSubjects.get(dn).getPublicKey()
 158                             .equals(chain[i].getPublicKey())) {
 159                     if (i == 0) {
 160                         return new X509Certificate[] {chain[0]};
 161                     }
 162                     // Remove and call validator
 163                     X509Certificate[] newChain = new X509Certificate[i];
 164                     System.arraycopy(chain, 0, newChain, 0, i);
 165                     return doValidate(newChain);
 166                 }
 167             }
 168 
 169             // not self issued and apparently issued by trust anchor?
 170             X509Certificate last = chain[chain.length - 1];
 171             X500Principal issuer = last.getIssuerX500Principal();
 172             X500Principal subject = last.getSubjectX500Principal();
 173             if (trustedSubjects.containsKey(issuer) && !issuer.equals(subject)
 174                 && isSignatureValid(trustedSubjects.get(issuer), last)) {
 175                 return doValidate(chain);
 176             }
 177 
 178             // don't fallback to builder if called from plugin/webstart
 179             if (plugin) {
 180                 // Validate chain even if no trust anchor is found. This
 181                 // allows plugin/webstart to make sure the chain is
 182                 // otherwise valid
 183                 if (chain.length > 1) {
 184                     X509Certificate[] newChain =
 185                         new X509Certificate[chain.length-1];
 186                     System.arraycopy(chain, 0, newChain, 0, newChain.length);
 187                     // temporarily set last cert as sole trust anchor
 188                     PKIXBuilderParameters params =
 189                         (PKIXBuilderParameters) parameterTemplate.clone();
 190                     try {
 191                         params.setTrustAnchors
 192                             (Collections.singleton(new TrustAnchor
 193                                 (chain[chain.length-1], null)));
 194                     } catch (InvalidAlgorithmParameterException iape) {
 195                         // should never occur, but ...
 196                         throw new CertificateException(iape);
 197                     }
 198                     doValidate(newChain, params);
 199                 }
 200                 // if the rest of the chain is valid, throw exception
 201                 // indicating no trust anchor was found
 202                 throw new ValidatorException
 203                     (ValidatorException.T_NO_TRUST_ANCHOR);
 204             }
 205             // otherwise, fall back to builder
 206         }
 207 
 208         return doBuild(chain, otherCerts);
 209     }
 210 
 211     private boolean isSignatureValid(X509Certificate iss, X509Certificate sub) {
 212         if (plugin) {
 213             try {
 214                 sub.verify(iss.getPublicKey());
 215             } catch (Exception ex) {
 216                 return false;
 217             }
 218             return true;
 219         }
 220         return true; // only check if PLUGIN is set
 221     }
 222 
 223     private static X509Certificate[] toArray(CertPath path, TrustAnchor anchor)
 224             throws CertificateException {
 225         List<? extends java.security.cert.Certificate> list =
 226                                                 path.getCertificates();
 227         X509Certificate[] chain = new X509Certificate[list.size() + 1];
 228         list.toArray(chain);
 229         X509Certificate trustedCert = anchor.getTrustedCert();
 230         if (trustedCert == null) {
 231             throw new ValidatorException
 232                 ("TrustAnchor must be specified as certificate");
 233         }
 234         chain[chain.length - 1] = trustedCert;
 235         return chain;
 236     }
 237 
 238     /**
 239      * Set the check date (for debugging).
 240      */
 241     private void setDate(PKIXBuilderParameters params) {
 242         Date date = validationDate;
 243         if (date != null) {
 244             params.setDate(date);
 245         }
 246     }
 247 
 248     private X509Certificate[] doValidate(X509Certificate[] chain)
 249             throws CertificateException {
 250         PKIXBuilderParameters params =
 251             (PKIXBuilderParameters)parameterTemplate.clone();
 252         return doValidate(chain, params);
 253     }
 254 
 255     private X509Certificate[] doValidate(X509Certificate[] chain,
 256             PKIXBuilderParameters params) throws CertificateException {
 257         try {
 258             setDate(params);
 259 
 260             // do the validation
 261             CertPathValidator validator = CertPathValidator.getInstance("PKIX");
 262             CertPath path = factory.generateCertPath(Arrays.asList(chain));
 263             certPathLength = chain.length;
 264             PKIXCertPathValidatorResult result =
 265                 (PKIXCertPathValidatorResult)validator.validate(path, params);
 266 
 267             return toArray(path, result.getTrustAnchor());
 268         } catch (GeneralSecurityException e) {
 269             throw new ValidatorException
 270                 ("PKIX path validation failed: " + e.toString(), e);
 271         }
 272     }
 273 
 274     private X509Certificate[] doBuild(X509Certificate[] chain,
 275         Collection<X509Certificate> otherCerts) throws CertificateException {
 276 
 277         try {
 278             PKIXBuilderParameters params =
 279                 (PKIXBuilderParameters)parameterTemplate.clone();
 280             setDate(params);
 281 
 282             // setup target constraints
 283             X509CertSelector selector = new X509CertSelector();
 284             selector.setCertificate(chain[0]);
 285             params.setTargetCertConstraints(selector);
 286 
 287             // setup CertStores
 288             Collection<X509Certificate> certs =
 289                                         new ArrayList<X509Certificate>();
 290             certs.addAll(Arrays.asList(chain));
 291             if (otherCerts != null) {
 292                 certs.addAll(otherCerts);
 293             }
 294             CertStore store = CertStore.getInstance("Collection",
 295                                 new CollectionCertStoreParameters(certs));
 296             params.addCertStore(store);
 297 
 298             // do the build
 299             CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
 300             PKIXCertPathBuilderResult result =
 301                 (PKIXCertPathBuilderResult)builder.build(params);
 302 
 303             return toArray(result.getCertPath(), result.getTrustAnchor());
 304         } catch (GeneralSecurityException e) {
 305             throw new ValidatorException
 306                 ("PKIX path building failed: " + e.toString(), e);
 307         }
 308     }
 309 
 310 }