DefaultPolicyParser.java revision f6c387128427e121477c1b32ad35cdcaa5101ba3
1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18/**
19* @author Alexey V. Varlamov
20* @version $Revision$
21*/
22
23package org.apache.harmony.security.fortress;
24
25import java.io.BufferedReader;
26import java.io.InputStream;
27import java.io.InputStreamReader;
28import java.io.File;
29import java.io.Reader;
30import java.net.URL;
31import java.security.cert.Certificate;
32import java.security.cert.CertificateException;
33import java.security.cert.X509Certificate;
34import java.security.AccessController;
35import java.security.CodeSource;
36import java.security.KeyStore;
37import java.security.KeyStoreException;
38import java.security.Permission;
39import java.security.Principal;
40import java.security.UnresolvedPermission;
41import java.util.ArrayList;
42import java.util.Collection;
43import java.util.HashSet;
44import java.util.Iterator;
45import java.util.List;
46import java.util.Properties;
47import java.util.Set;
48import java.util.StringTokenizer;
49
50import org.apache.harmony.security.DefaultPolicyScanner;
51import org.apache.harmony.security.PolicyEntry;
52import org.apache.harmony.security.UnresolvedPrincipal;
53import org.apache.harmony.security.DefaultPolicyScanner.GrantEntry;
54import org.apache.harmony.security.DefaultPolicyScanner.KeystoreEntry;
55import org.apache.harmony.security.DefaultPolicyScanner.PermissionEntry;
56import org.apache.harmony.security.DefaultPolicyScanner.PrincipalEntry;
57import org.apache.harmony.security.internal.nls.Messages;
58
59
60/**
61 * This is a basic loader of policy files. It delegates lexical analysis to
62 * a pluggable scanner and converts received tokens to a set of
63 * {@link org.apache.harmony.security.PolicyEntry PolicyEntries}.
64 * For details of policy format, see the
65 * {@link org.apache.harmony.security.fortress.DefaultPolicy default policy
66 * description}.
67 * <br>
68 * For ordinary uses, this class has just one public method <code>parse()</code>,
69 * which performs the main task.
70 * Extensions of this parser may redefine specific operations separately,
71 * by overriding corresponding protected methods.
72 * <br>
73 * This implementation is effectively thread-safe, as it has no field references
74 * to data being processed (that is, passes all the data as method parameters).
75 *
76 * @see org.apache.harmony.security.fortress.DefaultPolicy
77 * @see org.apache.harmony.security.DefaultPolicyScanner
78 * @see org.apache.harmony.security.PolicyEntry
79 */
80public class DefaultPolicyParser {
81
82    // Pluggable scanner for a specific file format
83    private final DefaultPolicyScanner scanner;
84
85    /**
86     * Default constructor,
87     * {@link org.apache.harmony.security.DefaultPolicyScanner DefaultPolicyScanner}
88     * is used.
89     */
90    public DefaultPolicyParser() {
91        scanner = new DefaultPolicyScanner();
92    }
93
94    /**
95     * Extension constructor for plugging-in custom scanner.
96     */
97    public DefaultPolicyParser(DefaultPolicyScanner s) {
98        this.scanner = s;
99    }
100
101    /**
102     * This is the main business method. It manages loading process as follows:
103     * the associated scanner is used to parse the stream to a set of
104     * {@link org.apache.harmony.security.DefaultPolicyScanner.GrantEntry composite tokens},
105     * then this set is iterated and each token is translated to a PolicyEntry.
106     * Semantically invalid tokens are ignored, the same as void PolicyEntries.
107     * <br>
108     * A policy file may refer to some KeyStore(s), and in this case the first
109     * valid reference is initialized and used in processing tokens.
110     *
111     * @param location an URL of a policy file to be loaded
112     * @param system system properties, used for property expansion
113     * @return a collection of PolicyEntry objects, may be empty
114     * @throws Exception IO error while reading location or file syntax error
115     */
116    public Collection<PolicyEntry>parse(URL location, Properties system)
117            throws Exception {
118
119        boolean resolve = PolicyUtils.canExpandProperties();
120        // BEGIN android-modified
121        Reader r =
122            new BufferedReader(
123                    new InputStreamReader(
124                            AccessController.doPrivileged(
125                                    new PolicyUtils.URLLoader(location))),
126                    8192);
127        // END android-modified
128
129        Collection<GrantEntry> grantEntries = new HashSet<GrantEntry>();
130        List<KeystoreEntry> keystores = new ArrayList<KeystoreEntry>();
131
132        try {
133            scanner.scanStream(r, grantEntries, keystores);
134        }
135        finally {
136            r.close();
137        }
138
139        //XXX KeyStore could be loaded lazily...
140        KeyStore ks = initKeyStore(keystores, location, system, resolve);
141
142        Collection<PolicyEntry> result = new HashSet<PolicyEntry>();
143        for (Iterator<GrantEntry> iter = grantEntries.iterator(); iter.hasNext();) {
144            DefaultPolicyScanner.GrantEntry ge = iter
145                    .next();
146            try {
147                PolicyEntry pe = resolveGrant(ge, ks, system, resolve);
148                if (!pe.isVoid()) {
149                    result.add(pe);
150                }
151            }
152            catch (Exception e) {
153                // TODO: log warning
154            }
155        }
156
157        return result;
158    }
159
160    /**
161     * Translates GrantEntry token to PolicyEntry object. It goes step by step,
162     * trying to resolve each component of the GrantEntry:
163     * <ul>
164     * <li> If <code>codebase</code> is specified, expand it and construct an URL.
165     * <li> If <code>signers</code> is specified, expand it and obtain
166     * corresponding Certificates.
167     * <li> If <code>principals</code> collection is specified, iterate over it.
168     * For each PrincipalEntry, expand name and if no class specified,
169     * resolve actual X500Principal from a KeyStore certificate; otherwise keep it
170     * as UnresolvedPrincipal.
171     * <li> Iterate over <code>permissions</code> collection. For each PermissionEntry,
172     * try to resolve (see method
173     * {@link #resolvePermission(DefaultPolicyScanner.PermissionEntry, DefaultPolicyScanner.GrantEntry, KeyStore, Properties, boolean) resolvePermission()})
174     * a corresponding permission. If resolution failed, ignore the PermissionEntry.
175     * </ul>
176     * In fact, property expansion in the steps above is conditional and is ruled by
177     * the parameter <i>resolve</i>.
178     * <br>
179     * Finally a new PolicyEntry is created, which associates the trinity
180     * of resolved URL, Certificates and Principals to a set of granted Permissions.
181     *
182     * @param ge GrantEntry token to be resolved
183     * @param ks KeyStore for resolving Certificates, may be <code>null</code>
184     * @param system system properties, used for property expansion
185     * @param resolve flag enabling/disabling property expansion
186     * @return resolved PolicyEntry
187     * @throws Exception if unable to resolve codebase, signers or principals
188     * of the GrantEntry
189     * @see DefaultPolicyScanner.PrincipalEntry
190     * @see DefaultPolicyScanner.PermissionEntry
191     * @see org.apache.harmony.security.fortress.PolicyUtils
192     */
193    protected PolicyEntry resolveGrant(DefaultPolicyScanner.GrantEntry ge,
194            KeyStore ks, Properties system, boolean resolve) throws Exception {
195
196        URL codebase = null;
197        Certificate[] signers = null;
198        Set<Principal>principals = new HashSet<Principal>();
199        Set<Permission>permissions = new HashSet<Permission>();
200        if (ge.codebase != null) {
201            codebase = new URL(resolve ? PolicyUtils.expandURL(ge.codebase,
202                    system) : ge.codebase);
203            //Fix HARMONY-1963
204            if ("file".equals(codebase.getProtocol())) { //$NON-NLS-1$
205                File codeFile = new File(codebase.getFile());
206                if (codeFile.isAbsolute()) {
207                    codebase = new URL("file://" +  //$NON-NLS-1$
208                            codeFile.getAbsolutePath());
209                }
210            }
211        }
212        if (ge.signers != null) {
213            if (resolve) {
214                ge.signers = PolicyUtils.expand(ge.signers, system);
215            }
216            signers = resolveSigners(ks, ge.signers);
217        }
218        if (ge.principals != null) {
219            for (Iterator<PrincipalEntry> iter = ge.principals.iterator(); iter.hasNext();) {
220                DefaultPolicyScanner.PrincipalEntry pe = iter
221                        .next();
222                if (resolve) {
223                    pe.name = PolicyUtils.expand(pe.name, system);
224                }
225                if (pe.klass == null) {
226                    principals.add(getPrincipalByAlias(ks, pe.name));
227                } else {
228                    principals.add(new UnresolvedPrincipal(pe.klass, pe.name));
229                }
230            }
231        }
232        if (ge.permissions != null) {
233            for (Iterator<PermissionEntry> iter = ge.permissions.iterator(); iter.hasNext();) {
234                DefaultPolicyScanner.PermissionEntry pe = iter
235                        .next();
236                try {
237                    permissions.add(resolvePermission(pe, ge, ks, system,
238                            resolve));
239                }
240                catch (Exception e) {
241                    // TODO: log warning
242                }
243            }
244        }
245        return new PolicyEntry(new CodeSource(codebase, signers), principals,
246                permissions);
247    }
248
249    /**
250     * Translates PermissionEntry token to Permission object.
251     * First, it performs general expansion for non-null <code>name</code> and
252     * properties expansion for non-null <code>name</code>, <code>action</code>
253     * and <code>signers</code>.
254     * Then, it obtains signing Certificates(if any), tries to find a class specified by
255     * <code>klass</code> name and instantiate a corresponding permission object.
256     * If class is not found or it is signed improperly, returns UnresolvedPermission.
257     *
258     * @param pe PermissionEntry token to be resolved
259     * @param ge parental GrantEntry of the PermissionEntry
260     * @param ks KeyStore for resolving Certificates, may be <code>null</code>
261     * @param system system properties, used for property expansion
262     * @param resolve flag enabling/disabling property expansion
263     * @return resolved Permission object, either of concrete class or UnresolvedPermission
264     * @throws Exception if failed to expand properties,
265     * or to get a Certificate,
266     * or to create an instance of a successfully found class
267     */
268    protected Permission resolvePermission(
269            DefaultPolicyScanner.PermissionEntry pe,
270            DefaultPolicyScanner.GrantEntry ge, KeyStore ks, Properties system,
271            boolean resolve) throws Exception {
272        if (pe.name != null) {
273            pe.name = PolicyUtils.expandGeneral(pe.name,
274                    new PermissionExpander().configure(ge, ks));
275        }
276        if (resolve) {
277            if (pe.name != null) {
278                pe.name = PolicyUtils.expand(pe.name, system);
279            }
280            if (pe.actions != null) {
281                pe.actions = PolicyUtils.expand(pe.actions, system);
282            }
283            if (pe.signers != null) {
284                pe.signers = PolicyUtils.expand(pe.signers, system);
285            }
286        }
287        Certificate[] signers = (pe.signers == null) ? null : resolveSigners(
288                ks, pe.signers);
289        try {
290            Class<?> klass = Class.forName(pe.klass);
291            if (PolicyUtils.matchSubset(signers, klass.getSigners())) {
292                return PolicyUtils.instantiatePermission(klass, pe.name,
293                        pe.actions);
294            }
295        }
296        catch (ClassNotFoundException cnfe) {}
297        //maybe properly signed class will be loaded later
298        return new UnresolvedPermission(pe.klass, pe.name, pe.actions, signers);
299    }
300
301    /**
302     * Specific handler for expanding <i>self</i> and <i>alias</i> protocols.
303     */
304    class PermissionExpander implements PolicyUtils.GeneralExpansionHandler {
305
306        // Store KeyStore
307        private KeyStore ks;
308
309        // Store GrantEntry
310        private DefaultPolicyScanner.GrantEntry ge;
311
312        /**
313         * Combined setter of all required fields.
314         */
315        public PermissionExpander configure(DefaultPolicyScanner.GrantEntry ge,
316                KeyStore ks) {
317            this.ge = ge;
318            this.ks = ks;
319            return this;
320        }
321
322        /**
323         * Resolves the following protocols:
324         * <dl>
325         * <dt>self
326         * <dd>Denotes substitution to a principal information of the parental
327         * GrantEntry. Returns a space-separated list of resolved Principals
328         * (including wildcarded), formatting each as <b>class &quot;name&quot;</b>.
329         * If parental GrantEntry has no Principals, throws ExpansionFailedException.
330         * <dt>alias:<i>name</i>
331         * <dd>Denotes substitution of a KeyStore alias. Namely, if a KeyStore has
332         * an X.509 certificate associated with the specified name, then returns
333         * <b>javax.security.auth.x500.X500Principal &quot;<i>DN</i>&quot;</b> string,
334         * where <i>DN</i> is a certificate's subject distinguished name.
335         * </dl>
336         * @throws ExpansionFailedException - if protocol is other than
337         * <i>self</i> or <i>alias</i>, or if data resolution failed
338         */
339        public String resolve(String protocol, String data)
340                throws PolicyUtils.ExpansionFailedException {
341
342            if ("self".equals(protocol)) { //$NON-NLS-1$
343                //need expanding to list of principals in grant clause
344                if (ge.principals != null && ge.principals.size() != 0) {
345                    StringBuffer sb = new StringBuffer();
346                    for (Iterator<PrincipalEntry> iter = ge.principals.iterator(); iter
347                            .hasNext();) {
348                        DefaultPolicyScanner.PrincipalEntry pr = iter
349                                .next();
350                        if (pr.klass == null) {
351                            // aliased X500Principal
352                            try {
353                                sb.append(pc2str(getPrincipalByAlias(ks,
354                                        pr.name)));
355                            }
356                            catch (Exception e) {
357                                throw new PolicyUtils.ExpansionFailedException(
358                                        Messages.getString("security.143", pr.name), e); //$NON-NLS-1$
359                            }
360                        } else {
361                            sb.append(pr.klass).append(" \"").append(pr.name) //$NON-NLS-1$
362                                    .append("\" "); //$NON-NLS-1$
363                        }
364                    }
365                    return sb.toString();
366                } else {
367                    throw new PolicyUtils.ExpansionFailedException(
368                            Messages.getString("security.144")); //$NON-NLS-1$
369                }
370            }
371            if ("alias".equals(protocol)) { //$NON-NLS-1$
372                try {
373                    return pc2str(getPrincipalByAlias(ks, data));
374                }
375                catch (Exception e) {
376                    throw new PolicyUtils.ExpansionFailedException(
377                            Messages.getString("security.143", data), e); //$NON-NLS-1$
378                }
379            }
380            throw new PolicyUtils.ExpansionFailedException(
381                    Messages.getString("security.145", protocol)); //$NON-NLS-1$
382        }
383
384        // Formats a string describing the passed Principal.
385        private String pc2str(Principal pc) {
386            String klass = pc.getClass().getName();
387            String name = pc.getName();
388            StringBuffer sb = new StringBuffer(klass.length() + name.length()
389                    + 5);
390            return sb.append(klass).append(" \"").append(name).append("\"") //$NON-NLS-1$ //$NON-NLS-2$
391                    .toString();
392        }
393    }
394
395    /**
396     * Takes a comma-separated list of aliases and obtains corresponding
397     * certificates.
398     * @param ks KeyStore for resolving Certificates, may be <code>null</code>
399     * @param signers comma-separated list of certificate aliases,
400     * must be not <code>null</code>
401     * @return an array of signing Certificates
402     * @throws Exception if KeyStore is <code>null</code>
403     * or if it failed to provide a certificate
404     */
405    protected Certificate[] resolveSigners(KeyStore ks, String signers)
406            throws Exception {
407        if (ks == null) {
408            throw new KeyStoreException(Messages.getString("security.146", //$NON-NLS-1$
409                    signers));
410        }
411
412        Collection<Certificate> certs = new HashSet<Certificate>();
413        StringTokenizer snt = new StringTokenizer(signers, ","); //$NON-NLS-1$
414        while (snt.hasMoreTokens()) {
415            //XXX cache found certs ??
416            certs.add(ks.getCertificate(snt.nextToken().trim()));
417        }
418        return certs.toArray(new Certificate[certs.size()]);
419    }
420
421    /**
422     * Returns a subject's X500Principal of an X509Certificate,
423     * which is associated with the specified keystore alias.
424     * @param ks KeyStore for resolving Certificate, may be <code>null</code>
425     * @param alias alias to a certificate
426     * @return X500Principal with a subject distinguished name
427     * @throws KeyStoreException if KeyStore is <code>null</code>
428     * or if it failed to provide a certificate
429     * @throws CertificateException if found certificate is not
430     * an X509Certificate
431     */
432    protected Principal getPrincipalByAlias(KeyStore ks, String alias)
433            throws KeyStoreException, CertificateException {
434
435        if (ks == null) {
436            throw new KeyStoreException(
437                    Messages.getString("security.147", alias)); //$NON-NLS-1$
438        }
439        //XXX cache found certs ??
440        Certificate x509 = ks.getCertificate(alias);
441        if (x509 instanceof X509Certificate) {
442            return ((X509Certificate) x509).getSubjectX500Principal();
443        } else {
444            throw new CertificateException(Messages.getString("security.148", //$NON-NLS-1$
445                    alias, x509));
446        }
447    }
448
449    /**
450     * Returns the first successfully loaded KeyStore, from the specified list of
451     * possible locations. This method iterates over the list of KeystoreEntries;
452     * for each entry expands <code>url</code> and <code>type</code>,
453     * tries to construct instances of specified URL and KeyStore and to load
454     * the keystore. If it is loaded, returns the keystore, otherwise proceeds to
455     * the next KeystoreEntry.
456     * <br>
457     * <b>Note:</b> an url may be relative to the policy file location or absolute.
458     * @param keystores list of available KeystoreEntries
459     * @param base the policy file location
460     * @param system system properties, used for property expansion
461     * @param resolve flag enabling/disabling property expansion
462     * @return the first successfully loaded KeyStore or <code>null</code>
463     */
464    protected KeyStore initKeyStore(List<KeystoreEntry>keystores,
465            URL base, Properties system, boolean resolve) {
466
467        for (int i = 0; i < keystores.size(); i++) {
468            try {
469                DefaultPolicyScanner.KeystoreEntry ke = keystores
470                        .get(i);
471                if (resolve) {
472                    ke.url = PolicyUtils.expandURL(ke.url, system);
473                    if (ke.type != null) {
474                        ke.type = PolicyUtils.expand(ke.type, system);
475                    }
476                }
477                if (ke.type == null || ke.type.length() == 0) {
478                    ke.type = KeyStore.getDefaultType();
479                }
480                KeyStore ks = KeyStore.getInstance(ke.type);
481                URL location = new URL(base, ke.url);
482                InputStream is = AccessController
483                        .doPrivileged(new PolicyUtils.URLLoader(location));
484                try {
485                    ks.load(is, null);
486                }
487                finally {
488                    is.close();
489                }
490                return ks;
491            }
492            catch (Exception e) {
493                // TODO: log warning
494            }
495        }
496        return null;
497    }
498}
499