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