/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Alexey V. Varlamov * @version $Revision$ */ package org.apache.harmony.security.fortress; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.File; import java.io.Reader; import java.net.URL; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.AccessController; import java.security.CodeSource; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Permission; import java.security.Principal; import java.security.UnresolvedPermission; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import org.apache.harmony.security.DefaultPolicyScanner; import org.apache.harmony.security.PolicyEntry; import org.apache.harmony.security.UnresolvedPrincipal; import org.apache.harmony.security.DefaultPolicyScanner.GrantEntry; import org.apache.harmony.security.DefaultPolicyScanner.KeystoreEntry; import org.apache.harmony.security.DefaultPolicyScanner.PermissionEntry; import org.apache.harmony.security.DefaultPolicyScanner.PrincipalEntry; import org.apache.harmony.security.internal.nls.Messages; /** * This is a basic loader of policy files. It delegates lexical analysis to * a pluggable scanner and converts received tokens to a set of * {@link org.apache.harmony.security.PolicyEntry PolicyEntries}. * For details of policy format, see the * {@link org.apache.harmony.security.fortress.DefaultPolicy default policy * description}. * <br> * For ordinary uses, this class has just one public method <code>parse()</code>, * which performs the main task. * Extensions of this parser may redefine specific operations separately, * by overriding corresponding protected methods. * <br> * This implementation is effectively thread-safe, as it has no field references * to data being processed (that is, passes all the data as method parameters). * * @see org.apache.harmony.security.fortress.DefaultPolicy * @see org.apache.harmony.security.DefaultPolicyScanner * @see org.apache.harmony.security.PolicyEntry */ public class DefaultPolicyParser { // Pluggable scanner for a specific file format private final DefaultPolicyScanner scanner; /** * Default constructor, * {@link org.apache.harmony.security.DefaultPolicyScanner DefaultPolicyScanner} * is used. */ public DefaultPolicyParser() { scanner = new DefaultPolicyScanner(); } /** * Extension constructor for plugging-in custom scanner. */ public DefaultPolicyParser(DefaultPolicyScanner s) { this.scanner = s; } /** * This is the main business method. It manages loading process as follows: * the associated scanner is used to parse the stream to a set of * {@link org.apache.harmony.security.DefaultPolicyScanner.GrantEntry composite tokens}, * then this set is iterated and each token is translated to a PolicyEntry. * Semantically invalid tokens are ignored, the same as void PolicyEntries. * <br> * A policy file may refer to some KeyStore(s), and in this case the first * valid reference is initialized and used in processing tokens. * * @param location an URL of a policy file to be loaded * @param system system properties, used for property expansion * @return a collection of PolicyEntry objects, may be empty * @throws Exception IO error while reading location or file syntax error */ public Collection<PolicyEntry>parse(URL location, Properties system) throws Exception { boolean resolve = PolicyUtils.canExpandProperties(); // BEGIN android-modified Reader r = new BufferedReader( new InputStreamReader( AccessController.doPrivileged( new PolicyUtils.URLLoader(location))), 8192); // END android-modified Collection<GrantEntry> grantEntries = new HashSet<GrantEntry>(); List<KeystoreEntry> keystores = new ArrayList<KeystoreEntry>(); try { scanner.scanStream(r, grantEntries, keystores); } finally { r.close(); } //XXX KeyStore could be loaded lazily... KeyStore ks = initKeyStore(keystores, location, system, resolve); Collection<PolicyEntry> result = new HashSet<PolicyEntry>(); for (Iterator<GrantEntry> iter = grantEntries.iterator(); iter.hasNext();) { DefaultPolicyScanner.GrantEntry ge = iter .next(); try { PolicyEntry pe = resolveGrant(ge, ks, system, resolve); if (!pe.isVoid()) { result.add(pe); } } catch (Exception e) { // TODO: log warning } } return result; } /** * Translates GrantEntry token to PolicyEntry object. It goes step by step, * trying to resolve each component of the GrantEntry: * <ul> * <li> If <code>codebase</code> is specified, expand it and construct an URL. * <li> If <code>signers</code> is specified, expand it and obtain * corresponding Certificates. * <li> If <code>principals</code> collection is specified, iterate over it. * For each PrincipalEntry, expand name and if no class specified, * resolve actual X500Principal from a KeyStore certificate; otherwise keep it * as UnresolvedPrincipal. * <li> Iterate over <code>permissions</code> collection. For each PermissionEntry, * try to resolve (see method * {@link #resolvePermission(DefaultPolicyScanner.PermissionEntry, DefaultPolicyScanner.GrantEntry, KeyStore, Properties, boolean) resolvePermission()}) * a corresponding permission. If resolution failed, ignore the PermissionEntry. * </ul> * In fact, property expansion in the steps above is conditional and is ruled by * the parameter <i>resolve</i>. * <br> * Finally a new PolicyEntry is created, which associates the trinity * of resolved URL, Certificates and Principals to a set of granted Permissions. * * @param ge GrantEntry token to be resolved * @param ks KeyStore for resolving Certificates, may be <code>null</code> * @param system system properties, used for property expansion * @param resolve flag enabling/disabling property expansion * @return resolved PolicyEntry * @throws Exception if unable to resolve codebase, signers or principals * of the GrantEntry * @see DefaultPolicyScanner.PrincipalEntry * @see DefaultPolicyScanner.PermissionEntry * @see org.apache.harmony.security.fortress.PolicyUtils */ protected PolicyEntry resolveGrant(DefaultPolicyScanner.GrantEntry ge, KeyStore ks, Properties system, boolean resolve) throws Exception { URL codebase = null; Certificate[] signers = null; Set<Principal>principals = new HashSet<Principal>(); Set<Permission>permissions = new HashSet<Permission>(); if (ge.codebase != null) { codebase = new URL(resolve ? PolicyUtils.expandURL(ge.codebase, system) : ge.codebase); //Fix HARMONY-1963 if ("file".equals(codebase.getProtocol())) { //$NON-NLS-1$ File codeFile = new File(codebase.getFile()); if (codeFile.isAbsolute()) { codebase = new URL("file://" + //$NON-NLS-1$ codeFile.getAbsolutePath()); } } } if (ge.signers != null) { if (resolve) { ge.signers = PolicyUtils.expand(ge.signers, system); } signers = resolveSigners(ks, ge.signers); } if (ge.principals != null) { for (Iterator<PrincipalEntry> iter = ge.principals.iterator(); iter.hasNext();) { DefaultPolicyScanner.PrincipalEntry pe = iter .next(); if (resolve) { pe.name = PolicyUtils.expand(pe.name, system); } if (pe.klass == null) { principals.add(getPrincipalByAlias(ks, pe.name)); } else { principals.add(new UnresolvedPrincipal(pe.klass, pe.name)); } } } if (ge.permissions != null) { for (Iterator<PermissionEntry> iter = ge.permissions.iterator(); iter.hasNext();) { DefaultPolicyScanner.PermissionEntry pe = iter .next(); try { permissions.add(resolvePermission(pe, ge, ks, system, resolve)); } catch (Exception e) { // TODO: log warning } } } return new PolicyEntry(new CodeSource(codebase, signers), principals, permissions); } /** * Translates PermissionEntry token to Permission object. * First, it performs general expansion for non-null <code>name</code> and * properties expansion for non-null <code>name</code>, <code>action</code> * and <code>signers</code>. * Then, it obtains signing Certificates(if any), tries to find a class specified by * <code>klass</code> name and instantiate a corresponding permission object. * If class is not found or it is signed improperly, returns UnresolvedPermission. * * @param pe PermissionEntry token to be resolved * @param ge parental GrantEntry of the PermissionEntry * @param ks KeyStore for resolving Certificates, may be <code>null</code> * @param system system properties, used for property expansion * @param resolve flag enabling/disabling property expansion * @return resolved Permission object, either of concrete class or UnresolvedPermission * @throws Exception if failed to expand properties, * or to get a Certificate, * or to create an instance of a successfully found class */ protected Permission resolvePermission( DefaultPolicyScanner.PermissionEntry pe, DefaultPolicyScanner.GrantEntry ge, KeyStore ks, Properties system, boolean resolve) throws Exception { if (pe.name != null) { pe.name = PolicyUtils.expandGeneral(pe.name, new PermissionExpander().configure(ge, ks)); } if (resolve) { if (pe.name != null) { pe.name = PolicyUtils.expand(pe.name, system); } if (pe.actions != null) { pe.actions = PolicyUtils.expand(pe.actions, system); } if (pe.signers != null) { pe.signers = PolicyUtils.expand(pe.signers, system); } } Certificate[] signers = (pe.signers == null) ? null : resolveSigners( ks, pe.signers); try { Class<?> klass = Class.forName(pe.klass); if (PolicyUtils.matchSubset(signers, klass.getSigners())) { return PolicyUtils.instantiatePermission(klass, pe.name, pe.actions); } } catch (ClassNotFoundException cnfe) {} //maybe properly signed class will be loaded later return new UnresolvedPermission(pe.klass, pe.name, pe.actions, signers); } /** * Specific handler for expanding <i>self</i> and <i>alias</i> protocols. */ class PermissionExpander implements PolicyUtils.GeneralExpansionHandler { // Store KeyStore private KeyStore ks; // Store GrantEntry private DefaultPolicyScanner.GrantEntry ge; /** * Combined setter of all required fields. */ public PermissionExpander configure(DefaultPolicyScanner.GrantEntry ge, KeyStore ks) { this.ge = ge; this.ks = ks; return this; } /** * Resolves the following protocols: * <dl> * <dt>self * <dd>Denotes substitution to a principal information of the parental * GrantEntry. Returns a space-separated list of resolved Principals * (including wildcarded), formatting each as <b>class "name"</b>. * If parental GrantEntry has no Principals, throws ExpansionFailedException. * <dt>alias:<i>name</i> * <dd>Denotes substitution of a KeyStore alias. Namely, if a KeyStore has * an X.509 certificate associated with the specified name, then returns * <b>javax.security.auth.x500.X500Principal "<i>DN</i>"</b> string, * where <i>DN</i> is a certificate's subject distinguished name. * </dl> * @throws ExpansionFailedException - if protocol is other than * <i>self</i> or <i>alias</i>, or if data resolution failed */ public String resolve(String protocol, String data) throws PolicyUtils.ExpansionFailedException { if ("self".equals(protocol)) { //$NON-NLS-1$ //need expanding to list of principals in grant clause if (ge.principals != null && ge.principals.size() != 0) { StringBuffer sb = new StringBuffer(); for (Iterator<PrincipalEntry> iter = ge.principals.iterator(); iter .hasNext();) { DefaultPolicyScanner.PrincipalEntry pr = iter .next(); if (pr.klass == null) { // aliased X500Principal try { sb.append(pc2str(getPrincipalByAlias(ks, pr.name))); } catch (Exception e) { throw new PolicyUtils.ExpansionFailedException( Messages.getString("security.143", pr.name), e); //$NON-NLS-1$ } } else { sb.append(pr.klass).append(" \"").append(pr.name) //$NON-NLS-1$ .append("\" "); //$NON-NLS-1$ } } return sb.toString(); } else { throw new PolicyUtils.ExpansionFailedException( Messages.getString("security.144")); //$NON-NLS-1$ } } if ("alias".equals(protocol)) { //$NON-NLS-1$ try { return pc2str(getPrincipalByAlias(ks, data)); } catch (Exception e) { throw new PolicyUtils.ExpansionFailedException( Messages.getString("security.143", data), e); //$NON-NLS-1$ } } throw new PolicyUtils.ExpansionFailedException( Messages.getString("security.145", protocol)); //$NON-NLS-1$ } // Formats a string describing the passed Principal. private String pc2str(Principal pc) { String klass = pc.getClass().getName(); String name = pc.getName(); StringBuffer sb = new StringBuffer(klass.length() + name.length() + 5); return sb.append(klass).append(" \"").append(name).append("\"") //$NON-NLS-1$ //$NON-NLS-2$ .toString(); } } /** * Takes a comma-separated list of aliases and obtains corresponding * certificates. * @param ks KeyStore for resolving Certificates, may be <code>null</code> * @param signers comma-separated list of certificate aliases, * must be not <code>null</code> * @return an array of signing Certificates * @throws Exception if KeyStore is <code>null</code> * or if it failed to provide a certificate */ protected Certificate[] resolveSigners(KeyStore ks, String signers) throws Exception { if (ks == null) { throw new KeyStoreException(Messages.getString("security.146", //$NON-NLS-1$ signers)); } Collection<Certificate> certs = new HashSet<Certificate>(); StringTokenizer snt = new StringTokenizer(signers, ","); //$NON-NLS-1$ while (snt.hasMoreTokens()) { //XXX cache found certs ?? certs.add(ks.getCertificate(snt.nextToken().trim())); } return certs.toArray(new Certificate[certs.size()]); } /** * Returns a subject's X500Principal of an X509Certificate, * which is associated with the specified keystore alias. * @param ks KeyStore for resolving Certificate, may be <code>null</code> * @param alias alias to a certificate * @return X500Principal with a subject distinguished name * @throws KeyStoreException if KeyStore is <code>null</code> * or if it failed to provide a certificate * @throws CertificateException if found certificate is not * an X509Certificate */ protected Principal getPrincipalByAlias(KeyStore ks, String alias) throws KeyStoreException, CertificateException { if (ks == null) { throw new KeyStoreException( Messages.getString("security.147", alias)); //$NON-NLS-1$ } //XXX cache found certs ?? Certificate x509 = ks.getCertificate(alias); if (x509 instanceof X509Certificate) { return ((X509Certificate) x509).getSubjectX500Principal(); } else { throw new CertificateException(Messages.getString("security.148", //$NON-NLS-1$ alias, x509)); } } /** * Returns the first successfully loaded KeyStore, from the specified list of * possible locations. This method iterates over the list of KeystoreEntries; * for each entry expands <code>url</code> and <code>type</code>, * tries to construct instances of specified URL and KeyStore and to load * the keystore. If it is loaded, returns the keystore, otherwise proceeds to * the next KeystoreEntry. * <br> * <b>Note:</b> an url may be relative to the policy file location or absolute. * @param keystores list of available KeystoreEntries * @param base the policy file location * @param system system properties, used for property expansion * @param resolve flag enabling/disabling property expansion * @return the first successfully loaded KeyStore or <code>null</code> */ protected KeyStore initKeyStore(List<KeystoreEntry>keystores, URL base, Properties system, boolean resolve) { for (int i = 0; i < keystores.size(); i++) { try { DefaultPolicyScanner.KeystoreEntry ke = keystores .get(i); if (resolve) { ke.url = PolicyUtils.expandURL(ke.url, system); if (ke.type != null) { ke.type = PolicyUtils.expand(ke.type, system); } } if (ke.type == null || ke.type.length() == 0) { ke.type = KeyStore.getDefaultType(); } KeyStore ks = KeyStore.getInstance(ke.type); URL location = new URL(base, ke.url); InputStream is = AccessController .doPrivileged(new PolicyUtils.URLLoader(location)); try { ks.load(is, null); } finally { is.close(); } return ks; } catch (Exception e) { // TODO: log warning } } return null; } }