1 /*
   2  * Copyright (c) 2009, 2017, 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.provider.certpath;
  27 
  28 import java.security.AlgorithmConstraints;
  29 import java.security.CryptoPrimitive;
  30 import java.security.Timestamp;
  31 import java.security.cert.CertPathValidator;
  32 import java.util.Collection;
  33 import java.util.Collections;
  34 import java.util.Date;
  35 import java.util.Set;
  36 import java.util.EnumSet;
  37 import java.math.BigInteger;
  38 import java.security.PublicKey;
  39 import java.security.KeyFactory;
  40 import java.security.AlgorithmParameters;
  41 import java.security.GeneralSecurityException;
  42 import java.security.cert.Certificate;
  43 import java.security.cert.X509CRL;
  44 import java.security.cert.X509Certificate;
  45 import java.security.cert.PKIXCertPathChecker;
  46 import java.security.cert.TrustAnchor;
  47 import java.security.cert.CRLException;
  48 import java.security.cert.CertificateException;
  49 import java.security.cert.CertPathValidatorException;
  50 import java.security.cert.CertPathValidatorException.BasicReason;
  51 import java.security.cert.PKIXReason;
  52 import java.security.interfaces.DSAParams;
  53 import java.security.interfaces.DSAPublicKey;
  54 import java.security.spec.DSAPublicKeySpec;
  55 
  56 import sun.security.util.AnchorCertificates;
  57 import sun.security.util.ConstraintsParameters;
  58 import sun.security.util.Debug;
  59 import sun.security.util.DisabledAlgorithmConstraints;
  60 import sun.security.validator.Validator;
  61 import sun.security.x509.X509CertImpl;
  62 import sun.security.x509.X509CRLImpl;
  63 import sun.security.x509.AlgorithmId;
  64 
  65 /**
  66  * A {@code PKIXCertPathChecker} implementation to check whether a
  67  * specified certificate contains the required algorithm constraints.
  68  * <p>
  69  * Certificate fields such as the subject public key, the signature
  70  * algorithm, key usage, extended key usage, etc. need to conform to
  71  * the specified algorithm constraints.
  72  *
  73  * @see PKIXCertPathChecker
  74  * @see PKIXParameters
  75  */
  76 public final class AlgorithmChecker extends PKIXCertPathChecker {
  77     private static final Debug debug = Debug.getInstance("certpath");
  78 
  79     private final AlgorithmConstraints constraints;
  80     private final PublicKey trustedPubKey;
  81     private final Date pkixdate;
  82     private PublicKey prevPubKey;
  83     private final Timestamp jarTimestamp;
  84     private final String variant;
  85 
  86     private static final Set<CryptoPrimitive> SIGNATURE_PRIMITIVE_SET =
  87         Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
  88 
  89     private static final Set<CryptoPrimitive> KU_PRIMITIVE_SET =
  90         Collections.unmodifiableSet(EnumSet.of(
  91             CryptoPrimitive.SIGNATURE,
  92             CryptoPrimitive.KEY_ENCAPSULATION,
  93             CryptoPrimitive.PUBLIC_KEY_ENCRYPTION,
  94             CryptoPrimitive.KEY_AGREEMENT));
  95 
  96     private static final DisabledAlgorithmConstraints
  97         certPathDefaultConstraints = new DisabledAlgorithmConstraints(
  98             DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS);
  99 
 100     // If there is no "cacerts" keyword, then disable anchor checking
 101     private static final boolean publicCALimits =
 102             certPathDefaultConstraints.checkProperty("jdkCA");
 103 
 104     // If anchor checking enabled, this will be true if the trust anchor
 105     // has a match in the cacerts file
 106     private boolean trustedMatch = false;
 107 
 108     /**
 109      * Create a new {@code AlgorithmChecker} with the given algorithm
 110      * given {@code TrustAnchor} and {@code String} variant.
 111      *
 112      * @param anchor the trust anchor selected to validate the target
 113      *     certificate
 114      * @param variant is the Validator variants of the operation. A null value
 115      *                passed will set it to Validator.GENERIC.
 116      */
 117     public AlgorithmChecker(TrustAnchor anchor, String variant) {
 118         this(anchor, certPathDefaultConstraints, null, null, variant);
 119     }
 120 
 121     /**
 122      * Create a new {@code AlgorithmChecker} with the given
 123      * {@code AlgorithmConstraints}, {@code Timestamp}, and {@code String}
 124      * variant.
 125      *
 126      * Note that this constructor can initialize a variation of situations where
 127      * the AlgorithmConstraints, Timestamp, or Variant maybe known.
 128      *
 129      * @param constraints the algorithm constraints (or null)
 130      * @param jarTimestamp Timestamp passed for JAR timestamp constraint
 131      *                     checking. Set to null if not applicable.
 132      * @param variant is the Validator variants of the operation. A null value
 133      *                passed will set it to Validator.GENERIC.
 134      */
 135     public AlgorithmChecker(AlgorithmConstraints constraints,
 136             Timestamp jarTimestamp, String variant) {
 137         this(null, constraints, null, jarTimestamp, variant);
 138     }
 139 
 140     /**
 141      * Create a new {@code AlgorithmChecker} with the
 142      * given {@code TrustAnchor}, {@code AlgorithmConstraints},
 143      * {@code Timestamp}, and {@code String} variant.
 144      *
 145      * @param anchor the trust anchor selected to validate the target
 146      *     certificate
 147      * @param constraints the algorithm constraints (or null)
 148      * @param pkixdate The date specified by the PKIXParameters date.  If the
 149      *                 PKIXParameters is null, the current date is used.  This
 150      *                 should be null when jar files are being checked.
 151      * @param jarTimestamp Timestamp passed for JAR timestamp constraint
 152      *                     checking. Set to null if not applicable.
 153      * @param variant is the Validator variants of the operation. A null value
 154      *                passed will set it to Validator.GENERIC.
 155      */
 156     public AlgorithmChecker(TrustAnchor anchor,
 157             AlgorithmConstraints constraints, Date pkixdate,
 158             Timestamp jarTimestamp, String variant) {
 159 
 160         if (anchor != null) {
 161             if (anchor.getTrustedCert() != null) {
 162                 this.trustedPubKey = anchor.getTrustedCert().getPublicKey();
 163                 // Check for anchor certificate restrictions
 164                 trustedMatch = checkFingerprint(anchor.getTrustedCert());
 165                 if (trustedMatch && debug != null) {
 166                     debug.println("trustedMatch = true");
 167                 }
 168             } else {
 169                 this.trustedPubKey = anchor.getCAPublicKey();
 170             }
 171         } else {
 172             this.trustedPubKey = null;
 173             if (debug != null) {
 174                 debug.println("TrustAnchor is null, trustedMatch is false.");
 175             }
 176         }
 177 
 178         this.prevPubKey = this.trustedPubKey;
 179         this.constraints = (constraints == null ? certPathDefaultConstraints :
 180                 constraints);
 181         // If we are checking jar files, set pkixdate the same as the timestamp
 182         // for certificate checking
 183         this.pkixdate = (jarTimestamp != null ? jarTimestamp.getTimestamp() :
 184                 pkixdate);
 185         this.jarTimestamp = jarTimestamp;
 186         this.variant = (variant == null ? Validator.VAR_GENERIC : variant);
 187     }
 188 
 189     /**
 190      * Create a new {@code AlgorithmChecker} with the given {@code TrustAnchor},
 191      * {@code PKIXParameter} date, and {@code varient}
 192      *
 193      * @param anchor the trust anchor selected to validate the target
 194      *     certificate
 195      * @param pkixdate Date the constraints are checked against. The value is
 196      *             either the PKIXParameters date or null for the current date.
 197      * @param variant is the Validator variants of the operation. A null value
 198      *                passed will set it to Validator.GENERIC.
 199      */
 200     public AlgorithmChecker(TrustAnchor anchor, Date pkixdate, String variant) {
 201         this(anchor, certPathDefaultConstraints, pkixdate, null, variant);
 202     }
 203 
 204     // Check this 'cert' for restrictions in the AnchorCertificates
 205     // trusted certificates list
 206     private static boolean checkFingerprint(X509Certificate cert) {
 207         if (!publicCALimits) {
 208             return false;
 209         }
 210 
 211         if (debug != null) {
 212             debug.println("AlgorithmChecker.contains: " + cert.getSigAlgName());
 213         }
 214         return AnchorCertificates.contains(cert);
 215     }
 216 
 217     Timestamp getJarTimestamp() {
 218         return jarTimestamp;
 219     }
 220 
 221     @Override
 222     public void init(boolean forward) throws CertPathValidatorException {
 223         //  Note that this class does not support forward mode.
 224         if (!forward) {
 225             if (trustedPubKey != null) {
 226                 prevPubKey = trustedPubKey;
 227             } else {
 228                 prevPubKey = null;
 229             }
 230         } else {
 231             throw new
 232                 CertPathValidatorException("forward checking not supported");
 233         }
 234     }
 235 
 236     @Override
 237     public boolean isForwardCheckingSupported() {
 238         //  Note that as this class does not support forward mode, the method
 239         //  will always returns false.
 240         return false;
 241     }
 242 
 243     @Override
 244     public Set<String> getSupportedExtensions() {
 245         return null;
 246     }
 247 
 248     @Override
 249     public void check(Certificate cert,
 250             Collection<String> unresolvedCritExts)
 251             throws CertPathValidatorException {
 252 
 253         if (!(cert instanceof X509Certificate) || constraints == null) {
 254             // ignore the check for non-x.509 certificate or null constraints
 255             return;
 256         }
 257 
 258         // check the key usage and key size
 259         boolean[] keyUsage = ((X509Certificate) cert).getKeyUsage();
 260         if (keyUsage != null && keyUsage.length < 9) {
 261             throw new CertPathValidatorException(
 262                 "incorrect KeyUsage extension",
 263                 null, null, -1, PKIXReason.INVALID_KEY_USAGE);
 264         }
 265 
 266         X509CertImpl x509Cert;
 267         AlgorithmId algorithmId;
 268         try {
 269             x509Cert = X509CertImpl.toImpl((X509Certificate)cert);
 270             algorithmId = (AlgorithmId)x509Cert.get(X509CertImpl.SIG_ALG);
 271         } catch (CertificateException ce) {
 272             throw new CertPathValidatorException(ce);
 273         }
 274 
 275         AlgorithmParameters currSigAlgParams = algorithmId.getParameters();
 276         PublicKey currPubKey = cert.getPublicKey();
 277         String currSigAlg = ((X509Certificate)cert).getSigAlgName();
 278 
 279         // Check the signature algorithm and parameters against constraints.
 280         if (!constraints.permits(SIGNATURE_PRIMITIVE_SET, currSigAlg,
 281                 currSigAlgParams)) {
 282             throw new CertPathValidatorException(
 283                     "Algorithm constraints check failed on signature " +
 284                             "algorithm: " + currSigAlg, null, null, -1,
 285                     BasicReason.ALGORITHM_CONSTRAINED);
 286         }
 287 
 288         // Assume all key usage bits are set if key usage is not present
 289         Set<CryptoPrimitive> primitives = KU_PRIMITIVE_SET;
 290 
 291         if (keyUsage != null) {
 292                 primitives = EnumSet.noneOf(CryptoPrimitive.class);
 293 
 294             if (keyUsage[0] || keyUsage[1] || keyUsage[5] || keyUsage[6]) {
 295                 // keyUsage[0]: KeyUsage.digitalSignature
 296                 // keyUsage[1]: KeyUsage.nonRepudiation
 297                 // keyUsage[5]: KeyUsage.keyCertSign
 298                 // keyUsage[6]: KeyUsage.cRLSign
 299                 primitives.add(CryptoPrimitive.SIGNATURE);
 300             }
 301 
 302             if (keyUsage[2]) {      // KeyUsage.keyEncipherment
 303                 primitives.add(CryptoPrimitive.KEY_ENCAPSULATION);
 304             }
 305 
 306             if (keyUsage[3]) {      // KeyUsage.dataEncipherment
 307                 primitives.add(CryptoPrimitive.PUBLIC_KEY_ENCRYPTION);
 308             }
 309 
 310             if (keyUsage[4]) {      // KeyUsage.keyAgreement
 311                 primitives.add(CryptoPrimitive.KEY_AGREEMENT);
 312             }
 313 
 314             // KeyUsage.encipherOnly and KeyUsage.decipherOnly are
 315             // undefined in the absence of the keyAgreement bit.
 316 
 317             if (primitives.isEmpty()) {
 318                 throw new CertPathValidatorException(
 319                     "incorrect KeyUsage extension bits",
 320                     null, null, -1, PKIXReason.INVALID_KEY_USAGE);
 321             }
 322         }
 323 
 324         ConstraintsParameters cp =
 325                 new ConstraintsParameters((X509Certificate)cert,
 326                         trustedMatch, pkixdate, jarTimestamp, variant);
 327 
 328         // Check against local constraints if it is DisabledAlgorithmConstraints
 329         if (constraints instanceof DisabledAlgorithmConstraints) {
 330             ((DisabledAlgorithmConstraints)constraints).permits(currSigAlg, cp);
 331             // DisabledAlgorithmsConstraints does not check primitives, so key
 332             // additional key check.
 333 
 334         } else {
 335             // Perform the default constraints checking anyway.
 336             certPathDefaultConstraints.permits(currSigAlg, cp);
 337             // Call locally set constraints to check key with primitives.
 338             if (!constraints.permits(primitives, currPubKey)) {
 339                 throw new CertPathValidatorException(
 340                         "Algorithm constraints check failed on key " +
 341                                 currPubKey.getAlgorithm() + " with size of " +
 342                                 sun.security.util.KeyUtil.getKeySize(currPubKey) +
 343                                 "bits",
 344                         null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
 345             }
 346         }
 347 
 348         // If there is no previous key, set one and exit
 349         if (prevPubKey == null) {
 350             prevPubKey = currPubKey;
 351             return;
 352         }
 353 
 354         // Check with previous cert for signature algorithm and public key
 355         if (!constraints.permits(
 356                 SIGNATURE_PRIMITIVE_SET,
 357                 currSigAlg, prevPubKey, currSigAlgParams)) {
 358             throw new CertPathValidatorException(
 359                     "Algorithm constraints check failed on " +
 360                             "signature algorithm: " + currSigAlg,
 361                     null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
 362         }
 363 
 364         // Inherit key parameters from previous key
 365         if (PKIX.isDSAPublicKeyWithoutParams(currPubKey)) {
 366             // Inherit DSA parameters from previous key
 367             if (!(prevPubKey instanceof DSAPublicKey)) {
 368                 throw new CertPathValidatorException("Input key is not " +
 369                         "of a appropriate type for inheriting parameters");
 370             }
 371 
 372             DSAParams params = ((DSAPublicKey)prevPubKey).getParams();
 373             if (params == null) {
 374                 throw new CertPathValidatorException(
 375                         "Key parameters missing from public key.");
 376             }
 377 
 378             try {
 379                 BigInteger y = ((DSAPublicKey)currPubKey).getY();
 380                 KeyFactory kf = KeyFactory.getInstance("DSA");
 381                 DSAPublicKeySpec ks = new DSAPublicKeySpec(y, params.getP(),
 382                         params.getQ(), params.getG());
 383                 currPubKey = kf.generatePublic(ks);
 384             } catch (GeneralSecurityException e) {
 385                 throw new CertPathValidatorException("Unable to generate " +
 386                         "key with inherited parameters: " + e.getMessage(), e);
 387             }
 388         }
 389 
 390         // reset the previous public key
 391         prevPubKey = currPubKey;
 392     }
 393 
 394     /**
 395      * Try to set the trust anchor of the checker.
 396      * <p>
 397      * If there is no trust anchor specified and the checker has not started,
 398      * set the trust anchor.
 399      *
 400      * @param anchor the trust anchor selected to validate the target
 401      *     certificate
 402      */
 403     void trySetTrustAnchor(TrustAnchor anchor) {
 404         // Don't bother if the check has started or trust anchor has already
 405         // specified.
 406         if (prevPubKey == null) {
 407             if (anchor == null) {
 408                 throw new IllegalArgumentException(
 409                         "The trust anchor cannot be null");
 410             }
 411 
 412             // Don't bother to change the trustedPubKey.
 413             if (anchor.getTrustedCert() != null) {
 414                 prevPubKey = anchor.getTrustedCert().getPublicKey();
 415                 // Check for anchor certificate restrictions
 416                 trustedMatch = checkFingerprint(anchor.getTrustedCert());
 417                 if (trustedMatch && debug != null) {
 418                     debug.println("trustedMatch = true");
 419                 }
 420             } else {
 421                 prevPubKey = anchor.getCAPublicKey();
 422             }
 423         }
 424     }
 425 
 426     /**
 427      * Check the signature algorithm with the specified public key.
 428      *
 429      * @param key the public key to verify the CRL signature
 430      * @param crl the target CRL
 431      * @param variant is the Validator variants of the operation. A null value
 432      *                passed will set it to Validator.GENERIC.
 433      */
 434     static void check(PublicKey key, X509CRL crl, String variant)
 435                         throws CertPathValidatorException {
 436 
 437         X509CRLImpl x509CRLImpl = null;
 438         try {
 439             x509CRLImpl = X509CRLImpl.toImpl(crl);
 440         } catch (CRLException ce) {
 441             throw new CertPathValidatorException(ce);
 442         }
 443 
 444         AlgorithmId algorithmId = x509CRLImpl.getSigAlgId();
 445         check(key, algorithmId, variant);
 446     }
 447 
 448     /**
 449      * Check the signature algorithm with the specified public key.
 450      *
 451      * @param key the public key to verify the CRL signature
 452      * @param algorithmId signature algorithm Algorithm ID
 453      * @param variant is the Validator variants of the operation. A null value
 454      *                passed will set it to Validator.GENERIC.
 455      */
 456     static void check(PublicKey key, AlgorithmId algorithmId, String variant)
 457                         throws CertPathValidatorException {
 458         String sigAlgName = algorithmId.getName();
 459         AlgorithmParameters sigAlgParams = algorithmId.getParameters();
 460 
 461         certPathDefaultConstraints.permits(new ConstraintsParameters(
 462                 sigAlgName, sigAlgParams, key, variant));
 463     }
 464 }
 465