| /* | |
| * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package javax.crypto; | |
| import java.io.*; | |
| import java.util.ArrayList; | |
| import java.util.Enumeration; | |
| import java.util.Hashtable; | |
| import java.util.Objects; | |
| import java.util.Vector; | |
| import static java.util.Locale.ENGLISH; | |
| import java.security.GeneralSecurityException; | |
| import java.security.spec.AlgorithmParameterSpec; | |
| import java.lang.reflect.*; | |
| /** | |
| * JCE has two pairs of jurisdiction policy files: one represents U.S. export | |
| * laws, and the other represents the local laws of the country where the | |
| * JCE will be used. | |
| * | |
| * The jurisdiction policy file has the same syntax as JDK policy files except | |
| * that JCE has new permission classes called | |
| * {@code javax.crypto.CryptoPermission} and | |
| * {@code javax.crypto.CryptoAllPermission}. | |
| * | |
| * The format of a permission entry in the jurisdiction policy file is: | |
| * | |
| * <pre>{@code | |
| * permission <crypto permission class name>[, <algorithm name> | |
| * [[, <exemption mechanism name>][, <maxKeySize> | |
| * [, <AlgorithmParameterSpec class name>, <parameters | |
| * for constructing an AlgorithmParameterSpec object>]]]]; | |
| * }</pre> | |
| * | |
| * @author Sharon Liu | |
| * | |
| * @see java.security.Permissions | |
| * @see java.security.spec.AlgorithmParameterSpec | |
| * @see javax.crypto.CryptoPermission | |
| * @see javax.crypto.CryptoAllPermission | |
| * @see javax.crypto.CryptoPermissions | |
| * @since 1.4 | |
| */ | |
| final class CryptoPolicyParser { | |
| private final Vector<GrantEntry> grantEntries; | |
| // Convenience variables for parsing | |
| private StreamTokenizer st; | |
| private int lookahead; | |
| private boolean allPermEntryFound = false; | |
| /** | |
| * Creates a {@code CryptoPolicyParser} object. | |
| */ | |
| CryptoPolicyParser() { | |
| grantEntries = new Vector<>(); | |
| } | |
| /** | |
| * Reads a policy configuration using a {@code Reader} object. <p> | |
| * | |
| * @param policy the policy {@code Reader} object. | |
| * | |
| * @exception ParsingException if the policy configuration | |
| * contains a syntax error. | |
| * | |
| * @exception IOException if an error occurs while reading | |
| * the policy configuration. | |
| */ | |
| void read(Reader policy) | |
| throws ParsingException, IOException | |
| { | |
| if (!(policy instanceof BufferedReader)) { | |
| policy = new BufferedReader(policy); | |
| } | |
| /* | |
| * Configure the stream tokenizer: | |
| * Recognize strings between "..." | |
| * Don't convert words to lowercase | |
| * Recognize both C-style and C++-style comments | |
| * Treat end-of-line as white space, not as a token | |
| */ | |
| st = new StreamTokenizer(policy); | |
| st.resetSyntax(); | |
| st.wordChars('a', 'z'); | |
| st.wordChars('A', 'Z'); | |
| st.wordChars('.', '.'); | |
| st.wordChars('0', '9'); | |
| st.wordChars('_', '_'); | |
| st.wordChars('$', '$'); | |
| st.wordChars(128 + 32, 255); | |
| st.whitespaceChars(0, ' '); | |
| st.commentChar('/'); | |
| st.quoteChar('\''); | |
| st.quoteChar('"'); | |
| st.lowerCaseMode(false); | |
| st.ordinaryChar('/'); | |
| st.slashSlashComments(true); | |
| st.slashStarComments(true); | |
| st.parseNumbers(); | |
| /* | |
| * The crypto jurisdiction policy must be consistent. The | |
| * following hashtable is used for checking consistency. | |
| */ | |
| Hashtable<String, Vector<String>> processedPermissions = new Hashtable<>(); | |
| /* | |
| * The main parsing loop. The loop is executed once for each entry | |
| * in the policy file. The entries are delimited by semicolons. Once | |
| * we've read in the information for an entry, go ahead and try to | |
| * add it to the grantEntries. | |
| */ | |
| lookahead = st.nextToken(); | |
| while (lookahead != StreamTokenizer.TT_EOF) { | |
| if (peek("grant")) { | |
| GrantEntry ge = parseGrantEntry(processedPermissions); | |
| grantEntries.addElement(ge); | |
| } else { | |
| throw new ParsingException(st.lineno(), "expected grant " + | |
| "statement"); | |
| } | |
| match(";"); | |
| } | |
| } | |
| /** | |
| * parse a Grant entry | |
| */ | |
| private GrantEntry parseGrantEntry( | |
| Hashtable<String, Vector<String>> processedPermissions) | |
| throws ParsingException, IOException | |
| { | |
| GrantEntry e = new GrantEntry(); | |
| match("grant"); | |
| match("{"); | |
| while(!peek("}")) { | |
| if (peek("Permission")) { | |
| CryptoPermissionEntry pe = | |
| parsePermissionEntry(processedPermissions); | |
| e.add(pe); | |
| match(";"); | |
| } else { | |
| throw new | |
| ParsingException(st.lineno(), "expected permission entry"); | |
| } | |
| } | |
| match("}"); | |
| return e; | |
| } | |
| /** | |
| * parse a CryptoPermission entry | |
| */ | |
| private CryptoPermissionEntry parsePermissionEntry( | |
| Hashtable<String, Vector<String>> processedPermissions) | |
| throws ParsingException, IOException | |
| { | |
| CryptoPermissionEntry e = new CryptoPermissionEntry(); | |
| match("Permission"); | |
| e.cryptoPermission = match("permission type"); | |
| if (e.cryptoPermission.equals("javax.crypto.CryptoAllPermission")) { | |
| /* | |
| * This catches while processing the "javax.crypto.CryptoAllPermission" | |
| * entry, but the "processedPermissions" Hashtable already contains | |
| * an entry for "javax.crypto.CryptoPermission". | |
| */ | |
| if (!processedPermissions.isEmpty()) { | |
| throw new ParsingException(st.lineno(), "Inconsistent policy"); | |
| } | |
| allPermEntryFound = true; | |
| // Done with the CryptoAllPermission entry. | |
| e.alg = CryptoAllPermission.ALG_NAME; | |
| e.maxKeySize = Integer.MAX_VALUE; | |
| return e; | |
| } | |
| // Should see the algorithm name. | |
| if (peek("\"")) { | |
| // Algorithm name - always convert to upper case after parsing. | |
| e.alg = match("quoted string").toUpperCase(ENGLISH); | |
| } else { | |
| // The algorithm name can be a wildcard. | |
| if (peek("*")) { | |
| match("*"); | |
| e.alg = CryptoPermission.ALG_NAME_WILDCARD; | |
| } else { | |
| throw new ParsingException(st.lineno(), | |
| "Missing the algorithm name"); | |
| } | |
| } | |
| peekAndMatch(","); | |
| // May see the exemption mechanism name. | |
| if (peek("\"")) { | |
| // Exemption mechanism name - convert to upper case too. | |
| e.exemptionMechanism = match("quoted string").toUpperCase(ENGLISH); | |
| } | |
| peekAndMatch(","); | |
| // Check whether this entry is consistent with other permission entries | |
| // that have been read. | |
| if (!isConsistent(e.alg, e.exemptionMechanism, processedPermissions)) { | |
| throw new ParsingException(st.lineno(), "Inconsistent policy"); | |
| } | |
| // Should see the maxKeySize if not at the end of this entry yet. | |
| if (peek("number")) { | |
| e.maxKeySize = match(); | |
| } else { | |
| if (peek("*")) { | |
| match("*"); | |
| e.maxKeySize = Integer.MAX_VALUE; | |
| } else { | |
| if (!peek(";")) { | |
| throw new ParsingException(st.lineno(), | |
| "Missing the maximum " + | |
| "allowable key size"); | |
| } else { | |
| // At the end of this permission entry | |
| e.maxKeySize = Integer.MAX_VALUE; | |
| } | |
| } | |
| } | |
| peekAndMatch(","); | |
| // May see an AlgorithmParameterSpec class name. | |
| if (peek("\"")) { | |
| // AlgorithmParameterSpec class name. | |
| String algParamSpecClassName = match("quoted string"); | |
| ArrayList<Integer> paramsV = new ArrayList<>(1); | |
| while (peek(",")) { | |
| match(","); | |
| if (peek("number")) { | |
| paramsV.add(match()); | |
| } else { | |
| if (peek("*")) { | |
| match("*"); | |
| paramsV.add(Integer.MAX_VALUE); | |
| } else { | |
| throw new ParsingException(st.lineno(), | |
| "Expecting an integer"); | |
| } | |
| } | |
| } | |
| Integer[] params = paramsV.toArray(new Integer[0]); | |
| e.checkParam = true; | |
| e.algParamSpec = getInstance(algParamSpecClassName, params); | |
| } | |
| return e; | |
| } | |
| private static AlgorithmParameterSpec getInstance(String type, | |
| Integer[] params) | |
| throws ParsingException | |
| { | |
| AlgorithmParameterSpec ret; | |
| try { | |
| Class<?> apsClass = Class.forName(type); | |
| Class<?>[] paramClasses = new Class<?>[params.length]; | |
| for (int i = 0; i < params.length; i++) { | |
| paramClasses[i] = int.class; | |
| } | |
| Constructor<?> c = apsClass.getConstructor(paramClasses); | |
| ret = (AlgorithmParameterSpec) c.newInstance((Object[]) params); | |
| } catch (Exception e) { | |
| throw new ParsingException("Cannot call the constructor of " + | |
| type + e); | |
| } | |
| return ret; | |
| } | |
| private boolean peekAndMatch(String expect) | |
| throws ParsingException, IOException | |
| { | |
| if (peek(expect)) { | |
| match(expect); | |
| return true; | |
| } | |
| return false; | |
| } | |
| private boolean peek(String expect) { | |
| boolean found = false; | |
| switch (lookahead) { | |
| case StreamTokenizer.TT_WORD: | |
| if (expect.equalsIgnoreCase(st.sval)) | |
| found = true; | |
| break; | |
| case StreamTokenizer.TT_NUMBER: | |
| if (expect.equalsIgnoreCase("number")) { | |
| found = true; | |
| } | |
| break; | |
| case ',': | |
| if (expect.equals(",")) | |
| found = true; | |
| break; | |
| case '{': | |
| if (expect.equals("{")) | |
| found = true; | |
| break; | |
| case '}': | |
| if (expect.equals("}")) | |
| found = true; | |
| break; | |
| case '"': | |
| if (expect.equals("\"")) | |
| found = true; | |
| break; | |
| case '*': | |
| if (expect.equals("*")) | |
| found = true; | |
| break; | |
| case ';': | |
| if (expect.equals(";")) | |
| found = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| return found; | |
| } | |
| /** | |
| * Excepts to match a non-negative number. | |
| */ | |
| private int match() | |
| throws ParsingException, IOException | |
| { | |
| int value = -1; | |
| int lineno = st.lineno(); | |
| String sValue = null; | |
| switch (lookahead) { | |
| case StreamTokenizer.TT_NUMBER: | |
| value = (int)st.nval; | |
| if (value < 0) { | |
| sValue = String.valueOf(st.nval); | |
| } | |
| lookahead = st.nextToken(); | |
| break; | |
| default: | |
| sValue = st.sval; | |
| break; | |
| } | |
| if (value <= 0) { | |
| throw new ParsingException(lineno, "a non-negative number", | |
| sValue); | |
| } | |
| return value; | |
| } | |
| private String match(String expect) | |
| throws ParsingException, IOException | |
| { | |
| String value = null; | |
| switch (lookahead) { | |
| case StreamTokenizer.TT_NUMBER: | |
| throw new ParsingException(st.lineno(), expect, | |
| "number " + st.nval); | |
| case StreamTokenizer.TT_EOF: | |
| throw new ParsingException("expected "+expect+", read end of file"); | |
| case StreamTokenizer.TT_WORD: | |
| if (expect.equalsIgnoreCase(st.sval)) { | |
| lookahead = st.nextToken(); | |
| } | |
| else if (expect.equalsIgnoreCase("permission type")) { | |
| value = st.sval; | |
| lookahead = st.nextToken(); | |
| } | |
| else | |
| throw new ParsingException(st.lineno(), expect, st.sval); | |
| break; | |
| case '"': | |
| if (expect.equalsIgnoreCase("quoted string")) { | |
| value = st.sval; | |
| lookahead = st.nextToken(); | |
| } else if (expect.equalsIgnoreCase("permission type")) { | |
| value = st.sval; | |
| lookahead = st.nextToken(); | |
| } | |
| else | |
| throw new ParsingException(st.lineno(), expect, st.sval); | |
| break; | |
| case ',': | |
| if (expect.equals(",")) | |
| lookahead = st.nextToken(); | |
| else | |
| throw new ParsingException(st.lineno(), expect, ","); | |
| break; | |
| case '{': | |
| if (expect.equals("{")) | |
| lookahead = st.nextToken(); | |
| else | |
| throw new ParsingException(st.lineno(), expect, "{"); | |
| break; | |
| case '}': | |
| if (expect.equals("}")) | |
| lookahead = st.nextToken(); | |
| else | |
| throw new ParsingException(st.lineno(), expect, "}"); | |
| break; | |
| case ';': | |
| if (expect.equals(";")) | |
| lookahead = st.nextToken(); | |
| else | |
| throw new ParsingException(st.lineno(), expect, ";"); | |
| break; | |
| case '*': | |
| if (expect.equals("*")) | |
| lookahead = st.nextToken(); | |
| else | |
| throw new ParsingException(st.lineno(), expect, "*"); | |
| break; | |
| default: | |
| throw new ParsingException(st.lineno(), expect, | |
| String.valueOf((char)lookahead)); | |
| } | |
| return value; | |
| } | |
| CryptoPermission[] getPermissions() { | |
| ArrayList<CryptoPermission> result = new ArrayList<>(); | |
| for (GrantEntry ge : grantEntries) { | |
| for (CryptoPermissionEntry pe : ge.permissionEntries) { | |
| if (pe.cryptoPermission.equals( | |
| "javax.crypto.CryptoAllPermission")) { | |
| result.add(CryptoAllPermission.INSTANCE); | |
| } else { | |
| if (pe.checkParam) { | |
| result.add(new CryptoPermission( | |
| pe.alg, | |
| pe.maxKeySize, | |
| pe.algParamSpec, | |
| pe.exemptionMechanism)); | |
| } else { | |
| result.add(new CryptoPermission( | |
| pe.alg, | |
| pe.maxKeySize, | |
| pe.exemptionMechanism)); | |
| } | |
| } | |
| } | |
| } | |
| return result.toArray(new CryptoPermission[0]); | |
| } | |
| private boolean isConsistent(String alg, String exemptionMechanism, | |
| Hashtable<String, Vector<String>> processedPermissions) { | |
| String thisExemptionMechanism = | |
| exemptionMechanism == null ? "none" : exemptionMechanism; | |
| /* | |
| * This catches while processing a "javax.crypto.CryptoPermission" entry, but | |
| * "javax.crypto.CryptoAllPermission" entry already exists. | |
| */ | |
| if (allPermEntryFound) { | |
| return false; | |
| } | |
| if (processedPermissions.isEmpty()) { | |
| Vector<String> exemptionMechanisms = new Vector<>(1); | |
| exemptionMechanisms.addElement(thisExemptionMechanism); | |
| processedPermissions.put(alg, exemptionMechanisms); | |
| return true; | |
| } | |
| Vector<String> exemptionMechanisms; | |
| if (processedPermissions.containsKey(alg)) { | |
| exemptionMechanisms = processedPermissions.get(alg); | |
| if (exemptionMechanisms.contains(thisExemptionMechanism)) { | |
| return false; | |
| } | |
| } else { | |
| exemptionMechanisms = new Vector<>(1); | |
| } | |
| exemptionMechanisms.addElement(thisExemptionMechanism); | |
| processedPermissions.put(alg, exemptionMechanisms); | |
| return true; | |
| } | |
| /** | |
| * Each grant entry in the policy configuration file is represented by a | |
| * {@code GrantEntry} object. | |
| * <p> | |
| * For example, the entry | |
| * <pre> | |
| * grant { | |
| * permission javax.crypto.CryptoPermission "DES", 56; | |
| * }; | |
| * | |
| * </pre> | |
| * is represented internally | |
| * <pre> | |
| * | |
| * pe = new CryptoPermissionEntry("javax.crypto.CryptoPermission", | |
| * "DES", 56); | |
| * | |
| * ge = new GrantEntry(); | |
| * | |
| * ge.add(pe); | |
| * | |
| * </pre> | |
| * | |
| * @see java.security.Permission | |
| * @see javax.crypto.CryptoPermission | |
| * @see javax.crypto.CryptoPermissions | |
| */ | |
| private static class GrantEntry { | |
| private final Vector<CryptoPermissionEntry> permissionEntries; | |
| GrantEntry() { | |
| permissionEntries = new Vector<>(); | |
| } | |
| void add(CryptoPermissionEntry pe) | |
| { | |
| permissionEntries.addElement(pe); | |
| } | |
| } | |
| /** | |
| * Each crypto permission entry in the policy configuration file is | |
| * represented by a {@code CryptoPermissionEntry} object. | |
| * <p> | |
| * For example, the entry | |
| * <pre> | |
| * permission javax.crypto.CryptoPermission "DES", 56; | |
| * </pre> | |
| * is represented internally | |
| * <pre> | |
| * | |
| * pe = new CryptoPermissionEntry("javax.crypto.cryptoPermission", | |
| * "DES", 56); | |
| * </pre> | |
| * | |
| * @see java.security.Permissions | |
| * @see javax.crypto.CryptoPermission | |
| * @see javax.crypto.CryptoAllPermission | |
| */ | |
| private static class CryptoPermissionEntry { | |
| String cryptoPermission; | |
| String alg; | |
| String exemptionMechanism; | |
| int maxKeySize; | |
| boolean checkParam; | |
| AlgorithmParameterSpec algParamSpec; | |
| CryptoPermissionEntry() { | |
| // Set default values. | |
| maxKeySize = 0; | |
| alg = null; | |
| exemptionMechanism = null; | |
| checkParam = false; | |
| algParamSpec = null; | |
| } | |
| /** | |
| * Calculates a hash code value for the object. Objects | |
| * which are equal will also have the same hashcode. | |
| */ | |
| @Override | |
| public int hashCode() { | |
| return cryptoPermission.hashCode() | |
| ^ Objects.hashCode(alg) | |
| ^ Objects.hashCode(exemptionMechanism) | |
| ^ maxKeySize | |
| ^ (checkParam ? 100 : 0) | |
| ^ Objects.hashCode(algParamSpec); | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (obj == this) | |
| return true; | |
| if (!(obj instanceof CryptoPermissionEntry that)) | |
| return false; | |
| if (!Objects.equals(this.cryptoPermission, that.cryptoPermission)) { | |
| return false; | |
| } | |
| if (this.alg == null) { | |
| if (that.alg != null) return false; | |
| } else { | |
| if (!this.alg.equalsIgnoreCase(that.alg)) | |
| return false; | |
| } | |
| if (this.maxKeySize != that.maxKeySize) return false; | |
| if (this.checkParam != that.checkParam) return false; | |
| return Objects.equals(this.algParamSpec, that.algParamSpec); | |
| } | |
| } | |
| static final class ParsingException extends GeneralSecurityException { | |
| @java.io.Serial | |
| private static final long serialVersionUID = 7147241245566588374L; | |
| /** | |
| * Constructs a {@code ParsingException} with the specified | |
| * detail message. | |
| * @param msg the detail message. | |
| */ | |
| ParsingException(String msg) { | |
| super(msg); | |
| } | |
| ParsingException(int line, String msg) { | |
| super("line " + line + ": " + msg); | |
| } | |
| ParsingException(int line, String expect, String actual) { | |
| super("line "+line+": expected '"+expect+"', found '"+actual+"'"); | |
| } | |
| } | |
| } |