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