/*
* @(#)Security.java 1.110 06/10/11
*
* Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*
*/
package java.security;
import java.lang.reflect.*;
import java.util.*;
import java.io.*;
import java.net.URL;
import java.security.InvalidParameterException;
import sun.security.util.Debug;
import sun.security.util.PropertyExpander;
/**
* <p>This class centralizes all security properties and common security
* methods. One of its primary uses is to manage providers.
*
* @author Benjamin Renaud
* @version 1.101 10/17/00
*/
public final class Security {
// Do providers need to be reloaded?
private static boolean reloadProviders = true;
/* Are we debugging? -- for developers */
static final boolean debug = false;
private static final Debug sdebug =
Debug.getInstance("properties");
/* Are we displaying errors? -- for users */
static final boolean error = true;
/* The java.security properties */
private static Properties props;
/* A vector of providers, in order of priority */
private static Vector providers;
// Where we cache provider properties
private static Hashtable providerPropertiesCache;
// Where we cache engine provider properties
private static Hashtable engineCache;
// Where we cache search results
private static Hashtable searchResultsCache;
// providers currently attempting to be loaded
private static Hashtable providerLoads;
// An element in the cache
private static class ProviderProperty {
String className;
Provider provider;
}
// Number of statically registered security providers. No duplicates.
private static int numOfStaticProviders = 0;
/* A vector of statically registered providers' master class names,
* in order of priority. No duplicates.
*/
private static Vector providerMasterClassNames = new Vector(6);
// Index for the vector providerMasterClassNames.
// It points to the next provider which we should try to load.
private static int indexStaticProviders = 0;
// Does the indexStaticProviders need to be reset?
private static boolean resetProviderIndex = false;
static {
// doPrivileged here because there are multiple
// things in initialize that might require privs.
// (the FileInputStream call and the File.exists call,
// the securityPropFile call, etc)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
initialize();
return null;
}
});
}
private static void initialize() {
props = new Properties();
providers = new Vector();
providerPropertiesCache = new Hashtable();
engineCache = new Hashtable();
searchResultsCache = new Hashtable(5);
providerLoads = new Hashtable(1);
boolean loadedProps = false;
boolean overrideAll = false;
// first load the system properties file
// to determine the value of security.overridePropertiesFile
File propFile = securityPropFile("java.security");
if (propFile.exists()) {
try {
FileInputStream is = new FileInputStream(propFile);
// Inputstream has been buffered in Properties class
props.load(is);
is.close();
loadedProps = true;
if (sdebug != null) {
sdebug.println("reading security properties file: " +
propFile);
}
} catch (IOException e) {
if (sdebug != null) {
sdebug.println("unable to load security properties from " +
propFile);
e.printStackTrace();
}
}
}
if ("true".equalsIgnoreCase(props.getProperty
("security.overridePropertiesFile"))) {
String extraPropFile = System.getProperty
("java.security.properties");
if (extraPropFile != null && extraPropFile.startsWith("=")) {
overrideAll = true;
extraPropFile = extraPropFile.substring(1);
}
if (overrideAll) {
props = new Properties();
if (sdebug != null) {
sdebug.println
("overriding other security properties files!");
}
}
// now load the user-specified file so its values
// will win if they conflict with the earlier values
if (extraPropFile != null) {
try {
URL propURL;
extraPropFile = PropertyExpander.expand(extraPropFile);
propFile = new File(extraPropFile);
if (propFile.exists()) {
propURL = new URL
("file:" + propFile.getCanonicalPath());
} else {
propURL = new URL(extraPropFile);
}
BufferedInputStream bis = new BufferedInputStream
(propURL.openStream());
props.load(bis);
bis.close();
loadedProps = true;
if (sdebug != null) {
sdebug.println("reading security properties file: " +
propURL);
if (overrideAll) {
sdebug.println
("overriding other security properties files!");
}
}
} catch (Exception e) {
if (sdebug != null) {
sdebug.println
("unable to load security properties from " +
extraPropFile);
e.printStackTrace();
}
}
}
}
if (!loadedProps) {
initializeStatic();
if (sdebug != null) {
sdebug.println("unable to load security properties " +
"-- using defaults");
}
}
// Not loading providers here. Just counts how many providers
// are statically registered. This reduces the startup
// footprint.
countProviders();
}
/*
* Initialize to default values, if <java.home>/lib/java.security
* is not found.
*/
private static void initializeStatic() {
props.put("security.provider.1", "sun.security.provider.Sun");
}
/**
* Don't let anyone instantiate this.
*/
private Security() {
}
/**
* Loops through provider declarations, which are expected to be
* of the form:
*
* security.provider.1=sun.security.provider.Sun
* security.provider.2=sun.security.jsafe.Jsafe
* etc.
*
* The order determines the default search order when looking for
* an algorithm.
*/
private static synchronized void countProviders() {
int i = 1;
while (true) {
String name = props.getProperty("security.provider." + i);
if (name == null) {
break;
} else {
String fullClassName = name.trim();
if (fullClassName.length() == 0) {
System.err.println("invalid entry for " +
"security.provider." + i);
break;
} else {
// Get rid of duplicate providers.
if (!providerMasterClassNames.contains(fullClassName)) {
providerMasterClassNames.add(fullClassName);
}
i++;
}
}
}
// Get the number of statically registered providers.
numOfStaticProviders = providerMasterClassNames.size();
}
/*
* Reload the providers (provided as extensions) that could not be loaded
* (because there was no system class loader available) when this class
* was initialized.
*/
private static synchronized void reloadProviders() {
if (reloadProviders) {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
synchronized (Security.class) {
reloadProviders = false;
// We don't want loadOneMoreProvider() to do
// anything from now on since this method will
// load all static providers.
indexStaticProviders = numOfStaticProviders;
resetProviderIndex = false;
providers.removeAllElements();
// i is an index for the vector
// providerMasterClassNames. So it starts from 0.
int i = 0;
while (i < numOfStaticProviders) {
final String name =
(String)providerMasterClassNames.elementAt(i);
i++;
Provider prov =
(Provider)AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Provider.loadProvider(name);
}
});
if (prov != null) {
providers.addElement(prov);
}
}
// empty provider-property cache
providerPropertiesCache.clear();
engineCache.clear();
searchResultsCache.clear();
}
}
}
}
/**
* Try our best to load one more statically registered provider.
* This is used by getEngineClassName(String algName, String engineType).
*/
private static synchronized void loadOneMoreProvider() {
// suspend provider reloading inside this method
boolean restore = false;
if (reloadProviders) {
restore = true;
reloadProviders = false;
}
try {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
/*
* Even if the launcher l is null, we still want to
* load providers if we can. See bug 4418903.
* When we first see that the launcher isn't null, we
* could be in one of the following situations:
* a) some providers were loaded out of the priority order.
* For example, 6 providers are statically configured, and
* provider 2 and 4 are loaded. The field resetProviderIndex
* should be "true". So we can try to load providers
* according to the priority order when the launcher isn't null.
* b) some providers were loaded, but not out of order.
* For example, 6 providers are statically configured, and
* provider 1 and 2 are loaded. The field resetProviderIndex
* should be "false". So we just try to load the next
* provider whose index is indexStaticProviders.
* c) no providers were loaded. The field resetProviderIndex
* should be "false". So we just try to load the first
* provider. Note: indexStaticProviders is 0 in this case.
*/
if (indexStaticProviders >= numOfStaticProviders) {
return;
}
Provider prov = null;
while (indexStaticProviders < numOfStaticProviders) {
final String name = (String)providerMasterClassNames.elementAt(
indexStaticProviders);
// determine if the loadProvider call below is looping.
// this may occur if the provider to be loaded is signed.
// if looping, continue
if (providerLoads.get(name) != null) {
indexStaticProviders++;
continue;
} else {
providerLoads.put(name, name);
}
prov = (Provider)AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Provider.loadProvider(name);
}
});
// indexStaticProviders points to the next provider we
// should try to load.
indexStaticProviders++;
providerLoads.remove(name);
if (prov != null) {
/* This must manipulate the datastructure
directly, because going through addProviders
causes a security check to happen, which
sometimes will cause the security
initialization to fail with bad
consequences. */
providers.addElement(prov);
// empty provider-property cache
providerPropertiesCache.clear();
engineCache.clear();
searchResultsCache.clear();
break;
} else {
if (l == null) {
// Set resetProviderIndex to true since we may load
// providers out of the priority order.
resetProviderIndex = true;
}
}
}
} finally {
// resume provider reloading if necessary
if (restore) {
reloadProviders = true;
}
}
}
private static File securityPropFile(String filename) {
// maybe check for a system property which will specify where to
// look. Someday.
String sep = File.separator;
return new File(System.getProperty("java.home") + sep + "lib" + sep +
"security" + sep + filename);
}
/**
* Looks up providers, and returns the property (and its associated
* provider) mapping the key, if any.
* The order in which the providers are looked up is the
* provider-preference order, as specificed in the security
* properties file.
*/
private static ProviderProperty getProviderProperty(String key) {
ProviderProperty entry
= (ProviderProperty)providerPropertiesCache.get(key);
if (entry != null) {
return entry;
}
for (int i = 0; i < providers.size(); i++) {
String matchKey = null;
Provider prov = (Provider)providers.elementAt(i);
String prop = prov.getProperty(key);
if (prop == null) {
// Is there a match if we do a case-insensitive property name
// comparison? Let's try ...
for (Enumeration enum_ = prov.keys();
enum_.hasMoreElements() && prop==null; ) {
matchKey = (String)enum_.nextElement();
if (key.equalsIgnoreCase(matchKey)) {
prop = prov.getProperty(matchKey);
break;
}
}
}
if (prop != null) {
ProviderProperty newEntry = new ProviderProperty();
newEntry.className = prop;
newEntry.provider = prov;
providerPropertiesCache.put(key, newEntry);
if (matchKey != null) {
// Store the property value in the cache under the exact
// property name, as specified by the provider
providerPropertiesCache.put(matchKey, newEntry);
}
return newEntry;
}
}
return entry;
}
/**
* Returns the property (if any) mapping the key for the given provider.
*/
private static String getProviderProperty(String key, Provider provider) {
String prop = provider.getProperty(key);
if (prop == null) {
// Is there a match if we do a case-insensitive property name
// comparison? Let's try ...
for (Enumeration enum_ = provider.keys();
enum_.hasMoreElements() && prop==null; ) {
String matchKey = (String)enum_.nextElement();
if (key.equalsIgnoreCase(matchKey)) {
prop = provider.getProperty(matchKey);
break;
}
}
}
return prop;
}
/**
* We always map names to standard names
*/
private static String getStandardName(String alias, String engineType,
Provider prov) {
return getProviderProperty("Alg.Alias." + engineType + "." + alias,
prov);
}
/**
* Gets a specified property for an algorithm. The algorithm name
* should be a standard name. See Appendix A in the <a href=
* "../../../guide/security/CryptoSpec.html#AppA">
* Java Cryptography Architecture API Specification & Reference </a>
* for information about standard algorithm names.
* One possible use is by specialized algorithm parsers, which may map
* classes to algorithms which they understand (much like Key parsers
* do).
*
* param algName the algorithm name.
*
* param propName the name of the property to get.
*
* return the value of the specified property.
*
* deprecated This method used to return the value of a proprietary
* property in the master file of the "SUN" Cryptographic Service
* Provider in order to determine how to parse algorithm-specific
* parameters. Use the new provider-based and algorithm-independent
* <code>AlgorithmParameters</code> and <code>KeyFactory</code> engine
* classes (introduced in the Java 2 platform) instead.
*
public static String getAlgorithmProperty(String algName,
String propName) {
reloadProviders();
ProviderProperty entry = getProviderProperty("Alg." + propName
+ "." + algName);
if (entry != null) {
return entry.className;
} else {
return null;
}
}
*/
/*
* Lookup the algorithm in our list of providers. Process
* each provider in priority order one at a time looking for
* either the direct engine property or a matching alias.
*/
private static ProviderProperty getEngineClassName(String algName,
String engineType)
throws NoSuchAlgorithmException
{
ProviderProperty pp;
String key = engineType;
if (algName != null)
key += "." + algName;
pp = (ProviderProperty)engineCache.get(key);
if (pp != null)
return pp;
synchronized (Security.class) {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
/*
* In case some providers have been loaded out of the
* priority order when the launcher l is null, we should
* clear the vector "providers" and reset the indexStaticProviders
* to zero when the launcher l isn't null.
*
* We should only do the above if the "reloadProviders" is true
* which means that the method reloadProviders() hasn't
* load all statically registered providers yet.
* Once the reloadProviders() method has loaded all statically
* registered providers, we shouldn't clear the vector
* "providers" in this getEngineClassName() method.
*/
if ((reloadProviders == true) &&
(l != null) && (resetProviderIndex == true)) {
resetProviderIndex = false;
indexStaticProviders = 0;
providers.removeAllElements();
providerPropertiesCache.clear();
engineCache.clear();
searchResultsCache.clear();
providerLoads.clear();
}
// We should call loadOneMoreProvider() if no provider
// has been loaded yet. Otherwise, we may not be able to
// get in the following "for" loop.
if (providers.size() == 0) {
loadOneMoreProvider();
}
for (int i = 0; i < providers.size(); i++) {
Provider prov = (Provider)providers.elementAt(i);
try {
pp = getEngineClassName(algName, prov,
engineType);
} catch (NoSuchAlgorithmException e) {
if (i == providers.size() - 1) {
// The requested algorithm may be available in
// a registered provider which hasn't been loaded
// yet. Let's try to load one more registered
// provider. The method loadOneMoreProvider()
// won't do anything if we have tried to load all
// registered providers.
loadOneMoreProvider();
}
continue;
}
/* Cache it */
engineCache.put(key, pp);
return pp;
}
}
throw new NoSuchAlgorithmException(algName.toUpperCase() + " " +
engineType + " not available");
}
private static ProviderProperty getEngineClassName(String algName,
String provider,
String engineType)
throws NoSuchAlgorithmException, NoSuchProviderException
{
if (provider == null) {
return getEngineClassName(algName, engineType);
}
// check if the provider is installed
Provider prov = getProvider(provider);
if (prov == null) {
throw new NoSuchProviderException("no such provider: " +
provider);
}
return getEngineClassName(algName, prov, engineType);
}
/**
* The parameter provider cannot be null.
*/
private static ProviderProperty getEngineClassName(String algName,
Provider provider,
String engineType)
throws NoSuchAlgorithmException
{
String key;
if (engineType.equalsIgnoreCase("SecureRandom") && algName == null)
key = engineType;
else
key = engineType + "." + algName;
String className = getProviderProperty(key, provider);
if (className == null) {
if (engineType.equalsIgnoreCase("SecureRandom") &&
algName == null)
throw new NoSuchAlgorithmException
("SecureRandom not available for provider " +
provider.getName());
else {
// try algName as alias name
String stdName = getStandardName(algName, engineType, provider);
if (stdName != null) key = engineType + "." + stdName;
if ((stdName == null)
|| (className = getProviderProperty(key, provider)) == null)
throw new NoSuchAlgorithmException("no such algorithm: " +
algName
+ " for provider " +
provider.getName());
}
}
ProviderProperty entry = new ProviderProperty();
entry.className = className;
entry.provider = provider;
return entry;
}
/**
* Adds a new provider, at a specified position. The position is
* the preference order in which providers are searched for
* requested algorithms. Note that it is not guaranteed that this
* preference will be respected. The position is 1-based, that is,
* 1 is most preferred, followed by 2, and so on.
*
* <p>If the given provider is installed at the requested position,
* the provider that used to be at that position, and all providers
* with a position greater than <code>position</code>, are shifted up
* one position (towards the end of the list of installed providers).
*
* <p>A provider cannot be added if it is already installed.
*
* <p>First, if there is a security manager, its
* <code>checkSecurityAccess</code>
* method is called with the string
* <code>"insertProvider."+provider.getName()</code>
* to see if it's ok to add a new provider.
* If the default implementation of <code>checkSecurityAccess</code>
* is used (i.e., that method is not overriden), then this will result in
* a call to the security manager's <code>checkPermission</code> method
* with a
* <code>SecurityPermission("insertProvider."+provider.getName())</code>
* permission.
*
* @param provider the provider to be added.
*
* @param position the preference position that the caller would
* like for this provider.
*
* @return the actual preference position in which the provider was
* added, or -1 if the provider was not added because it is
* already installed.
*
* @throws SecurityException
* if a security manager exists and its <code>{@link
* java.lang.SecurityManager#checkSecurityAccess}</code> method
* denies access to add a new provider
*
* @see #getProvider
* @see #removeProvider
* @see java.security.SecurityPermission
*/
public static synchronized int insertProviderAt(Provider provider,
int position) {
reloadProviders();
check("insertProvider."+provider.getName());
/* First check if the provider is already installed */
Provider already = getProvider(provider.getName());
if (already != null) {
return -1;
}
int size = providers.size();
if (position > size || position <= 0) {
position = size+1;
}
providers.insertElementAt(provider, position-1);
// empty provider-property cache
providerPropertiesCache.clear();
engineCache.clear();
searchResultsCache.clear();
return position;
}
/**
* Adds a provider to the next position available.
*
* <p>First, if there is a security manager, its
* <code>checkSecurityAccess</code>
* method is called with the string
* <code>"insertProvider."+provider.getName()</code>
* to see if it's ok to add a new provider.
* If the default implementation of <code>checkSecurityAccess</code>
* is used (i.e., that method is not overriden), then this will result in
* a call to the security manager's <code>checkPermission</code> method
* with a
* <code>SecurityPermission("insertProvider."+provider.getName())</code>
* permission.
*
* @param provider the provider to be added.
*
* @return the preference position in which the provider was
* added, or -1 if the provider was not added because it is
* already installed.
*
* @throws SecurityException
* if a security manager exists and its <code>{@link
* java.lang.SecurityManager#checkSecurityAccess}</code> method
* denies access to add a new provider
*
* @see #getProvider
* @see #removeProvider
* @see java.security.SecurityPermission
*/
public static int addProvider(Provider provider) {
/*
* We can't assign a position here because the statically
* registered providers may not have been installed yet.
* insertProviderAt() will fix that value after it has
* loaded the static providers.
*/
return insertProviderAt(provider, 0);
}
/**
* Removes the provider with the specified name.
*
* <p>When the specified provider is removed, all providers located
* at a position greater than where the specified provider was are shifted
* down one position (towards the head of the list of installed
* providers).
*
* <p>This method returns silently if the provider is not installed.
*
* <p>First, if there is a security manager, its
* <code>checkSecurityAccess</code>
* method is called with the string <code>"removeProvider."+name</code>
* to see if it's ok to remove the provider.
* If the default implementation of <code>checkSecurityAccess</code>
* is used (i.e., that method is not overriden), then this will result in
* a call to the security manager's <code>checkPermission</code> method
* with a <code>SecurityPermission("removeProvider."+name)</code>
* permission.
*
* @param name the name of the provider to remove.
*
* @throws SecurityException
* if a security manager exists and its <code>{@link
* java.lang.SecurityManager#checkSecurityAccess}</code> method
* denies
* access to remove the provider
*
* @see #getProvider
* @see #addProvider
*/
public static synchronized void removeProvider(String name) {
reloadProviders();
check("removeProvider."+name);
Provider provider = getProvider(name);
if (provider != null) {
for (Iterator i=providers.iterator(); i.hasNext(); )
if (i.next()==provider)
i.remove();
// empty provider-property cache
providerPropertiesCache.clear();
engineCache.clear();
searchResultsCache.clear();
}
}
/**
* Returns an array containing all the installed providers. The order of
* the providers in the array is their preference order.
*
* @return an array of all the installed providers.
*/
public static synchronized Provider[] getProviders() {
reloadProviders();
Provider[] result = new Provider[providers.size()];
providers.copyInto(result);
return result;
}
/**
* Returns the provider installed with the specified name, if
* any. Returns null if no provider with the specified name is
* installed.
*
* @param name the name of the provider to get.
*
* @return the provider of the specified name.
*
* @see #removeProvider
* @see #addProvider
*/
public static synchronized Provider getProvider(String name) {
reloadProviders();
Enumeration enum_ = providers.elements();
while (enum_.hasMoreElements()) {
Provider prov = (Provider)enum_.nextElement();
if (prov.getName().equals(name)) {
return prov;
}
}
return null;
}
/**
* Returns an array containing all installed providers that satisfy the
* specified selection criterion, or null if no such providers have been
* installed. The returned providers are ordered
* according to their <a href=
* "#insertProviderAt(java.security.Provider, int)">preference order</a>.
*
* <p> A cryptographic service is always associated with a particular
* algorithm or type. For example, a digital signature service is
* always associated with a particular algorithm (e.g., DSA),
* and a CertificateFactory service is always associated with
* a particular certificate type (e.g., X.509).
* NOTE: <B>java.security.cert.CertificateFactory</B> is found in J2ME CDC
* profiles such as J2ME Foundation Profile.
*
* <p>The selection criterion must be specified in one of the following two formats:
* <ul>
* <li> <i><crypto_service>.<algorithm_or_type></i> <p> The
* cryptographic service name must not contain any dots.
* <p> A
* provider satisfies the specified selection criterion iff the provider implements the
* specified algorithm or type for the specified cryptographic service.
* <p> For example, "CertificateFactory.X.509"
* would be satisfied by any provider that supplied
* a CertificateFactory implementation for X.509 certificates.
* NOTE: <B>java.security.cert.CertificateFactory</B> is found in J2ME CDC
* profiles such as J2ME Foundation Profile.
* <li> <i><crypto_service>.<algorithm_or_type> <attribute_name>:< attribute_value></i>
* <p> The cryptographic service name must not contain any dots. There
* must be one or more space charaters between the the <i><algorithm_or_type></i>
* and the <i><attribute_name></i>.
* <p> A provider satisfies this selection criterion iff the
* provider implements the specified algorithm or type for the specified
* cryptographic service and its implementation meets the
* constraint expressed by the specified attribute name/value pair.
* <p> For example, "Signature.SHA1withDSA KeySize:1024" would be
* satisfied by any provider that implemented
* the SHA1withDSA signature algorithm with a keysize of 1024 (or larger).
* NOTE: <B>java.security.Signature</B> is found in J2ME CDC profiles such as
* J2ME Foundation Profile.
*
* </ul>
*
* <p> See Appendix A in the <a href=
* "../../../guide/security/CryptoSpec.html#AppA">
* Java Cryptogaphy Architecture API Specification & Reference </a>
* for information about standard cryptographic service names, standard
* algorithm names and standard attribute names.
*
* @param filter the criterion for selecting
* providers. The filter is case-insensitive.
*
* @return all the installed providers that satisfy the selection
* criterion, or null if no such providers have been installed.
*
* @throws InvalidParameterException
* if the filter is not in the required format
*
* @see #getProviders(java.util.Map)
*/
public static Provider[] getProviders(String filter) {
String key = null;
String value = null;
int index = filter.indexOf(':');
if (index == -1) {
key = new String(filter);
value = "";
} else {
key = filter.substring(0, index);
value = filter.substring(index + 1);
}
Hashtable hashtableFilter = new Hashtable(1);
hashtableFilter.put(key, value);
return (getProviders(hashtableFilter));
}
/**
* Returns an array containing all installed providers that satisfy the specified
* selection criteria, or null if no such providers have been installed.
* The returned providers are ordered
* according to their <a href=
* "#insertProviderAt(java.security.Provider, int)">preference order</a>.
*
* <p>The selection criteria are represented by a map.
* Each map entry represents a selection criterion.
* A provider is selected iff it satisfies all selection
* criteria. The key for any entry in such a map must be in one of the
* following two formats:
* <ul>
* <li> <i><crypto_service>.<algorithm_or_type></i>
* <p> The cryptographic service name must not contain any dots.
* <p> The value associated with the key must be an empty string.
* <p> A provider
* satisfies this selection criterion iff the provider implements the
* specified algorithm or type for the specified cryptographic service.
* <li> <i><crypto_service>.<algorithm_or_type> <attribute_name></i>
* <p> The cryptographic service name must not contain any dots. There
* must be one or more space charaters between the <i><algorithm_or_type></i>
* and the <i><attribute_name></i>.
* <p> The value associated with the key must be a non-empty string.
* A provider satisfies this selection criterion iff the
* provider implements the specified algorithm or type for the specified
* cryptographic service and its implementation meets the
* constraint expressed by the specified attribute name/value pair.
* </ul>
*
* <p> See Appendix A in the <a href=
* "../../../guide/security/CryptoSpec.html#AppA">
* Java Cryptogaphy Architecture API Specification & Reference </a>
* for information about standard cryptographic service names, standard
* algorithm names and standard attribute names.
*
* @param filter the criteria for selecting
* providers. The filter is case-insensitive.
*
* @return all the installed providers that satisfy the selection
* criteria, or null if no such providers have been installed.
*
* @throws InvalidParameterException
* if the filter is not in the required format
*
* @see #getProviders(java.lang.String)
*/
public static Provider[] getProviders(Map filter) {
// Get all installed providers first.
// Then only return those providers who satisfy the selection criteria.
Provider[] allProviders = Security.getProviders();
Set keySet = filter.keySet();
LinkedHashSet candidates = new LinkedHashSet(5);
// Returns all installed providers
// if the selection criteria is null.
if ((keySet == null) || (allProviders == null)) {
return allProviders;
}
boolean firstSearch = true;
// For each selection criterion, remove providers
// which don't satisfy the criterion from the candidate set.
for (Iterator ite = keySet.iterator(); ite.hasNext(); ) {
String key = (String)ite.next();
String value = (String)filter.get(key);
LinkedHashSet newCandidates = getAllQualifyingCandidates(key, value,
allProviders);
if (firstSearch) {
candidates = newCandidates;
firstSearch = false;
}
if ((newCandidates != null) && !newCandidates.isEmpty()) {
// For each provider in the candidates set, if it
// isn't in the newCandidate set, we should remove
// it from the candidate set.
for (Iterator cansIte = candidates.iterator();
cansIte.hasNext(); ) {
Provider prov = (Provider)cansIte.next();
if (!newCandidates.contains(prov)) {
cansIte.remove();
}
}
} else {
candidates = null;
break;
}
}
if ((candidates == null) || (candidates.isEmpty()))
return null;
Object[] candidatesArray = candidates.toArray();
Provider[] result = new Provider[candidatesArray.length];
for (int i = 0; i < result.length; i++) {
result[i] = (Provider)candidatesArray[i];
}
return result;
}
private static boolean checkSuperclass(Class subclass, Class superclass) {
while(!subclass.equals(superclass)) {
subclass = subclass.getSuperclass();
if (subclass == null) {
return false;
}
}
return true;
}
/*
* Returns an array of objects: the first object in the array is
* an instance of an implementation of the requested algorithm
* and type, and the second object in the array identifies the provider
* of that implementation.
* The <code>provider</code> argument can be null, in which case all
* configured providers will be searched in order of preference.
*/
static Object[] getImpl(String algorithm, String type, String provider)
throws NoSuchAlgorithmException, NoSuchProviderException
{
ProviderProperty pp = getEngineClassName(algorithm, provider, type);
return doGetImpl(algorithm, type, pp);
}
static Object[] getImpl(String algorithm, String type, String provider,
Object params)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException
{
ProviderProperty pp = getEngineClassName(algorithm, provider, type);
return doGetImpl(algorithm, type, pp, params);
}
/*
* Returns an array of objects: the first object in the array is
* an instance of an implementation of the requested algorithm
* and type, and the second object in the array identifies the provider
* of that implementation.
* The <code>provider</code> argument cannot be null.
*/
static Object[] getImpl(String algorithm, String type, Provider provider)
throws NoSuchAlgorithmException
{
ProviderProperty pp = getEngineClassName(algorithm, provider, type);
return doGetImpl(algorithm, type, pp);
}
static Object[] getImpl(String algorithm, String type, Provider provider,
Object params)
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException
{
ProviderProperty pp = getEngineClassName(algorithm, provider, type);
return doGetImpl(algorithm, type, pp, params);
}
private static Object[] doGetImpl(String algorithm, String type,
ProviderProperty pp)
throws NoSuchAlgorithmException
{
try {
return doGetImpl(algorithm, type, pp, null);
} catch (InvalidAlgorithmParameterException e) {
// should not occur
throw new NoSuchAlgorithmException(e.getMessage());
}
}
private static Object[] doGetImpl(String algorithm, String type,
ProviderProperty pp, Object params)
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException
{
String className = pp.className;
String providerName = pp.provider.getName();
try {
// java.security.<type>.Spi is a system class, therefore
// Class.forName() always works
Class typeClass;
if (type.equals("CertPathBuilder") || type.equals("CertStore")) {
typeClass = Class.forName("com.sun.security.cert." + type
+ "Spi");
} else if (type.equals("CertificateFactory") ||
type.equals("CertPathValidator")) {
typeClass = Class.forName("java.security.cert." + type
+ "Spi");
} else {
typeClass = Class.forName("java.security." + type + "Spi");
}
// Load the implementation class using the same class loader that
// was used to load the associated provider.
// In order to get the class loader of a class, the caller's class
// loader must be the same as or an ancestor of the class loader
// being returned.
// Since java.security.Security is a system class, it can get the
// class loader of any class (the system class loader is an
// ancestor of all class loaders).
ClassLoader cl = pp.provider.getClass().getClassLoader();
Class implClass;
if (cl != null) {
implClass = cl.loadClass(className);
} else {
implClass = Class.forName(className);
}
if (checkSuperclass(implClass, typeClass)) {
Object obj;
if (type.equals("CertStore")) {
Constructor cons =
implClass.getConstructor(new Class[]
{ Class.forName
("java.security.cert.CertStoreParameters") });
obj = cons.newInstance(new Object[] {params});
} else
obj = implClass.newInstance();
return new Object[] { obj, pp.provider };
} else {
throw new NoSuchAlgorithmException("class configured for " +
type + ": " + className +
" not a " + type);
}
} catch (ClassNotFoundException e) {
throw new NoSuchAlgorithmException("class configured for " +
type + "(provider: " +
providerName + ")" +
"cannot be found.\n" +
e.getMessage());
} catch (InstantiationException e) {
throw (NoSuchAlgorithmException) new NoSuchAlgorithmException("class " + className +
" configured for " + type +
"(provider: " + providerName +
") cannot be " +
"instantiated.\n").initCause(e);
} catch (IllegalAccessException e) {
throw new NoSuchAlgorithmException("class " + className +
" configured for " + type +
"(provider: " + providerName +
") cannot be accessed.\n" +
e.getMessage());
} catch (SecurityException e) {
throw new NoSuchAlgorithmException("class " + className +
" configured for " + type +
"(provider: " + providerName +
") cannot be accessed.\n" +
e.getMessage());
} catch (NoSuchMethodException e) {
throw new NoSuchAlgorithmException("constructor for " +
"class " + className +
" configured for " + type +
"(provider: " + providerName +
") cannot be instantiated.\n" +
e.getMessage());
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t != null && t instanceof InvalidAlgorithmParameterException)
throw (InvalidAlgorithmParameterException) t;
else
throw new InvalidAlgorithmParameterException("constructor " +
"for class " + className +
" configured for " + type +
"(provider: " + providerName +
") cannot be instantiated.\n" +
e.getMessage());
}
}
/**
* Gets a security property value.
*
* <p>First, if there is a security manager, its
* <code>checkPermission</code> method is called with a
* <code>java.security.SecurityPermission("getProperty."+key)</code>
* permission to see if it's ok to retrieve the specified
* security property value..
*
* @param key the key of the property being retrieved.
*
* @return the value of the security property corresponding to key.
*
* @throws SecurityException
* if a security manager exists and its <code>{@link
* java.lang.SecurityManager#checkPermission}</code> method
* denies
* access to retrieve the specified security property value
*
* @see #setProperty
* @see java.security.SecurityPermission
*/
public static String getProperty(String key) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SecurityPermission("getProperty."+
key));
}
String name = props.getProperty(key);
if (name != null)
name = name.trim(); // could be a class name with trailing ws
return name;
}
/**
* Sets a security property value.
*
* <p>First, if there is a security manager, its
* <code>checkPermission</code> method is called with a
* <code>java.security.SecurityPermission("setProperty."+key)</code>
* permission to see if it's ok to set the specified
* security property value.
*
* @param key the name of the property to be set.
*
* @param datum the value of the property to be set.
*
* @throws SecurityException
* if a security manager exists and its <code>{@link
* java.lang.SecurityManager#checkPermission}</code> method
* denies access to set the specified security property value
*
* @see #getProperty
* @see java.security.SecurityPermission
*/
public static void setProperty(String key, String datum) {
check("setProperty."+key);
props.put(key, datum);
invalidateSMCache(key); /* See below. */
}
/*
* Implementation detail: If the property we just set in
* setProperty() was either "package.access" or
* "package.definition", we need to signal to the SecurityManager
* class that the value has just changed, and that it should
* invalidate it's local cache values.
*
* Rather than create a new API entry for this function,
* we use reflection to set a private variable.
*/
private static void invalidateSMCache(String key) {
final boolean pa = key.equals("package.access");
final boolean pd = key.equals("package.definition");
if (pa || pd) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
/* Get the class via the bootstrap class loader. */
Class cl = Class.forName(
"java.lang.SecurityManager", false, null);
Field f = null;
boolean accessible = false;
if (pa) {
f = cl.getDeclaredField("packageAccessValid");
accessible = f.isAccessible();
f.setAccessible(true);
} else {
f = cl.getDeclaredField("packageDefinitionValid");
accessible = f.isAccessible();
f.setAccessible(true);
}
f.setBoolean(f, false);
f.setAccessible(accessible);
}
catch (Exception e1) {
/* If we couldn't get the class, it hasn't
* been loaded yet. If there is no such
* field, we shouldn't try to set it. There
* shouldn't be a security execption, as we
* are loaded by boot class loader, and we
* are inside a doPrivileged() here.
*
* NOOP: don't do anything...
*/
}
return null;
} /* run */
}); /* PrivilegedAction */
} /* if */
}
private static void check(String directive) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkSecurityAccess(directive);
}
}
/**
* Print an error message that may be significant to a user.
*/
static void error(String msg) {
if (debug) {
System.err.println(msg);
}
}
/**
* Print an error message that may be significant to a user.
*/
static void error(String msg, Throwable t) {
error(msg);
if (debug) {
t.printStackTrace();
}
}
/**
* Print an debugging message that may be significant to a developer.
*/
static void debug(String msg) {
if (debug) {
System.err.println(msg);
}
}
/**
* Print an debugging message that may be significant to a developer.
*/
static void debug(String msg, Throwable t) {
if (debug) {
t.printStackTrace();
System.err.println(msg);
}
}
/*
* Returns all providers who satisfy the specified
* criterion.
*/
private static LinkedHashSet getAllQualifyingCandidates(String filterKey,
String filterValue,
Provider[] allProviders) {
String[] filterComponents = getFilterComponents(filterKey,
filterValue);
// The first component is the service name.
// The second is the algorithm name.
// If the third isn't null, that is the attrinute name.
String serviceName = filterComponents[0];
String algName = filterComponents[1];
String attrName = filterComponents[2];
// Check whether we can find anything in the cache
String cacheKey = serviceName + '.' + algName;
LinkedHashSet candidates = (LinkedHashSet)searchResultsCache.get(cacheKey);
// If there is no entry for the cacheKey in the cache,
// let's build an entry for it first.
LinkedHashSet forCache = getProvidersNotUsingCache(serviceName,
algName,
null,
null,
null,
allProviders);
if ((forCache == null) || (forCache.isEmpty())) {
return null;
} else {
searchResultsCache.put(cacheKey, forCache);
if (attrName == null) {
return forCache;
}
return getProvidersNotUsingCache(serviceName, algName, attrName,
filterValue, candidates,
allProviders);
}
}
private static LinkedHashSet getProvidersNotUsingCache(String serviceName,
String algName,
String attrName,
String filterValue,
LinkedHashSet candidates,
Provider[] allProviders) {
if ((attrName != null) && (candidates != null) &&
(!candidates.isEmpty())) {
for (Iterator cansIte = candidates.iterator();
cansIte.hasNext(); ) {
Provider prov = (Provider)cansIte.next();
if (!isCriterionSatisfied(prov, serviceName, algName,
attrName, filterValue)) {
cansIte.remove();
}
}
}
if ((candidates == null) || (candidates.isEmpty())) {
if (candidates == null)
candidates = new LinkedHashSet(5);
for (int i = 0; i < allProviders.length; i++) {
if (isCriterionSatisfied(allProviders[i], serviceName,
algName,
attrName, filterValue)) {
candidates.add(allProviders[i]);
}
}
}
return candidates;
}
/*
* Returns true if the given provider satisfies
* the selection criterion key:value.
*/
private static boolean isCriterionSatisfied(Provider prov,
String serviceName,
String algName,
String attrName,
String filterValue) {
String key = serviceName + '.' + algName;
if (attrName != null) {
key += ' ' + attrName;
}
// Check whether the provider has a property
// whose key is the same as the given key.
String propValue = getProviderProperty(key, prov);
if (propValue == null) {
// Check whether we have an alias instead
// of a standard name in the key.
String standardName = getProviderProperty("Alg.Alias." +
serviceName + "." +
algName,
prov);
if (standardName != null) {
key = serviceName + "." + standardName;
if (attrName != null) {
key += ' ' + attrName;
}
propValue = getProviderProperty(key, prov);
}
if (propValue == null) {
// The provider doesn't have the given
// key in its property list.
return false;
}
}
// If the key is in the format of:
// <crypto_service>.<algorithm_or_type>,
// there is no need to check the value.
if (attrName == null) {
return true;
}
// If we get here, the key must be in the
// format of <crypto_service>.<algorithm_or_provider> <attribute_name>.
if (isStandardAttr(attrName)) {
return isConstraintSatisfied(attrName, filterValue, propValue);
} else {
return filterValue.equalsIgnoreCase(propValue);
}
}
/*
* Returns true if the attribute is a standard attribute;
* otherwise, returns false.
*/
private static boolean isStandardAttr(String attribute) {
// For now, we just have two standard attributes: KeySize and ImplementedIn.
if (attribute.equalsIgnoreCase("KeySize"))
return true;
if (attribute.equalsIgnoreCase("ImplementedIn"))
return true;
return false;
}
/*
* Returns true if the requested attribute value is supported;
* otherwise, returns false.
*/
private static boolean isConstraintSatisfied(String attribute,
String value,
String prop) {
// For KeySize, prop is the max key size the
// provider supports for a specific <crypto_service>.<algorithm>.
if (attribute.equalsIgnoreCase("KeySize")) {
int requestedSize = (new Integer(value)).intValue();
int maxSize = (new Integer(prop)).intValue();
if (requestedSize <= maxSize) {
return true;
} else {
return false;
}
}
// For Type, prop is the type of the implementation
// for a specific <crypto service>.<algorithm>.
if (attribute.equalsIgnoreCase("ImplementedIn")) {
return value.equalsIgnoreCase(prop);
}
return false;
}
static String[] getFilterComponents(String filterKey, String filterValue) {
int algIndex = filterKey.indexOf('.');
if (algIndex < 0) {
// There must be a dot in the filter, and the dot
// shouldn't be at the beginning of this string.
throw new InvalidParameterException("Invalid filter");
}
String serviceName = filterKey.substring(0, algIndex);
String algName = null;
String attrName = null;
if (filterValue.length() == 0) {
// The filterValue is an empty string. So the filterKey
// should be in the format of <crypto_service>.<algorithm_or_type>.
algName = filterKey.substring(algIndex + 1).trim();
if (algName.length() == 0) {
// There must be a algorithm or type name.
throw new InvalidParameterException("Invalid filter");
}
} else {
// The filterValue is a non-empty string. So the filterKey must be
// in the format of
// <crypto_service>.<algorithm_or_type> <attribute_name>
int attrIndex = filterKey.indexOf(' ');
if (attrIndex == -1) {
// There is no attribute name in the filter.
throw new InvalidParameterException("Invalid filter");
} else {
attrName = filterKey.substring(attrIndex + 1).trim();
if (attrName.length() == 0) {
// There is no attribute name in the filter.
throw new InvalidParameterException("Invalid filter");
}
}
// There must be an algorithm name in the filter.
if ((attrIndex < algIndex) ||
(algIndex == attrIndex - 1)) {
throw new InvalidParameterException("Invalid filter");
} else {
algName = filterKey.substring(algIndex + 1, attrIndex);
}
}
String[] result = new String[3];
result[0] = serviceName;
result[1] = algName;
result[2] = attrName;
return result;
}
/**
* Returns a Set of Strings containing the names of all available
* algorithms or types for the specified Java cryptographic service
* (e.g., Signature, MessageDigest, Cipher, Mac, KeyStore). Returns
* an empty Set if there is no provider that supports the
* specified service. For a complete list of Java cryptographic
* services, please see the
* <a href="../../../guide/security/CryptoSpec.html">Java
* Cryptography Architecture API Specification & Reference</a>.
* Note: the returned set is immutable.
*
* @param serviceName the name of the Java cryptographic
* service (e.g., Signature, MessageDigest, Cipher, Mac, KeyStore).
* Note: this parameter is case-insensitive.
*
* NOTE: <B>java.security.Signature, java.security.KeyStore</B> are found
* in J2ME CDC profiles such as J2ME Foundation Profile. <B> Cipher,
* Mac </B> are found in J2ME CDC optional packages such as J2ME Security
* Optional Package.
*
* @return a Set of Strings containing the names of all available
* algorithms or types for the specified Java cryptographic service
* or an empty set if no provider supports the specified service.
*
* @since 1.4
**/
public static Set getAlgorithms(String serviceName) {
HashSet result = new HashSet();
if ((serviceName == null) || (serviceName.length() == 0) ||
(serviceName.endsWith("."))) {
return result;
}
Provider[] providers = Security.getProviders();
for (int i = 0; i < providers.length; i++) {
// Check the keys for each provider.
for (Enumeration e = providers[i].keys(); e.hasMoreElements(); ) {
String currentKey = ((String)e.nextElement()).toUpperCase();
if (currentKey.startsWith(serviceName.toUpperCase())) {
// We should skip the currentKey if it contains a
// whitespace. The reason is: such an entry in the
// provider property contains attributes for the
// implementation of an algorithm. We are only interested
// in entries which lead to the implementation
// classes.
if (currentKey.indexOf(" ") < 0) {
result.add(currentKey.substring(serviceName.length() + 1));
}
}
}
}
return Collections.unmodifiableSet(result);
}
}