1 /*
   2  * Copyright (c) 2002, 2014, Oracle and/or its affiliates. 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * 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 import sun.security.action.GetBooleanAction;
  35 import sun.security.provider.certpath.AlgorithmChecker;
  36 
  37 /**
  38  * Validator implementation built on the PKIX CertPath API. This
  39  * implementation will be emphasized going forward.<p>
  40  * <p>
  41  * Note that the validate() implementation tries to use a PKIX validator
  42  * if that appears possible and a PKIX builder otherwise. This increases
  43  * performance and currently also leads to better exception messages
  44  * in case of failures.
  45  * <p>
  46  * {@code PKIXValidator} objects are immutable once they have been created.
  47  * Please DO NOT add methods that can change the state of an instance once
  48  * it has been created.
  49  *
  50  * @author Andreas Sterbenz
  51  */
  52 public final class PKIXValidator extends Validator {
  53 
  54     /**
  55      * Flag indicating whether to enable revocation check for the PKIX trust
  56      * manager. Typically, this will only work if the PKIX implementation
  57      * supports CRL distribution points as we do not manually setup CertStores.
  58      */
  59     private final static boolean checkTLSRevocation =
  60         AccessController.doPrivileged
  61             (new GetBooleanAction("com.sun.net.ssl.checkRevocation"));
  62 
  63     private final Set<X509Certificate> trustedCerts;
  64     private final PKIXBuilderParameters parameterTemplate;
  65     private int certPathLength = -1;
  66 
  67     // needed only for the validator
  68     private final Map<X500Principal, List<PublicKey>> trustedSubjects;
  69     private final CertificateFactory factory;
  70 
  71     private final boolean plugin;
  72 
  73     PKIXValidator(String variant, Collection<X509Certificate> trustedCerts) {
  74         super(TYPE_PKIX, variant);
  75         this.trustedCerts = (trustedCerts instanceof Set) ?
  76                             (Set<X509Certificate>)trustedCerts :
  77                             new HashSet<X509Certificate>(trustedCerts);
  78 
  79         Set<TrustAnchor> trustAnchors = new HashSet<>();
  80         for (X509Certificate cert : trustedCerts) {
  81             trustAnchors.add(new TrustAnchor(cert, null));
  82         }
  83 
  84         try {
  85             parameterTemplate = new PKIXBuilderParameters(trustAnchors, null);
  86             factory = CertificateFactory.getInstance("X.509");
  87         } catch (InvalidAlgorithmParameterException e) {
  88             throw new RuntimeException("Unexpected error: " + e.toString(), e);
  89         } catch (CertificateException e) {
  90             throw new RuntimeException("Internal error", e);
  91         }
  92 
  93         setDefaultParameters(variant);
  94         plugin = variant.equals(VAR_PLUGIN_CODE_SIGNING);
  95 
  96         trustedSubjects = setTrustedSubjects();
  97     }
  98 
  99     PKIXValidator(String variant, PKIXBuilderParameters params) {
 100         super(TYPE_PKIX, variant);
 101         trustedCerts = new HashSet<X509Certificate>();
 102         for (TrustAnchor anchor : params.getTrustAnchors()) {
 103             X509Certificate cert = anchor.getTrustedCert();
 104             if (cert != null) {
 105                 trustedCerts.add(cert);
 106             }
 107         }
 108         parameterTemplate = params;
 109 
 110         try {
 111             factory = CertificateFactory.getInstance("X.509");
 112         } catch (CertificateException e) {
 113             throw new RuntimeException("Internal error", e);
 114         }
 115 
 116         plugin = variant.equals(VAR_PLUGIN_CODE_SIGNING);
 117 
 118         trustedSubjects = setTrustedSubjects();
 119     }
 120 
 121     /**
 122      * Populate the trustedSubjects Map using the DN and public keys from
 123      * the list of trusted certificates
 124      *
 125      * @return Map containing each subject DN and one or more public keys
 126      *    tied to those DNs.
 127      */
 128     private Map<X500Principal, List<PublicKey>> setTrustedSubjects() {
 129         Map<X500Principal, List<PublicKey>> subjectMap = new HashMap<>();
 130 
 131         for (X509Certificate cert : trustedCerts) {
 132             X500Principal dn = cert.getSubjectX500Principal();
 133             List<PublicKey> keys;
 134             if (subjectMap.containsKey(dn)) {
 135                 keys = subjectMap.get(dn);
 136             } else {
 137                 keys = new ArrayList<PublicKey>();
 138                 subjectMap.put(dn, keys);
 139             }
 140             keys.add(cert.getPublicKey());
 141         }
 142 
 143         return subjectMap;
 144     }
 145 
 146     public Collection<X509Certificate> getTrustedCertificates() {
 147         return trustedCerts;
 148     }
 149 
 150     /**
 151      * Returns the length of the last certification path that is validated by
 152      * CertPathValidator. This is intended primarily as a callback mechanism
 153      * for PKIXCertPathCheckers to determine the length of the certification
 154      * path that is being validated. It is necessary since engineValidate()
 155      * may modify the length of the path.
 156      *
 157      * @return the length of the last certification path passed to
 158      *   CertPathValidator.validate, or -1 if it has not been invoked yet
 159      */
 160     public int getCertPathLength() { // mutable, should be private
 161         return certPathLength;
 162     }
 163 
 164     /**
 165      * Set J2SE global default PKIX parameters. Currently, hardcoded to disable
 166      * revocation checking. In the future, this should be configurable.
 167      */
 168     private void setDefaultParameters(String variant) {
 169         if ((variant == Validator.VAR_TLS_SERVER) ||
 170                 (variant == Validator.VAR_TLS_CLIENT)) {
 171             parameterTemplate.setRevocationEnabled(checkTLSRevocation);
 172         } else {
 173             parameterTemplate.setRevocationEnabled(false);
 174         }
 175     }
 176 
 177     /**
 178      * Return the PKIX parameters used by this instance. An application may
 179      * modify the parameters but must make sure not to perform any concurrent
 180      * validations.
 181      */
 182     public PKIXBuilderParameters getParameters() { // mutable, should be private
 183         return parameterTemplate;
 184     }
 185 
 186     @Override
 187     X509Certificate[] engineValidate(X509Certificate[] chain,
 188             Collection<X509Certificate> otherCerts,
 189             AlgorithmConstraints constraints,
 190             Object parameter) throws CertificateException {
 191         if ((chain == null) || (chain.length == 0)) {
 192             throw new CertificateException
 193                 ("null or zero-length certificate chain");
 194         }
 195 
 196         // add  new algorithm constraints checker
 197         PKIXBuilderParameters pkixParameters =
 198                     (PKIXBuilderParameters) parameterTemplate.clone();
 199         AlgorithmChecker algorithmChecker = null;
 200         if (constraints != null) {
 201             algorithmChecker = new AlgorithmChecker(constraints);
 202             pkixParameters.addCertPathChecker(algorithmChecker);
 203         }
 204 
 205         // check that chain is in correct order and check if chain contains
 206         // trust anchor
 207         X500Principal prevIssuer = null;
 208         for (int i = 0; i < chain.length; i++) {
 209             X509Certificate cert = chain[i];
 210             X500Principal dn = cert.getSubjectX500Principal();
 211             if (i != 0 && !dn.equals(prevIssuer)) {
 212                 // chain is not ordered correctly, call builder instead
 213                 return doBuild(chain, otherCerts, pkixParameters);
 214             }
 215 
 216             // Check if chain[i] is already trusted. It may be inside
 217             // trustedCerts, or has the same dn and public key as a cert
 218             // inside trustedCerts. The latter happens when a CA has
 219             // updated its cert with a stronger signature algorithm in JRE
 220             // but the weak one is still in circulation.
 221 
 222             if (trustedCerts.contains(cert) ||          // trusted cert
 223                     (trustedSubjects.containsKey(dn) && // replacing ...
 224                      trustedSubjects.get(dn).contains(  // ... weak cert
 225                         cert.getPublicKey()))) {
 226                 if (i == 0) {
 227                     return new X509Certificate[] {chain[0]};
 228                 }
 229                 // Remove and call validator on partial chain [0 .. i-1]
 230                 X509Certificate[] newChain = new X509Certificate[i];
 231                 System.arraycopy(chain, 0, newChain, 0, i);
 232                 return doValidate(newChain, pkixParameters);
 233             }
 234             prevIssuer = cert.getIssuerX500Principal();
 235         }
 236 
 237         // apparently issued by trust anchor?
 238         X509Certificate last = chain[chain.length - 1];
 239         X500Principal issuer = last.getIssuerX500Principal();
 240         X500Principal subject = last.getSubjectX500Principal();
 241         if (trustedSubjects.containsKey(issuer) &&
 242                 isSignatureValid(trustedSubjects.get(issuer), last)) {
 243             return doValidate(chain, pkixParameters);
 244         }
 245 
 246         // don't fallback to builder if called from plugin/webstart
 247         if (plugin) {
 248             // Validate chain even if no trust anchor is found. This
 249             // allows plugin/webstart to make sure the chain is
 250             // otherwise valid
 251             if (chain.length > 1) {
 252                 X509Certificate[] newChain =
 253                     new X509Certificate[chain.length-1];
 254                 System.arraycopy(chain, 0, newChain, 0, newChain.length);
 255 
 256                 // temporarily set last cert as sole trust anchor
 257                 try {
 258                     pkixParameters.setTrustAnchors
 259                         (Collections.singleton(new TrustAnchor
 260                             (chain[chain.length-1], null)));
 261                 } catch (InvalidAlgorithmParameterException iape) {
 262                     // should never occur, but ...
 263                     throw new CertificateException(iape);
 264                 }
 265                 doValidate(newChain, pkixParameters);
 266             }
 267             // if the rest of the chain is valid, throw exception
 268             // indicating no trust anchor was found
 269             throw new ValidatorException
 270                 (ValidatorException.T_NO_TRUST_ANCHOR);
 271         }
 272         // otherwise, fall back to builder
 273 
 274         return doBuild(chain, otherCerts, pkixParameters);
 275     }
 276 
 277     private boolean isSignatureValid(List<PublicKey> keys,
 278             X509Certificate sub) {
 279         if (plugin) {
 280             for (PublicKey key: keys) {
 281                 try {
 282                     sub.verify(key);
 283                     return true;
 284                 } catch (Exception ex) {
 285                     continue;
 286                 }
 287             }
 288             return false;
 289         }
 290         return true; // only check if PLUGIN is set
 291     }
 292 
 293     private static X509Certificate[] toArray(CertPath path, TrustAnchor anchor)
 294             throws CertificateException {
 295         List<? extends java.security.cert.Certificate> list =
 296                                                 path.getCertificates();
 297         X509Certificate[] chain = new X509Certificate[list.size() + 1];
 298         list.toArray(chain);
 299         X509Certificate trustedCert = anchor.getTrustedCert();
 300         if (trustedCert == null) {
 301             throw new ValidatorException
 302                 ("TrustAnchor must be specified as certificate");
 303         }
 304         chain[chain.length - 1] = trustedCert;
 305         return chain;
 306     }
 307 
 308     /**
 309      * Set the check date (for debugging).
 310      */
 311     private void setDate(PKIXBuilderParameters params) {
 312         @SuppressWarnings("deprecation")
 313         Date date = validationDate;
 314         if (date != null) {
 315             params.setDate(date);
 316         }
 317     }
 318 
 319     private X509Certificate[] doValidate(X509Certificate[] chain,
 320             PKIXBuilderParameters params) throws CertificateException {
 321         try {
 322             setDate(params);
 323 
 324             // do the validation
 325             CertPathValidator validator = CertPathValidator.getInstance("PKIX");
 326             CertPath path = factory.generateCertPath(Arrays.asList(chain));
 327             certPathLength = chain.length;
 328             PKIXCertPathValidatorResult result =
 329                 (PKIXCertPathValidatorResult)validator.validate(path, params);
 330 
 331             return toArray(path, result.getTrustAnchor());
 332         } catch (GeneralSecurityException e) {
 333             throw new ValidatorException
 334                 ("PKIX path validation failed: " + e.toString(), e);
 335         }
 336     }
 337 
 338     private X509Certificate[] doBuild(X509Certificate[] chain,
 339         Collection<X509Certificate> otherCerts,
 340         PKIXBuilderParameters params) throws CertificateException {
 341 
 342         try {
 343             setDate(params);
 344 
 345             // setup target constraints
 346             X509CertSelector selector = new X509CertSelector();
 347             selector.setCertificate(chain[0]);
 348             params.setTargetCertConstraints(selector);
 349 
 350             // setup CertStores
 351             Collection<X509Certificate> certs =
 352                                         new ArrayList<X509Certificate>();
 353             certs.addAll(Arrays.asList(chain));
 354             if (otherCerts != null) {
 355                 certs.addAll(otherCerts);
 356             }
 357             CertStore store = CertStore.getInstance("Collection",
 358                                 new CollectionCertStoreParameters(certs));
 359             params.addCertStore(store);
 360 
 361             // do the build
 362             CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
 363             PKIXCertPathBuilderResult result =
 364                 (PKIXCertPathBuilderResult)builder.build(params);
 365 
 366             return toArray(result.getCertPath(), result.getTrustAnchor());
 367         } catch (GeneralSecurityException e) {
 368             throw new ValidatorException
 369                 ("PKIX path building failed: " + e.toString(), e);
 370         }
 371     }
 372 }