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     @Override
 218     public void init(boolean forward) throws CertPathValidatorException {
 219         //  Note that this class does not support forward mode.
 220         if (!forward) {
 221             if (trustedPubKey != null) {
 222                 prevPubKey = trustedPubKey;
 223             } else {
 224                 prevPubKey = null;
 225             }
 226         } else {
 227             throw new
 228                 CertPathValidatorException("forward checking not supported");
 229         }
 230     }
 231 
 232     @Override
 233     public boolean isForwardCheckingSupported() {
 234         //  Note that as this class does not support forward mode, the method
 235         //  will always returns false.
 236         return false;
 237     }
 238 
 239     @Override
 240     public Set<String> getSupportedExtensions() {
 241         return null;
 242     }
 243 
 244     @Override
 245     public void check(Certificate cert,
 246             Collection<String> unresolvedCritExts)
 247             throws CertPathValidatorException {
 248 
 249         if (!(cert instanceof X509Certificate) || constraints == null) {
 250             // ignore the check for non-x.509 certificate or null constraints
 251             return;
 252         }
 253 
 254         // check the key usage and key size
 255         boolean[] keyUsage = ((X509Certificate) cert).getKeyUsage();
 256         if (keyUsage != null && keyUsage.length < 9) {
 257             throw new CertPathValidatorException(
 258                 "incorrect KeyUsage extension",
 259                 null, null, -1, PKIXReason.INVALID_KEY_USAGE);
 260         }
 261 
 262         X509CertImpl x509Cert;
 263         AlgorithmId algorithmId;
 264         try {
 265             x509Cert = X509CertImpl.toImpl((X509Certificate)cert);
 266             algorithmId = (AlgorithmId)x509Cert.get(X509CertImpl.SIG_ALG);
 267         } catch (CertificateException ce) {
 268             throw new CertPathValidatorException(ce);
 269         }
 270 
 271         AlgorithmParameters currSigAlgParams = algorithmId.getParameters();
 272         PublicKey currPubKey = cert.getPublicKey();
 273         String currSigAlg = ((X509Certificate)cert).getSigAlgName();
 274 
 275         // Check the signature algorithm and parameters against constraints.
 276         if (!constraints.permits(SIGNATURE_PRIMITIVE_SET, currSigAlg,
 277                 currSigAlgParams)) {
 278             throw new CertPathValidatorException(
 279                     "Algorithm constraints check failed on signature " +
 280                             "algorithm: " + currSigAlg, null, null, -1,
 281                     BasicReason.ALGORITHM_CONSTRAINED);
 282         }
 283 
 284         // Assume all key usage bits are set if key usage is not present
 285         Set<CryptoPrimitive> primitives = KU_PRIMITIVE_SET;
 286 
 287         if (keyUsage != null) {
 288                 primitives = EnumSet.noneOf(CryptoPrimitive.class);
 289 
 290             if (keyUsage[0] || keyUsage[1] || keyUsage[5] || keyUsage[6]) {
 291                 // keyUsage[0]: KeyUsage.digitalSignature
 292                 // keyUsage[1]: KeyUsage.nonRepudiation
 293                 // keyUsage[5]: KeyUsage.keyCertSign
 294                 // keyUsage[6]: KeyUsage.cRLSign
 295                 primitives.add(CryptoPrimitive.SIGNATURE);
 296             }
 297 
 298             if (keyUsage[2]) {      // KeyUsage.keyEncipherment
 299                 primitives.add(CryptoPrimitive.KEY_ENCAPSULATION);
 300             }
 301 
 302             if (keyUsage[3]) {      // KeyUsage.dataEncipherment
 303                 primitives.add(CryptoPrimitive.PUBLIC_KEY_ENCRYPTION);
 304             }
 305 
 306             if (keyUsage[4]) {      // KeyUsage.keyAgreement
 307                 primitives.add(CryptoPrimitive.KEY_AGREEMENT);
 308             }
 309 
 310             // KeyUsage.encipherOnly and KeyUsage.decipherOnly are
 311             // undefined in the absence of the keyAgreement bit.
 312 
 313             if (primitives.isEmpty()) {
 314                 throw new CertPathValidatorException(
 315                     "incorrect KeyUsage extension bits",
 316                     null, null, -1, PKIXReason.INVALID_KEY_USAGE);
 317             }
 318         }
 319 
 320         ConstraintsParameters cp =
 321                 new ConstraintsParameters((X509Certificate)cert,
 322                         trustedMatch, pkixdate, jarTimestamp, variant);
 323 
 324         // Check against local constraints if it is DisabledAlgorithmConstraints
 325         if (constraints instanceof DisabledAlgorithmConstraints) {
 326             ((DisabledAlgorithmConstraints)constraints).permits(currSigAlg, cp);
 327             // DisabledAlgorithmsConstraints does not check primitives, so key
 328             // additional key check.
 329 
 330         } else {
 331             // Perform the default constraints checking anyway.
 332             certPathDefaultConstraints.permits(currSigAlg, cp);
 333             // Call locally set constraints to check key with primitives.
 334             if (!constraints.permits(primitives, currPubKey)) {
 335                 throw new CertPathValidatorException(
 336                         "Algorithm constraints check failed on key " +
 337                                 currPubKey.getAlgorithm() + " with size of " +
 338                                 sun.security.util.KeyUtil.getKeySize(currPubKey) +
 339                                 "bits",
 340                         null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
 341             }
 342         }
 343 
 344         // If there is no previous key, set one and exit
 345         if (prevPubKey == null) {
 346             prevPubKey = currPubKey;
 347             return;
 348         }
 349 
 350         // Check with previous cert for signature algorithm and public key
 351         if (!constraints.permits(
 352                 SIGNATURE_PRIMITIVE_SET,
 353                 currSigAlg, prevPubKey, currSigAlgParams)) {
 354             throw new CertPathValidatorException(
 355                     "Algorithm constraints check failed on " +
 356                             "signature algorithm: " + currSigAlg,
 357                     null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
 358         }
 359 
 360         // Inherit key parameters from previous key
 361         if (PKIX.isDSAPublicKeyWithoutParams(currPubKey)) {
 362             // Inherit DSA parameters from previous key
 363             if (!(prevPubKey instanceof DSAPublicKey)) {
 364                 throw new CertPathValidatorException("Input key is not " +
 365                         "of a appropriate type for inheriting parameters");
 366             }
 367 
 368             DSAParams params = ((DSAPublicKey)prevPubKey).getParams();
 369             if (params == null) {
 370                 throw new CertPathValidatorException(
 371                         "Key parameters missing from public key.");
 372             }
 373 
 374             try {
 375                 BigInteger y = ((DSAPublicKey)currPubKey).getY();
 376                 KeyFactory kf = KeyFactory.getInstance("DSA");
 377                 DSAPublicKeySpec ks = new DSAPublicKeySpec(y, params.getP(),
 378                         params.getQ(), params.getG());
 379                 currPubKey = kf.generatePublic(ks);
 380             } catch (GeneralSecurityException e) {
 381                 throw new CertPathValidatorException("Unable to generate " +
 382                         "key with inherited parameters: " + e.getMessage(), e);
 383             }
 384         }
 385 
 386         // reset the previous public key
 387         prevPubKey = currPubKey;
 388     }
 389 
 390     /**
 391      * Try to set the trust anchor of the checker.
 392      * <p>
 393      * If there is no trust anchor specified and the checker has not started,
 394      * set the trust anchor.
 395      *
 396      * @param anchor the trust anchor selected to validate the target
 397      *     certificate
 398      */
 399     void trySetTrustAnchor(TrustAnchor anchor) {
 400         // Don't bother if the check has started or trust anchor has already
 401         // specified.
 402         if (prevPubKey == null) {
 403             if (anchor == null) {
 404                 throw new IllegalArgumentException(
 405                         "The trust anchor cannot be null");
 406             }
 407 
 408             // Don't bother to change the trustedPubKey.
 409             if (anchor.getTrustedCert() != null) {
 410                 prevPubKey = anchor.getTrustedCert().getPublicKey();
 411                 // Check for anchor certificate restrictions
 412                 trustedMatch = checkFingerprint(anchor.getTrustedCert());
 413                 if (trustedMatch && debug != null) {
 414                     debug.println("trustedMatch = true");
 415                 }
 416             } else {
 417                 prevPubKey = anchor.getCAPublicKey();
 418             }
 419         }
 420     }
 421 
 422     /**
 423      * Check the signature algorithm with the specified public key.
 424      *
 425      * @param key the public key to verify the CRL signature
 426      * @param crl the target CRL
 427      * @param variant is the Validator variants of the operation. A null value
 428      *                passed will set it to Validator.GENERIC.
 429      */
 430     static void check(PublicKey key, X509CRL crl, String variant)
 431                         throws CertPathValidatorException {
 432 
 433         X509CRLImpl x509CRLImpl = null;
 434         try {
 435             x509CRLImpl = X509CRLImpl.toImpl(crl);
 436         } catch (CRLException ce) {
 437             throw new CertPathValidatorException(ce);
 438         }
 439 
 440         AlgorithmId algorithmId = x509CRLImpl.getSigAlgId();
 441         check(key, algorithmId, variant);
 442     }
 443 
 444     /**
 445      * Check the signature algorithm with the specified public key.
 446      *
 447      * @param key the public key to verify the CRL signature
 448      * @param algorithmId signature algorithm Algorithm ID
 449      * @param variant is the Validator variants of the operation. A null value
 450      *                passed will set it to Validator.GENERIC.
 451      */
 452     static void check(PublicKey key, AlgorithmId algorithmId, String variant)
 453                         throws CertPathValidatorException {
 454         String sigAlgName = algorithmId.getName();
 455         AlgorithmParameters sigAlgParams = algorithmId.getParameters();
 456 
 457         certPathDefaultConstraints.permits(new ConstraintsParameters(
 458                 sigAlgName, sigAlgParams, key, variant));
 459     }
 460 }
 461