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 final public 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 final static Set<CryptoPrimitive> SIGNATURE_PRIMITIVE_SET =
80         Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
81 
82     private final static 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 final static 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      */
AlgorithmChecker(TrustAnchor anchor)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      */
AlgorithmChecker(AlgorithmConstraints constraints)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      */
AlgorithmChecker(TrustAnchor anchor, AlgorithmConstraints constraints)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
checkFingerprint(X509Certificate cert)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
init(boolean forward)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
isForwardCheckingSupported()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
getSupportedExtensions()198     public Set<String> getSupportedExtensions() {
199         return null;
200     }
201 
202     @Override
check(Certificate cert, Collection<String> unresolvedCritExts)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         if (constraints instanceof DisabledAlgorithmConstraints) {
259             // Check against DisabledAlgorithmConstraints certpath constraints.
260             // permits() will throw exception on failure.
261             ((DisabledAlgorithmConstraints)constraints).permits(primitives,
262                 new CertConstraintParameters((X509Certificate)cert,
263                         trustedMatch));
264             // If there is no previous key, set one and exit
265             if (prevPubKey == null) {
266                 prevPubKey = currPubKey;
267                 return;
268             }
269         }
270 
271         X509CertImpl x509Cert;
272         AlgorithmId algorithmId;
273         try {
274             x509Cert = X509CertImpl.toImpl((X509Certificate)cert);
275             algorithmId = (AlgorithmId)x509Cert.get(X509CertImpl.SIG_ALG);
276         } catch (CertificateException ce) {
277             throw new CertPathValidatorException(ce);
278         }
279 
280         AlgorithmParameters currSigAlgParams = algorithmId.getParameters();
281         String currSigAlg = x509Cert.getSigAlgName();
282 
283         // If 'constraints' is not of DisabledAlgorithmConstraints, check all
284         // everything individually
285         if (!(constraints instanceof DisabledAlgorithmConstraints)) {
286             // Check the current signature algorithm
287             if (!constraints.permits(
288                     SIGNATURE_PRIMITIVE_SET,
289                     currSigAlg, currSigAlgParams)) {
290                 throw new CertPathValidatorException(
291                         "Algorithm constraints check failed on signature " +
292                                 "algorithm: " + currSigAlg, null, null, -1,
293                         BasicReason.ALGORITHM_CONSTRAINED);
294             }
295 
296         if (!constraints.permits(primitives, currPubKey)) {
297             throw new CertPathValidatorException(
298                         "Algorithm constraints check failed on keysize: " +
299                                 sun.security.util.KeyUtil.getKeySize(currPubKey),
300                 null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
301         }
302         }
303 
304         // Check with previous cert for signature algorithm and public key
305         if (prevPubKey != null) {
306                 if (!constraints.permits(
307                         SIGNATURE_PRIMITIVE_SET,
308                         currSigAlg, prevPubKey, currSigAlgParams)) {
309                     throw new CertPathValidatorException(
310                     "Algorithm constraints check failed on " +
311                             "signature algorithm: " + currSigAlg,
312                         null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
313                 }
314 
315             // Inherit key parameters from previous key
316             if (PKIX.isDSAPublicKeyWithoutParams(currPubKey)) {
317                 // Inherit DSA parameters from previous key
318                 if (!(prevPubKey instanceof DSAPublicKey)) {
319                     throw new CertPathValidatorException("Input key is not " +
320                         "of a appropriate type for inheriting parameters");
321                 }
322 
323                 DSAParams params = ((DSAPublicKey)prevPubKey).getParams();
324                 if (params == null) {
325                     throw new CertPathValidatorException(
326                         "Key parameters missing from public key.");
327                 }
328 
329                 try {
330                     BigInteger y = ((DSAPublicKey)currPubKey).getY();
331                     KeyFactory kf = KeyFactory.getInstance("DSA");
332                     DSAPublicKeySpec ks = new DSAPublicKeySpec(y,
333                                                        params.getP(),
334                                                        params.getQ(),
335                                                        params.getG());
336                     currPubKey = kf.generatePublic(ks);
337                 } catch (GeneralSecurityException e) {
338                     throw new CertPathValidatorException("Unable to generate " +
339                         "key with inherited parameters: " + e.getMessage(), e);
340                 }
341             }
342         }
343 
344         // reset the previous public key
345         prevPubKey = currPubKey;
346 
347         // check the extended key usage, ignore the check now
348         // List<String> extendedKeyUsages = x509Cert.getExtendedKeyUsage();
349 
350         // DO NOT remove any unresolved critical extensions
351     }
352 
353     /**
354      * Try to set the trust anchor of the checker.
355      * <p>
356      * If there is no trust anchor specified and the checker has not started,
357      * set the trust anchor.
358      *
359      * @param anchor the trust anchor selected to validate the target
360      *     certificate
361      */
trySetTrustAnchor(TrustAnchor anchor)362     void trySetTrustAnchor(TrustAnchor anchor) {
363         // Don't bother if the check has started or trust anchor has already
364         // specified.
365         if (prevPubKey == null) {
366             if (anchor == null) {
367                 throw new IllegalArgumentException(
368                         "The trust anchor cannot be null");
369             }
370 
371             // Don't bother to change the trustedPubKey.
372             if (anchor.getTrustedCert() != null) {
373                 prevPubKey = anchor.getTrustedCert().getPublicKey();
374                 // Check for anchor certificate restrictions
375                 trustedMatch = checkFingerprint(anchor.getTrustedCert());
376                 if (trustedMatch && debug != null) {
377                     debug.println("trustedMatch = true");
378                 }
379             } else {
380                 prevPubKey = anchor.getCAPublicKey();
381             }
382         }
383     }
384 
385     /**
386      * Check the signature algorithm with the specified public key.
387      *
388      * @param key the public key to verify the CRL signature
389      * @param crl the target CRL
390      */
check(PublicKey key, X509CRL crl)391     static void check(PublicKey key, X509CRL crl)
392                         throws CertPathValidatorException {
393 
394         X509CRLImpl x509CRLImpl = null;
395         try {
396             x509CRLImpl = X509CRLImpl.toImpl(crl);
397         } catch (CRLException ce) {
398             throw new CertPathValidatorException(ce);
399         }
400 
401         AlgorithmId algorithmId = x509CRLImpl.getSigAlgId();
402         check(key, algorithmId);
403     }
404 
405     /**
406      * Check the signature algorithm with the specified public key.
407      *
408      * @param key the public key to verify the CRL signature
409      * @param crl the target CRL
410      */
check(PublicKey key, AlgorithmId algorithmId)411     static void check(PublicKey key, AlgorithmId algorithmId)
412                         throws CertPathValidatorException {
413         String sigAlgName = algorithmId.getName();
414         AlgorithmParameters sigAlgParams = algorithmId.getParameters();
415 
416         if (!certPathDefaultConstraints.permits(
417                 SIGNATURE_PRIMITIVE_SET, sigAlgName, key, sigAlgParams)) {
418             throw new CertPathValidatorException(
419                 "Algorithm constraints check failed on signature algorithm: " +
420                 sigAlgName + " is disabled",
421                 null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
422         }
423     }
424 
425 }
426