Home | History | Annotate | Download | only in fortress
      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 
     23 package org.apache.harmony.security.fortress;
     24 
     25 import java.io.BufferedReader;
     26 import java.io.InputStream;
     27 import java.io.InputStreamReader;
     28 import java.io.Reader;
     29 import java.net.URL;
     30 import java.security.AccessController;
     31 import java.security.CodeSource;
     32 import java.security.KeyStore;
     33 import java.security.KeyStoreException;
     34 import java.security.Permission;
     35 import java.security.Principal;
     36 import java.security.UnresolvedPermission;
     37 import java.security.cert.Certificate;
     38 import java.security.cert.CertificateException;
     39 import java.security.cert.X509Certificate;
     40 import java.util.ArrayList;
     41 import java.util.Collection;
     42 import java.util.HashSet;
     43 import java.util.Iterator;
     44 import java.util.List;
     45 import java.util.Properties;
     46 import java.util.Set;
     47 import java.util.StringTokenizer;
     48 import org.apache.harmony.security.DefaultPolicyScanner;
     49 import org.apache.harmony.security.DefaultPolicyScanner.GrantEntry;
     50 import org.apache.harmony.security.DefaultPolicyScanner.KeystoreEntry;
     51 import org.apache.harmony.security.DefaultPolicyScanner.PermissionEntry;
     52 import org.apache.harmony.security.DefaultPolicyScanner.PrincipalEntry;
     53 import org.apache.harmony.security.PolicyEntry;
     54 import org.apache.harmony.security.UnresolvedPrincipal;
     55 
     56 /**
     57  * This is a basic loader of policy files. It delegates lexical analysis to
     58  * a pluggable scanner and converts received tokens to a set of
     59  * {@link org.apache.harmony.security.PolicyEntry PolicyEntries}.
     60  * For details of policy format, see the
     61  * {@link org.apache.harmony.security.DefaultPolicy default policy description}.
     62  * <br>
     63  * For ordinary uses, this class has just one public method <code>parse()</code>,
     64  * which performs the main task.
     65  * Extensions of this parser may redefine specific operations separately,
     66  * by overriding corresponding protected methods.
     67  * <br>
     68  * This implementation is effectively thread-safe, as it has no field references
     69  * to data being processed (that is, passes all the data as method parameters).
     70  *
     71  * @see org.apache.harmony.security.DefaultPolicy
     72  * @see org.apache.harmony.security.DefaultPolicyScanner
     73  * @see org.apache.harmony.security.PolicyEntry
     74  */
     75 public class DefaultPolicyParser {
     76 
     77     // Pluggable scanner for a specific file format
     78     private final DefaultPolicyScanner scanner;
     79 
     80     /**
     81      * Default constructor,
     82      * {@link org.apache.harmony.security.DefaultPolicyScanner DefaultPolicyScanner}
     83      * is used.
     84      */
     85     public DefaultPolicyParser() {
     86         scanner = new DefaultPolicyScanner();
     87     }
     88 
     89     /**
     90      * Extension constructor for plugging-in custom scanner.
     91      */
     92     public DefaultPolicyParser(DefaultPolicyScanner s) {
     93         this.scanner = s;
     94     }
     95 
     96     /**
     97      * This is the main business method. It manages loading process as follows:
     98      * the associated scanner is used to parse the stream to a set of
     99      * {@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 =
    116             new BufferedReader(
    117                     new InputStreamReader(
    118                             AccessController.doPrivileged(
    119                                     new PolicyUtils.URLLoader(location))));
    120 
    121         Collection<GrantEntry> grantEntries = new HashSet<GrantEntry>();
    122         List<KeystoreEntry> keystores = new ArrayList<KeystoreEntry>();
    123 
    124         try {
    125             scanner.scanStream(r, grantEntries, keystores);
    126         }
    127         finally {
    128             r.close();
    129         }
    130 
    131         //XXX KeyStore could be loaded lazily...
    132         KeyStore ks = initKeyStore(keystores, location, system, resolve);
    133 
    134         Collection<PolicyEntry> result = new HashSet<PolicyEntry>();
    135         for (Iterator<GrantEntry> iter = grantEntries.iterator(); iter.hasNext();) {
    136             DefaultPolicyScanner.GrantEntry ge = iter
    137                     .next();
    138             try {
    139                 PolicyEntry pe = resolveGrant(ge, ks, system, resolve);
    140                 if (!pe.isVoid()) {
    141                     result.add(pe);
    142                 }
    143             }
    144             catch (Exception e) {
    145                 // TODO: log warning
    146             }
    147         }
    148 
    149         return result;
    150     }
    151 
    152     /**
    153      * Translates GrantEntry token to PolicyEntry object. It goes step by step,
    154      * trying to resolve each component of the GrantEntry:
    155      * <ul>
    156      * <li> If <code>codebase</code> is specified, expand it and construct an URL.
    157      * <li> If <code>signers</code> is specified, expand it and obtain
    158      * corresponding Certificates.
    159      * <li> If <code>principals</code> collection is specified, iterate over it.
    160      * For each PrincipalEntry, expand name and if no class specified,
    161      * resolve actual X500Principal from a KeyStore certificate; otherwise keep it
    162      * as UnresolvedPrincipal.
    163      * <li> Iterate over <code>permissions</code> collection. For each PermissionEntry,
    164      * try to resolve (see method
    165      * {@link #resolvePermission(DefaultPolicyScanner.PermissionEntry, DefaultPolicyScanner.GrantEntry, KeyStore, Properties, boolean) resolvePermission()})
    166      * a corresponding permission. If resolution failed, ignore the PermissionEntry.
    167      * </ul>
    168      * In fact, property expansion in the steps above is conditional and is ruled by
    169      * the parameter <i>resolve</i>.
    170      * <br>
    171      * Finally a new PolicyEntry is created, which associates the trinity
    172      * of resolved URL, Certificates and Principals to a set of granted Permissions.
    173      *
    174      * @param ge GrantEntry token to be resolved
    175      * @param ks KeyStore for resolving Certificates, may be <code>null</code>
    176      * @param system system properties, used for property expansion
    177      * @param resolve flag enabling/disabling property expansion
    178      * @return resolved PolicyEntry
    179      * @throws Exception if unable to resolve codebase, signers or principals
    180      * of the GrantEntry
    181      * @see DefaultPolicyScanner.PrincipalEntry
    182      * @see DefaultPolicyScanner.PermissionEntry
    183      * @see org.apache.harmony.security.PolicyUtils
    184      */
    185     protected PolicyEntry resolveGrant(DefaultPolicyScanner.GrantEntry ge,
    186             KeyStore ks, Properties system, boolean resolve) throws Exception {
    187 
    188         URL codebase = null;
    189         Certificate[] signers = null;
    190         Set<Principal>principals = new HashSet<Principal>();
    191         Set<Permission>permissions = new HashSet<Permission>();
    192         if (ge.codebase != null) {
    193             codebase = new URL(resolve ? PolicyUtils.expandURL(ge.codebase,
    194                     system) : ge.codebase);
    195         }
    196         if (ge.signers != null) {
    197             if (resolve) {
    198                 ge.signers = PolicyUtils.expand(ge.signers, system);
    199             }
    200             signers = resolveSigners(ks, ge.signers);
    201         }
    202         if (ge.principals != null) {
    203             for (Iterator<PrincipalEntry> iter = ge.principals.iterator(); iter.hasNext();) {
    204                 DefaultPolicyScanner.PrincipalEntry pe = iter
    205                         .next();
    206                 if (resolve) {
    207                     pe.name = PolicyUtils.expand(pe.name, system);
    208                 }
    209                 if (pe.klass == null) {
    210                     principals.add(getPrincipalByAlias(ks, pe.name));
    211                 } else {
    212                     principals.add(new UnresolvedPrincipal(pe.klass, pe.name));
    213                 }
    214             }
    215         }
    216         if (ge.permissions != null) {
    217             for (Iterator<PermissionEntry> iter = ge.permissions.iterator(); iter.hasNext();) {
    218                 DefaultPolicyScanner.PermissionEntry pe = iter
    219                         .next();
    220                 try {
    221                     permissions.add(resolvePermission(pe, ge, ks, system,
    222                             resolve));
    223                 }
    224                 catch (Exception e) {
    225                     // TODO: log warning
    226                 }
    227             }
    228         }
    229         return new PolicyEntry(new CodeSource(codebase, signers), principals,
    230                 permissions);
    231     }
    232 
    233     /**
    234      * Translates PermissionEntry token to Permission object.
    235      * First, it performs general expansion for non-null <code>name</code> and
    236      * properties expansion for non-null <code>name</code>, <code>action</code>
    237      * and <code>signers</code>.
    238      * Then, it obtains signing Certificates(if any), tries to find a class specified by
    239      * <code>klass</code> name and instantiate a corresponding permission object.
    240      * If class is not found or it is signed improperly, returns UnresolvedPermission.
    241      *
    242      * @param pe PermissionEntry token to be resolved
    243      * @param ge parental GrantEntry of the PermissionEntry
    244      * @param ks KeyStore for resolving Certificates, may be <code>null</code>
    245      * @param system system properties, used for property expansion
    246      * @param resolve flag enabling/disabling property expansion
    247      * @return resolved Permission object, either of concrete class or UnresolvedPermission
    248      * @throws Exception if failed to expand properties,
    249      * or to get a Certificate,
    250      * or to create an instance of a successfully found class
    251      */
    252     protected Permission resolvePermission(
    253             DefaultPolicyScanner.PermissionEntry pe,
    254             DefaultPolicyScanner.GrantEntry ge, KeyStore ks, Properties system,
    255             boolean resolve) throws Exception {
    256         if (pe.name != null) {
    257             pe.name = PolicyUtils.expandGeneral(pe.name,
    258                     new PermissionExpander().configure(ge, ks));
    259         }
    260         if (resolve) {
    261             if (pe.name != null) {
    262                 pe.name = PolicyUtils.expand(pe.name, system);
    263             }
    264             if (pe.actions != null) {
    265                 pe.actions = PolicyUtils.expand(pe.actions, system);
    266             }
    267             if (pe.signers != null) {
    268                 pe.signers = PolicyUtils.expand(pe.signers, system);
    269             }
    270         }
    271         Certificate[] signers = (pe.signers == null) ? null : resolveSigners(
    272                 ks, pe.signers);
    273         try {
    274             Class<?> klass = Class.forName(pe.klass);
    275             if (PolicyUtils.matchSubset(signers, klass.getSigners())) {
    276                 return PolicyUtils.instantiatePermission(klass, pe.name,
    277                         pe.actions);
    278             }
    279         }
    280         catch (ClassNotFoundException cnfe) {}
    281         //maybe properly signed class will be loaded later
    282         return new UnresolvedPermission(pe.klass, pe.name, pe.actions, signers);
    283     }
    284 
    285     /**
    286      * Specific handler for expanding <i>self</i> and <i>alias</i> protocols.
    287      */
    288     class PermissionExpander implements 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(DefaultPolicyScanner.GrantEntry ge,
    300                 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 &quot;name&quot;</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 &quot;<i>DN</i>&quot;</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)) {
    327                 //need expanding to list of principals in grant clause
    328                 if (ge.principals != null && ge.principals.size() != 0) {
    329                     StringBuilder sb = new StringBuilder();
    330                     for (Iterator<PrincipalEntry> iter = ge.principals.iterator(); iter
    331                             .hasNext();) {
    332                         DefaultPolicyScanner.PrincipalEntry pr = iter
    333                                 .next();
    334                         if (pr.klass == null) {
    335                             // aliased X500Principal
    336                             try {
    337                                 sb.append(pc2str(getPrincipalByAlias(ks,
    338                                         pr.name)));
    339                             }
    340                             catch (Exception e) {
    341                                 throw new PolicyUtils.ExpansionFailedException("Error expanding alias: " + pr.name, e);
    342                             }
    343                         } else {
    344                             sb.append(pr.klass).append(" \"").append(pr.name)
    345                                     .append("\" ");
    346                         }
    347                     }
    348                     return sb.toString();
    349                 } else {
    350                     throw new PolicyUtils.ExpansionFailedException("Self protocol is valid only in context of Principal-based grant entries");
    351                 }
    352             }
    353             if ("alias".equals(protocol)) {
    354                 try {
    355                     return pc2str(getPrincipalByAlias(ks, data));
    356                 } catch (Exception e) {
    357                     throw new PolicyUtils.ExpansionFailedException("Error expanding alias: " + data, e);
    358                 }
    359             }
    360             throw new PolicyUtils.ExpansionFailedException("Unknown expansion protocol: " + protocol);
    361         }
    362 
    363         // Formats a string describing the passed Principal.
    364         private String pc2str(Principal pc) {
    365             String klass = pc.getClass().getName();
    366             String name = pc.getName();
    367             StringBuilder sb = new StringBuilder(klass.length() + name.length()
    368                     + 5);
    369             return sb.append(klass).append(" \"").append(name).append("\"")
    370                     .toString();
    371         }
    372     }
    373 
    374     /**
    375      * Takes a comma-separated list of aliases and obtains corresponding
    376      * certificates.
    377      * @param ks KeyStore for resolving Certificates, may be <code>null</code>
    378      * @param signers comma-separated list of certificate aliases,
    379      * must be not <code>null</code>
    380      * @return an array of signing Certificates
    381      * @throws Exception if KeyStore is <code>null</code>
    382      * or if it failed to provide a certificate
    383      */
    384     protected Certificate[] resolveSigners(KeyStore ks, String signers)
    385             throws Exception {
    386         if (ks == null) {
    387             throw new KeyStoreException("No KeyStore to resolve signers: " + signers);
    388         }
    389 
    390         Collection<Certificate> certs = new HashSet<Certificate>();
    391         StringTokenizer snt = new StringTokenizer(signers, ",");
    392         while (snt.hasMoreTokens()) {
    393             //XXX cache found certs ??
    394             certs.add(ks.getCertificate(snt.nextToken().trim()));
    395         }
    396         return certs.toArray(new Certificate[certs.size()]);
    397     }
    398 
    399     /**
    400      * Returns a subject's X500Principal of an X509Certificate,
    401      * which is associated with the specified keystore alias.
    402      * @param ks KeyStore for resolving Certificate, may be <code>null</code>
    403      * @param alias alias to a certificate
    404      * @return X500Principal with a subject distinguished name
    405      * @throws KeyStoreException if KeyStore is <code>null</code>
    406      * or if it failed to provide a certificate
    407      * @throws CertificateException if found certificate is not
    408      * an X509Certificate
    409      */
    410     protected Principal getPrincipalByAlias(KeyStore ks, String alias)
    411             throws KeyStoreException, CertificateException {
    412 
    413         if (ks == null) {
    414             throw new KeyStoreException("No KeyStore to resolve principal by alias: " + alias);
    415         }
    416         //XXX cache found certs ??
    417         Certificate x509 = ks.getCertificate(alias);
    418         if (x509 instanceof X509Certificate) {
    419             return ((X509Certificate) x509).getSubjectX500Principal();
    420         } else {
    421             throw new CertificateException("Invalid certificate for alias '" + alias + "': " +
    422                     x509 + ". Only X509Certificate should be aliased to principals.");
    423         }
    424     }
    425 
    426     /**
    427      * Returns the first successfully loaded KeyStore, from the specified list of
    428      * possible locations. This method iterates over the list of KeystoreEntries;
    429      * for each entry expands <code>url</code> and <code>type</code>,
    430      * tries to construct instances of specified URL and KeyStore and to load
    431      * the keystore. If it is loaded, returns the keystore, otherwise proceeds to
    432      * the next KeystoreEntry.
    433      * <br>
    434      * <b>Note:</b> an url may be relative to the policy file location or absolute.
    435      * @param keystores list of available KeystoreEntries
    436      * @param base the policy file location
    437      * @param system system properties, used for property expansion
    438      * @param resolve flag enabling/disabling property expansion
    439      * @return the first successfully loaded KeyStore or <code>null</code>
    440      */
    441     protected KeyStore initKeyStore(List<KeystoreEntry>keystores,
    442             URL base, Properties system, boolean resolve) {
    443 
    444         for (int i = 0; i < keystores.size(); i++) {
    445             try {
    446                 DefaultPolicyScanner.KeystoreEntry ke = keystores
    447                         .get(i);
    448                 if (resolve) {
    449                     ke.url = PolicyUtils.expandURL(ke.url, system);
    450                     if (ke.type != null) {
    451                         ke.type = PolicyUtils.expand(ke.type, system);
    452                     }
    453                 }
    454                 if (ke.type == null || ke.type.length() == 0) {
    455                     ke.type = KeyStore.getDefaultType();
    456                 }
    457                 KeyStore ks = KeyStore.getInstance(ke.type);
    458                 URL location = new URL(base, ke.url);
    459                 InputStream is = AccessController
    460                         .doPrivileged(new PolicyUtils.URLLoader(location));
    461                 try {
    462                     ks.load(is, null);
    463                 }
    464                 finally {
    465                     is.close();
    466                 }
    467                 return ks;
    468             }
    469             catch (Exception e) {
    470                 // TODO: log warning
    471             }
    472         }
    473         return null;
    474     }
    475 }
    476