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