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.util.Locale;
  32 import java.util.Set;
  33 import java.util.Collections;
  34 import java.util.HashSet;
  35 import java.util.Map;
  36 import java.util.HashMap;
  37 import java.util.regex.Pattern;
  38 import java.util.regex.Matcher;
  39 
  40 /**
  41  * Algorithm constraints for disabled algorithms property
  42  *
  43  * See the "jdk.certpath.disabledAlgorithms" specification in java.security
  44  * for the syntax of the disabled algorithm string.
  45  */
  46 public class DisabledAlgorithmConstraints extends AbstractAlgorithmConstraints {
  47 
  48     // the known security property, jdk.certpath.disabledAlgorithms
  49     public static final String PROPERTY_CERTPATH_DISABLED_ALGS =
  50             "jdk.certpath.disabledAlgorithms";
  51 
  52     // the known security property, jdk.tls.disabledAlgorithms
  53     public static final String PROPERTY_TLS_DISABLED_ALGS =
  54             "jdk.tls.disabledAlgorithms";
  55 
  56     private static final Map<String, String[]> disabledAlgorithmsMap =
  57                                                             new HashMap<>();
  58     private static final Map<String, KeySizeConstraints> keySizeConstraintsMap =
  59                                                             new HashMap<>();
  60 
  61     private final String[] disabledAlgorithms;
  62     private final KeySizeConstraints keySizeConstraints;
  63 
  64     /**
  65      * Initialize algorithm constraints with the specified security property.
  66      *
  67      * @param propertyName the security property name that define the disabled
  68      *        algorithm constraints
  69      */
  70     public DisabledAlgorithmConstraints(String propertyName) {
  71         this(propertyName, new AlgorithmDecomposer());
  72     }
  73 
  74     public DisabledAlgorithmConstraints(String propertyName,
  75             AlgorithmDecomposer decomposer) {
  76         super(decomposer);
  77         disabledAlgorithms = getAlgorithms(disabledAlgorithmsMap, propertyName);
  78         keySizeConstraints = getKeySizeConstraints(disabledAlgorithms,
  79                 propertyName);
  80     }
  81 
  82     @Override
  83     public final boolean permits(Set<CryptoPrimitive> primitives,
  84             String algorithm, AlgorithmParameters parameters) {
  85 
  86         if (primitives == null || primitives.isEmpty()) {
  87             throw new IllegalArgumentException(
  88                         "No cryptographic primitive specified");
  89         }
  90 
  91         return checkAlgorithm(disabledAlgorithms, algorithm, decomposer);
  92     }
  93 
  94     @Override
  95     public final boolean permits(Set<CryptoPrimitive> primitives, Key key) {
  96         return checkConstraints(primitives, "", key, null);
  97     }
  98 
  99     @Override
 100     public final boolean permits(Set<CryptoPrimitive> primitives,
 101             String algorithm, Key key, AlgorithmParameters parameters) {
 102 
 103         if (algorithm == null || algorithm.length() == 0) {
 104             throw new IllegalArgumentException("No algorithm name specified");
 105         }
 106 
 107         return checkConstraints(primitives, algorithm, key, parameters);
 108     }
 109 
 110     // Check algorithm constraints
 111     private boolean checkConstraints(Set<CryptoPrimitive> primitives,
 112             String algorithm, Key key, AlgorithmParameters parameters) {
 113 
 114         // check the key parameter, it cannot be null.
 115         if (key == null) {
 116             throw new IllegalArgumentException("The key cannot be null");
 117         }
 118 
 119         // check the target algorithm
 120         if (algorithm != null && algorithm.length() != 0) {
 121             if (!permits(primitives, algorithm, parameters)) {
 122                 return false;
 123             }
 124         }
 125 
 126         // check the key algorithm
 127         if (!permits(primitives, key.getAlgorithm(), null)) {
 128             return false;
 129         }
 130 
 131         // check the key constraints
 132         if (keySizeConstraints.disables(key)) {
 133             return false;
 134         }
 135 
 136         return true;
 137     }
 138 
 139     private static KeySizeConstraints getKeySizeConstraints(
 140             String[] disabledAlgorithms, String propertyName) {
 141         synchronized (keySizeConstraintsMap) {
 142             if(!keySizeConstraintsMap.containsKey(propertyName)) {
 143                 // map the key constraints
 144                 KeySizeConstraints keySizeConstraints =
 145                         new KeySizeConstraints(disabledAlgorithms);
 146                 keySizeConstraintsMap.put(propertyName, keySizeConstraints);
 147             }
 148 
 149             return keySizeConstraintsMap.get(propertyName);
 150         }
 151     }
 152 
 153     /**
 154      * key constraints
 155      */
 156     private static class KeySizeConstraints {
 157         private static final Pattern pattern = Pattern.compile(
 158                 "(\\S+)\\s+keySize\\s*(<=|<|==|!=|>|>=)\\s*(\\d+)");
 159 
 160         private Map<String, Set<KeySizeConstraint>> constraintsMap =
 161             Collections.synchronizedMap(
 162                         new HashMap<String, Set<KeySizeConstraint>>());
 163 
 164         public KeySizeConstraints(String[] restrictions) {
 165             for (String restriction : restrictions) {
 166                 if (restriction == null || restriction.isEmpty()) {
 167                     continue;
 168                 }
 169 
 170                 Matcher matcher = pattern.matcher(restriction);
 171                 if (matcher.matches()) {
 172                     String algorithm = matcher.group(1);
 173 
 174                     KeySizeConstraint.Operator operator =
 175                              KeySizeConstraint.Operator.of(matcher.group(2));
 176                     int length = Integer.parseInt(matcher.group(3));
 177 
 178                     algorithm = algorithm.toLowerCase(Locale.ENGLISH);
 179 
 180                     synchronized (constraintsMap) {
 181                         if (!constraintsMap.containsKey(algorithm)) {
 182                             constraintsMap.put(algorithm,
 183                                 new HashSet<KeySizeConstraint>());
 184                         }
 185 
 186                         Set<KeySizeConstraint> constraintSet =
 187                             constraintsMap.get(algorithm);
 188                         KeySizeConstraint constraint =
 189                             new KeySizeConstraint(operator, length);
 190                         constraintSet.add(constraint);
 191                     }
 192                 }
 193             }
 194         }
 195 
 196         // Does this KeySizeConstraints disable the specified key?
 197         public boolean disables(Key key) {
 198             String algorithm = key.getAlgorithm().toLowerCase(Locale.ENGLISH);
 199             synchronized (constraintsMap) {
 200                 if (constraintsMap.containsKey(algorithm)) {
 201                     Set<KeySizeConstraint> constraintSet =
 202                                         constraintsMap.get(algorithm);
 203                     for (KeySizeConstraint constraint : constraintSet) {
 204                         if (constraint.disables(key)) {
 205                             return true;
 206                         }
 207                     }
 208                 }
 209             }
 210 
 211             return false;
 212         }
 213     }
 214 
 215     /**
 216      * Key size constraint.
 217      *
 218      * e.g.  "keysize <= 1024"
 219      */
 220     private static class KeySizeConstraint {
 221         // operator
 222         static enum Operator {
 223             EQ,         // "=="
 224             NE,         // "!="
 225             LT,         // "<"
 226             LE,         // "<="
 227             GT,         // ">"
 228             GE;         // ">="
 229 
 230             static Operator of(String s) {
 231                 switch (s) {
 232                     case "==":
 233                         return EQ;
 234                     case "!=":
 235                         return NE;
 236                     case "<":
 237                         return LT;
 238                     case "<=":
 239                         return LE;
 240                     case ">":
 241                         return GT;
 242                     case ">=":
 243                         return GE;
 244                 }
 245 
 246                 throw new IllegalArgumentException(
 247                         s + " is not a legal Operator");
 248             }
 249         }
 250 
 251         private int minSize;            // the minimal available key size
 252         private int maxSize;            // the maximal available key size
 253         private int prohibitedSize = -1;    // unavailable key sizes
 254 
 255         public KeySizeConstraint(Operator operator, int length) {
 256             switch (operator) {
 257                 case EQ:      // an unavailable key size
 258                     this.minSize = 0;
 259                     this.maxSize = Integer.MAX_VALUE;
 260                     prohibitedSize = length;
 261                     break;
 262                 case NE:
 263                     this.minSize = length;
 264                     this.maxSize = length;
 265                     break;
 266                 case LT:
 267                     this.minSize = length;
 268                     this.maxSize = Integer.MAX_VALUE;
 269                     break;
 270                 case LE:
 271                     this.minSize = length + 1;
 272                     this.maxSize = Integer.MAX_VALUE;
 273                     break;
 274                 case GT:
 275                     this.minSize = 0;
 276                     this.maxSize = length;
 277                     break;
 278                 case GE:
 279                     this.minSize = 0;
 280                     this.maxSize = length > 1 ? (length - 1) : 0;
 281                     break;
 282                 default:
 283                     // unlikely to happen
 284                     this.minSize = Integer.MAX_VALUE;
 285                     this.maxSize = -1;
 286             }
 287         }
 288 
 289         // Does this key constraint disable the specified key?
 290         public boolean disables(Key key) {
 291             int size = KeyUtil.getKeySize(key);
 292 
 293             if (size == 0) {
 294                 return true;    // we don't allow any key of size 0.
 295             } else if (size > 0) {
 296                 return ((size < minSize) || (size > maxSize) ||
 297                     (prohibitedSize == size));
 298             }   // Otherwise, the key size is not accessible. Conservatively,
 299                 // please don't disable such keys.
 300 
 301             return false;
 302         }
 303     }
 304 
 305 }
 306