blob: 9857680ba78c1ba405658999908acf5848e16744 [file] [log] [blame]
/*
* 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.Reader;
import java.net.URL;
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.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
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.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.PolicyEntry;
import org.apache.harmony.security.UnresolvedPrincipal;
/**
* 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.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.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();
Reader r =
new BufferedReader(
new InputStreamReader(
AccessController.doPrivileged(
new PolicyUtils.URLLoader(location))));
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.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);
}
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 &quot;name&quot;</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 &quot;<i>DN</i>&quot;</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)) {
//need expanding to list of principals in grant clause
if (ge.principals != null && ge.principals.size() != 0) {
StringBuilder sb = new StringBuilder();
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("Error expanding alias: " + pr.name, e);
}
} else {
sb.append(pr.klass).append(" \"").append(pr.name)
.append("\" ");
}
}
return sb.toString();
} else {
throw new PolicyUtils.ExpansionFailedException("Self protocol is valid only in context of Principal-based grant entries");
}
}
if ("alias".equals(protocol)) {
try {
return pc2str(getPrincipalByAlias(ks, data));
} catch (Exception e) {
throw new PolicyUtils.ExpansionFailedException("Error expanding alias: " + data, e);
}
}
throw new PolicyUtils.ExpansionFailedException("Unknown expansion protocol: " + protocol);
}
// Formats a string describing the passed Principal.
private String pc2str(Principal pc) {
String klass = pc.getClass().getName();
String name = pc.getName();
StringBuilder sb = new StringBuilder(klass.length() + name.length()
+ 5);
return sb.append(klass).append(" \"").append(name).append("\"")
.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("No KeyStore to resolve signers: " + signers);
}
Collection<Certificate> certs = new HashSet<Certificate>();
StringTokenizer snt = new StringTokenizer(signers, ",");
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("No KeyStore to resolve principal by alias: " + alias);
}
//XXX cache found certs ??
Certificate x509 = ks.getCertificate(alias);
if (x509 instanceof X509Certificate) {
return ((X509Certificate) x509).getSubjectX500Principal();
} else {
throw new CertificateException("Invalid certificate for alias '" + alias + "': " +
x509 + ". Only X509Certificate should be aliased to principals.");
}
}
/**
* 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;
}
}