Home | History | Annotate | Download | only in util
      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      */
     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      */
     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
     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
    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
    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      */
    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      */
    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
    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
    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      */
    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 
    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
    321         private Set<Constraint> getConstraints(String algorithm) {
    322             return constraintsMap.get(algorithm);
    323         }
    324 
    325         // Check if KeySizeConstraints permit the specified 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.
    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 
    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          */
    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          */
    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 {
    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          */
    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 
    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          */
    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()
    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 
    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