1 /* 2 * Copyright (c) 1999, 2019, 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 javax.crypto; 27 28 import java.io.*; 29 import java.util.Enumeration; 30 import java.util.Hashtable; 31 import java.util.Vector; 32 import static java.util.Locale.ENGLISH; 33 34 import java.security.GeneralSecurityException; 35 import java.security.spec.AlgorithmParameterSpec; 36 import java.lang.reflect.*; 37 38 /** 39 * JCE has two pairs of jurisdiction policy files: one represents U.S. export 40 * laws, and the other represents the local laws of the country where the 41 * JCE will be used. 42 * 43 * The jurisdiction policy file has the same syntax as JDK policy files except 44 * that JCE has new permission classes called javax.crypto.CryptoPermission 45 * and javax.crypto.CryptoAllPermission. 46 * 47 * The format of a permission entry in the jurisdiction policy file is: 48 * 49 * <pre>{@code 50 * permission <crypto permission class name>[, <algorithm name> 51 * [[, <exemption mechanism name>][, <maxKeySize> 52 * [, <AlgorithmParameterSpec class name>, <parameters 53 * for constructing an AlgorithmParameterSpec object>]]]]; 54 * }</pre> 55 * 56 * @author Sharon Liu 57 * 58 * @see java.security.Permissions 59 * @see java.security.spec.AlgorithmParameterSpec 60 * @see javax.crypto.CryptoPermission 61 * @see javax.crypto.CryptoAllPermission 62 * @see javax.crypto.CryptoPermissions 63 * @since 1.4 64 */ 65 66 final class CryptoPolicyParser { 67 68 private Vector<GrantEntry> grantEntries; 69 70 // Convenience variables for parsing 71 private StreamTokenizer st; 72 private int lookahead; 73 74 /** 75 * Creates a CryptoPolicyParser object. 76 */ 77 CryptoPolicyParser() { 78 grantEntries = new Vector<GrantEntry>(); 79 } 80 81 /** 82 * Reads a policy configuration using a Reader object. <p> 83 * 84 * @param policy the policy Reader object. 85 * 86 * @exception ParsingException if the policy configuration 87 * contains a syntax error. 88 * 89 * @exception IOException if an error occurs while reading 90 * the policy configuration. 91 */ 92 93 void read(Reader policy) 94 throws ParsingException, IOException 95 { 96 if (!(policy instanceof BufferedReader)) { 97 policy = new BufferedReader(policy); 98 } 99 100 /* 101 * Configure the stream tokenizer: 102 * Recognize strings between "..." 103 * Don't convert words to lowercase 104 * Recognize both C-style and C++-style comments 105 * Treat end-of-line as white space, not as a token 106 */ 107 st = new StreamTokenizer(policy); 108 109 st.resetSyntax(); 110 st.wordChars('a', 'z'); 111 st.wordChars('A', 'Z'); 112 st.wordChars('.', '.'); 113 st.wordChars('0', '9'); 114 st.wordChars('_', '_'); 115 st.wordChars('$', '$'); 116 st.wordChars(128 + 32, 255); 117 st.whitespaceChars(0, ' '); 118 st.commentChar('/'); 119 st.quoteChar('\''); 120 st.quoteChar('"'); 121 st.lowerCaseMode(false); 122 st.ordinaryChar('/'); 123 st.slashSlashComments(true); 124 st.slashStarComments(true); 125 st.parseNumbers(); 126 127 /* 128 * The crypto jurisdiction policy must be consistent. The 129 * following hashtable is used for checking consistency. 130 */ 131 Hashtable<String, Vector<String>> processedPermissions = null; 132 133 /* 134 * The main parsing loop. The loop is executed once for each entry 135 * in the policy file. The entries are delimited by semicolons. Once 136 * we've read in the information for an entry, go ahead and try to 137 * add it to the grantEntries. 138 */ 139 lookahead = st.nextToken(); 140 while (lookahead != StreamTokenizer.TT_EOF) { 141 if (peek("grant")) { 142 GrantEntry ge = parseGrantEntry(processedPermissions); 143 if (ge != null) 144 grantEntries.addElement(ge); 145 } else { 146 throw new ParsingException(st.lineno(), "expected grant " + 147 "statement"); 148 } 149 match(";"); 150 } 151 } 152 153 /** 154 * parse a Grant entry 155 */ 156 private GrantEntry parseGrantEntry( 157 Hashtable<String, Vector<String>> processedPermissions) 158 throws ParsingException, IOException 159 { 160 GrantEntry e = new GrantEntry(); 161 162 match("grant"); 163 match("{"); 164 165 while(!peek("}")) { 166 if (peek("Permission")) { 167 CryptoPermissionEntry pe = 168 parsePermissionEntry(processedPermissions); 169 e.add(pe); 170 match(";"); 171 } else { 172 throw new 173 ParsingException(st.lineno(), "expected permission entry"); 174 } 175 } 176 match("}"); 177 178 return e; 179 } 180 181 /** 182 * parse a CryptoPermission entry 183 */ 184 private CryptoPermissionEntry parsePermissionEntry( 185 Hashtable<String, Vector<String>> processedPermissions) 186 throws ParsingException, IOException 187 { 188 CryptoPermissionEntry e = new CryptoPermissionEntry(); 189 190 match("Permission"); 191 e.cryptoPermission = match("permission type"); 192 193 if (e.cryptoPermission.equals("javax.crypto.CryptoAllPermission")) { 194 // Done with the CryptoAllPermission entry. 195 e.alg = CryptoAllPermission.ALG_NAME; 196 e.maxKeySize = Integer.MAX_VALUE; 197 return e; 198 } 199 200 // Should see the algorithm name. 201 if (peek("\"")) { 202 // Algorithm name - always convert to upper case after parsing. 203 e.alg = match("quoted string").toUpperCase(ENGLISH); 204 } else { 205 // The algorithm name can be a wildcard. 206 if (peek("*")) { 207 match("*"); 208 e.alg = CryptoPermission.ALG_NAME_WILDCARD; 209 } else { 210 throw new ParsingException(st.lineno(), 211 "Missing the algorithm name"); 212 } 213 } 214 215 peekAndMatch(","); 216 217 // May see the exemption mechanism name. 218 if (peek("\"")) { 219 // Exemption mechanism name - convert to upper case too. 220 e.exemptionMechanism = match("quoted string").toUpperCase(ENGLISH); 221 } 222 223 peekAndMatch(","); 224 225 // Check whether this entry is consistent with other permission entries 226 // that have been read. 227 if (!isConsistent(e.alg, e.exemptionMechanism, processedPermissions)) { 228 throw new ParsingException(st.lineno(), "Inconsistent policy"); 229 } 230 231 // Should see the maxKeySize if not at the end of this entry yet. 232 if (peek("number")) { 233 e.maxKeySize = match(); 234 } else { 235 if (peek("*")) { 236 match("*"); 237 e.maxKeySize = Integer.MAX_VALUE; 238 } else { 239 if (!peek(";")) { 240 throw new ParsingException(st.lineno(), 241 "Missing the maximum " + 242 "allowable key size"); 243 } else { 244 // At the end of this permission entry 245 e.maxKeySize = Integer.MAX_VALUE; 246 } 247 } 248 } 249 250 peekAndMatch(","); 251 252 // May see an AlgorithmParameterSpec class name. 253 if (peek("\"")) { 254 // AlgorithmParameterSpec class name. 255 String algParamSpecClassName = match("quoted string"); 256 257 Vector<Integer> paramsV = new Vector<>(1); 258 while (peek(",")) { 259 match(","); 260 if (peek("number")) { 261 paramsV.addElement(match()); 262 } else { 263 if (peek("*")) { 264 match("*"); 265 paramsV.addElement(Integer.MAX_VALUE); 266 } else { 267 throw new ParsingException(st.lineno(), 268 "Expecting an integer"); 269 } 270 } 271 } 272 273 Integer[] params = new Integer[paramsV.size()]; 274 paramsV.copyInto(params); 275 276 e.checkParam = true; 277 e.algParamSpec = getInstance(algParamSpecClassName, params); 278 } 279 280 return e; 281 } 282 283 private static final AlgorithmParameterSpec getInstance(String type, 284 Integer[] params) 285 throws ParsingException 286 { 287 AlgorithmParameterSpec ret = null; 288 289 try { 290 Class<?> apsClass = Class.forName(type); 291 Class<?>[] paramClasses = new Class<?>[params.length]; 292 293 for (int i = 0; i < params.length; i++) { 294 paramClasses[i] = int.class; 295 } 296 297 Constructor<?> c = apsClass.getConstructor(paramClasses); 298 ret = (AlgorithmParameterSpec) c.newInstance((Object[]) params); 299 } catch (Exception e) { 300 throw new ParsingException("Cannot call the constructor of " + 301 type + e); 302 } 303 return ret; 304 } 305 306 307 private boolean peekAndMatch(String expect) 308 throws ParsingException, IOException 309 { 310 if (peek(expect)) { 311 match(expect); 312 return true; 313 } 314 return false; 315 } 316 317 private boolean peek(String expect) { 318 boolean found = false; 319 320 switch (lookahead) { 321 322 case StreamTokenizer.TT_WORD: 323 if (expect.equalsIgnoreCase(st.sval)) 324 found = true; 325 break; 326 case StreamTokenizer.TT_NUMBER: 327 if (expect.equalsIgnoreCase("number")) { 328 found = true; 329 } 330 break; 331 case ',': 332 if (expect.equals(",")) 333 found = true; 334 break; 335 case '{': 336 if (expect.equals("{")) 337 found = true; 338 break; 339 case '}': 340 if (expect.equals("}")) 341 found = true; 342 break; 343 case '"': 344 if (expect.equals("\"")) 345 found = true; 346 break; 347 case '*': 348 if (expect.equals("*")) 349 found = true; 350 break; 351 case ';': 352 if (expect.equals(";")) 353 found = true; 354 break; 355 default: 356 break; 357 } 358 return found; 359 } 360 361 /** 362 * Excepts to match a non-negative number. 363 */ 364 private int match() 365 throws ParsingException, IOException 366 { 367 int value = -1; 368 int lineno = st.lineno(); 369 String sValue = null; 370 371 switch (lookahead) { 372 case StreamTokenizer.TT_NUMBER: 373 value = (int)st.nval; 374 if (value < 0) { 375 sValue = String.valueOf(st.nval); 376 } 377 lookahead = st.nextToken(); 378 break; 379 default: 380 sValue = st.sval; 381 break; 382 } 383 if (value <= 0) { 384 throw new ParsingException(lineno, "a non-negative number", 385 sValue); 386 } 387 return value; 388 } 389 390 private String match(String expect) 391 throws ParsingException, IOException 392 { 393 String value = null; 394 395 switch (lookahead) { 396 case StreamTokenizer.TT_NUMBER: 397 throw new ParsingException(st.lineno(), expect, 398 "number "+String.valueOf(st.nval)); 399 case StreamTokenizer.TT_EOF: 400 throw new ParsingException("expected "+expect+", read end of file"); 401 case StreamTokenizer.TT_WORD: 402 if (expect.equalsIgnoreCase(st.sval)) { 403 lookahead = st.nextToken(); 404 } 405 else if (expect.equalsIgnoreCase("permission type")) { 406 value = st.sval; 407 lookahead = st.nextToken(); 408 } 409 else 410 throw new ParsingException(st.lineno(), expect, st.sval); 411 break; 412 case '"': 413 if (expect.equalsIgnoreCase("quoted string")) { 414 value = st.sval; 415 lookahead = st.nextToken(); 416 } else if (expect.equalsIgnoreCase("permission type")) { 417 value = st.sval; 418 lookahead = st.nextToken(); 419 } 420 else 421 throw new ParsingException(st.lineno(), expect, st.sval); 422 break; 423 case ',': 424 if (expect.equals(",")) 425 lookahead = st.nextToken(); 426 else 427 throw new ParsingException(st.lineno(), expect, ","); 428 break; 429 case '{': 430 if (expect.equals("{")) 431 lookahead = st.nextToken(); 432 else 433 throw new ParsingException(st.lineno(), expect, "{"); 434 break; 435 case '}': 436 if (expect.equals("}")) 437 lookahead = st.nextToken(); 438 else 439 throw new ParsingException(st.lineno(), expect, "}"); 440 break; 441 case ';': 442 if (expect.equals(";")) 443 lookahead = st.nextToken(); 444 else 445 throw new ParsingException(st.lineno(), expect, ";"); 446 break; 447 case '*': 448 if (expect.equals("*")) 449 lookahead = st.nextToken(); 450 else 451 throw new ParsingException(st.lineno(), expect, "*"); 452 break; 453 default: 454 throw new ParsingException(st.lineno(), expect, 455 new String(new char[] {(char)lookahead})); 456 } 457 return value; 458 } 459 460 CryptoPermission[] getPermissions() { 461 Vector<CryptoPermission> result = new Vector<>(); 462 463 Enumeration<GrantEntry> grantEnum = grantEntries.elements(); 464 while (grantEnum.hasMoreElements()) { 465 GrantEntry ge = grantEnum.nextElement(); 466 Enumeration<CryptoPermissionEntry> permEnum = 467 ge.permissionElements(); 468 while (permEnum.hasMoreElements()) { 469 CryptoPermissionEntry pe = permEnum.nextElement(); 470 if (pe.cryptoPermission.equals( 471 "javax.crypto.CryptoAllPermission")) { 472 result.addElement(CryptoAllPermission.INSTANCE); 473 } else { 474 if (pe.checkParam) { 475 result.addElement(new CryptoPermission( 476 pe.alg, 477 pe.maxKeySize, 478 pe.algParamSpec, 479 pe.exemptionMechanism)); 480 } else { 481 result.addElement(new CryptoPermission( 482 pe.alg, 483 pe.maxKeySize, 484 pe.exemptionMechanism)); 485 } 486 } 487 } 488 } 489 490 CryptoPermission[] ret = new CryptoPermission[result.size()]; 491 result.copyInto(ret); 492 493 return ret; 494 } 495 496 private boolean isConsistent(String alg, String exemptionMechanism, 497 Hashtable<String, Vector<String>> processedPermissions) { 498 String thisExemptionMechanism = 499 exemptionMechanism == null ? "none" : exemptionMechanism; 500 501 if (processedPermissions == null) { 502 processedPermissions = new Hashtable<String, Vector<String>>(); 503 Vector<String> exemptionMechanisms = new Vector<>(1); 504 exemptionMechanisms.addElement(thisExemptionMechanism); 505 processedPermissions.put(alg, exemptionMechanisms); 506 return true; 507 } 508 509 if (processedPermissions.containsKey(CryptoAllPermission.ALG_NAME)) { 510 return false; 511 } 512 513 Vector<String> exemptionMechanisms; 514 515 if (processedPermissions.containsKey(alg)) { 516 exemptionMechanisms = processedPermissions.get(alg); 517 if (exemptionMechanisms.contains(thisExemptionMechanism)) { 518 return false; 519 } 520 } else { 521 exemptionMechanisms = new Vector<String>(1); 522 } 523 524 exemptionMechanisms.addElement(thisExemptionMechanism); 525 processedPermissions.put(alg, exemptionMechanisms); 526 return true; 527 } 528 529 /** 530 * Each grant entry in the policy configuration file is represented by a 531 * GrantEntry object. 532 * <p> 533 * For example, the entry 534 * <pre> 535 * grant { 536 * permission javax.crypto.CryptoPermission "DES", 56; 537 * }; 538 * 539 * </pre> 540 * is represented internally 541 * <pre> 542 * 543 * pe = new CryptoPermissionEntry("javax.crypto.CryptoPermission", 544 * "DES", 56); 545 * 546 * ge = new GrantEntry(); 547 * 548 * ge.add(pe); 549 * 550 * </pre> 551 * 552 * @see java.security.Permission 553 * @see javax.crypto.CryptoPermission 554 * @see javax.crypto.CryptoPermissions 555 */ 556 557 private static class GrantEntry { 558 559 private Vector<CryptoPermissionEntry> permissionEntries; 560 561 GrantEntry() { 562 permissionEntries = new Vector<CryptoPermissionEntry>(); 563 } 564 565 void add(CryptoPermissionEntry pe) 566 { 567 permissionEntries.addElement(pe); 568 } 569 570 boolean remove(CryptoPermissionEntry pe) 571 { 572 return permissionEntries.removeElement(pe); 573 } 574 575 boolean contains(CryptoPermissionEntry pe) 576 { 577 return permissionEntries.contains(pe); 578 } 579 580 /** 581 * Enumerate all the permission entries in this GrantEntry. 582 */ 583 Enumeration<CryptoPermissionEntry> permissionElements(){ 584 return permissionEntries.elements(); 585 } 586 587 } 588 589 /** 590 * Each crypto permission entry in the policy configuration file is 591 * represented by a CryptoPermissionEntry object. 592 * <p> 593 * For example, the entry 594 * <pre> 595 * permission javax.crypto.CryptoPermission "DES", 56; 596 * </pre> 597 * is represented internally 598 * <pre> 599 * 600 * pe = new CryptoPermissionEntry("javax.crypto.cryptoPermission", 601 * "DES", 56); 602 * </pre> 603 * 604 * @see java.security.Permissions 605 * @see javax.crypto.CryptoPermission 606 * @see javax.crypto.CryptoAllPermission 607 */ 608 609 private static class CryptoPermissionEntry { 610 611 String cryptoPermission; 612 String alg; 613 String exemptionMechanism; 614 int maxKeySize; 615 boolean checkParam; 616 AlgorithmParameterSpec algParamSpec; 617 618 CryptoPermissionEntry() { 619 // Set default values. 620 maxKeySize = 0; 621 alg = null; 622 exemptionMechanism = null; 623 checkParam = false; 624 algParamSpec = null; 625 } 626 627 /** 628 * Calculates a hash code value for the object. Objects 629 * which are equal will also have the same hashcode. 630 */ 631 public int hashCode() { 632 int retval = cryptoPermission.hashCode(); 633 if (alg != null) retval ^= alg.hashCode(); 634 if (exemptionMechanism != null) { 635 retval ^= exemptionMechanism.hashCode(); 636 } 637 retval ^= maxKeySize; 638 if (checkParam) retval ^= 100; 639 if (algParamSpec != null) { 640 retval ^= algParamSpec.hashCode(); 641 } 642 return retval; 643 } 644 645 public boolean equals(Object obj) { 646 if (obj == this) 647 return true; 648 649 if (!(obj instanceof CryptoPermissionEntry)) 650 return false; 651 652 CryptoPermissionEntry that = (CryptoPermissionEntry) obj; 653 654 if (this.cryptoPermission == null) { 655 if (that.cryptoPermission != null) return false; 656 } else { 657 if (!this.cryptoPermission.equals( 658 that.cryptoPermission)) 659 return false; 660 } 661 662 if (this.alg == null) { 663 if (that.alg != null) return false; 664 } else { 665 if (!this.alg.equalsIgnoreCase(that.alg)) 666 return false; 667 } 668 669 if (!(this.maxKeySize == that.maxKeySize)) return false; 670 671 if (this.checkParam != that.checkParam) return false; 672 673 if (this.algParamSpec == null) { 674 if (that.algParamSpec != null) return false; 675 } else { 676 if (!this.algParamSpec.equals(that.algParamSpec)) 677 return false; 678 } 679 680 // everything matched -- the 2 objects are equal 681 return true; 682 } 683 } 684 685 static final class ParsingException extends GeneralSecurityException { 686 687 @java.io.Serial 688 private static final long serialVersionUID = 7147241245566588374L; 689 690 /** 691 * Constructs a ParsingException with the specified 692 * detail message. 693 * @param msg the detail message. 694 */ 695 ParsingException(String msg) { 696 super(msg); 697 } 698 699 ParsingException(int line, String msg) { 700 super("line " + line + ": " + msg); 701 } 702 703 ParsingException(int line, String expect, String actual) { 704 super("line "+line+": expected '"+expect+"', found '"+actual+"'"); 705 } 706 } 707 }