1 /*
2  * Copyright (c) 2010, 2015, 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.util;
27 
28 import java.security.CryptoPrimitive;
29 import java.security.AlgorithmParameters;
30 import java.security.Key;
31 import java.security.cert.CertPathValidatorException;
32 import java.security.cert.CertPathValidatorException.BasicReason;
33 import java.security.cert.X509Certificate;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.regex.Pattern;
40 import java.util.regex.Matcher;
41 
42 /**
43  * Algorithm constraints for disabled algorithms property
44  *
45  * See the "jdk.certpath.disabledAlgorithms" specification in java.security
46  * for the syntax of the disabled algorithm string.
47  */
48 public class DisabledAlgorithmConstraints extends AbstractAlgorithmConstraints {
49     private static final Debug debug = Debug.getInstance("certpath");
50 
51     // the known security property, jdk.certpath.disabledAlgorithms
52     public final static String PROPERTY_CERTPATH_DISABLED_ALGS =
53             "jdk.certpath.disabledAlgorithms";
54 
55     // the known security property, jdk.tls.disabledAlgorithms
56     public final static String PROPERTY_TLS_DISABLED_ALGS =
57             "jdk.tls.disabledAlgorithms";
58 
59     // the known security property, jdk.jar.disabledAlgorithms
60     public static final String PROPERTY_JAR_DISABLED_ALGS =
61             "jdk.jar.disabledAlgorithms";
62 
63     private final String[] disabledAlgorithms;
64     private final Constraints algorithmConstraints;
65 
66     /**
67      * Initialize algorithm constraints with the specified security property.
68      *
69      * @param propertyName the security property name that define the disabled
70      *        algorithm constraints
71      */
DisabledAlgorithmConstraints(String propertyName)72     public DisabledAlgorithmConstraints(String propertyName) {
73         this(propertyName, new AlgorithmDecomposer());
74     }
75 
76     /**
77      * Initialize algorithm constraints with the specified security property
78      * for a specific usage type.
79      *
80      * @param propertyName the security property name that define the disabled
81      *        algorithm constraints
82      * @param decomposer an alternate AlgorithmDecomposer.
83      */
DisabledAlgorithmConstraints(String propertyName, AlgorithmDecomposer decomposer)84     public DisabledAlgorithmConstraints(String propertyName,
85             AlgorithmDecomposer decomposer) {
86         super(decomposer);
87         disabledAlgorithms = getAlgorithms(propertyName);
88         algorithmConstraints = new Constraints(disabledAlgorithms);
89     }
90 
91     /*
92      * This only checks if the algorithm has been completely disabled.  If
93      * there are keysize or other limit, this method allow the algorithm.
94      */
95     @Override
permits(Set<CryptoPrimitive> primitives, String algorithm, AlgorithmParameters parameters)96     final public boolean permits(Set<CryptoPrimitive> primitives,
97             String algorithm, AlgorithmParameters parameters) {
98         if (primitives == null || primitives.isEmpty()) {
99             throw new IllegalArgumentException(
100                         "No cryptographic primitive specified");
101         }
102 
103         return checkAlgorithm(disabledAlgorithms, algorithm, decomposer);
104     }
105 
106     /*
107      * Checks if the key algorithm has been disabled or constraints have been
108      * placed on the key.
109      */
110     @Override
permits(Set<CryptoPrimitive> primitives, Key key)111     final public boolean permits(Set<CryptoPrimitive> primitives, Key key) {
112         return checkConstraints(primitives, "", key, null);
113     }
114 
115     /*
116      * Checks if the key algorithm has been disabled or if constraints have
117      * been placed on the key.
118      */
119     @Override
permits(Set<CryptoPrimitive> primitives, String algorithm, Key key, AlgorithmParameters parameters)120     final public boolean permits(Set<CryptoPrimitive> primitives,
121             String algorithm, Key key, AlgorithmParameters parameters) {
122 
123         if (algorithm == null || algorithm.length() == 0) {
124             throw new IllegalArgumentException("No algorithm name specified");
125         }
126 
127         return checkConstraints(primitives, algorithm, key, parameters);
128     }
129 
130     /*
131      * Check if a x509Certificate object is permitted.  Check if all
132      * algorithms are allowed, certificate constraints, and the
133      * public key against key constraints.
134      *
135      * Uses new style permit() which throws exceptions.
136      */
permits(Set<CryptoPrimitive> primitives, CertConstraintParameters cp)137     public final void permits(Set<CryptoPrimitive> primitives,
138             CertConstraintParameters cp) throws CertPathValidatorException {
139         checkConstraints(primitives, cp);
140     }
141 
142     /*
143      * Check if Certificate object is within the constraints.
144      * Uses new style permit() which throws exceptions.
145      */
permits(Set<CryptoPrimitive> primitives, X509Certificate cert)146     public final void permits(Set<CryptoPrimitive> primitives,
147             X509Certificate cert) throws CertPathValidatorException {
148         checkConstraints(primitives, new CertConstraintParameters(cert));
149     }
150 
151     // Check if a string is contained inside the property
checkProperty(String param)152     public boolean checkProperty(String param) {
153         param = param.toLowerCase(Locale.ENGLISH);
154         for (String block : disabledAlgorithms) {
155             if (block.toLowerCase(Locale.ENGLISH).indexOf(param) >= 0) {
156                 return true;
157             }
158         }
159         return false;
160     }
161 
162     // Check algorithm constraints with key and algorithm
checkConstraints(Set<CryptoPrimitive> primitives, String algorithm, Key key, AlgorithmParameters parameters)163     private boolean checkConstraints(Set<CryptoPrimitive> primitives,
164             String algorithm, Key key, AlgorithmParameters parameters) {
165 
166         // check the key parameter, it cannot be null.
167         if (key == null) {
168             throw new IllegalArgumentException("The key cannot be null");
169         }
170 
171         // check the signature algorithm
172         if (algorithm != null && algorithm.length() != 0) {
173             if (!permits(primitives, algorithm, parameters)) {
174                 return false;
175             }
176         }
177 
178         // check the key algorithm
179         if (!permits(primitives, key.getAlgorithm(), null)) {
180             return false;
181         }
182 
183         // check the key constraints
184         return algorithmConstraints.permits(key);
185     }
186 
187     /*
188      * Check algorithm constraints with Certificate
189      * Uses new style permit() which throws exceptions.
190      */
checkConstraints(Set<CryptoPrimitive> primitives, CertConstraintParameters cp)191     private void checkConstraints(Set<CryptoPrimitive> primitives,
192             CertConstraintParameters cp) throws CertPathValidatorException {
193 
194         X509Certificate cert = cp.getCertificate();
195         String algorithm = cert.getSigAlgName();
196 
197         // Check signature algorithm is not disabled
198         if (!permits(primitives, algorithm, null)) {
199             throw new CertPathValidatorException(
200                     "Algorithm constraints check failed on disabled "+
201                             "signature algorithm: " + algorithm,
202                     null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
203         }
204 
205         // Check key algorithm is not disabled
206         if (!permits(primitives, cert.getPublicKey().getAlgorithm(), null)) {
207             throw new CertPathValidatorException(
208                     "Algorithm constraints check failed on disabled "+
209                             "public key algorithm: " + algorithm,
210                     null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
211         }
212 
213         // Check the certificate and key constraints
214         algorithmConstraints.permits(cp);
215 
216     }
217 
218     /**
219      * Key and Certificate Constraints
220      *
221      * The complete disabling of an algorithm is not handled by Constraints or
222      * Constraint classes.  That is addressed with
223      *   permit(Set<CryptoPrimitive>, String, AlgorithmParameters)
224      *
225      * When passing a Key to permit(), the boolean return values follow the
226      * same as the interface class AlgorithmConstraints.permit().  This is to
227      * maintain compatibility:
228      * 'true' means the operation is allowed.
229      * 'false' means it failed the constraints and is disallowed.
230      *
231      * When passing CertConstraintParameters through permit(), an exception
232      * will be thrown on a failure to better identify why the operation was
233      * disallowed.
234      */
235 
236     private static class Constraints {
237         private Map<String, Set<Constraint>> constraintsMap = new HashMap<>();
238         private static final Pattern keySizePattern = Pattern.compile(
239                 "keySize\\s*(<=|<|==|!=|>|>=)\\s*(\\d+)");
240 
Constraints(String[] constraintArray)241         public Constraints(String[] constraintArray) {
242             for (String constraintEntry : constraintArray) {
243                 if (constraintEntry == null || constraintEntry.isEmpty()) {
244                     continue;
245                 }
246 
247                 constraintEntry = constraintEntry.trim();
248                 if (debug != null) {
249                     debug.println("Constraints: " + constraintEntry);
250                 }
251 
252                 // Check if constraint is a complete disabling of an
253                 // algorithm or has conditions.
254                 String algorithm;
255                 String policy;
256                 int space = constraintEntry.indexOf(' ');
257                 if (space > 0) {
258                     algorithm = AlgorithmDecomposer.hashName(
259                             constraintEntry.substring(0, space).
260                                     toUpperCase(Locale.ENGLISH));
261                     policy = constraintEntry.substring(space + 1);
262                 } else {
263                     constraintsMap.putIfAbsent(
264                             constraintEntry.toUpperCase(Locale.ENGLISH),
265                             new HashSet<>());
266                     continue;
267                 }
268 
269                 // Convert constraint conditions into Constraint classes
270                 Constraint c = null;
271                 Constraint lastConstraint = null;
272                 // Allow only one jdkCA entry per constraint entry
273                 boolean jdkCALimit = false;
274 
275                 for (String entry : policy.split("&")) {
276                     entry = entry.trim();
277 
278                     Matcher matcher = keySizePattern.matcher(entry);
279                     if (matcher.matches()) {
280                         if (debug != null) {
281                             debug.println("Constraints set to keySize: " +
282                                     entry);
283                         }
284                         c = new KeySizeConstraint(algorithm,
285                                 KeySizeConstraint.Operator.of(matcher.group(1)),
286                                 Integer.parseInt(matcher.group(2)));
287 
288                     } else if (entry.equalsIgnoreCase("jdkCA")) {
289                         if (debug != null) {
290                             debug.println("Constraints set to jdkCA.");
291                         }
292                         if (jdkCALimit) {
293                             throw new IllegalArgumentException("Only one " +
294                                     "jdkCA entry allowed in property. " +
295                                     "Constraint: " + constraintEntry);
296                         }
297                         c = new jdkCAConstraint(algorithm);
298                         jdkCALimit = true;
299                     }
300                     // Link multiple conditions for a single constraint
301                     // into a linked list.
302                     if (lastConstraint == null) {
303                         if (!constraintsMap.containsKey(algorithm)) {
304                             constraintsMap.putIfAbsent(algorithm,
305                                     new HashSet<>());
306                         }
307                         if (c != null) {
308                             constraintsMap.get(algorithm).add(c);
309                         }
310                     } else {
311                         lastConstraint.nextConstraint = c;
312                     }
313                     lastConstraint = c;
314                 }
315             }
316         }
317 
318         // Get applicable constraints based off the signature algorithm
getConstraints(String algorithm)319         private Set<Constraint> getConstraints(String algorithm) {
320             return constraintsMap.get(algorithm);
321         }
322 
323         // Check if KeySizeConstraints permit the specified key
permits(Key key)324         public boolean permits(Key key) {
325             Set<Constraint> set = getConstraints(key.getAlgorithm());
326             if (set == null) {
327                 return true;
328             }
329             for (Constraint constraint : set) {
330                 if (!constraint.permits(key)) {
331                     if (debug != null) {
332                         debug.println("keySizeConstraint: failed key " +
333                                 "constraint check " + KeyUtil.getKeySize(key));
334                     }
335             return false;
336         }
337             }
338         return true;
339     }
340 
341         // Check if constraints permit this cert.
permits(CertConstraintParameters cp)342         public void permits(CertConstraintParameters cp)
343                 throws CertPathValidatorException {
344             X509Certificate cert = cp.getCertificate();
345 
346             if (debug != null) {
347                 debug.println("Constraints.permits(): " + cert.getSigAlgName());
348             }
349 
350             // Get all signature algorithms to check for constraints
351             Set<String> algorithms =
352                     AlgorithmDecomposer.decomposeOneHash(cert.getSigAlgName());
353             if (algorithms == null || algorithms.isEmpty()) {
354                 return;
355     }
356 
357             // Attempt to add the public key algorithm to the set
358             algorithms.add(cert.getPublicKey().getAlgorithm());
359 
360             // Check all applicable constraints
361             for (String algorithm : algorithms) {
362                 Set<Constraint> set = getConstraints(algorithm);
363                 if (set == null) {
364                     continue;
365                 }
366                 for (Constraint constraint : set) {
367                     constraint.permits(cp);
368                 }
369             }
370         }
371                         }
372 
373     // Abstract class for algorithm constraint checking
374     private abstract static class Constraint {
375         String algorithm;
376         Constraint nextConstraint = null;
377 
378         // operator
379         enum Operator {
380             EQ,         // "=="
381             NE,         // "!="
382             LT,         // "<"
383             LE,         // "<="
384             GT,         // ">"
385             GE;         // ">="
386 
of(String s)387             static Operator of(String s) {
388                 switch (s) {
389                     case "==":
390                         return EQ;
391                     case "!=":
392                         return NE;
393                     case "<":
394                         return LT;
395                     case "<=":
396                         return LE;
397                     case ">":
398                         return GT;
399                     case ">=":
400                         return GE;
401                 }
402 
403                 throw new IllegalArgumentException("Error in security " +
404                         "property. " + s + " is not a legal Operator");
405             }
406         }
407 
408         /**
409          * Check if an algorithm constraint permit this key to be used.
410          * @param key Public key
411          * @return true if constraints do not match
412          */
permits(Key key)413         public boolean permits(Key key) {
414             return true;
415         }
416 
417         /**
418          * Check if an algorithm constraint is permit this certificate to
419          * be used.
420          * @param cp CertificateParameter containing certificate and state info
421          * @return true if constraints do not match
422          */
permits(CertConstraintParameters cp)423         public abstract void permits(CertConstraintParameters cp)
424                 throws CertPathValidatorException;
425     }
426 
427     /*
428      * This class contains constraints dealing with the certificate chain
429      * of the certificate.
430      */
431     private static class jdkCAConstraint extends Constraint {
jdkCAConstraint(String algo)432         jdkCAConstraint(String algo) {
433             algorithm = algo;
434         }
435 
436         /*
437          * Check if each constraint fails and check if there is a linked
438          * constraint  Any permitted constraint will exit the linked list
439          * to allow the operation.
440          */
permits(CertConstraintParameters cp)441         public void permits(CertConstraintParameters cp)
442                 throws CertPathValidatorException {
443             if (debug != null) {
444                 debug.println("jdkCAConstraints.permits(): " + algorithm);
445             }
446 
447             // Return false if the chain has a trust anchor in cacerts
448             if (cp.isTrustedMatch()) {
449                 if (nextConstraint != null) {
450                     nextConstraint.permits(cp);
451                     return;
452                 }
453                 throw new CertPathValidatorException(
454                         "Algorithm constraints check failed on certificate " +
455                                 "anchor limits",
456                         null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
457             }
458         }
459     }
460 
461 
462     /*
463      * This class contains constraints dealing with the key size
464      * support limits per algorithm.   e.g.  "keySize <= 1024"
465      */
466     private static class KeySizeConstraint extends Constraint {
467 
468         private int minSize;            // the minimal available key size
469         private int maxSize;            // the maximal available key size
470         private int prohibitedSize = -1;    // unavailable key sizes
471 
KeySizeConstraint(String algo, Operator operator, int length)472         public KeySizeConstraint(String algo, Operator operator, int length) {
473             algorithm = algo;
474             switch (operator) {
475                 case EQ:      // an unavailable key size
476                     this.minSize = 0;
477                     this.maxSize = Integer.MAX_VALUE;
478                     prohibitedSize = length;
479                     break;
480                 case NE:
481                     this.minSize = length;
482                     this.maxSize = length;
483                     break;
484                 case LT:
485                     this.minSize = length;
486                     this.maxSize = Integer.MAX_VALUE;
487                     break;
488                 case LE:
489                     this.minSize = length + 1;
490                     this.maxSize = Integer.MAX_VALUE;
491                     break;
492                 case GT:
493                     this.minSize = 0;
494                     this.maxSize = length;
495                     break;
496                 case GE:
497                     this.minSize = 0;
498                     this.maxSize = length > 1 ? (length - 1) : 0;
499                     break;
500                 default:
501                     // unlikely to happen
502                     this.minSize = Integer.MAX_VALUE;
503                     this.maxSize = -1;
504             }
505         }
506 
507         /*
508          * If we are passed a certificate, extract the public key and use it.
509          *
510          * Check if each constraint fails and check if there is a linked
511          * constraint  Any permitted constraint will exit the linked list
512          * to allow the operation.
513          */
permits(CertConstraintParameters cp)514         public void permits(CertConstraintParameters cp)
515                 throws CertPathValidatorException {
516             if (!permitsImpl(cp.getCertificate().getPublicKey())) {
517                 if (nextConstraint != null) {
518                     nextConstraint.permits(cp);
519                     return;
520                 }
521                 throw new CertPathValidatorException(
522                         "Algorithm constraints check failed on keysize limits",
523                         null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
524             }
525         }
526 
527 
528         // Check if key constraint disable the specified key
529         // Uses old style permit()
permits(Key key)530         public boolean permits(Key key) {
531             // If we recursively find a constraint that permits us to use
532             // this key, return true and skip any other constraint checks.
533             if (nextConstraint != null && nextConstraint.permits(key)) {
534                 return true;
535             }
536             if (debug != null) {
537                 debug.println("KeySizeConstraints.permits(): " + algorithm);
538             }
539 
540             return permitsImpl(key);
541         }
542 
permitsImpl(Key key)543         private boolean permitsImpl(Key key) {
544             // Verify this constraint is for this public key algorithm
545             if (algorithm.compareToIgnoreCase(key.getAlgorithm()) != 0) {
546                 return true;
547             }
548 
549             int size = KeyUtil.getKeySize(key);
550             if (size == 0) {
551                 return false;    // we don't allow any key of size 0.
552             } else if (size > 0) {
553                 return !((size < minSize) || (size > maxSize) ||
554                     (prohibitedSize == size));
555             }   // Otherwise, the key size is not accessible. Conservatively,
556                 // please don't disable such keys.
557 
558             return true;
559         }
560         }
561     }
562