/* * 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 com.sun.jini.tool; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URL; import java.security.AccessController; import java.security.CodeSource; import java.security.Permission; import java.security.Policy; import java.security.Principal; import java.security.PrivilegedAction; import java.security.ProtectionDomain; import java.security.Security; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import net.jini.security.policy.DynamicPolicy; import net.jini.security.policy.DynamicPolicyProvider; import net.jini.security.policy.PolicyInitializationException; /** * Defines a {@link DynamicPolicy} that logs information about missing * permissions, and optionally grants all permissions, which is <b>FOR * DEBUGGING ONLY</b>. Do not use this security policy provider to grant * all permissions in a production environment. <p> * * This class is intended to simplify the process of deciding what security * permissions to grant to run an application. While it is generally * acceptable to grant all permissions to local, trusted code, downloaded * code should typically be granted the least permission possible. <p> * * The usual approach to choosing which permissions to grant is to start by * running the application with a security policy file that grants all * permissions to local, trusted code. When the application fails with an * exception message that identifies a missing permission, add that * permission to the security policy file, and repeat the process. Although * straight forward, this process can be time consuming if the application * requires many permission grants. <p> * * Another approach is to set the value of the * <code>"java.security.debug"</code> system property to * <code>"access,failure"</code>, which produces debugging output that * describes permission grants and failures. Unfortunately, this approach * produces voluminous output, making it difficult to determine which * permission grants are needed. <p> * * This security policy provider permits another, hopefully more * convenient, approach. When this class is specified as the security * policy provider, and granting all permissions is enabled, it uses the * standard dynamic security policy to determine what permissions are * granted. If a permission is not granted by the standard policy, though, * then rather than denying permission, this class logs the missing * permission in the form required by the security policy file, and grants * the permission, allowing the program to continue. In this way, * developers can determine the complete set of security permissions * required by the application. <p> * * Note that the information printed by this security policy provider may * not be in the form you wish to use in your policy file. In particular, * using system property substitutions and <code>KeyStore</code> aliases * may produce a more portable file than one containing the exact entries * logged. Note, too, that the information printed for * <code>signedBy</code> fields specifies the principal name for * <code>X.509</code> certificates, rather than the <code>KeyStore</code> * alias, which is not a valid security policy file format. <p> * * Using this security policy provider without granting all permissions is * also useful since it prints information about security exceptions that * were caught, but that might have an affect on program behavior. <p> * * This class uses uses the {@link Logger} named * <code>net.jini.security.policy</code> to log information at the following * levels: <ul> * * <li> {@link Level#WARNING WARNING} - Permissions that were needed but not * granted by the policy file. * * <li> {@link Level#FINE FINE} - Also include stack traces. * * <li> {@link Level#FINER FINER} - All permissions granted, with stack traces * for ones not granted by the policy file, and dynamic grants. * * <li> {@link Level#FINEST FINEST} - All permissions granted, with all stack * traces, and dynamic grants. </ul> * * To use this security policy provider, do the following: <ul> * * <li> Copy the <code>jsk-policy.jar</code> file from the <code>lib-ext</code> * subdirectory of the Apache River release * installation to the extensions directory of the Java(TM) 2 SDK (or JRE) * installation, and copy the <code>jsk-debug-policy.jar</code> file * from the <code>lib</code> subdirectory of the Apache River release installation to * the extensions directory of the Java 2 SDK (or JRE) installation. * * <li> Specify this class as the security policy provider. Create a copy of * the file <code>jre/lib/security/security/java.security</code>, modify the * file to contain the line: * * <blockquote> * <pre> * policy.provider=com.sun.jini.tool.DebugDynamicPolicyProvider * </pre> * </blockquote> * * and then specify this new file as the value of the * <code>java.security.properties</code> system property. * * <li> Specify whether all permissions should be granted by setting the * <code>com.sun.jini.tool.DebugDynamicPolicyProvider.grantAll</code> security * property to <code>true</code> by adding the following line to the security * properties file: * * <blockquote> * <pre> * com.sun.jini.tool.DebugDynamicPolicyProvider.grantAll=true * </pre> * </blockquote> </ul> <p> * * Granting all permissions is disabled by default. <p> * * Make sure to specify a security manager, either by setting the * <code>java.security.manager</code> system property, or putting the following * code in the main method of the application: * * <blockquote> * <pre> * if (System.getSecurityManager() == null) { * System.setSecurityManager(new SecurityManager()); * } * </pre> * </blockquote> * * <p>This provider can be used in conjunction with the provider * <code>com.sun.jini.start.AggregatePolicyProvider</code> by setting the * <code>com.sun.jini.start.AggregatePolicyProvider.mainPolicyClass</code> * system property to the fully qualified name of this class. If this * provider is used with the <code>AggregatePolicyProvider</code>, then the * JAR file <code>jsk-debug-policy.jar</code> needs to be in the * application's class path, and this class needs to be granted all * permissions. * * * @author Sun Microsystems, Inc. **/ public class DebugDynamicPolicyProvider extends DynamicPolicyProvider { /* Logger to use */ private static final Logger logger = Logger.getLogger("net.jini.security.policy"); /* If true, always grant permission */ private static boolean grantAll = ((Boolean) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return Boolean.valueOf( Security.getProperty( "com.sun.jini.tool." + "DebugDynamicPolicyProvider.grantAll")); } })).booleanValue(); /* Cache of permission requests already made */ private static final Set requests = new HashSet(); /** The empty codesource. */ private static final CodeSource emptyCS = new CodeSource(null, (Certificate[]) null); /** * Creates an instance of this class that wraps a default underlying * policy, as specified by {@link * DynamicPolicyProvider#DynamicPolicyProvider() DynamicPolicyProvider()}. * * @throws PolicyInitializationException if unable to construct the base * policy * @throws SecurityException if there is a security manager and the calling * context does not have adequate permissions to read the <code> * net.jini.security.policy.DynamicPolicyProvider.basePolicyClass * </code> security property, or if the calling context does not * have adequate permissions to access the base policy class */ public DebugDynamicPolicyProvider() throws PolicyInitializationException { } /** * Creates an instance of this class that wraps around the given * non-<code>null</code> base policy object. * * @param basePolicy base policy object containing information about * non-dynamic grants * @throws NullPointerException if <code>basePolicy</code> is * <code>null</code> */ public DebugDynamicPolicyProvider(Policy basePolicy) { super(basePolicy); } /** Log calls. */ public void grant(Class cl, Principal[] principals, Permission[] permissions) { try { super.grant(cl, principals, permissions); if (permissions == null || permissions.length == 0 || !logger.isLoggable(Level.FINER)) { return; } Request req = new Request(cl, principals, permissions); if (cl == null) { logger.log(Level.FINER, "Granting permissions for all classes:\n{0}", req.toString()); } else { logger.log(Level.FINER, "Granting permissions for {0}:\n{1}", new Object[] { cl, req.toString() }); } } catch (SecurityException e) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Granting permissions failed", e); } throw e; } } /** Always returns true, but logs unique requests */ public boolean implies(ProtectionDomain pd, Permission perm) { boolean implies = super.implies(pd, perm); boolean result = implies ? true : grantAll; if (!(logger.isLoggable(Level.FINER) || (!implies && logger.isLoggable(Level.WARNING)))) { return result; } Request request = new Request(pd, perm); synchronized (requests) { if (requests.contains(request)) { return result; } requests.add(request); } String stackTrace = null; if (logger.isLoggable(Level.FINEST) || (!implies && logger.isLoggable(Level.FINE))) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); new Exception("Stack trace:").printStackTrace(pw); pw.close(); stackTrace = sw.toString(); } if (implies) { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "Permission granted:\n{0}\n{1}", new Object[] { request.toString(), stackTrace }); } else { logger.log(Level.FINER, "Permission granted:\n{0}", request.toString()); } } else if (logger.isLoggable(Level.FINE)) { logger.log(Level.WARNING, (grantAll ? "Permission not granted by base policy:\n{0}\n{1}" : "Permission not granted:\n{0}\n{1}"), new Object[] { request.toString(), stackTrace }); } else { logger.log(Level.WARNING, (grantAll ? "Permission not granted by base policy:\n{0}" : "Permission not granted:\n{0}"), request.toString()); } return result; } /** Returns the name of the certificate. */ private static String getCertName(Certificate cert) { if (cert instanceof X509Certificate) { return ((X509Certificate) cert).getSubjectDN().getName(); } else { return cert.toString(); } } /** * Returns a quoted version of the argument, such that it would result in * the argument if read from a file with the standard String syntax. */ private static String quoteString(String s) { if (s == null) { return ""; } int len = s.length(); StringBuffer buf = new StringBuffer(len + 2); buf.append('"'); for (int off = 0; off < len; ) { int quote = s.indexOf('"', off); int slash = s.indexOf('\\', off); if (quote >= 0 && (slash < 0 || slash > quote)) { buf.append(s.substring(off, quote)); buf.append("\\\""); off = quote + 1; } else if (slash >= 0) { buf.append(s.substring(off, slash)); buf.append("\\\\"); off = slash + 1; } else { buf.append(s.substring(off)); break; } } buf.append('"'); return buf.toString(); } /* Keeps track of an individual permission request */ private static class Request { final CodeSource codeSource; final Certificate[] certs; final Principal[] principals; final Permission[] perms; Request(ProtectionDomain pd, Permission perm) { codeSource = pd.getCodeSource(); certs = codeSource == null ? null : codeSource.getCertificates(); principals = pd.getPrincipals(); this.perms = new Permission[] { perm }; } Request(final Class cl, Principal[] principals, Permission[] perms) { codeSource = (cl == null) ? emptyCS : (CodeSource) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return cl.getProtectionDomain().getCodeSource(); } }); certs = null; this.principals = principals; this.perms = perms; } public boolean equals(Object o) { if (o instanceof Request) { Request other = (Request) o; return (codeSource == null ? other.codeSource == null : equals(codeSource.getLocation(), other.codeSource.getLocation())) && equals(principals, other.principals) && equals(perms, other.perms); } else { return false; } } private static boolean equals(Object x, Object y) { return (x == null) ? y == null : x.equals(y); } private static boolean equals(Object[] x, Object[] y) { if (x == null) { return y == null; } else if (y == null) { return false; } else { return Arrays.equals(x, y); } } public int hashCode() { return hash(codeSource == null ? null : codeSource.getLocation()) ^ hash(certs) ^ hash(principals) ^ hash(perms); } private static int hash(Object obj) { return (obj == null) ? 0 : obj.hashCode(); } private static int hash(Object[] array) { int result = 0; if (array != null) { for (int i = array.length; --i >= 0; ) { result ^= hash(array[i]); } } return result; } public String toString() { StringBuffer buf = new StringBuffer(); buf.append("grant\n"); if (codeSource == null) { buf.append(" /* bootstrap codebase */\n"); } else { URL location = codeSource.getLocation(); if (location != null) { buf.append(" codeBase "); buf.append(quoteString(location.toString())); buf.append('\n'); } if (certs != null) { for (int i = 0; i < certs.length; i++) { buf.append(" signedby "); buf.append(quoteString(getCertName(certs[i]))); buf.append('\n'); } } } if (principals != null) { for (int i = 0; i < principals.length; i++) { buf.append(" principal "); buf.append(principals[i].getClass().getName()); buf.append(' '); buf.append(quoteString(principals[i].getName())); buf.append('\n'); } } buf.append("{\n"); for (int i = 0; i < perms.length; i++) { Permission perm = perms[i]; buf.append(" permission "); buf.append(perm.getClass().getName()); buf.append('\n'); buf.append(" "); buf.append(quoteString(perm.getName())); String actions = perm.getActions(); if (actions != null && actions.length() != 0) { buf.append(",\n"); buf.append(" "); buf.append(quoteString(perm.getActions())); } buf.append(";\n"); } buf.append("};"); return buf.toString(); } } }