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 }