/* * 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. */ package net.jini.security; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamField; import java.security.Permission; import java.security.PermissionCollection; import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.StringTokenizer; /** * Represents permission to use the private credentials of subjects for the * purpose of authenticating as any subset of the local principals specified * in the target name, during secure remote calls with any peer that * authenticates as at least the set of peer principals specified in the * target name. In general, security providers check for this permission * instead of checking for * {@link javax.security.auth.PrivateCredentialPermission}. This * permission does not need to be granted for anonymous communication; * it only needs to be granted if an entity needs to authenticate itself. * <p> * An instance of this class contains a name (also referred to as a "target * name") and a set of actions. The target name specifies both the maximum * set of principals that an entity can authenticate as, and the minimum * set of principals that the peer must authenticate as. The actions specify * whether the permission is granted for making outbound remote calls with or * without delegation, listening for incoming remote calls, receiving * incoming remote calls, or some combination. * <p> * The syntax of the target name is either: * <pre><i>LocalPrincipals</i></pre> * or: * <pre><i>LocalPrincipals</i> <code>peer</code> <i>PeerPrincipals</i></pre> * where <i>LocalPrincipals</i> specifies the maximum set of principals that * an entity can authenticate as (that is, the entity can authenticate as any * subset of these principals), and <i>PeerPrincipals</i> specifies the * minimum set of principals that the peer must authenticate as (that is, * the peer must authenticate as at least all of these principals). If the * first syntactic form is used, the peer can authenticate as anyone (and can * be anonymous). The syntax of both <i>LocalPrincipals</i> and * <i>PeerPrincipals</i> is: * <pre><i>PrincipalClass</i> "<i>PrincipalName</i>" ...</pre> * That is, alternating principal classes and principal names, separated by * spaces, with each principal name surrounded by quotes. The order in which * principals are specified does not matter, but both class names and * principal names are case sensitive. For <i>LocalPrincipals</i>, in any * given principal specification, a wildcard value of "*" can be used for * both <i>PrincipalClass</i> and <i>PrincipalName</i> or for just * <i>PrincipalName</i>, but it is illegal to use a wildcard value for just * <i>PrincipalClass</i>. Explicit wildcard values cannot be used in * <i>PeerPrincipals</i>; only complete wildcarding of the peer is supported, * and is expressed by using the first syntactic form instead. * <p> * The syntax of the actions is a comma-separated list of any of the following * (case-insensitive) action names: <code>listen</code>, <code>accept</code>, * <code>connect</code>, <code>delegate</code>. The <code>listen</code> action * grants permission to authenticate as the server when listening for * incoming remote calls; in this case, the peer principals are ignored * (because it is assumed that in general servers authenticate themselves * before clients do). The <code>accept</code> action grants permission to * receive authenticated incoming remote calls; in this case, the entity has * authenticated as the server, and the peer has authenticated as the client. * If the <code>accept</code> action is specified, the <code>listen</code> * action is implied and need not be specified explicitly. The * <code>connect</code> action grants permission to authenticate when * making outgoing remote calls; in this case, the entity authenticates as * the client, and the peer authenticates as the server. The * <code>delegate</code> action grants permission to authenticate with * (or without) delegation when making outgoing remote calls. If the * <code>delegate</code> action is specified, the <code>connect</code> * action is implied and need not be specified explicitly. * <p> * A principal <code>p</code> matches <i>LocalPrincipals</i> if * <i>LocalPrincipals</i> has any of the following principal specifications: * <ul> * <li>"*" for both <i>PrincipalClass</i> and <i>PrincipalName</i> * <li>a <i>PrincipalClass</i> equal to the value of * <code>p.getClass().getName()</code> and a <i>PrincipalName</i> equal to "*" * <li>a <i>PrincipalClass</i> equal to the value of * <code>p.getClass().getName()</code> and a <i>PrincipalName</i> equal to * the value of <code>p.getName()</code> * </ul> * A principal <code>p</code> matches <i>PeerPrincipals</i> if * <i>PeerPrincipals</i> has a <i>PrincipalClass</i> equal to the value of * <code>p.getClass().getName()</code> and a <i>PrincipalName</i> equal to * the value of <code>p.getName()</code>. * <p> * Some example policy file permissions: * <pre> * // client authenticate as jack, with or without delegation, to any server * permission net.jini.security.AuthenticationPermission * "javax.security.auth.x500.X500Principal \"CN=jack\"", "delegate"; * * // client authenticate as joe and/or sue, without delegation, to any server * permission net.jini.security.AuthenticationPermission * "javax.security.auth.x500.X500Principal \"CN=joe\" javax.security.auth.x500.X500Principal \"CN=sue\"", "connect"; * * // client authenticate as any X500 principals, without delegation, to jack * permission net.jini.security.AuthenticationPermission * "javax.security.auth.x500.X500Principal \"*\" peer javax.security.auth.x500.X500Principal \"CN=jack\"", "connect"; * * // authenticate as jack to jack, bi-directional, with or without delegation * permission net.jini.security.AuthenticationPermission * "javax.security.auth.x500.X500Principal \"CN=jack\" peer javax.security.auth.x500.X500Principal \"CN=jack\"", "accept,delegate"; * * // authenticate as anyone to jack, bi-directional, without delegation * permission net.jini.security.AuthenticationPermission * "* \"*\" peer javax.security.auth.x500.X500Principal \"CN=jack\"", "accept,connect"; * </pre> * * @author Sun Microsystems, Inc. * * @since 2.0 */ public final class AuthenticationPermission extends Permission { private static final long serialVersionUID = -4733723479228998183L; /** * The listen action. */ private final static int LISTEN = 0x1; /** * The connect action. */ private final static int CONNECT = 0x2; /** * The accept action (includes the listen action). */ private final static int ACCEPT = 0x4 | LISTEN; /** * The delegate action (includes the connect action). */ private final static int DELEGATE = 0x8 | CONNECT; /** * The actions. * * @serial */ private String actions; /** * The parsed elements of the local principals with wildcard principal * names replaced by null, or null if there is a principal with both a * wildcard class and a wildcard name. If there is an element with a * null principal name, no other element with the same class name will * exist. */ private transient String[] me; /** * The parsed elements of the peer principals, or null if no peer * principals were specified. */ private transient String[] peer; /** * The parsed actions as a bitmask. */ private transient int mask; /** * Creates an instance with the specified target name and actions. * * @param name the target name * @param actions the actions * @throws NullPointerException if the target name or actions string is * <code>null</code> * @throws IllegalArgumentException if the target name or actions string * does not match the syntax specified in the comments at the beginning * of this class */ public AuthenticationPermission(String name, String actions) { super(name); this.actions = actions; init(); } /** * Creates an instance with the specified actions and a target name * constructed from the specified local and peer principals. * * @param local the local principals * @param peer the peer principals, or <code>null</code> * @param actions the actions * @throws NullPointerException if the local principals set or the * actions string is <code>null</code> * @throws IllegalArgumentException if the local principals set is * empty, or either set contains objects that are not * <code>java.security.Principal</code> instances, or the actions string * does not match the syntax specified in the comments at the beginning * of this class */ public AuthenticationPermission(Set local, Set peer, String actions) { this(parseName(local, peer), actions); } /** * Internal structure to work around the fact that you can't do * computation on this prior to calling super() in a constructor. */ private static final class Data { /** * The target name. */ String name; /** * The parsed elements of the local principals. */ String[] me; /** * The parsed elements of the peer principals. */ String[] peer; /** * Simple constructor. */ Data() {} } /** * Creates an instance with the specified data and actions. */ private AuthenticationPermission(Data data, String actions) { super(data.name); this.me = data.me; this.peer = data.peer; this.actions = actions; parseActions(); } /** * Parses the target name and actions, and initializes the transient * fields. */ private void init() { parseActions(); parseName(new StringTokenizer(getName(), " ", true), false); } /** * Parses the actions field and initializes the transient mask field. */ private void parseActions() { StringTokenizer st = new StringTokenizer(actions, " ,", true); boolean comma = false; while (st.hasMoreTokens()) { String act = st.nextToken(); if (act.equals(" ")) { continue; } else if (comma) { if (!act.equals(",")) { comma = false; break; } } else if (act.equalsIgnoreCase("connect")) { mask |= CONNECT; } else if (act.equalsIgnoreCase("accept")) { mask |= ACCEPT; } else if (act.equalsIgnoreCase("delegate")) { mask |= DELEGATE; } else if (act.equalsIgnoreCase("listen")) { mask |= LISTEN; } else { break; } comma = !comma; } if (!comma) { throw new IllegalArgumentException("invalid actions"); } } /** * Parses what's left of the target name in the specified tokenizer, * and initializes the transient fields. Peer is false when parsing * the local principals, true when parsing the peer principals. */ private void parseName(StringTokenizer st, boolean peer) { List vals = new ArrayList(2); outer: while (true) { String cls; do { if (!st.hasMoreTokens()) { break outer; } cls = st.nextToken(); } while (cls.equals(" ")); if (!peer && cls.equalsIgnoreCase("peer")) { parseName(st, true); break; } if (cls.equals("*")) { if (peer) { throw new IllegalArgumentException( "peer class cannot be *"); } cls = null; vals = null; } String nm; do { if (!st.hasMoreTokens()) { throw new IllegalArgumentException( "missing name after class"); } nm = st.nextToken(); } while (nm.equals(" ")); if (!nm.startsWith("\"")) { throw new IllegalArgumentException("name must be in quotes"); } while (!nm.endsWith("\"")) { if (!st.hasMoreTokens()) { throw new IllegalArgumentException( "name must be in quotes"); } nm = nm + st.nextToken(); } if (nm.equals("\"*\"")) { if (peer) { throw new IllegalArgumentException( "peer name cannot be \"*\""); } if (cls == null) { continue; } nm = null; } else if (cls == null) { throw new IllegalArgumentException( "class cannot be * unless name is \"*\""); } else { nm = nm.substring(1, nm.length() - 1); } if (vals != null) { for (int i = vals.size(); i > 0; ) { String onm = (String) vals.get(--i); String ocls = (String) vals.get(--i); if (cls.equals(ocls)) { if (onm == null || (onm != null && onm.equals(nm))) { continue outer; } else if (nm == null) { vals.remove(i); vals.remove(i); } } } vals.add(cls); vals.add(nm); } } String[] res = null; if (vals != null) { if (vals.isEmpty()) { throw new IllegalArgumentException( "target name is missing elements"); } res = (String[]) vals.toArray(new String[vals.size()]); } if (peer) { this.peer = res; } else { this.me = res; } } /** * Returns an array of alternating class and principal names for the * specified set of principals, and appends all of those strings to * the specified buffer, separated by spaces, with the principal * names in quotes. */ private static String[] cons(Set s, StringBuffer b) { String[] vals = new String[s.size() * 2]; int i = 0; for (Iterator iter = s.iterator(); iter.hasNext(); ) { Principal p; try { p = (Principal) iter.next(); } catch (ClassCastException e) { throw new IllegalArgumentException( "sets must contain Principals"); } String v = p.getClass().getName(); if (i > 0) { b.append(' '); } b.append(v); vals[i++] = v; v = p.getName(); b.append(" \""); b.append(v); b.append('"'); vals[i++] = v; } return vals; } /** * Constructs the target name and transient field data for the * specified principal sets. */ private static Data parseName(Set me, Set peer) { if (me == null) { throw new NullPointerException( "local principals must be non-empty"); } else if (me.isEmpty()) { throw new IllegalArgumentException( "local principals must be non-empty"); } Data data = new Data(); StringBuffer b = new StringBuffer(); data.me = cons(me, b); if (peer != null && !peer.isEmpty()) { b.append(" peer "); data.peer = cons(peer, b); } data.name = b.toString(); return data; } /** * Returns <code>true</code> if the specified permission is an instance * of <code>AuthenticationPermission</code>, and every action included in * the specified permission is included as an action of this permission, * and every principal that matches the local principals of the specified * permission also matches the local principals of this permission, and * (if the specified permission has any action besides * <code>listen</code>) every principal that matches the peer principals * of this permission also matches the peer principals of the specified * permission; returns <code>false</code> otherwise. * * @param perm the permission to check * @return <code>true</code> if the specified permission is an instance * of <code>AuthenticationPermission</code>, and every action included in * the specified permission is included as an action of this permission, * and every principal that matches the local principals of the specified * permission also matches the local principals of this permission, and * (if the specified permission has any action besides * <code>listen</code>) every principal that matches the peer principals * of this permission also matches the peer principals of the specified * permission; <code>false</code> otherwise */ public boolean implies(Permission perm) { if (!(perm instanceof AuthenticationPermission)) { return false; } AuthenticationPermission ap = (AuthenticationPermission) perm; return (mask & ap.mask) == ap.mask && implies0(ap); } private boolean implies0(AuthenticationPermission ap) { return ((me == null || (ap.me != null && covers(me, ap.me))) && (ap.mask == LISTEN || peer == null || (ap.peer != null && covers(ap.peer, peer)))); } /** * Returns true if every principal that matches sub also matches sup. */ private static boolean covers(String[] sup, String[] sub) { outer: for (int i = sub.length; i > 0; ) { String onm = sub[--i]; String ocls = sub[--i]; for (int j = sup.length; j > 0; ) { String nm = sup[--j]; String cls = sup[--j]; if (cls.equals(ocls) && (nm == null || (onm != null && nm.equals(onm)))) { continue outer; } } return false; } return true; } /** * Returns the actions. */ public String getActions() { return actions; } /** * Returns an empty <code>PermissionCollection</code> for storing * <code>AuthenticationPermission</code> instances. * * @return an empty <code>PermissionCollection</code> for storing * <code>AuthenticationPermission</code> instances */ public PermissionCollection newPermissionCollection() { return new AuthenticationPermissionCollection(); } /** * Two instances of this class are equal if each implies the other; * that is, both instances have the same actions, every principal that * matches the local principals of one instance matches the local * principals of the other instance, and (if the instances have any * action besides <code>listen</code>) every principal that matches the * peer principals of one instance matches the peer principals of the * other instance. */ public boolean equals(Object obj) { if (!(obj instanceof AuthenticationPermission)) { return false; } AuthenticationPermission ap = (AuthenticationPermission) obj; return (mask == ap.mask && same(me, ap.me) && (mask == LISTEN || same(peer, ap.peer))); } /** * Returns true if both arrays are null, or both arrays are the same * length and contain the same pairs (ignoring order). */ private static boolean same(String[] s1, String[] s2) { if (s1 == null) { return s2 == null; } else if (s2 == null || s1.length != s2.length) { return false; } outer: for (int i = s2.length; i > 0; ) { String onm = s2[--i]; String ocls = s2[--i]; for (int j = s1.length; j > 0; ) { String nm = s1[--j]; String cls = s1[--j]; if (cls.equals(ocls) && (nm == null ? onm == null : nm.equals(onm))) { continue outer; } } return false; } return true; } /** * Returns a hash code value for this object. */ public int hashCode() { int h = mask; if (me != null) { for (int i = me.length; --i >= 0; ) { if (me[i] != null) { h += me[i].hashCode(); } } } if (mask != LISTEN && peer != null) { for (int i = peer.length; --i >= 0; ) { h += peer[i].hashCode(); } } return h; } /** * Verifies the syntax of the target name and recreates any transient * state. * * @throws java.io.InvalidObjectException if the target name or actions * string is <code>null</code>, or if the target name or actions string * does not match the syntax specified in the comments at the beginning * of this class */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); try { init(); } catch (RuntimeException e) { if (e instanceof NullPointerException || e instanceof IllegalArgumentException) { InvalidObjectException ee = new InvalidObjectException(e.getMessage()); ee.initCause(e); throw ee; } throw e; } } /** * @serial include */ static class AuthenticationPermissionCollection extends PermissionCollection { private static final long serialVersionUID = -2967578431368213049L; /** * @serialField permissions List The permissions. */ private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("permissions", List.class, true) }; private List permissions = new ArrayList(); AuthenticationPermissionCollection() { } public synchronized void add(Permission perm) { if (!(perm instanceof AuthenticationPermission)) { throw new IllegalArgumentException( "element must be an AuthenticationPermission"); } else if (isReadOnly()) { throw new SecurityException("collection is read-only"); } permissions.add(perm); } public synchronized boolean implies(Permission perm) { if (!(perm instanceof AuthenticationPermission)) { return false; } AuthenticationPermission ap = (AuthenticationPermission) perm; int needed = ap.mask; for (int i = permissions.size(); --i >= 0; ) { AuthenticationPermission cp = (AuthenticationPermission) permissions.get(i); if ((needed & cp.mask) != 0 && cp.implies0(ap)) { needed &= ~cp.mask; if (needed == 0) { return true; } } } return false; } public synchronized Enumeration elements() { return Collections.enumeration(permissions); } public synchronized void setReadOnly() { super.setReadOnly(); } public synchronized boolean isReadOnly() { return super.isReadOnly(); } /** * Verifies the permissions list. * * @throws java.io.InvalidObjectException if the list is * <code>null</code> or any element is not an instance of * <code>AuthenticationPermission</code> */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); if (permissions == null) { throw new InvalidObjectException("list cannot be null"); } if (!permissions.getClass().equals(ArrayList.class)) { permissions = new ArrayList(permissions); } for (int i = permissions.size(); --i >= 0; ) { if (!(permissions.get(i) instanceof AuthenticationPermission)) { throw new InvalidObjectException( "element must be an AuthenticationPermission"); } } } /** * Writes the state to the stream. */ private synchronized void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); } } }