001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /**
019: * @author Alexey V. Varlamov
020: * @version $Revision$
021: */package org.apache.harmony.security.fortress;
022:
023: import java.io.BufferedReader;
024: import java.io.InputStream;
025: import java.io.InputStreamReader;
026: import java.io.Reader;
027: import java.net.URL;
028: import java.security.cert.Certificate;
029: import java.security.cert.CertificateException;
030: import java.security.cert.X509Certificate;
031: import java.security.AccessController;
032: import java.security.CodeSource;
033: import java.security.KeyStore;
034: import java.security.KeyStoreException;
035: import java.security.Permission;
036: import java.security.Principal;
037: import java.security.UnresolvedPermission;
038: import java.util.ArrayList;
039: import java.util.Collection;
040: import java.util.HashSet;
041: import java.util.Iterator;
042: import java.util.List;
043: import java.util.Properties;
044: import java.util.Set;
045: import java.util.StringTokenizer;
046:
047: import org.apache.harmony.security.DefaultPolicyScanner;
048: import org.apache.harmony.security.PolicyEntry;
049: import org.apache.harmony.security.UnresolvedPrincipal;
050: import org.apache.harmony.security.DefaultPolicyScanner.GrantEntry;
051: import org.apache.harmony.security.DefaultPolicyScanner.KeystoreEntry;
052: import org.apache.harmony.security.DefaultPolicyScanner.PermissionEntry;
053: import org.apache.harmony.security.DefaultPolicyScanner.PrincipalEntry;
054: import org.apache.harmony.security.internal.nls.Messages;
055:
056: /**
057: * This is a basic loader of policy files. It delegates lexical analysis to
058: * a pluggable scanner and converts received tokens to a set of
059: * {@link org.apache.harmony.security.PolicyEntry PolicyEntries}.
060: * For details of policy format, see the
061: * {@link org.apache.harmony.security.DefaultPolicy default policy description}.
062: * <br>
063: * For ordinary uses, this class has just one public method <code>parse()</code>,
064: * which performs the main task.
065: * Extensions of this parser may redefine specific operations separately,
066: * by overriding corresponding protected methods.
067: * <br>
068: * This implementation is effectively thread-safe, as it has no field references
069: * to data being processed (that is, passes all the data as method parameters).
070: *
071: * @see org.apache.harmony.security.DefaultPolicy
072: * @see org.apache.harmony.security.DefaultPolicyScanner
073: * @see org.apache.harmony.security.PolicyEntry
074: */
075: public class DefaultPolicyParser {
076:
077: // Pluggable scanner for a specific file format
078: private final DefaultPolicyScanner scanner;
079:
080: /**
081: * Default constructor,
082: * {@link org.apache.harmony.security.DefaultPolicyScanner DefaultPolicyScanner}
083: * is used.
084: */
085: public DefaultPolicyParser() {
086: scanner = new DefaultPolicyScanner();
087: }
088:
089: /**
090: * Extension constructor for plugging-in custom scanner.
091: */
092: public DefaultPolicyParser(DefaultPolicyScanner s) {
093: this .scanner = s;
094: }
095:
096: /**
097: * This is the main business method. It manages loading process as follows:
098: * the associated scanner is used to parse the stream to a set of
099: * {@link org.apache.harmony.security.DefaultPolicyScanner.GrantEntry composite tokens},
100: * then this set is iterated and each token is translated to a PolicyEntry.
101: * Semantically invalid tokens are ignored, the same as void PolicyEntries.
102: * <br>
103: * A policy file may refer to some KeyStore(s), and in this case the first
104: * valid reference is initialized and used in processing tokens.
105: *
106: * @param location an URL of a policy file to be loaded
107: * @param system system properties, used for property expansion
108: * @return a collection of PolicyEntry objects, may be empty
109: * @throws Exception IO error while reading location or file syntax error
110: */
111: public Collection<PolicyEntry> parse(URL location, Properties system)
112: throws Exception {
113:
114: boolean resolve = PolicyUtils.canExpandProperties();
115: Reader r = new BufferedReader(new InputStreamReader(
116: AccessController
117: .doPrivileged(new PolicyUtils.URLLoader(
118: location))));
119:
120: Collection<GrantEntry> grantEntries = new HashSet<GrantEntry>();
121: List<KeystoreEntry> keystores = new ArrayList<KeystoreEntry>();
122:
123: try {
124: scanner.scanStream(r, grantEntries, keystores);
125: } finally {
126: r.close();
127: }
128:
129: //XXX KeyStore could be loaded lazily...
130: KeyStore ks = initKeyStore(keystores, location, system, resolve);
131:
132: Collection<PolicyEntry> result = new HashSet<PolicyEntry>();
133: for (Iterator<GrantEntry> iter = grantEntries.iterator(); iter
134: .hasNext();) {
135: DefaultPolicyScanner.GrantEntry ge = iter.next();
136: try {
137: PolicyEntry pe = resolveGrant(ge, ks, system, resolve);
138: if (!pe.isVoid()) {
139: result.add(pe);
140: }
141: } catch (Exception e) {
142: // TODO: log warning
143: }
144: }
145:
146: return result;
147: }
148:
149: /**
150: * Translates GrantEntry token to PolicyEntry object. It goes step by step,
151: * trying to resolve each component of the GrantEntry:
152: * <ul>
153: * <li> If <code>codebase</code> is specified, expand it and construct an URL.
154: * <li> If <code>signers</code> is specified, expand it and obtain
155: * corresponding Certificates.
156: * <li> If <code>principals</code> collection is specified, iterate over it.
157: * For each PrincipalEntry, expand name and if no class specified,
158: * resolve actual X500Principal from a KeyStore certificate; otherwise keep it
159: * as UnresolvedPrincipal.
160: * <li> Iterate over <code>permissions</code> collection. For each PermissionEntry,
161: * try to resolve (see method
162: * {@link #resolvePermission(DefaultPolicyScanner.PermissionEntry, DefaultPolicyScanner.GrantEntry, KeyStore, Properties, boolean) resolvePermission()})
163: * a corresponding permission. If resolution failed, ignore the PermissionEntry.
164: * </ul>
165: * In fact, property expansion in the steps above is conditional and is ruled by
166: * the parameter <i>resolve</i>.
167: * <br>
168: * Finally a new PolicyEntry is created, which associates the trinity
169: * of resolved URL, Certificates and Principals to a set of granted Permissions.
170: *
171: * @param ge GrantEntry token to be resolved
172: * @param ks KeyStore for resolving Certificates, may be <code>null</code>
173: * @param system system properties, used for property expansion
174: * @param resolve flag enabling/disabling property expansion
175: * @return resolved PolicyEntry
176: * @throws Exception if unable to resolve codebase, signers or principals
177: * of the GrantEntry
178: * @see DefaultPolicyScanner.PrincipalEntry
179: * @see DefaultPolicyScanner.PermissionEntry
180: * @see org.apache.harmony.security.PolicyUtils
181: */
182: protected PolicyEntry resolveGrant(
183: DefaultPolicyScanner.GrantEntry ge, KeyStore ks,
184: Properties system, boolean resolve) throws Exception {
185:
186: URL codebase = null;
187: Certificate[] signers = null;
188: Set<Principal> principals = new HashSet<Principal>();
189: Set<Permission> permissions = new HashSet<Permission>();
190: if (ge.codebase != null) {
191: codebase = new URL(resolve ? PolicyUtils.expandURL(
192: ge.codebase, system) : ge.codebase);
193: }
194: if (ge.signers != null) {
195: if (resolve) {
196: ge.signers = PolicyUtils.expand(ge.signers, system);
197: }
198: signers = resolveSigners(ks, ge.signers);
199: }
200: if (ge.principals != null) {
201: for (Iterator<PrincipalEntry> iter = ge.principals
202: .iterator(); iter.hasNext();) {
203: DefaultPolicyScanner.PrincipalEntry pe = iter.next();
204: if (resolve) {
205: pe.name = PolicyUtils.expand(pe.name, system);
206: }
207: if (pe.klass == null) {
208: principals.add(getPrincipalByAlias(ks, pe.name));
209: } else {
210: principals.add(new UnresolvedPrincipal(pe.klass,
211: pe.name));
212: }
213: }
214: }
215: if (ge.permissions != null) {
216: for (Iterator<PermissionEntry> iter = ge.permissions
217: .iterator(); iter.hasNext();) {
218: DefaultPolicyScanner.PermissionEntry pe = iter.next();
219: try {
220: permissions.add(resolvePermission(pe, ge, ks,
221: system, resolve));
222: } catch (Exception e) {
223: // TODO: log warning
224: }
225: }
226: }
227: return new PolicyEntry(new CodeSource(codebase, signers),
228: principals, permissions);
229: }
230:
231: /**
232: * Translates PermissionEntry token to Permission object.
233: * First, it performs general expansion for non-null <code>name</code> and
234: * properties expansion for non-null <code>name</code>, <code>action</code>
235: * and <code>signers</code>.
236: * Then, it obtains signing Certificates(if any), tries to find a class specified by
237: * <code>klass</code> name and instantiate a corresponding permission object.
238: * If class is not found or it is signed improperly, returns UnresolvedPermission.
239: *
240: * @param pe PermissionEntry token to be resolved
241: * @param ge parental GrantEntry of the PermissionEntry
242: * @param ks KeyStore for resolving Certificates, may be <code>null</code>
243: * @param system system properties, used for property expansion
244: * @param resolve flag enabling/disabling property expansion
245: * @return resolved Permission object, either of concrete class or UnresolvedPermission
246: * @throws Exception if failed to expand properties,
247: * or to get a Certificate,
248: * or to create an instance of a successfully found class
249: */
250: protected Permission resolvePermission(
251: DefaultPolicyScanner.PermissionEntry pe,
252: DefaultPolicyScanner.GrantEntry ge, KeyStore ks,
253: Properties system, boolean resolve) throws Exception {
254: if (pe.name != null) {
255: pe.name = PolicyUtils.expandGeneral(pe.name,
256: new PermissionExpander().configure(ge, ks));
257: }
258: if (resolve) {
259: if (pe.name != null) {
260: pe.name = PolicyUtils.expand(pe.name, system);
261: }
262: if (pe.actions != null) {
263: pe.actions = PolicyUtils.expand(pe.actions, system);
264: }
265: if (pe.signers != null) {
266: pe.signers = PolicyUtils.expand(pe.signers, system);
267: }
268: }
269: Certificate[] signers = (pe.signers == null) ? null
270: : resolveSigners(ks, pe.signers);
271: try {
272: Class<?> klass = Class.forName(pe.klass);
273: if (PolicyUtils.matchSubset(signers, klass.getSigners())) {
274: return PolicyUtils.instantiatePermission(klass,
275: pe.name, pe.actions);
276: }
277: } catch (ClassNotFoundException cnfe) {
278: }
279: //maybe properly signed class will be loaded later
280: return new UnresolvedPermission(pe.klass, pe.name, pe.actions,
281: signers);
282: }
283:
284: /**
285: * Specific handler for expanding <i>self</i> and <i>alias</i> protocols.
286: */
287: class PermissionExpander implements
288: PolicyUtils.GeneralExpansionHandler {
289:
290: // Store KeyStore
291: private KeyStore ks;
292:
293: // Store GrantEntry
294: private DefaultPolicyScanner.GrantEntry ge;
295:
296: /**
297: * Combined setter of all required fields.
298: */
299: public PermissionExpander configure(
300: DefaultPolicyScanner.GrantEntry ge, KeyStore ks) {
301: this .ge = ge;
302: this .ks = ks;
303: return this ;
304: }
305:
306: /**
307: * Resolves the following protocols:
308: * <dl>
309: * <dt>self
310: * <dd>Denotes substitution to a principal information of the parental
311: * GrantEntry. Returns a space-separated list of resolved Principals
312: * (including wildcarded), formatting each as <b>class "name"</b>.
313: * If parental GrantEntry has no Principals, throws ExpansionFailedException.
314: * <dt>alias:<i>name</i>
315: * <dd>Denotes substitution of a KeyStore alias. Namely, if a KeyStore has
316: * an X.509 certificate associated with the specified name, then returns
317: * <b>javax.security.auth.x500.X500Principal "<i>DN</i>"</b> string,
318: * where <i>DN</i> is a certificate's subject distinguished name.
319: * </dl>
320: * @throws ExpansionFailedException - if protocol is other than
321: * <i>self</i> or <i>alias</i>, or if data resolution failed
322: */
323: public String resolve(String protocol, String data)
324: throws PolicyUtils.ExpansionFailedException {
325:
326: if ("self".equals(protocol)) { //$NON-NLS-1$
327: //need expanding to list of principals in grant clause
328: if (ge.principals != null && ge.principals.size() != 0) {
329: StringBuffer sb = new StringBuffer();
330: for (Iterator<PrincipalEntry> iter = ge.principals
331: .iterator(); iter.hasNext();) {
332: DefaultPolicyScanner.PrincipalEntry pr = iter
333: .next();
334: if (pr.klass == null) {
335: // aliased X500Principal
336: try {
337: sb.append(pc2str(getPrincipalByAlias(
338: ks, pr.name)));
339: } catch (Exception e) {
340: throw new PolicyUtils.ExpansionFailedException(
341: Messages
342: .getString(
343: "security.143", pr.name), e); //$NON-NLS-1$
344: }
345: } else {
346: sb.append(pr.klass)
347: .append(" \"").append(pr.name) //$NON-NLS-1$
348: .append("\" "); //$NON-NLS-1$
349: }
350: }
351: return sb.toString();
352: } else {
353: throw new PolicyUtils.ExpansionFailedException(
354: Messages.getString("security.144")); //$NON-NLS-1$
355: }
356: }
357: if ("alias".equals(protocol)) { //$NON-NLS-1$
358: try {
359: return pc2str(getPrincipalByAlias(ks, data));
360: } catch (Exception e) {
361: throw new PolicyUtils.ExpansionFailedException(
362: Messages.getString("security.143", data), e); //$NON-NLS-1$
363: }
364: }
365: throw new PolicyUtils.ExpansionFailedException(Messages
366: .getString("security.145", protocol)); //$NON-NLS-1$
367: }
368:
369: // Formats a string describing the passed Principal.
370: private String pc2str(Principal pc) {
371: String klass = pc.getClass().getName();
372: String name = pc.getName();
373: StringBuffer sb = new StringBuffer(klass.length()
374: + name.length() + 5);
375: return sb.append(klass)
376: .append(" \"").append(name).append("\"") //$NON-NLS-1$ //$NON-NLS-2$
377: .toString();
378: }
379: }
380:
381: /**
382: * Takes a comma-separated list of aliases and obtains corresponding
383: * certificates.
384: * @param ks KeyStore for resolving Certificates, may be <code>null</code>
385: * @param signers comma-separated list of certificate aliases,
386: * must be not <code>null</code>
387: * @return an array of signing Certificates
388: * @throws Exception if KeyStore is <code>null</code>
389: * or if it failed to provide a certificate
390: */
391: protected Certificate[] resolveSigners(KeyStore ks, String signers)
392: throws Exception {
393: if (ks == null) {
394: throw new KeyStoreException(Messages.getString(
395: "security.146", //$NON-NLS-1$
396: signers));
397: }
398:
399: Collection<Certificate> certs = new HashSet<Certificate>();
400: StringTokenizer snt = new StringTokenizer(signers, ","); //$NON-NLS-1$
401: while (snt.hasMoreTokens()) {
402: //XXX cache found certs ??
403: certs.add(ks.getCertificate(snt.nextToken().trim()));
404: }
405: return certs.toArray(new Certificate[certs.size()]);
406: }
407:
408: /**
409: * Returns a subject's X500Principal of an X509Certificate,
410: * which is associated with the specified keystore alias.
411: * @param ks KeyStore for resolving Certificate, may be <code>null</code>
412: * @param alias alias to a certificate
413: * @return X500Principal with a subject distinguished name
414: * @throws KeyStoreException if KeyStore is <code>null</code>
415: * or if it failed to provide a certificate
416: * @throws CertificateException if found certificate is not
417: * an X509Certificate
418: */
419: protected Principal getPrincipalByAlias(KeyStore ks, String alias)
420: throws KeyStoreException, CertificateException {
421:
422: if (ks == null) {
423: throw new KeyStoreException(Messages.getString(
424: "security.147", alias)); //$NON-NLS-1$
425: }
426: //XXX cache found certs ??
427: Certificate x509 = ks.getCertificate(alias);
428: if (x509 instanceof X509Certificate) {
429: return ((X509Certificate) x509).getSubjectX500Principal();
430: } else {
431: throw new CertificateException(Messages.getString(
432: "security.148", //$NON-NLS-1$
433: alias, x509));
434: }
435: }
436:
437: /**
438: * Returns the first successfully loaded KeyStore, from the specified list of
439: * possible locations. This method iterates over the list of KeystoreEntries;
440: * for each entry expands <code>url</code> and <code>type</code>,
441: * tries to construct instances of specified URL and KeyStore and to load
442: * the keystore. If it is loaded, returns the keystore, otherwise proceeds to
443: * the next KeystoreEntry.
444: * <br>
445: * <b>Note:</b> an url may be relative to the policy file location or absolute.
446: * @param keystores list of available KeystoreEntries
447: * @param base the policy file location
448: * @param system system properties, used for property expansion
449: * @param resolve flag enabling/disabling property expansion
450: * @return the first successfully loaded KeyStore or <code>null</code>
451: */
452: protected KeyStore initKeyStore(List<KeystoreEntry> keystores,
453: URL base, Properties system, boolean resolve) {
454:
455: for (int i = 0; i < keystores.size(); i++) {
456: try {
457: DefaultPolicyScanner.KeystoreEntry ke = keystores
458: .get(i);
459: if (resolve) {
460: ke.url = PolicyUtils.expandURL(ke.url, system);
461: if (ke.type != null) {
462: ke.type = PolicyUtils.expand(ke.type, system);
463: }
464: }
465: if (ke.type == null || ke.type.length() == 0) {
466: ke.type = KeyStore.getDefaultType();
467: }
468: KeyStore ks = KeyStore.getInstance(ke.type);
469: URL location = new URL(base, ke.url);
470: InputStream is = AccessController
471: .doPrivileged(new PolicyUtils.URLLoader(
472: location));
473: try {
474: ks.load(is, null);
475: } finally {
476: is.close();
477: }
478: return ks;
479: } catch (Exception e) {
480: // TODO: log warning
481: }
482: }
483: return null;
484: }
485: }
|