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