jdk/src/java.base/share/classes/sun/security/jca/ProviderConfig.java
2024-12-13 22:40:42 +00:00

401 lines
14 KiB
Java

/*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 in the LICENSE file that
* accompanied this code).
*
* 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.jca;
import java.io.File;
import java.lang.reflect.*;
import java.util.*;
import java.security.*;
import sun.security.util.PropertyExpander;
/**
* Class representing a configured provider which encapsulates configuration
* (provider name + optional argument), the provider loading logic, and
* the loaded Provider object itself.
*
* @author Andreas Sterbenz
* @since 1.5
*/
final class ProviderConfig {
private static final sun.security.util.Debug debug =
sun.security.util.Debug.getInstance("jca", "ProviderConfig");
// suffix for identifying the SunPKCS11-Solaris provider
private static final String P11_SOL_NAME = "SunPKCS11";
// config file argument of the SunPKCS11-Solaris provider
private static final String P11_SOL_ARG =
"${java.home}/conf/security/sunpkcs11-solaris.cfg";
// maximum number of times to try loading a provider before giving up
private static final int MAX_LOAD_TRIES = 30;
// could be provider name (module) or provider class name (legacy)
private final String provName;
// argument to the Provider.configure() call, never null
private final String argument;
// number of times we have already tried to load this provider
private int tries;
// Provider object, if loaded
private volatile Provider provider;
// flag indicating if we are currently trying to load the provider
// used to detect recursion
private boolean isLoading;
ProviderConfig(String provName, String argument) {
if (provName.endsWith(P11_SOL_NAME) && argument.equals(P11_SOL_ARG)) {
checkSunPKCS11Solaris();
}
this.provName = provName;
this.argument = expand(argument);
}
ProviderConfig(String provName) {
this(provName, "");
}
ProviderConfig(Provider provider) {
this.provName = provider.getName();
this.argument = "";
this.provider = provider;
}
// check if we should try to load the SunPKCS11-Solaris provider
// avoid if not available (pre Solaris 10) to reduce startup time
// or if disabled via system property
private void checkSunPKCS11Solaris() {
File file = new File("/usr/lib/libpkcs11.so");
if (file.exists() == false ||
("false".equalsIgnoreCase(System.getProperty
("sun.security.pkcs11.enable-solaris")))) {
tries = MAX_LOAD_TRIES;
}
}
private boolean hasArgument() {
return !argument.isEmpty();
}
// should we try to load this provider?
private boolean shouldLoad() {
return (tries < MAX_LOAD_TRIES);
}
// do not try to load this provider again
private void disableLoad() {
tries = MAX_LOAD_TRIES;
}
boolean isLoaded() {
return (provider != null);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ProviderConfig other)) {
return false;
}
return this.provName.equals(other.provName)
&& this.argument.equals(other.argument);
}
@Override
public int hashCode() {
return Objects.hash(provName, argument);
}
public String toString() {
if (hasArgument()) {
return provName + "('" + argument + "')";
} else {
return provName;
}
}
/**
* Get the provider object. Loads the provider if it is not already loaded.
*/
Provider getProvider() {
// volatile variable load
Provider p = provider;
if (p != null) {
return p;
}
// DCL
synchronized (this) {
p = provider;
if (p != null) {
return p;
}
if (!shouldLoad()) {
return null;
}
p = switch (provName) {
case "SUN", "sun.security.provider.Sun" ->
new sun.security.provider.Sun();
case "SunRsaSign", "sun.security.rsa.SunRsaSign" ->
new sun.security.rsa.SunRsaSign();
case "SunJCE", "com.sun.crypto.provider.SunJCE" ->
new com.sun.crypto.provider.SunJCE();
case "SunJSSE" -> new sun.security.ssl.SunJSSE();
case "SunEC" -> new sun.security.ec.SunEC();
case "Apple", "apple.security.AppleProvider" -> {
// Reflection is needed for compile time as the class
// is not available for non-macosx systems
Provider ap = null;
try {
Class<?> c = Class.forName(
"apple.security.AppleProvider");
if (Provider.class.isAssignableFrom(c)) {
@SuppressWarnings("deprecation")
Object tmp = c.newInstance();
ap = (Provider) tmp;
}
} catch (Exception ex) {
if (debug != null) {
debug.println("Error loading provider Apple");
ex.printStackTrace();
}
}
yield ap;
}
default -> {
if (isLoading) {
// because this method is synchronized, this can only
// happen if there is recursion.
if (debug != null) {
debug.println("Recursion loading provider: " + this);
new Exception("Call trace").printStackTrace();
}
yield null;
}
try {
isLoading = true;
tries++;
yield doLoadProvider();
} finally {
isLoading = false;
}
}
};
provider = p;
}
return p;
}
/**
* Load and instantiate the Provider described by this class.
*
* @return null if the Provider could not be loaded
*
* @throws ProviderException if executing the Provider's constructor
* throws a ProviderException. All other Exceptions are ignored.
*/
private Provider doLoadProvider() {
if (debug != null) {
debug.println("Loading provider " + ProviderConfig.this);
}
try {
Provider p = ProviderLoader.INSTANCE.load(provName);
if (p != null) {
if (hasArgument()) {
p = p.configure(argument);
}
if (debug != null) {
debug.println("Loaded provider " + p.getName());
}
} else {
if (debug != null) {
debug.println("Error loading provider " +
ProviderConfig.this);
}
disableLoad();
}
return p;
} catch (Exception e) {
if (e instanceof ProviderException) {
// pass up
throw e;
} else {
if (debug != null) {
debug.println("Error loading provider " +
ProviderConfig.this);
e.printStackTrace();
}
disableLoad();
return null;
}
} catch (ExceptionInInitializerError err) {
// unable to initialize provider class
if (debug != null) {
debug.println("Error loading provider " + ProviderConfig.this);
err.printStackTrace();
}
disableLoad();
return null;
}
}
/**
* Perform property expansion of the provider value.
*/
private static String expand(final String value) {
// shortcut if value does not contain any properties
if (value.contains("${") == false) {
return value;
}
try {
return PropertyExpander.expand(value);
} catch (GeneralSecurityException e) {
throw new ProviderException(e);
}
}
// Inner class for loading security providers listed in java.security file
private static final class ProviderLoader {
static final ProviderLoader INSTANCE = new ProviderLoader();
private final ServiceLoader<Provider> services;
private ProviderLoader() {
// VM should already been booted at this point, if not
// - Only providers in java.base should be loaded, don't use
// ServiceLoader
// - ClassLoader.getSystemClassLoader() will throw InternalError
services = ServiceLoader.load(java.security.Provider.class,
ClassLoader.getSystemClassLoader());
}
/**
* Loads the provider with the specified class name.
*
* @param pn the name of the provider
* @return the Provider, or null if it cannot be found or loaded
* @throws ProviderException all other exceptions are ignored
*/
public Provider load(String pn) {
if (debug != null) {
debug.println("Attempt to load " + pn + " using SL");
}
Iterator<Provider> iter = services.iterator();
while (iter.hasNext()) {
try {
Provider p = iter.next();
String pName = p.getName();
if (debug != null) {
debug.println("Found SL Provider named " + pName);
}
if (pName.equals(pn)) {
return p;
}
} catch (ServiceConfigurationError |
InvalidParameterException ex) {
// if provider loading failed
// log it and move on to next provider
if (debug != null) {
debug.println("Encountered " + ex +
" while iterating through SL, ignore and move on");
ex.printStackTrace();
}
}
}
// No success with ServiceLoader. Try loading provider the legacy,
// i.e. pre-module, way via reflection
try {
return legacyLoad(pn);
} catch (ProviderException pe) {
// pass through
throw pe;
} catch (Exception ex) {
// logged and ignored
if (debug != null) {
debug.println("Encountered " + ex +
" during legacy load of " + pn);
ex.printStackTrace();
}
return null;
}
}
@SuppressWarnings("deprecation") // Class.newInstance
private Provider legacyLoad(String classname) {
if (debug != null) {
debug.println("Loading legacy provider: " + classname);
}
try {
Class<?> provClass =
ClassLoader.getSystemClassLoader().loadClass(classname);
// only continue if the specified class extends Provider
if (!Provider.class.isAssignableFrom(provClass)) {
if (debug != null) {
debug.println(classname + " is not a provider");
}
return null;
}
return (Provider) provClass.newInstance();
} catch (Exception e) {
Throwable t;
if (e instanceof InvocationTargetException) {
t = e.getCause();
} else {
t = e;
}
if (debug != null) {
debug.println("Error loading legacy provider " + classname);
t.printStackTrace();
}
// provider indicates fatal error, pass through exception
if (t instanceof ProviderException) {
throw (ProviderException) t;
}
return null;
} catch (ExceptionInInitializerError | NoClassDefFoundError err) {
// unable to access/initialize provider class
if (debug != null) {
debug.println("Error loading legacy provider " + classname);
err.printStackTrace();
}
return null;
}
}
}
}