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