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 }