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