1 /* 2 * Copyright (c) 2010, 2012, 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.AlgorithmConstraints; 29 import java.security.CryptoPrimitive; 30 import java.security.AlgorithmParameters; 31 32 import java.security.Key; 33 import java.security.Security; 34 import java.security.PrivilegedAction; 35 import java.security.AccessController; 36 37 import java.util.Locale; 38 import java.util.Set; 39 import java.util.Collections; 40 import java.util.HashSet; 41 import java.util.Map; 42 import java.util.HashMap; 43 import java.util.regex.Pattern; 44 import java.util.regex.Matcher; 45 46 /** 47 * Algorithm constraints for disabled algorithms property 48 * 49 * See the "jdk.certpath.disabledAlgorithms" specification in java.security 50 * for the syntax of the disabled algorithm string. 51 */ 52 public class DisabledAlgorithmConstraints implements AlgorithmConstraints { 53 54 // the known security property, jdk.certpath.disabledAlgorithms 55 public final static String PROPERTY_CERTPATH_DISABLED_ALGS = 56 "jdk.certpath.disabledAlgorithms"; 57 58 // the known security property, jdk.tls.disabledAlgorithms 59 public final static String PROPERTY_TLS_DISABLED_ALGS = 60 "jdk.tls.disabledAlgorithms"; 61 62 private static Map<String, String[]> disabledAlgorithmsMap = 63 Collections.synchronizedMap(new HashMap<String, String[]>()); 64 private static Map<String, KeySizeConstraints> keySizeConstraintsMap = 65 Collections.synchronizedMap(new HashMap<String, KeySizeConstraints>()); 66 67 private String[] disabledAlgorithms; 68 private KeySizeConstraints keySizeConstraints; 69 70 /** 71 * Initialize algorithm constraints with the specified security property. 72 * 73 * @param propertyName the security property name that define the disabled 74 * algorithm constraints 75 */ DisabledAlgorithmConstraints(String propertyName)76 public DisabledAlgorithmConstraints(String propertyName) { 77 synchronized (disabledAlgorithmsMap) { 78 if(!disabledAlgorithmsMap.containsKey(propertyName)) { 79 loadDisabledAlgorithmsMap(propertyName); 80 } 81 82 disabledAlgorithms = disabledAlgorithmsMap.get(propertyName); 83 keySizeConstraints = keySizeConstraintsMap.get(propertyName); 84 } 85 } 86 87 @Override permits(Set<CryptoPrimitive> primitives, String algorithm, AlgorithmParameters parameters)88 final public boolean permits(Set<CryptoPrimitive> primitives, 89 String algorithm, AlgorithmParameters parameters) { 90 91 if (algorithm == null || algorithm.length() == 0) { 92 throw new IllegalArgumentException("No algorithm name specified"); 93 } 94 95 if (primitives == null || primitives.isEmpty()) { 96 throw new IllegalArgumentException( 97 "No cryptographic primitive specified"); 98 } 99 100 Set<String> elements = null; 101 for (String disabled : disabledAlgorithms) { 102 if (disabled == null || disabled.isEmpty()) { 103 continue; 104 } 105 106 // check the full name 107 if (disabled.equalsIgnoreCase(algorithm)) { 108 return false; 109 } 110 111 // decompose the algorithm into sub-elements 112 if (elements == null) { 113 elements = decomposes(algorithm); 114 } 115 116 // check the items of the algorithm 117 for (String element : elements) { 118 if (disabled.equalsIgnoreCase(element)) { 119 return false; 120 } 121 } 122 } 123 124 return true; 125 } 126 127 @Override permits(Set<CryptoPrimitive> primitives, Key key)128 final public boolean permits(Set<CryptoPrimitive> primitives, Key key) { 129 return checkConstraints(primitives, "", key, null); 130 } 131 132 @Override permits(Set<CryptoPrimitive> primitives, String algorithm, Key key, AlgorithmParameters parameters)133 final public boolean permits(Set<CryptoPrimitive> primitives, 134 String algorithm, Key key, AlgorithmParameters parameters) { 135 136 if (algorithm == null || algorithm.length() == 0) { 137 throw new IllegalArgumentException("No algorithm name specified"); 138 } 139 140 return checkConstraints(primitives, algorithm, key, parameters); 141 } 142 143 /** 144 * Decompose the standard algorithm name into sub-elements. 145 * <p> 146 * For example, we need to decompose "SHA1WithRSA" into "SHA1" and "RSA" 147 * so that we can check the "SHA1" and "RSA" algorithm constraints 148 * separately. 149 * <p> 150 * Please override the method if need to support more name pattern. 151 */ decomposes(String algorithm)152 protected Set<String> decomposes(String algorithm) { 153 if (algorithm == null || algorithm.length() == 0) { 154 return new HashSet<String>(); 155 } 156 157 // algorithm/mode/padding 158 Pattern transPattern = Pattern.compile("/"); 159 String[] transTockens = transPattern.split(algorithm); 160 161 Set<String> elements = new HashSet<String>(); 162 for (String transTocken : transTockens) { 163 if (transTocken == null || transTocken.length() == 0) { 164 continue; 165 } 166 167 // PBEWith<digest>And<encryption> 168 // PBEWith<prf>And<encryption> 169 // OAEPWith<digest>And<mgf>Padding 170 // <digest>with<encryption> 171 // <digest>with<encryption>and<mgf> 172 Pattern pattern = 173 Pattern.compile("with|and", Pattern.CASE_INSENSITIVE); 174 String[] tokens = pattern.split(transTocken); 175 176 for (String token : tokens) { 177 if (token == null || token.length() == 0) { 178 continue; 179 } 180 181 elements.add(token); 182 } 183 } 184 185 // In Java standard algorithm name specification, for different 186 // purpose, the SHA-1 and SHA-2 algorithm names are different. For 187 // example, for MessageDigest, the standard name is "SHA-256", while 188 // for Signature, the digest algorithm component is "SHA256" for 189 // signature algorithm "SHA256withRSA". So we need to check both 190 // "SHA-256" and "SHA256" to make the right constraint checking. 191 192 // handle special name: SHA-1 and SHA1 193 if (elements.contains("SHA1") && !elements.contains("SHA-1")) { 194 elements.add("SHA-1"); 195 } 196 if (elements.contains("SHA-1") && !elements.contains("SHA1")) { 197 elements.add("SHA1"); 198 } 199 200 // handle special name: SHA-224 and SHA224 201 if (elements.contains("SHA224") && !elements.contains("SHA-224")) { 202 elements.add("SHA-224"); 203 } 204 if (elements.contains("SHA-224") && !elements.contains("SHA224")) { 205 elements.add("SHA224"); 206 } 207 208 // handle special name: SHA-256 and SHA256 209 if (elements.contains("SHA256") && !elements.contains("SHA-256")) { 210 elements.add("SHA-256"); 211 } 212 if (elements.contains("SHA-256") && !elements.contains("SHA256")) { 213 elements.add("SHA256"); 214 } 215 216 // handle special name: SHA-384 and SHA384 217 if (elements.contains("SHA384") && !elements.contains("SHA-384")) { 218 elements.add("SHA-384"); 219 } 220 if (elements.contains("SHA-384") && !elements.contains("SHA384")) { 221 elements.add("SHA384"); 222 } 223 224 // handle special name: SHA-512 and SHA512 225 if (elements.contains("SHA512") && !elements.contains("SHA-512")) { 226 elements.add("SHA-512"); 227 } 228 if (elements.contains("SHA-512") && !elements.contains("SHA512")) { 229 elements.add("SHA512"); 230 } 231 232 return elements; 233 } 234 235 // Check algorithm constraints checkConstraints(Set<CryptoPrimitive> primitives, String algorithm, Key key, AlgorithmParameters parameters)236 private boolean checkConstraints(Set<CryptoPrimitive> primitives, 237 String algorithm, Key key, AlgorithmParameters parameters) { 238 239 // check the key parameter, it cannot be null. 240 if (key == null) { 241 throw new IllegalArgumentException("The key cannot be null"); 242 } 243 244 // check the target algorithm 245 if (algorithm != null && algorithm.length() != 0) { 246 if (!permits(primitives, algorithm, parameters)) { 247 return false; 248 } 249 } 250 251 // check the key algorithm 252 if (!permits(primitives, key.getAlgorithm(), null)) { 253 return false; 254 } 255 256 // check the key constraints 257 if (keySizeConstraints.disables(key)) { 258 return false; 259 } 260 261 return true; 262 } 263 264 // Get disabled algorithm constraints from the specified security property. loadDisabledAlgorithmsMap( final String propertyName)265 private static void loadDisabledAlgorithmsMap( 266 final String propertyName) { 267 268 String property = AccessController.doPrivileged( 269 new PrivilegedAction<String>() { 270 public String run() { 271 return Security.getProperty(propertyName); 272 } 273 }); 274 275 String[] algorithmsInProperty = null; 276 277 if (property != null && !property.isEmpty()) { 278 279 // remove double quote marks from beginning/end of the property 280 if (property.charAt(0) == '"' && 281 property.charAt(property.length() - 1) == '"') { 282 property = property.substring(1, property.length() - 1); 283 } 284 285 algorithmsInProperty = property.split(","); 286 for (int i = 0; i < algorithmsInProperty.length; i++) { 287 algorithmsInProperty[i] = algorithmsInProperty[i].trim(); 288 } 289 } 290 291 // map the disabled algorithms 292 if (algorithmsInProperty == null) { 293 algorithmsInProperty = new String[0]; 294 } 295 disabledAlgorithmsMap.put(propertyName, algorithmsInProperty); 296 297 // map the key constraints 298 KeySizeConstraints keySizeConstraints = 299 new KeySizeConstraints(algorithmsInProperty); 300 keySizeConstraintsMap.put(propertyName, keySizeConstraints); 301 } 302 303 /** 304 * key constraints 305 */ 306 private static class KeySizeConstraints { 307 private static final Pattern pattern = Pattern.compile( 308 "(\\S+)\\s+keySize\\s*(<=|<|==|!=|>|>=)\\s*(\\d+)"); 309 310 private Map<String, Set<KeySizeConstraint>> constraintsMap = 311 Collections.synchronizedMap( 312 new HashMap<String, Set<KeySizeConstraint>>()); 313 KeySizeConstraints(String[] restrictions)314 public KeySizeConstraints(String[] restrictions) { 315 for (String restriction : restrictions) { 316 if (restriction == null || restriction.isEmpty()) { 317 continue; 318 } 319 320 Matcher matcher = pattern.matcher(restriction); 321 if (matcher.matches()) { 322 String algorithm = matcher.group(1); 323 324 KeySizeConstraint.Operator operator = 325 KeySizeConstraint.Operator.of(matcher.group(2)); 326 int length = Integer.parseInt(matcher.group(3)); 327 328 algorithm = algorithm.toLowerCase(Locale.ENGLISH); 329 330 synchronized (constraintsMap) { 331 if (!constraintsMap.containsKey(algorithm)) { 332 constraintsMap.put(algorithm, 333 new HashSet<KeySizeConstraint>()); 334 } 335 336 Set<KeySizeConstraint> constraintSet = 337 constraintsMap.get(algorithm); 338 KeySizeConstraint constraint = 339 new KeySizeConstraint(operator, length); 340 constraintSet.add(constraint); 341 } 342 } 343 } 344 } 345 346 // Does this KeySizeConstraints disable the specified key? disables(Key key)347 public boolean disables(Key key) { 348 String algorithm = key.getAlgorithm().toLowerCase(Locale.ENGLISH); 349 synchronized (constraintsMap) { 350 if (constraintsMap.containsKey(algorithm)) { 351 Set<KeySizeConstraint> constraintSet = 352 constraintsMap.get(algorithm); 353 for (KeySizeConstraint constraint : constraintSet) { 354 if (constraint.disables(key)) { 355 return true; 356 } 357 } 358 } 359 } 360 361 return false; 362 } 363 } 364 365 /** 366 * Key size constraint. 367 * 368 * e.g. "keysize <= 1024" 369 */ 370 private static class KeySizeConstraint { 371 // operator 372 static enum Operator { 373 EQ, // "==" 374 NE, // "!=" 375 LT, // "<" 376 LE, // "<=" 377 GT, // ">" 378 GE; // ">=" 379 of(String s)380 static Operator of(String s) { 381 switch (s) { 382 case "==": 383 return EQ; 384 case "!=": 385 return NE; 386 case "<": 387 return LT; 388 case "<=": 389 return LE; 390 case ">": 391 return GT; 392 case ">=": 393 return GE; 394 } 395 396 throw new IllegalArgumentException( 397 s + " is not a legal Operator"); 398 } 399 } 400 401 private int minSize; // the minimal available key size 402 private int maxSize; // the maximal available key size 403 private int prohibitedSize = -1; // unavailable key sizes 404 KeySizeConstraint(Operator operator, int length)405 public KeySizeConstraint(Operator operator, int length) { 406 switch (operator) { 407 case EQ: // an unavailable key size 408 this.minSize = 0; 409 this.maxSize = Integer.MAX_VALUE; 410 prohibitedSize = length; 411 break; 412 case NE: 413 this.minSize = length; 414 this.maxSize = length; 415 break; 416 case LT: 417 this.minSize = length; 418 this.maxSize = Integer.MAX_VALUE; 419 break; 420 case LE: 421 this.minSize = length + 1; 422 this.maxSize = Integer.MAX_VALUE; 423 break; 424 case GT: 425 this.minSize = 0; 426 this.maxSize = length; 427 break; 428 case GE: 429 this.minSize = 0; 430 this.maxSize = length > 1 ? (length - 1) : 0; 431 break; 432 default: 433 // unlikely to happen 434 this.minSize = Integer.MAX_VALUE; 435 this.maxSize = -1; 436 } 437 } 438 439 // Does this key constraint disable the specified key? disables(Key key)440 public boolean disables(Key key) { 441 int size = KeyUtil.getKeySize(key); 442 443 if (size == 0) { 444 return true; // we don't allow any key of size 0. 445 } else if (size > 0) { 446 return ((size < minSize) || (size > maxSize) || 447 (prohibitedSize == size)); 448 } // Otherwise, the key size is not accessible. Conservatively, 449 // please don't disable such keys. 450 451 return false; 452 } 453 } 454 455 } 456 457