1/*
2 * Copyright (c) 1999, 2017, 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
26package javax.crypto;
27
28import java.io.*;
29import java.util.Enumeration;
30import java.util.Hashtable;
31import java.util.Vector;
32import static java.util.Locale.ENGLISH;
33
34import java.security.GeneralSecurityException;
35import java.security.spec.AlgorithmParameterSpec;
36import 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 *              [, <AlgrithomParameterSpec class name>, <parameters
53 *              for constructing an AlgrithomParameterSpec 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
66final 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        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}
707