View Javadoc
1   /*
2    * Copyright (c) 1999, 2012, 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.AlgorithmParameterSpec
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 }