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