blob: 342398d6544a934e0d1b5788af4ced7994896d79 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1999-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.crypto;
27
28import java.io.*;
29import java.util.Enumeration;
30import java.util.Hashtable;
31import java.util.Vector;
32import java.util.StringTokenizer;
33import static java.util.Locale.ENGLISH;
34
35import java.security.GeneralSecurityException;
36import java.security.spec.AlgorithmParameterSpec;
37import java.lang.reflect.*;
38
39/**
40 * JCE has two pairs of jurisdiction policy files: one represents U.S. export
41 * laws, and the other represents the local laws of the country where the
42 * JCE will be used.
43 *
44 * The jurisdiction policy file has the same syntax as JDK policy files except
45 * that JCE has new permission classes called javax.crypto.CryptoPermission
46 * and javax.crypto.CryptoAllPermission.
47 *
48 * The format of a permission entry in the jurisdiction policy file is:
49 *
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 *
55 * @author Sharon Liu
56 *
57 * @see java.security.Permissions
58 * @see java.security.spec.AlgrithomParameterSpec
59 * @see javax.crypto.CryptoPermission
60 * @see javax.crypto.CryptoAllPermission
61 * @see javax.crypto.CryptoPermissions
62 * @since 1.4
63 */
64
65final class CryptoPolicyParser {
66
67 private Vector grantEntries;
68
69 // Convenience variables for parsing
70 private StreamTokenizer st;
71 private int lookahead;
72
73 /**
74 * Creates a CryptoPolicyParser object.
75 */
76 CryptoPolicyParser() {
77 grantEntries = new Vector();
78 }
79
80 /**
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 configuration
86 * contains a syntax error.
87 *
88 * @exception IOException if an error occurs while reading
89 * the policy configuration.
90 */
91
92 void read(Reader policy)
93 throws ParsingException, IOException
94 {
95 if (!(policy instanceof BufferedReader)) {
96 policy = new BufferedReader(policy);
97 }
98
99 /*
100 * Configure the stream tokenizer:
101 * Recognize strings between "..."
102 * Don't convert words to lowercase
103 * Recognize both C-style and C++-style comments
104 * Treat end-of-line as white space, not as a token
105 */
106 st = new StreamTokenizer(policy);
107
108 st.resetSyntax();
109 st.wordChars('a', 'z');
110 st.wordChars('A', 'Z');
111 st.wordChars('.', '.');
112 st.wordChars('0', '9');
113 st.wordChars('_', '_');
114 st.wordChars('$', '$');
115 st.wordChars(128 + 32, 255);
116 st.whitespaceChars(0, ' ');
117 st.commentChar('/');
118 st.quoteChar('\'');
119 st.quoteChar('"');
120 st.lowerCaseMode(false);
121 st.ordinaryChar('/');
122 st.slashSlashComments(true);
123 st.slashStarComments(true);
124 st.parseNumbers();
125
126 /*
127 * The crypto jurisdiction policy must be consistent. The
128 * following hashtable is used for checking consistency.
129 */
130 Hashtable processedPermissions = null;
131
132 /*
133 * The main parsing loop. The loop is executed once for each entry
134 * in the policy file. The entries are delimited by semicolons. Once
135 * we've read in the information for an entry, go ahead and try to
136 * add it to the grantEntries.
137 */
138 lookahead = st.nextToken();
139 while (lookahead != StreamTokenizer.TT_EOF) {
140 if (peek("grant")) {
141 GrantEntry ge = parseGrantEntry(processedPermissions);
142 if (ge != null)
143 grantEntries.addElement(ge);
144 } else {
145 throw new ParsingException(st.lineno(), "expected grant " +
146 "statement");
147 }
148 match(";");
149 }
150 }
151
152 /**
153 * parse a Grant entry
154 */
155 private GrantEntry parseGrantEntry(Hashtable 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 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 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 result = new Vector();
460
461 Enumeration grantEnum = grantEntries.elements();
462 while (grantEnum.hasMoreElements()) {
463 GrantEntry ge = (GrantEntry)grantEnum.nextElement();
464 Enumeration permEnum = ge.permissionElements();
465 while (permEnum.hasMoreElements()) {
466 CryptoPermissionEntry pe =
467 (CryptoPermissionEntry)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,
495 String exemptionMechanism,
496 Hashtable processedPermissions) {
497 String thisExemptionMechanism =
498 exemptionMechanism == null ? "none" : exemptionMechanism;
499
500 if (processedPermissions == null) {
501 processedPermissions = new Hashtable();
502 Vector exemptionMechanisms = new Vector(1);
503 exemptionMechanisms.addElement(thisExemptionMechanism);
504 processedPermissions.put(alg, exemptionMechanisms);
505 return true;
506 }
507
508 if (processedPermissions.containsKey(CryptoAllPermission.ALG_NAME)) {
509 return false;
510 }
511
512 Vector exemptionMechanisms;
513
514 if (processedPermissions.containsKey(alg)) {
515 exemptionMechanisms = (Vector)processedPermissions.get(alg);
516 if (exemptionMechanisms.contains(thisExemptionMechanism)) {
517 return false;
518 }
519 } else {
520 exemptionMechanisms = new Vector(1);
521 }
522
523 exemptionMechanisms.addElement(thisExemptionMechanism);
524 processedPermissions.put(alg, exemptionMechanisms);
525 return true;
526 }
527
528 /**
529 * Each grant entry in the policy configuration file is represented by a
530 * GrantEntry object. <p>
531 *
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 permissionEntries;
560
561 GrantEntry() {
562 permissionEntries = new Vector();
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 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. <p>
592 *
593 * <p>
594 * For example, the entry
595 * <pre>
596 * permission javax.crypto.CryptoPermission "DES", 56;
597 * </pre>
598 * is represented internally
599 * <pre>
600 *
601 * pe = new CryptoPermissionEntry("javax.crypto.cryptoPermission",
602 * "DES", 56);
603 * </pre>
604 *
605 * @see java.security.Permissions
606 * @see javax.crypto.CryptoPermission
607 * @see javax.crypto.CryptoAllPermission
608 */
609
610 private static class CryptoPermissionEntry {
611
612 String cryptoPermission;
613 String alg;
614 String exemptionMechanism;
615 int maxKeySize;
616 boolean checkParam;
617 AlgorithmParameterSpec algParamSpec;
618
619 CryptoPermissionEntry() {
620 // Set default values.
621 maxKeySize = 0;
622 alg = null;
623 exemptionMechanism = null;
624 checkParam = false;
625 algParamSpec = null;
626 }
627
628 /**
629 * Calculates a hash code value for the object. Objects
630 * which are equal will also have the same hashcode.
631 */
632 public int hashCode() {
633 int retval = cryptoPermission.hashCode();
634 if (alg != null) retval ^= alg.hashCode();
635 if (exemptionMechanism != null) {
636 retval ^= exemptionMechanism.hashCode();
637 }
638 retval ^= maxKeySize;
639 if (checkParam) retval ^= 100;
640 if (algParamSpec != null) {
641 retval ^= algParamSpec.hashCode();
642 }
643 return retval;
644 }
645
646 public boolean equals(Object obj) {
647 if (obj == this)
648 return true;
649
650 if (!(obj instanceof CryptoPermissionEntry))
651 return false;
652
653 CryptoPermissionEntry that = (CryptoPermissionEntry) obj;
654
655 if (this.cryptoPermission == null) {
656 if (that.cryptoPermission != null) return false;
657 } else {
658 if (!this.cryptoPermission.equals(
659 that.cryptoPermission))
660 return false;
661 }
662
663 if (this.alg == null) {
664 if (that.alg != null) return false;
665 } else {
666 if (!this.alg.equalsIgnoreCase(that.alg))
667 return false;
668 }
669
670 if (!(this.maxKeySize == that.maxKeySize)) return false;
671
672 if (this.checkParam != that.checkParam) return false;
673
674 if (this.algParamSpec == null) {
675 if (that.algParamSpec != null) return false;
676 } else {
677 if (!this.algParamSpec.equals(that.algParamSpec))
678 return false;
679 }
680
681 // everything matched -- the 2 objects are equal
682 return true;
683 }
684 }
685
686 static final class ParsingException extends GeneralSecurityException {
687
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}