1 /* 2 * Copyright (c) 1999, 2007, 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 java.util.StringTokenizer; 33 import static java.util.Locale.ENGLISH; 34 35 import java.security.GeneralSecurityException; 36 import java.security.spec.AlgorithmParameterSpec; 37 import java.lang.reflect; 38 39 /** 40 * JCE has two pairs of jurisdiction policy files: one represents U.S. export 41 * laws, and the other represents the local laws of the country where the 42 * JCE will be used. 43 * 44 * The jurisdiction policy file has the same syntax as JDK policy files except 45 * that JCE has new permission classes called javax.crypto.CryptoPermission 46 * and javax.crypto.CryptoAllPermission. 47 * 48 * The format of a permission entry in the jurisdiction policy file is: 49 * 50 * permission <crypto permission class name>[, <algorithm name> 51 * [[, <exemption mechanism name>][, <maxKeySize> 52 * [, <AlgrithomParameterSpec class name>, <parameters 53 * for constructing an AlgrithomParameterSpec object>]]]]; 54 * 55 * @author Sharon Liu 56 * 57 * @see java.security.Permissions 58 * @see java.security.spec.AlgrithomParameterSpec 59 * @see javax.crypto.CryptoPermission 60 * @see javax.crypto.CryptoAllPermission 61 * @see javax.crypto.CryptoPermissions 62 * @since 1.4 63 */ 64 65 final class CryptoPolicyParser { 66 67 private Vector grantEntries; 68 69 // Convenience variables for parsing 70 private StreamTokenizer st; 71 private int lookahead; 72 73 /** 74 * Creates a CryptoPolicyParser object. 75 */ 76 CryptoPolicyParser() { 77 grantEntries = new Vector(); 78 } 79 80 /** 81 * Reads a policy configuration using a Reader object. <p> 82 * 83 * @param policy the policy Reader object. 84 * 85 * @exception ParsingException if the policy configuration 86 * contains a syntax error. 87 * 88 * @exception IOException if an error occurs while reading 89 * the policy configuration. 90 */ 91 92 void read(Reader policy) 93 throws ParsingException, IOException 94 { 95 if (!(policy instanceof BufferedReader)) { 96 policy = new BufferedReader(policy); 97 } 98 99 /* 100 * Configure the stream tokenizer: 101 * Recognize strings between "..." 102 * Don't convert words to lowercase 103 * Recognize both C-style and C++-style comments 104 * Treat end-of-line as white space, not as a token 105 */ 106 st = new StreamTokenizer(policy); 107 108 st.resetSyntax(); 109 st.wordChars('a', 'z'); 110 st.wordChars('A', 'Z'); 111 st.wordChars('.', '.'); 112 st.wordChars('0', '9'); 113 st.wordChars('_', '_'); 114 st.wordChars('$', '$'); 115 st.wordChars(128 + 32, 255); 116 st.whitespaceChars(0, ' '); 117 st.commentChar('/'); 118 st.quoteChar('\''); 119 st.quoteChar('"'); 120 st.lowerCaseMode(false); 121 st.ordinaryChar('/'); 122 st.slashSlashComments(true); 123 st.slashStarComments(true); 124 st.parseNumbers(); 125 126 /* 127 * The crypto jurisdiction policy must be consistent. The 128 * following hashtable is used for checking consistency. 129 */ 130 Hashtable processedPermissions = null; 131 132 /* 133 * The main parsing loop. The loop is executed once for each entry 134 * in the policy file. The entries are delimited by semicolons. Once 135 * we've read in the information for an entry, go ahead and try to 136 * add it to the grantEntries. 137 */ 138 lookahead = st.nextToken(); 139 while (lookahead != StreamTokenizer.TT_EOF) { 140 if (peek("grant")) { 141 GrantEntry ge = parseGrantEntry(processedPermissions); 142 if (ge != null) 143 grantEntries.addElement(ge); 144 } else { 145 throw new ParsingException(st.lineno(), "expected grant " + 146 "statement"); 147 } 148 match(";"); 149 } 150 } 151 152 /** 153 * parse a Grant entry 154 */ 155 private GrantEntry parseGrantEntry(Hashtable 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 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 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 result = new Vector(); 460 461 Enumeration grantEnum = grantEntries.elements(); 462 while (grantEnum.hasMoreElements()) { 463 GrantEntry ge = (GrantEntry)grantEnum.nextElement(); 464 Enumeration permEnum = ge.permissionElements(); 465 while (permEnum.hasMoreElements()) { 466 CryptoPermissionEntry pe = 467 (CryptoPermissionEntry)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, 495 String exemptionMechanism, 496 Hashtable processedPermissions) { 497 String thisExemptionMechanism = 498 exemptionMechanism == null ? "none" : exemptionMechanism; 499 500 if (processedPermissions == null) { 501 processedPermissions = new Hashtable(); 502 Vector exemptionMechanisms = new Vector(1); 503 exemptionMechanisms.addElement(thisExemptionMechanism); 504 processedPermissions.put(alg, exemptionMechanisms); 505 return true; 506 } 507 508 if (processedPermissions.containsKey(CryptoAllPermission.ALG_NAME)) { 509 return false; 510 } 511 512 Vector exemptionMechanisms; 513 514 if (processedPermissions.containsKey(alg)) { 515 exemptionMechanisms = (Vector)processedPermissions.get(alg); 516 if (exemptionMechanisms.contains(thisExemptionMechanism)) { 517 return false; 518 } 519 } else { 520 exemptionMechanisms = new Vector(1); 521 } 522 523 exemptionMechanisms.addElement(thisExemptionMechanism); 524 processedPermissions.put(alg, exemptionMechanisms); 525 return true; 526 } 527 528 /** 529 * Each grant entry in the policy configuration file is represented by a 530 * GrantEntry object. <p> 531 * 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 permissionEntries; 560 561 GrantEntry() { 562 permissionEntries = new Vector(); 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 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. <p> 592 * 593 * <p> 594 * For example, the entry 595 * <pre> 596 * permission javax.crypto.CryptoPermission "DES", 56; 597 * </pre> 598 * is represented internally 599 * <pre> 600 * 601 * pe = new CryptoPermissionEntry("javax.crypto.cryptoPermission", 602 * "DES", 56); 603 * </pre> 604 * 605 * @see java.security.Permissions 606 * @see javax.crypto.CryptoPermission 607 * @see javax.crypto.CryptoAllPermission 608 */ 609 610 private static class CryptoPermissionEntry { 611 612 String cryptoPermission; 613 String alg; 614 String exemptionMechanism; 615 int maxKeySize; 616 boolean checkParam; 617 AlgorithmParameterSpec algParamSpec; 618 619 CryptoPermissionEntry() { 620 // Set default values. 621 maxKeySize = 0; 622 alg = null; 623 exemptionMechanism = null; 624 checkParam = false; 625 algParamSpec = null; 626 } 627 628 /** 629 * Calculates a hash code value for the object. Objects 630 * which are equal will also have the same hashcode. 631 */ 632 public int hashCode() { 633 int retval = cryptoPermission.hashCode(); 634 if (alg != null) retval ^= alg.hashCode(); 635 if (exemptionMechanism != null) { 636 retval ^= exemptionMechanism.hashCode(); 637 } 638 retval ^= maxKeySize; 639 if (checkParam) retval ^= 100; 640 if (algParamSpec != null) { 641 retval ^= algParamSpec.hashCode(); 642 } 643 return retval; 644 } 645 646 public boolean equals(Object obj) { 647 if (obj == this) 648 return true; 649 650 if (!(obj instanceof CryptoPermissionEntry)) 651 return false; 652 653 CryptoPermissionEntry that = (CryptoPermissionEntry) obj; 654 655 if (this.cryptoPermission == null) { 656 if (that.cryptoPermission != null) return false; 657 } else { 658 if (!this.cryptoPermission.equals( 659 that.cryptoPermission)) 660 return false; 661 } 662 663 if (this.alg == null) { 664 if (that.alg != null) return false; 665 } else { 666 if (!this.alg.equalsIgnoreCase(that.alg)) 667 return false; 668 } 669 670 if (!(this.maxKeySize == that.maxKeySize)) return false; 671 672 if (this.checkParam != that.checkParam) return false; 673 674 if (this.algParamSpec == null) { 675 if (that.algParamSpec != null) return false; 676 } else { 677 if (!this.algParamSpec.equals(that.algParamSpec)) 678 return false; 679 } 680 681 // everything matched -- the 2 objects are equal 682 return true; 683 } 684 } 685 686 static final class ParsingException extends GeneralSecurityException { 687 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 }