| /* | |
| * 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; | |
| } | |
| } | |
| } | |
| } |