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