Path: blob/master/src/java.base/share/classes/javax/crypto/CryptoPolicyParser.java
41152 views
/*1* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.crypto;2627import java.io.*;28import java.util.Enumeration;29import java.util.Hashtable;30import java.util.Vector;31import static java.util.Locale.ENGLISH;3233import java.security.GeneralSecurityException;34import java.security.spec.AlgorithmParameterSpec;35import java.lang.reflect.*;3637/**38* JCE has two pairs of jurisdiction policy files: one represents U.S. export39* laws, and the other represents the local laws of the country where the40* JCE will be used.41*42* The jurisdiction policy file has the same syntax as JDK policy files except43* that JCE has new permission classes called javax.crypto.CryptoPermission44* and javax.crypto.CryptoAllPermission.45*46* The format of a permission entry in the jurisdiction policy file is:47*48* <pre>{@code49* permission <crypto permission class name>[, <algorithm name>50* [[, <exemption mechanism name>][, <maxKeySize>51* [, <AlgorithmParameterSpec class name>, <parameters52* for constructing an AlgorithmParameterSpec object>]]]];53* }</pre>54*55* @author Sharon Liu56*57* @see java.security.Permissions58* @see java.security.spec.AlgorithmParameterSpec59* @see javax.crypto.CryptoPermission60* @see javax.crypto.CryptoAllPermission61* @see javax.crypto.CryptoPermissions62* @since 1.463*/6465final class CryptoPolicyParser {6667private Vector<GrantEntry> grantEntries;6869// Convenience variables for parsing70private StreamTokenizer st;71private int lookahead;7273/**74* Creates a CryptoPolicyParser object.75*/76CryptoPolicyParser() {77grantEntries = new Vector<GrantEntry>();78}7980/**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 configuration86* contains a syntax error.87*88* @exception IOException if an error occurs while reading89* the policy configuration.90*/9192void read(Reader policy)93throws ParsingException, IOException94{95if (!(policy instanceof BufferedReader)) {96policy = new BufferedReader(policy);97}9899/*100* Configure the stream tokenizer:101* Recognize strings between "..."102* Don't convert words to lowercase103* Recognize both C-style and C++-style comments104* Treat end-of-line as white space, not as a token105*/106st = new StreamTokenizer(policy);107108st.resetSyntax();109st.wordChars('a', 'z');110st.wordChars('A', 'Z');111st.wordChars('.', '.');112st.wordChars('0', '9');113st.wordChars('_', '_');114st.wordChars('$', '$');115st.wordChars(128 + 32, 255);116st.whitespaceChars(0, ' ');117st.commentChar('/');118st.quoteChar('\'');119st.quoteChar('"');120st.lowerCaseMode(false);121st.ordinaryChar('/');122st.slashSlashComments(true);123st.slashStarComments(true);124st.parseNumbers();125126/*127* The crypto jurisdiction policy must be consistent. The128* following hashtable is used for checking consistency.129*/130Hashtable<String, Vector<String>> processedPermissions = null;131132/*133* The main parsing loop. The loop is executed once for each entry134* in the policy file. The entries are delimited by semicolons. Once135* we've read in the information for an entry, go ahead and try to136* add it to the grantEntries.137*/138lookahead = st.nextToken();139while (lookahead != StreamTokenizer.TT_EOF) {140if (peek("grant")) {141GrantEntry ge = parseGrantEntry(processedPermissions);142if (ge != null)143grantEntries.addElement(ge);144} else {145throw new ParsingException(st.lineno(), "expected grant " +146"statement");147}148match(";");149}150}151152/**153* parse a Grant entry154*/155private GrantEntry parseGrantEntry(156Hashtable<String, Vector<String>> processedPermissions)157throws ParsingException, IOException158{159GrantEntry e = new GrantEntry();160161match("grant");162match("{");163164while(!peek("}")) {165if (peek("Permission")) {166CryptoPermissionEntry pe =167parsePermissionEntry(processedPermissions);168e.add(pe);169match(";");170} else {171throw new172ParsingException(st.lineno(), "expected permission entry");173}174}175match("}");176177return e;178}179180/**181* parse a CryptoPermission entry182*/183private CryptoPermissionEntry parsePermissionEntry(184Hashtable<String, Vector<String>> processedPermissions)185throws ParsingException, IOException186{187CryptoPermissionEntry e = new CryptoPermissionEntry();188189match("Permission");190e.cryptoPermission = match("permission type");191192if (e.cryptoPermission.equals("javax.crypto.CryptoAllPermission")) {193// Done with the CryptoAllPermission entry.194e.alg = CryptoAllPermission.ALG_NAME;195e.maxKeySize = Integer.MAX_VALUE;196return e;197}198199// Should see the algorithm name.200if (peek("\"")) {201// Algorithm name - always convert to upper case after parsing.202e.alg = match("quoted string").toUpperCase(ENGLISH);203} else {204// The algorithm name can be a wildcard.205if (peek("*")) {206match("*");207e.alg = CryptoPermission.ALG_NAME_WILDCARD;208} else {209throw new ParsingException(st.lineno(),210"Missing the algorithm name");211}212}213214peekAndMatch(",");215216// May see the exemption mechanism name.217if (peek("\"")) {218// Exemption mechanism name - convert to upper case too.219e.exemptionMechanism = match("quoted string").toUpperCase(ENGLISH);220}221222peekAndMatch(",");223224// Check whether this entry is consistent with other permission entries225// that have been read.226if (!isConsistent(e.alg, e.exemptionMechanism, processedPermissions)) {227throw new ParsingException(st.lineno(), "Inconsistent policy");228}229230// Should see the maxKeySize if not at the end of this entry yet.231if (peek("number")) {232e.maxKeySize = match();233} else {234if (peek("*")) {235match("*");236e.maxKeySize = Integer.MAX_VALUE;237} else {238if (!peek(";")) {239throw new ParsingException(st.lineno(),240"Missing the maximum " +241"allowable key size");242} else {243// At the end of this permission entry244e.maxKeySize = Integer.MAX_VALUE;245}246}247}248249peekAndMatch(",");250251// May see an AlgorithmParameterSpec class name.252if (peek("\"")) {253// AlgorithmParameterSpec class name.254String algParamSpecClassName = match("quoted string");255256Vector<Integer> paramsV = new Vector<>(1);257while (peek(",")) {258match(",");259if (peek("number")) {260paramsV.addElement(match());261} else {262if (peek("*")) {263match("*");264paramsV.addElement(Integer.MAX_VALUE);265} else {266throw new ParsingException(st.lineno(),267"Expecting an integer");268}269}270}271272Integer[] params = new Integer[paramsV.size()];273paramsV.copyInto(params);274275e.checkParam = true;276e.algParamSpec = getInstance(algParamSpecClassName, params);277}278279return e;280}281282private static final AlgorithmParameterSpec getInstance(String type,283Integer[] params)284throws ParsingException285{286AlgorithmParameterSpec ret = null;287288try {289Class<?> apsClass = Class.forName(type);290Class<?>[] paramClasses = new Class<?>[params.length];291292for (int i = 0; i < params.length; i++) {293paramClasses[i] = int.class;294}295296Constructor<?> c = apsClass.getConstructor(paramClasses);297ret = (AlgorithmParameterSpec) c.newInstance((Object[]) params);298} catch (Exception e) {299throw new ParsingException("Cannot call the constructor of " +300type + e);301}302return ret;303}304305306private boolean peekAndMatch(String expect)307throws ParsingException, IOException308{309if (peek(expect)) {310match(expect);311return true;312}313return false;314}315316private boolean peek(String expect) {317boolean found = false;318319switch (lookahead) {320321case StreamTokenizer.TT_WORD:322if (expect.equalsIgnoreCase(st.sval))323found = true;324break;325case StreamTokenizer.TT_NUMBER:326if (expect.equalsIgnoreCase("number")) {327found = true;328}329break;330case ',':331if (expect.equals(","))332found = true;333break;334case '{':335if (expect.equals("{"))336found = true;337break;338case '}':339if (expect.equals("}"))340found = true;341break;342case '"':343if (expect.equals("\""))344found = true;345break;346case '*':347if (expect.equals("*"))348found = true;349break;350case ';':351if (expect.equals(";"))352found = true;353break;354default:355break;356}357return found;358}359360/**361* Excepts to match a non-negative number.362*/363private int match()364throws ParsingException, IOException365{366int value = -1;367int lineno = st.lineno();368String sValue = null;369370switch (lookahead) {371case StreamTokenizer.TT_NUMBER:372value = (int)st.nval;373if (value < 0) {374sValue = String.valueOf(st.nval);375}376lookahead = st.nextToken();377break;378default:379sValue = st.sval;380break;381}382if (value <= 0) {383throw new ParsingException(lineno, "a non-negative number",384sValue);385}386return value;387}388389private String match(String expect)390throws ParsingException, IOException391{392String value = null;393394switch (lookahead) {395case StreamTokenizer.TT_NUMBER:396throw new ParsingException(st.lineno(), expect,397"number "+String.valueOf(st.nval));398case StreamTokenizer.TT_EOF:399throw new ParsingException("expected "+expect+", read end of file");400case StreamTokenizer.TT_WORD:401if (expect.equalsIgnoreCase(st.sval)) {402lookahead = st.nextToken();403}404else if (expect.equalsIgnoreCase("permission type")) {405value = st.sval;406lookahead = st.nextToken();407}408else409throw new ParsingException(st.lineno(), expect, st.sval);410break;411case '"':412if (expect.equalsIgnoreCase("quoted string")) {413value = st.sval;414lookahead = st.nextToken();415} else if (expect.equalsIgnoreCase("permission type")) {416value = st.sval;417lookahead = st.nextToken();418}419else420throw new ParsingException(st.lineno(), expect, st.sval);421break;422case ',':423if (expect.equals(","))424lookahead = st.nextToken();425else426throw new ParsingException(st.lineno(), expect, ",");427break;428case '{':429if (expect.equals("{"))430lookahead = st.nextToken();431else432throw new ParsingException(st.lineno(), expect, "{");433break;434case '}':435if (expect.equals("}"))436lookahead = st.nextToken();437else438throw new ParsingException(st.lineno(), expect, "}");439break;440case ';':441if (expect.equals(";"))442lookahead = st.nextToken();443else444throw new ParsingException(st.lineno(), expect, ";");445break;446case '*':447if (expect.equals("*"))448lookahead = st.nextToken();449else450throw new ParsingException(st.lineno(), expect, "*");451break;452default:453throw new ParsingException(st.lineno(), expect,454String.valueOf((char)lookahead));455}456return value;457}458459CryptoPermission[] getPermissions() {460Vector<CryptoPermission> result = new Vector<>();461462Enumeration<GrantEntry> grantEnum = grantEntries.elements();463while (grantEnum.hasMoreElements()) {464GrantEntry ge = grantEnum.nextElement();465Enumeration<CryptoPermissionEntry> permEnum =466ge.permissionElements();467while (permEnum.hasMoreElements()) {468CryptoPermissionEntry pe = permEnum.nextElement();469if (pe.cryptoPermission.equals(470"javax.crypto.CryptoAllPermission")) {471result.addElement(CryptoAllPermission.INSTANCE);472} else {473if (pe.checkParam) {474result.addElement(new CryptoPermission(475pe.alg,476pe.maxKeySize,477pe.algParamSpec,478pe.exemptionMechanism));479} else {480result.addElement(new CryptoPermission(481pe.alg,482pe.maxKeySize,483pe.exemptionMechanism));484}485}486}487}488489CryptoPermission[] ret = new CryptoPermission[result.size()];490result.copyInto(ret);491492return ret;493}494495private boolean isConsistent(String alg, String exemptionMechanism,496Hashtable<String, Vector<String>> processedPermissions) {497String thisExemptionMechanism =498exemptionMechanism == null ? "none" : exemptionMechanism;499500if (processedPermissions == null) {501processedPermissions = new Hashtable<String, Vector<String>>();502Vector<String> exemptionMechanisms = new Vector<>(1);503exemptionMechanisms.addElement(thisExemptionMechanism);504processedPermissions.put(alg, exemptionMechanisms);505return true;506}507508if (processedPermissions.containsKey(CryptoAllPermission.ALG_NAME)) {509return false;510}511512Vector<String> exemptionMechanisms;513514if (processedPermissions.containsKey(alg)) {515exemptionMechanisms = processedPermissions.get(alg);516if (exemptionMechanisms.contains(thisExemptionMechanism)) {517return false;518}519} else {520exemptionMechanisms = new Vector<String>(1);521}522523exemptionMechanisms.addElement(thisExemptionMechanism);524processedPermissions.put(alg, exemptionMechanisms);525return true;526}527528/**529* Each grant entry in the policy configuration file is represented by a530* GrantEntry object.531* <p>532* For example, the entry533* <pre>534* grant {535* permission javax.crypto.CryptoPermission "DES", 56;536* };537*538* </pre>539* is represented internally540* <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.Permission552* @see javax.crypto.CryptoPermission553* @see javax.crypto.CryptoPermissions554*/555556private static class GrantEntry {557558private Vector<CryptoPermissionEntry> permissionEntries;559560GrantEntry() {561permissionEntries = new Vector<CryptoPermissionEntry>();562}563564void add(CryptoPermissionEntry pe)565{566permissionEntries.addElement(pe);567}568569boolean remove(CryptoPermissionEntry pe)570{571return permissionEntries.removeElement(pe);572}573574boolean contains(CryptoPermissionEntry pe)575{576return permissionEntries.contains(pe);577}578579/**580* Enumerate all the permission entries in this GrantEntry.581*/582Enumeration<CryptoPermissionEntry> permissionElements(){583return permissionEntries.elements();584}585586}587588/**589* Each crypto permission entry in the policy configuration file is590* represented by a CryptoPermissionEntry object.591* <p>592* For example, the entry593* <pre>594* permission javax.crypto.CryptoPermission "DES", 56;595* </pre>596* is represented internally597* <pre>598*599* pe = new CryptoPermissionEntry("javax.crypto.cryptoPermission",600* "DES", 56);601* </pre>602*603* @see java.security.Permissions604* @see javax.crypto.CryptoPermission605* @see javax.crypto.CryptoAllPermission606*/607608private static class CryptoPermissionEntry {609610String cryptoPermission;611String alg;612String exemptionMechanism;613int maxKeySize;614boolean checkParam;615AlgorithmParameterSpec algParamSpec;616617CryptoPermissionEntry() {618// Set default values.619maxKeySize = 0;620alg = null;621exemptionMechanism = null;622checkParam = false;623algParamSpec = null;624}625626/**627* Calculates a hash code value for the object. Objects628* which are equal will also have the same hashcode.629*/630public int hashCode() {631int retval = cryptoPermission.hashCode();632if (alg != null) retval ^= alg.hashCode();633if (exemptionMechanism != null) {634retval ^= exemptionMechanism.hashCode();635}636retval ^= maxKeySize;637if (checkParam) retval ^= 100;638if (algParamSpec != null) {639retval ^= algParamSpec.hashCode();640}641return retval;642}643644public boolean equals(Object obj) {645if (obj == this)646return true;647648if (!(obj instanceof CryptoPermissionEntry))649return false;650651CryptoPermissionEntry that = (CryptoPermissionEntry) obj;652653if (this.cryptoPermission == null) {654if (that.cryptoPermission != null) return false;655} else {656if (!this.cryptoPermission.equals(657that.cryptoPermission))658return false;659}660661if (this.alg == null) {662if (that.alg != null) return false;663} else {664if (!this.alg.equalsIgnoreCase(that.alg))665return false;666}667668if (!(this.maxKeySize == that.maxKeySize)) return false;669670if (this.checkParam != that.checkParam) return false;671672if (this.algParamSpec == null) {673if (that.algParamSpec != null) return false;674} else {675if (!this.algParamSpec.equals(that.algParamSpec))676return false;677}678679// everything matched -- the 2 objects are equal680return true;681}682}683684static final class ParsingException extends GeneralSecurityException {685686@java.io.Serial687private static final long serialVersionUID = 7147241245566588374L;688689/**690* Constructs a ParsingException with the specified691* detail message.692* @param msg the detail message.693*/694ParsingException(String msg) {695super(msg);696}697698ParsingException(int line, String msg) {699super("line " + line + ": " + msg);700}701702ParsingException(int line, String expect, String actual) {703super("line "+line+": expected '"+expect+"', found '"+actual+"'");704}705}706}707708709