From d2b2cefc7f1898ea0803ed4f372b24372071e0d4 Mon Sep 17 00:00:00 2001 From: jovsteva Date: Wed, 18 Sep 2024 11:58:25 +0200 Subject: [PATCH 1/4] Initialize security providers at run time. Add a test for security provider run time registration. Add entry to a CHANGELOG.md Parse java.security.properties file at run time. --- substratevm/CHANGELOG.md | 1 + substratevm/mx.substratevm/suite.py | 3 + .../core/jdk/SecurityProvidersSupport.java | 140 +++++++++++ .../svm/core/jdk/SecuritySubstitutions.java | 192 ++++++++++++-- ...et_sun_security_ssl_TrustStoreManager.java | 7 - .../svm/hosted/SecurityServicesFeature.java | 234 ++++-------------- .../svm/hosted/ServiceLoaderFeature.java | 153 +++++++----- .../hosted/jdk/JDKInitializationFeature.java | 3 - .../test/services/SecurityServiceTest.java | 25 +- 9 files changed, 469 insertions(+), 289 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 71de48943e9e..36f3a9f5d87c 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -19,6 +19,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-60209) New syntax for configuration of the [Foreign Function & Memory API](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ForeignInterface.md) * (GR-64584) Experimental option `-H:+RelativeCodePointers` to significantly reduce relocation entries in position-independent executables and shared libraries. * (GR-60238) JNI registration is now included as part of the `"reflection"` section of _reachability-metadata.json_ using the `"jniAccessible"` attribute. Registrations performed through the `"jni"` section of _reachability-metadata.json_ and through _jni-config.json_ will still be accepted and parsed correctly. +* (GR-57827) Move the initialization of security providers from build time to runtime. ## GraalVM for JDK 24 (Internal Version 24.2.0) * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning. diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 3d9609bb3da5..c3b9be7b207f 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -356,9 +356,12 @@ "sun.reflect.generics.reflectiveObjects", "sun.reflect.generics.repository", "sun.reflect.generics.tree", + "sun.security.rsa", "sun.security.jca", "sun.security.ssl", "sun.security.util", + "sun.security.provider", + "com.sun.crypto.provider", "sun.text.spi", "sun.util", "sun.util.locale", diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java new file mode 100644 index 000000000000..8e314299c6b2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024, 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 com.oracle.svm.core.jdk; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.security.Provider; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.api.replacements.Fold; + +/** + * The class that holds various build-time and runtime structures necessary for security providers. + */ +public final class SecurityProvidersSupport { + /** + * A set of providers to be loaded using the service-loading technique at runtime, but not + * discoverable at build-time when processing services in the feature (see + * ServiceLoaderFeature#handleServiceClassIsReachable). This occurs when the user does not + * explicitly request a provider, but the provider is discovered via static analysis from a + * JCA-compliant security service used by the user's code (see + * SecurityServicesFeature#registerServiceReachabilityHandlers). + */ + @Platforms(Platform.HOSTED_ONLY.class)// + private final Set markedAsNotLoaded = Collections.synchronizedSet(new HashSet<>()); + + /** Set of fully qualified provider names, required for runtime resource access. */ + private final Set userRequestedSecurityProviders = Collections.synchronizedSet(new HashSet<>()); + + /** + * A map of providers, identified by their names (see {@link Provider#getName()}), and the + * results of their verification (see javax.crypto.JceSecurity#getVerificationResult). This + * structure is used instead of the (see javax.crypto.JceSecurity#verifyingProviders) map to + * avoid keeping provider objects in the image heap. + */ + private final Map verifiedSecurityProviders = Collections.synchronizedMap(new HashMap<>()); + + private Properties savedInitialSecurityProperties; + + private Constructor sunECConstructor; + + @Platforms(Platform.HOSTED_ONLY.class) + public SecurityProvidersSupport(List userRequestedSecurityProviders) { + this.userRequestedSecurityProviders.addAll(userRequestedSecurityProviders); + } + + @Fold + public static SecurityProvidersSupport singleton() { + return ImageSingletons.lookup(SecurityProvidersSupport.class); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void addVerifiedSecurityProvider(String key, Object value) { + verifiedSecurityProviders.put(key, value); + } + + public Object getSecurityProviderVerificationResult(String key) { + return verifiedSecurityProviders.get(key); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void markSecurityProviderAsNotLoaded(String provider) { + markedAsNotLoaded.add(provider); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public boolean isSecurityProviderNotLoaded(String provider) { + return markedAsNotLoaded.contains(provider); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public boolean isUserRequestedSecurityProvider(String provider) { + return userRequestedSecurityProviders.contains(provider); + } + + /** + * Returns {@code true} if the provider, identified by either its name (e.g., SUN) or fully + * qualified name (e.g., sun.security.provider.Sun), is either user-requested or reachable via a + * security service. + */ + public boolean isSecurityProviderExpected(String providerName, String providerFQName) { + return verifiedSecurityProviders.containsKey(providerName) || userRequestedSecurityProviders.contains(providerFQName); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setSunECConstructor(Constructor sunECConstructor) { + this.sunECConstructor = sunECConstructor; + } + + public Provider allocateSunECProvider() { + try { + return (Provider) sunECConstructor.newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw VMError.shouldNotReachHere("The SunEC constructor is not present."); + } + } + + public void setSavedInitialSecurityProperties(Properties savedSecurityProperties) { + this.savedInitialSecurityProperties = savedSecurityProperties; + } + + public Properties getSavedInitialSecurityProperties() { + return savedInitialSecurityProperties; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index f023a02fcc7c..ada1340f21c5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -26,7 +26,7 @@ import static com.oracle.svm.core.snippets.KnownIntrinsics.readCallerStackPointer; -import java.lang.ref.ReferenceQueue; +import java.io.File; import java.lang.reflect.Constructor; import java.net.URL; import java.security.CodeSource; @@ -40,6 +40,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Properties; import java.util.WeakHashMap; import java.util.function.BooleanSupplier; import java.util.function.Predicate; @@ -64,6 +65,9 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; +import jdk.graal.compiler.core.common.SuppressFBWarnings; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; +import sun.security.util.Debug; import sun.security.util.SecurityConstants; /* @@ -125,6 +129,54 @@ final class Target_java_security_Provider_Service { private Object constructorCache; } +@TargetClass(value = java.security.Security.class) +final class Target_java_security_Security { + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) // + static Properties props; + + @Alias // + private static Properties initialSecurityProperties; + + @Alias // + private static Debug sdebug; + + @Substitute + @TargetElement(onlyWith = JDK21OrEarlier.class) + private static void initialize() { + props = SecurityProvidersSupport.singleton().getSavedInitialSecurityProperties(); + boolean overrideAll = false; + + 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); + } + loadProps(null, extraPropFile, overrideAll); + } + initialSecurityProperties = (Properties) props.clone(); + if (sdebug != null) { + for (String key : props.stringPropertyNames()) { + sdebug.println("Initial security property: " + key + "=" + props.getProperty(key)); + } + } + } + + @Alias + @TargetElement(onlyWith = JDK21OrEarlier.class) + private static native boolean loadProps(File masterFile, String extraPropFile, boolean overrideAll); +} + +@TargetClass(value = java.security.Security.class, innerClass = "SecPropLoader", onlyWith = JDKLatest.class) +final class Target_java_security_Security_SecPropLoader { + + @Substitute + private static void loadMaster() { + Target_java_security_Security.props = SecurityProvidersSupport.singleton().getSavedInitialSecurityProperties(); + } +} + class ServiceKeyProvider { static Object getNewServiceKey() { Class serviceKey = ReflectionUtil.lookupClass("java.security.Provider$ServiceKey"); @@ -265,6 +317,7 @@ final class Target_javax_crypto_JceSecurity { // value == PROVIDER_VERIFIED is successfully verified // value is failure cause Exception in error case @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // private static Map verificationResults; @Alias // @@ -275,16 +328,11 @@ final class Target_javax_crypto_JceSecurity { @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) // private static Map, URL> codeBaseCacheRef = new WeakHashMap<>(); - @Alias // - @TargetElement // - private static ReferenceQueue queue; - @Substitute static Exception getVerificationResult(Provider p) { /* Start code block copied from original method. */ /* The verification results map key is an identity wrapper object. */ - Object key = new Target_javax_crypto_JceSecurity_WeakIdentityWrapper(p, queue); - Object o = verificationResults.get(key); + Object o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p.getName()); if (o == PROVIDER_VERIFIED) { return null; } else if (o != null) { @@ -302,15 +350,6 @@ static Exception getVerificationResult(Provider p) { } } -@TargetClass(className = "javax.crypto.JceSecurity", innerClass = "WeakIdentityWrapper") -@SuppressWarnings({"unused"}) -final class Target_javax_crypto_JceSecurity_WeakIdentityWrapper { - - @Alias // - Target_javax_crypto_JceSecurity_WeakIdentityWrapper(Provider obj, ReferenceQueue queue) { - } -} - class JceSecurityAccessor { private static volatile SecureRandom RANDOM; @@ -408,19 +447,120 @@ final class Target_sun_security_jca_ProviderConfig { @Alias // private String provName; + @Alias// + private static sun.security.util.Debug debug; + + @Alias// + private Provider provider; + + @Alias// + private boolean isLoading; + + @Alias// + private int tries; + + @Alias + private native Provider doLoadProvider(); + + @Alias + private native boolean shouldLoad(); + /** - * All security providers used in a native-image must be registered during image build time. At - * runtime, we shouldn't have a call to doLoadProvider. However, this method is still reachable - * at runtime, and transitively includes other types in the image, among which is - * sun.security.jca.ProviderConfig.ProviderLoader. This class contains a static field with a - * cache of providers loaded during the image build. The contents of this cache can vary even - * when building the same image due to the way services are loaded on Java 11. This cache can - * increase the final image size substantially (if it contains, for example, - * {@code org.jcp.xml.dsig.internal.dom.XMLDSigRI}. + * The `entrypoint` for allocating security providers at runtime. The implementation is copied + * from the JDK with a small tweak to filter out providers that are neither user-requested nor + * reachable via a security service. */ @Substitute - private Provider doLoadProvider() { - throw VMError.unsupportedFeature("Cannot load new security provider at runtime: " + provName + "."); + @SuppressWarnings("fallthrough") + @SuppressFBWarnings(value = "DC_DOUBLECHECK", justification = "This double-check is implemented correctly and is intentional.") + 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; + } + + // Create providers which are in java.base directly + SecurityProvidersSupport support = SecurityProvidersSupport.singleton(); + switch (provName) { + case "SUN", "sun.security.provider.Sun": { + p = support.isSecurityProviderExpected("SUN", "sun.security.provider.Sun") ? new sun.security.provider.Sun() : null; + break; + } + case "SunRsaSign", "sun.security.rsa.SunRsaSign": { + p = support.isSecurityProviderExpected("SunRsaSign", "sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : null; + break; + } + case "SunJCE", "com.sun.crypto.provider.SunJCE": { + p = support.isSecurityProviderExpected("SunJCE", "com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : null; + break; + } + case "SunJSSE": { + p = support.isSecurityProviderExpected("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null; + break; + } + case "Apple", "apple.security.AppleProvider": { + // need to use reflection since this class only exists on MacOsx + try { + Class c = Class.forName("apple.security.AppleProvider"); + if (Provider.class.isAssignableFrom(c)) { + @SuppressWarnings("deprecation") + Object newInstance = c.newInstance(); + p = (Provider) newInstance; + } + } catch (Exception ex) { + if (debug != null) { + debug.println("Error loading provider Apple"); + ex.printStackTrace(); + } + } + break; + } + case "SunEC": { + if (JavaVersionUtil.JAVA_SPEC > 21) { + // Constructor inside method and then allocate. ModuleSupport to open. + p = support.isSecurityProviderExpected("SunEC", "sun.security.ec.SunEC") ? support.allocateSunECProvider() : null; + break; + } + /* + * On older JDK versions, SunEC was part of the `jdk.crypto.ec` module and was + * allocated via the service loading mechanism, so this fallthrough is + * intentional. On newer JDK versions, SunEC is part of `java.base` and is + * allocated directly. + */ + } + // fall through + 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(); + } + return null; + } + try { + isLoading = true; + tries++; + p = doLoadProvider(); + } finally { + isLoading = false; + } + } + } + provider = p; + } + return p; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java index 180bf4bde2f6..11a8c563905b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java @@ -89,13 +89,6 @@ public void afterRegistration(AfterRegistrationAccess access) { */ RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); rci.initializeAtBuildTime("sun.security.util.UntrustedCertificates", "Required for TrustStoreManager"); - /* - * All security providers must be registered (and initialized) at buildtime (see - * SecuritySubstitutions.java). XMLDSigRI is used for validating XML Signatures from - * certificate files while generating X509Certificates. - */ - rci.initializeAtBuildTime("org.jcp.xml.dsig.internal.dom.XMLDSigRI", "Required for TrustStoreManager"); - rci.initializeAtBuildTime("org.jcp.xml.dsig.internal.dom.XMLDSigRI$ProviderService", "Required for TrustStoreManager"); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 7334c354f2a8..c14b041abef2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -31,7 +31,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; -import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -63,6 +63,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; @@ -86,15 +87,14 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.reports.ReportUtils; -import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; import com.oracle.svm.core.jdk.JNIRegistrationUtil; import com.oracle.svm.core.jdk.NativeLibrarySupport; import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport; +import com.oracle.svm.core.jdk.SecurityProvidersSupport; +import com.oracle.svm.core.jdk.SecuritySubstitutions; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.util.UserError; @@ -102,16 +102,14 @@ import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; -import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.TypeResult; -import jdk.graal.compiler.debug.Assertions; import jdk.graal.compiler.options.Option; -import sun.security.jca.ProviderList; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; +import jdk.internal.access.SharedSecrets; import sun.security.provider.NativePRNG; import sun.security.x509.OIDMap; @@ -131,7 +129,8 @@ public static class Options { @Option(help = "Comma-separated list of additional security provider fully qualified class names to mark as used." + "Note that this option is only necessary if you use custom engine classes not available in JCA that are not JCA compliant.")// - public static final HostedOptionKey AdditionalSecurityProviders = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); + public static final HostedOptionKey AdditionalSecurityProviders = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); } /* @@ -154,6 +153,7 @@ public static class Options { private static final String JKS = "JKS"; private static final String X509 = "X.509"; private static final String[] emptyStringArray = new String[0]; + private static final String SECURITY_PROVIDERS_INITIALIZATION = "Initialize security provider at run time."; /** The list of known service classes defined by the JCA. */ private static final List> knownServices; @@ -198,25 +198,15 @@ public static class Options { /** All providers deemed to be used by this feature. */ private final Set usedProviders = ConcurrentHashMap.newKeySet(); - /** Providers marked as used by the user. */ - private final Set manuallyMarkedUsedProviderClassNames = new HashSet<>(); - - private Field verificationResultsField; - private Field providerListField; private Field oidTableField; private Field oidMapField; - private Field classCacheField; - private Field constructorCacheField; - - private ConcurrentHashMap, Object> cachedVerificationCache; - private ProviderList cachedProviders; private Class jceSecurityClass; - private AnnotationSubstitutionProcessor substitutionProcessor; - @Override public void afterRegistration(AfterRegistrationAccess a) { + ImageSingletons.add(SecurityProvidersSupport.class, new SecurityProvidersSupport(Options.AdditionalSecurityProviders.getValue().values())); + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, getClass(), false, "java.base", "sun.security.x509"); ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, getClass(), Security.class); ImageSingletons.lookup(RuntimeClassInitializationSupport.class).initializeAtBuildTime("javax.security.auth.kerberos.KeyTab", @@ -226,16 +216,30 @@ public void afterRegistration(AfterRegistrationAccess a) { @Override public void duringSetup(DuringSetupAccess a) { DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; - addManuallyConfiguredUsedProviders(a); - verificationResultsField = access.findField("javax.crypto.JceSecurity", "verificationResults"); - providerListField = access.findField("sun.security.jca.Providers", "providerList"); oidTableField = access.findField("sun.security.util.ObjectIdentifier", "oidTable"); oidMapField = access.findField(OIDMap.class, "oidMap"); - classCacheField = access.findField(Service.class, "classCache"); - constructorCacheField = access.findField(Service.class, "constructorCache"); + + if (JavaVersionUtil.JAVA_SPEC > 21) { + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, SecuritySubstitutions.class, false, "java.base", "sun.security.ec"); + Constructor sunECConstructor = constructor(a, "sun.security.ec.SunEC"); + SecurityProvidersSupport.singleton().setSunECConstructor(sunECConstructor); + } + + Properties securityProperties = SharedSecrets.getJavaSecurityPropertiesAccess().getInitialProperties(); + SecurityProvidersSupport.singleton().setSavedInitialSecurityProperties(securityProperties); RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); + /* + * Security providers will be initialized at run time because the class initialization + * simulation will determine that automatically. For the three classes below, however, we + * need to handle this explicitly because their packages are already marked for + * initialization at build time by JdkInitializationFeature#afterRegistration. + */ + rci.initializeAtRunTime("java.security.Security", SECURITY_PROVIDERS_INITIALIZATION); + rci.initializeAtRunTime("sun.security.jca.Providers", SECURITY_PROVIDERS_INITIALIZATION); + rci.initializeAtRunTime("sun.security.provider.certpath.ldap.JdkLDAP", SECURITY_PROVIDERS_INITIALIZATION); + /* * The SecureRandom implementations open the /dev/random and /dev/urandom files which are * used as sources for entropy. These files are opened in the static initializers. @@ -332,118 +336,6 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { /* Resolve calls to sun_security_mscapi* as builtIn. */ PlatformNativeLibrarySupport.singleton().addBuiltinPkgNativePrefix("sun_security_mscapi"); } - - substitutionProcessor = ((Inflation) access.getBigBang()).getAnnotationSubstitutionProcessor(); - - access.registerFieldValueTransformer(providerListField, new FieldValueTransformerWithAvailability() { - /* - * We must wait until all providers have been registered before filtering the list. - */ - @Override - public boolean isAvailable() { - return BuildPhaseProvider.isHostedUniverseBuilt(); - } - - @Override - public Object transform(Object receiver, Object originalValue) { - if (cachedProviders != null) { - if (SubstrateUtil.assertionsEnabled()) { - var filteredProviders = filterProviderList(originalValue); - assert cachedProviders.providers().equals(filteredProviders) : Assertions.errorMessage(cachedProviders.providers(), filteredProviders); - } - if (Options.TraceSecurityServices.getValue()) { - ProviderList providerList = (ProviderList) originalValue; - List removedProviders = providerList.providers().stream().filter(p -> shouldRemoveProvider(p)).toList(); - traceRemovedProviders(removedProviders); - } - } - /* - * This object is manually rescanned during analysis to ensure its entire type - * structure is part of the analysis universe. - */ - return cachedProviders; - } - }); - - access.registerFieldValueTransformer(verificationResultsField, new FieldValueTransformerWithAvailability() { - /* - * We must wait until all providers have been registered before filtering the list. - */ - @Override - public boolean isAvailable() { - return BuildPhaseProvider.isHostedUniverseBuilt(); - } - - @Override - public Object transform(Object receiver, Object originalValue) { - if (cachedVerificationCache != null) { - if (SubstrateUtil.assertionsEnabled()) { - var filteredCache = filterVerificationCache(originalValue); - assert cachedVerificationCache.equals(filteredCache) : Assertions.errorMessage(cachedVerificationCache, filteredCache); - } - } - /* - * This object is manually rescanned during analysis to ensure its entire type - * structure is part of the analysis universe. - */ - return cachedVerificationCache; - } - }); - } - - @SuppressWarnings("unchecked") - private ConcurrentHashMap, Object> filterVerificationCache(Object originalValue) { - /* - * The verification cache is an WeakIdentityWrapper -> Verification result - * ConcurrentHashMap. We do not care about the private WeakIdentityWrapper class, it extends - * WeakReference and so using WeakReference.get() is sufficient for us. - */ - var cleanedCache = new ConcurrentHashMap<>((ConcurrentHashMap, Object>) originalValue); - cleanedCache.keySet().removeIf(key -> shouldRemoveProvider(key.get())); - return cleanedCache; - } - - private List filterProviderList(Object originalValue) { - return ((ProviderList) originalValue).providers().stream().filter(p -> !shouldRemoveProvider(p)).toList(); - } - - private void addManuallyConfiguredUsedProviders(DuringSetupAccess access) { - for (String value : Options.AdditionalSecurityProviders.getValue().values()) { - for (String className : value.split(",")) { - Class classByName = access.findClassByName(className); - UserError.guarantee(classByName != null, - "Manually marked security provider class doesn't exist: %s. Make sure that the class name is correct and that the class is on the image builder classpath.", className); - trace("Marked provider %s as used", className); - manuallyMarkedUsedProviderClassNames.add(className); - } - } - } - - public boolean shouldRemoveProvider(Provider p) { - if (p == null) { - return true; - } - if (usedProviders.contains(p)) { - return false; - } - if (substitutionProcessor.isDeleted(p.getClass())) { - return true; - } - return !manuallyMarkedUsedProviderClassNames.contains(p.getClass().getName()); - } - - private static void traceRemovedProviders(List removedProviders) { - if (removedProviders == null || removedProviders.isEmpty()) { - trace("No security providers have been removed."); - } else { - trace("The following security providers were deemed to be unused and removed:"); - SecurityServicesPrinter.indent(); - trace("ProviderName - ProviderClass"); - for (Provider p : removedProviders) { - trace("%s - %s", p.getName(), p.getClass().getName()); - } - SecurityServicesPrinter.dedent(); - } } private static void registerSunMSCAPIConfig(BeforeAnalysisAccess a) { @@ -745,7 +637,7 @@ private static void registerSpiClass(Method getSpiClassMethod, String serviceTyp } } - private void registerProvider(Provider provider) { + private void registerProvider(DuringAnalysisAccess access, Provider provider) { if (usedProviders.add(provider)) { registerForReflection(provider.getClass()); /* Trigger initialization of lazy field java.security.Provider.entrySet. */ @@ -757,8 +649,30 @@ private void registerProvider(Provider provider) { * JceSecurity.canUseProvider() at runtime to check whether a provider is properly * signed and can be used by JCE. It does that via jar verification which we cannot * support. See also Target_javax_crypto_JceSecurity. + * + * Note that after verification, we move the result to a separate structure since we + * don't want to keep the provider object in the image heap. + * + * The verification result can be either null, in case of success, or an Exception, + * in case of failure. Null is interpreted as Boolean.TRUE at runtime, signifying + * successful verification. */ - getVerificationResult.invoke(null, provider); + Object result = getVerificationResult.invoke(null, provider); + String providerName = provider.getName(); + SecurityProvidersSupport support = SecurityProvidersSupport.singleton(); + support.addVerifiedSecurityProvider(providerName, result instanceof Exception ? result : Boolean.TRUE); + + /* + * If this provider is not yet loaded via the service loading mechanism, we need to + * manually prepare reflection metadata now, so that service loading works at + * runtime (see sun.security.jca.ProviderConfig.doLoadProvider). + */ + String providerFQName = provider.getClass().getName(); + if (support.isSecurityProviderNotLoaded(providerFQName)) { + Set registeredProviders = new HashSet<>(); + ServiceLoaderFeature.registerProviderForRuntimeReflectionAccess(access, providerFQName, registeredProviders); + ServiceLoaderFeature.registerProviderForRuntimeResourceAccess(access.getApplicationClassLoader().getUnnamedModule(), Provider.class.getName(), registeredProviders); + } } catch (ReflectiveOperationException ex) { throw VMError.shouldNotReachHere(ex); } @@ -789,7 +703,7 @@ private void registerService(DuringAnalysisAccess a, Service service) { if (isCertificateFactory(service) && service.getAlgorithm().equals(X509)) { registerX509Extensions(a); } - registerProvider(service.getProvider()); + registerProvider(a, service.getProvider()); } } else { trace("Cannot register service %s. Reason: %s.", asString(service), serviceClassResult.getException()); @@ -835,45 +749,7 @@ private void registerX509Extensions(DuringAnalysisAccess a) { @Override public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; - maybeScanVerificationResultsField(access); - maybeScanProvidersField(access); access.rescanRoot(oidTableField); - if (cachedProviders != null) { - for (Provider provider : cachedProviders.providers()) { - for (Service service : provider.getServices()) { - access.rescanField(service, classCacheField); - access.rescanField(service, constructorCacheField); - } - } - } - } - - private void maybeScanVerificationResultsField(DuringAnalysisAccessImpl access) { - if (access.getMetaAccess().lookupJavaField(verificationResultsField).isRead()) { - try { - var filteredVerificationCache = filterVerificationCache(verificationResultsField.get(null)); - if (cachedVerificationCache == null || !cachedVerificationCache.equals(filteredVerificationCache)) { - cachedVerificationCache = filteredVerificationCache; - access.rescanObject(cachedVerificationCache); - } - } catch (IllegalAccessException ex) { - throw VMError.shouldNotReachHere("Cannot access field: " + verificationResultsField.getName(), ex); - } - } - } - - private void maybeScanProvidersField(DuringAnalysisAccessImpl access) { - if (access.getMetaAccess().lookupJavaField(providerListField).isRead()) { - try { - List filteredProviders = filterProviderList(providerListField.get(null)); - if (cachedProviders == null || !cachedProviders.providers().equals(filteredProviders)) { - cachedProviders = ProviderList.newList(filteredProviders.toArray(new Provider[0])); - access.rescanObject(cachedProviders); - } - } catch (IllegalAccessException ex) { - throw VMError.shouldNotReachHere("Cannot access field: " + providerListField.getName(), ex); - } - } } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 0dfaeed8752c..d14b0837c6a1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -37,26 +37,27 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import jdk.graal.compiler.hotspot.CompilerConfigurationFactory; -import jdk.graal.compiler.hotspot.HotSpotBackendFactory; -import jdk.graal.compiler.hotspot.meta.DefaultHotSpotLoweringProvider; -import jdk.graal.compiler.hotspot.meta.HotSpotInvocationPluginProvider; -import jdk.graal.compiler.truffle.hotspot.TruffleCallBoundaryInstrumentationFactory; -import jdk.vm.ci.hotspot.HotSpotJVMCIBackendFactory; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.hosted.RuntimeResourceAccess; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.jdk.SecurityProvidersSupport; import com.oracle.svm.core.jdk.ServiceCatalogSupport; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.hosted.analysis.Inflation; +import jdk.graal.compiler.hotspot.CompilerConfigurationFactory; +import jdk.graal.compiler.hotspot.HotSpotBackendFactory; +import jdk.graal.compiler.hotspot.meta.DefaultHotSpotLoweringProvider; +import jdk.graal.compiler.hotspot.meta.HotSpotInvocationPluginProvider; import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionType; +import jdk.graal.compiler.truffle.hotspot.TruffleCallBoundaryInstrumentationFactory; +import jdk.vm.ci.hotspot.HotSpotJVMCIBackendFactory; import sun.util.locale.provider.LocaleDataMetaInfo; /** @@ -114,7 +115,6 @@ public static class Options { * initialized at image build time. */ RandomGenerator.class, - java.security.Provider.class, // see SecurityServicesFeature LocaleDataMetaInfo.class, // see LocaleSubstitutions /* Graal hotspot-specific services */ @@ -180,81 +180,98 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { }); } - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+11/src/java.base/share/classes/java/util/ServiceLoader.java#L745-L793") void handleServiceClassIsReachable(DuringAnalysisAccess access, Class serviceProvider, Collection providers) { LinkedHashSet registeredProviders = new LinkedHashSet<>(); + SecurityProvidersSupport support = SecurityProvidersSupport.singleton(); for (String provider : providers) { if (serviceProvidersToSkip.contains(provider)) { continue; } - /* Make provider reflectively instantiable */ - Class providerClass = access.findClassByName(provider); - - if (providerClass == null || providerClass.isArray() || providerClass.isPrimitive()) { - continue; - } - FeatureImpl.DuringAnalysisAccessImpl accessImpl = (FeatureImpl.DuringAnalysisAccessImpl) access; - if (!accessImpl.getHostVM().platformSupported(providerClass)) { - continue; - } - if (((Inflation) accessImpl.getBigBang()).getAnnotationSubstitutionProcessor().isDeleted(providerClass)) { - /* Disallow services with implementation classes that are marked as @Deleted */ + if (serviceProvider.equals(java.security.Provider.class)) { + /* Only register the security providers that are requested. */ + if (support.isUserRequestedSecurityProvider(provider)) { + registerProviderForRuntimeReflectionAccess(access, provider, registeredProviders); + } else { + support.markSecurityProviderAsNotLoaded(provider); + } continue; } + registerProviderForRuntimeReflectionAccess(access, provider, registeredProviders); + } + registerProviderForRuntimeResourceAccess(access.getApplicationClassLoader().getUnnamedModule(), serviceProvider.getName(), registeredProviders); + } - /* - * Find either a public static provider() method or a nullary constructor (or both). - * Skip providers that do not comply with requirements. - * - * See ServiceLoader#loadProvider and ServiceLoader#findStaticProviderMethod. - */ - Method nullaryProviderMethod = findProviderMethod(providerClass); - Constructor nullaryConstructor = findNullaryConstructor(providerClass); - if (nullaryConstructor != null || nullaryProviderMethod != null) { - RuntimeReflection.register(providerClass); - if (nullaryConstructor != null) { - /* - * Registering a constructor with - * RuntimeReflection.registerConstructorLookup(providerClass) does not produce - * the same behavior as using RuntimeReflection.register(nullaryConstructor). In - * the first case, the constructor is marked for query purposes only, so this - * if-statement cannot be eliminated. - * - */ - RuntimeReflection.register(nullaryConstructor); - } else { - /* - * If there's no nullary constructor, register it as negative lookup to avoid - * throwing a MissingReflectionRegistrationError at run time. - */ - RuntimeReflection.registerConstructorLookup(providerClass); - } - if (nullaryProviderMethod != null) { - RuntimeReflection.register(nullaryProviderMethod); - } else { - /* - * If there's no declared public provider() method, register it as negative - * lookup to avoid throwing a MissingReflectionRegistrationError at run time. - */ - RuntimeReflection.registerMethodLookup(providerClass, "provider"); - } + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+14/src/java.base/share/classes/java/util/ServiceLoader.java#L745-L793") + public static void registerProviderForRuntimeReflectionAccess(DuringAnalysisAccess access, String provider, Set registeredProviders) { + /* Make provider reflectively instantiable */ + Class providerClass = access.findClassByName(provider); + + if (providerClass == null || providerClass.isArray() || providerClass.isPrimitive()) { + return; + } + FeatureImpl.DuringAnalysisAccessImpl accessImpl = (FeatureImpl.DuringAnalysisAccessImpl) access; + if (!accessImpl.getHostVM().platformSupported(providerClass)) { + return; + } + if (((Inflation) accessImpl.getBigBang()).getAnnotationSubstitutionProcessor().isDeleted(providerClass)) { + /* Disallow services with implementation classes that are marked as @Deleted */ + return; + } + + /* + * Find either a public static provider() method or a nullary constructor (or both). Skip + * providers that do not comply with requirements. + * + * See ServiceLoader#loadProvider and ServiceLoader#findStaticProviderMethod. + */ + Method nullaryProviderMethod = findProviderMethod(providerClass); + Constructor nullaryConstructor = findNullaryConstructor(providerClass); + if (nullaryConstructor != null || nullaryProviderMethod != null) { + RuntimeReflection.register(providerClass); + if (nullaryConstructor != null) { + /* + * Registering a constructor with + * RuntimeReflection.registerConstructorLookup(providerClass) does not produce the + * same behavior as using RuntimeReflection.register(nullaryConstructor). In the + * first case, the constructor is marked for query purposes only, so this + * if-statement cannot be eliminated. + * + */ + RuntimeReflection.register(nullaryConstructor); + } else { + /* + * If there's no nullary constructor, register it as negative lookup to avoid + * throwing a MissingReflectionRegistrationError at run time. + */ + RuntimeReflection.registerConstructorLookup(providerClass); + } + if (nullaryProviderMethod != null) { + RuntimeReflection.register(nullaryProviderMethod); + } else { + /* + * If there's no declared public provider() method, register it as negative lookup + * to avoid throwing a MissingReflectionRegistrationError at run time. + */ + RuntimeReflection.registerMethodLookup(providerClass, "provider"); } - /* - * Register the provider in both cases: when it is JCA-compliant (has a nullary - * constructor or a provider method) or when it lacks both. If neither is present, a - * ServiceConfigurationError will be thrown at runtime, consistent with HotSpot - * behavior. - */ - registeredProviders.add(provider); } + /* + * Register the provider in both cases: when it is JCA-compliant (has a nullary constructor + * or a provider method) or when it lacks both. If neither is present, a + * ServiceConfigurationError will be thrown at runtime, consistent with HotSpot behavior. + */ + registeredProviders.add(provider); + } + + public static void registerProviderForRuntimeResourceAccess(Module module, String serviceProviderName, Set registeredProviders) { if (!registeredProviders.isEmpty()) { - String serviceResourceLocation = "META-INF/services/" + serviceProvider.getName(); - byte[] serviceFileData = registeredProviders.stream().collect(Collectors.joining("\n")).getBytes(StandardCharsets.UTF_8); - RuntimeResourceAccess.addResource(access.getApplicationClassLoader().getUnnamedModule(), serviceResourceLocation, serviceFileData); + String serviceResourceLocation = "META-INF/services/" + serviceProviderName; + byte[] serviceFileData = String.join("\n", registeredProviders).getBytes(StandardCharsets.UTF_8); + RuntimeResourceAccess.addResource(module, serviceResourceLocation, serviceFileData); } } - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+11/src/java.base/share/classes/java/util/ServiceLoader.java#L620-L631") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+14/src/java.base/share/classes/java/util/ServiceLoader.java#L620-L631") private static Constructor findNullaryConstructor(Class providerClass) { Constructor nullaryConstructor = null; try { @@ -268,7 +285,7 @@ private static Constructor findNullaryConstructor(Class providerClass) { return nullaryConstructor; } - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+11/src/java.base/share/classes/java/util/ServiceLoader.java#L583-L612") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+14/src/java.base/share/classes/java/util/ServiceLoader.java#L583-L612") private static Method findProviderMethod(Class providerClass) { Method nullaryProviderMethod = null; try { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java index a6f5fb32c145..e7e3d2f966c8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java @@ -160,7 +160,6 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("com.sun.security.jgss", JDK_CLASS_REASON); rci.initializeAtBuildTime("com.sun.security.cert.internal.x509", JDK_CLASS_REASON); rci.initializeAtBuildTime("com.sun.security.ntlm", JDK_CLASS_REASON); - rci.initializeAtBuildTime("com.sun.security.sasl", JDK_CLASS_REASON); rci.initializeAtBuildTime("java.security", JDK_CLASS_REASON); rci.initializeAtRunTime("sun.security.pkcs11.P11Util", "Cleaner reference"); @@ -182,7 +181,6 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("sun.security.krb5", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.pkcs", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.pkcs10", JDK_CLASS_REASON); - rci.initializeAtBuildTime("sun.security.pkcs11", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.pkcs12", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.provider", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.rsa", JDK_CLASS_REASON); @@ -192,7 +190,6 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("sun.security.util", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.validator", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.x509", JDK_CLASS_REASON); - rci.initializeAtBuildTime("sun.security.smartcardio", JDK_CLASS_REASON); rci.initializeAtBuildTime("com.sun.jndi", JDK_CLASS_REASON); if (Platform.includedIn(Platform.DARWIN.class)) { rci.initializeAtBuildTime("apple.security", JDK_CLASS_REASON); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index c24493f494a0..2b3a624d2406 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -29,7 +29,6 @@ import java.security.Security; import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; @@ -59,11 +58,6 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void duringSetup(final DuringSetupAccess access) { - // we use these (application) classes during Native image build - RuntimeClassInitialization.initializeAtBuildTime(NoOpService.class); - RuntimeClassInitialization.initializeAtBuildTime(NoOpProvider.class); - RuntimeClassInitialization.initializeAtBuildTime(NoOpProviderTwo.class); - // register the service implementation for reflection explicitly, // non-standard services are not processed automatically RuntimeReflection.register(NoOpImpl.class); @@ -71,6 +65,21 @@ public void duringSetup(final DuringSetupAccess access) { } } + /** + * This test ensures that the list of security providers is populated at run time, and not at + * build time. + */ + @Test + public void testSecurityProviderRuntimeRegistration() { + Provider notRegistered = Security.getProvider("no-op-provider"); + Assert.assertNull("Provider is registered.", notRegistered); + + Security.addProvider(new NoOpProvider()); + + Provider registered = Security.getProvider("no-op-provider"); + Assert.assertNotNull("Provider is not registered.", registered); + } + /** * Tests that native-image generation doesn't run into an issue (like NPE) if the application * uses a java.security.Provider.Service which isn't part of the services shipped in the JDK. @@ -80,6 +89,8 @@ public void duringSetup(final DuringSetupAccess access) { */ @Test public void testUnknownSecurityServices() throws Exception { + /* Register the provider at run time. */ + Security.addProvider(new NoOpProvider()); final Provider registered = Security.getProvider("no-op-provider"); Assert.assertNotNull("Provider is not registered", registered); final Object impl = registered.getService("NoOp", "no-op-algo").newInstance(null); @@ -90,6 +101,8 @@ public void testUnknownSecurityServices() throws Exception { @Test public void testAutomaticSecurityServiceRegistration() { try { + /* Register the provider at run time. */ + Security.addProvider(new NoOpProviderTwo()); JCACompliantNoOpService service = JCACompliantNoOpService.getInstance("no-op-algo-two"); Assert.assertNotNull("No service instance was created", service); MatcherAssert.assertThat("Unexpected service implementation class", service, CoreMatchers.instanceOf(JcaCompliantNoOpServiceImpl.class)); From a0210311bf931dfc76da381e054ba1a7fdacab86 Mon Sep 17 00:00:00 2001 From: jovsteva Date: Wed, 14 May 2025 11:37:47 +0200 Subject: [PATCH 2/4] Remove JDK21 code for security provider handling. --- .../svm/core/jdk/SecuritySubstitutions.java | 21 +++++-------------- .../svm/hosted/SecurityServicesFeature.java | 8 +++---- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index ada1340f21c5..888eaf5ec5ec 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -66,7 +66,6 @@ import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.core.common.SuppressFBWarnings; -import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import sun.security.util.Debug; import sun.security.util.SecurityConstants; @@ -471,7 +470,6 @@ final class Target_sun_security_jca_ProviderConfig { * reachable via a security service. */ @Substitute - @SuppressWarnings("fallthrough") @SuppressFBWarnings(value = "DC_DOUBLECHECK", justification = "This double-check is implemented correctly and is intentional.") Provider getProvider() { // volatile variable load @@ -508,6 +506,11 @@ Provider getProvider() { p = support.isSecurityProviderExpected("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null; break; } + case "SunEC": { + // Constructor inside method and then allocate. ModuleSupport to open. + p = support.isSecurityProviderExpected("SunEC", "sun.security.ec.SunEC") ? support.allocateSunECProvider() : null; + break; + } case "Apple", "apple.security.AppleProvider": { // need to use reflection since this class only exists on MacOsx try { @@ -525,20 +528,6 @@ Provider getProvider() { } break; } - case "SunEC": { - if (JavaVersionUtil.JAVA_SPEC > 21) { - // Constructor inside method and then allocate. ModuleSupport to open. - p = support.isSecurityProviderExpected("SunEC", "sun.security.ec.SunEC") ? support.allocateSunECProvider() : null; - break; - } - /* - * On older JDK versions, SunEC was part of the `jdk.crypto.ec` module and was - * allocated via the service loading mechanism, so this fallthrough is - * intentional. On newer JDK versions, SunEC is part of `java.base` and is - * allocated directly. - */ - } - // fall through default: { if (isLoading) { // because this method is synchronized, this can only diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index c14b041abef2..c3020d32a25f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -220,11 +220,9 @@ public void duringSetup(DuringSetupAccess a) { oidTableField = access.findField("sun.security.util.ObjectIdentifier", "oidTable"); oidMapField = access.findField(OIDMap.class, "oidMap"); - if (JavaVersionUtil.JAVA_SPEC > 21) { - ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, SecuritySubstitutions.class, false, "java.base", "sun.security.ec"); - Constructor sunECConstructor = constructor(a, "sun.security.ec.SunEC"); - SecurityProvidersSupport.singleton().setSunECConstructor(sunECConstructor); - } + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, SecuritySubstitutions.class, false, "java.base", "sun.security.ec"); + Constructor sunECConstructor = constructor(a, "sun.security.ec.SunEC"); + SecurityProvidersSupport.singleton().setSunECConstructor(sunECConstructor); Properties securityProperties = SharedSecrets.getJavaSecurityPropertiesAccess().getInitialProperties(); SecurityProvidersSupport.singleton().setSavedInitialSecurityProperties(securityProperties); From adc30fea82433b28f1bff341129e329c08eda8d5 Mon Sep 17 00:00:00 2001 From: jovsteva Date: Wed, 14 May 2025 11:44:22 +0200 Subject: [PATCH 3/4] Remove substitution for Security class for JDK21. --- .../svm/core/jdk/SecuritySubstitutions.java | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index 888eaf5ec5ec..0e9374d9f335 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -26,7 +26,6 @@ import static com.oracle.svm.core.snippets.KnownIntrinsics.readCallerStackPointer; -import java.io.File; import java.lang.reflect.Constructor; import java.net.URL; import java.security.CodeSource; @@ -66,7 +65,6 @@ import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.core.common.SuppressFBWarnings; -import sun.security.util.Debug; import sun.security.util.SecurityConstants; /* @@ -133,41 +131,9 @@ final class Target_java_security_Security { @Alias // @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) // static Properties props; - - @Alias // - private static Properties initialSecurityProperties; - - @Alias // - private static Debug sdebug; - - @Substitute - @TargetElement(onlyWith = JDK21OrEarlier.class) - private static void initialize() { - props = SecurityProvidersSupport.singleton().getSavedInitialSecurityProperties(); - boolean overrideAll = false; - - 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); - } - loadProps(null, extraPropFile, overrideAll); - } - initialSecurityProperties = (Properties) props.clone(); - if (sdebug != null) { - for (String key : props.stringPropertyNames()) { - sdebug.println("Initial security property: " + key + "=" + props.getProperty(key)); - } - } - } - - @Alias - @TargetElement(onlyWith = JDK21OrEarlier.class) - private static native boolean loadProps(File masterFile, String extraPropFile, boolean overrideAll); } -@TargetClass(value = java.security.Security.class, innerClass = "SecPropLoader", onlyWith = JDKLatest.class) +@TargetClass(value = java.security.Security.class, innerClass = "SecPropLoader") final class Target_java_security_Security_SecPropLoader { @Substitute From 32501d81b7a1b37fcfc67134a838a75363b843b7 Mon Sep 17 00:00:00 2001 From: jovsteva Date: Wed, 14 May 2025 12:03:56 +0200 Subject: [PATCH 4/4] Update BasedOnJDKFile annotations. --- .../com/oracle/svm/core/jdk/SecurityProvidersSupport.java | 2 +- .../src/com/oracle/svm/hosted/ServiceLoaderFeature.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 8e314299c6b2..1be72aa6a89e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -45,7 +45,7 @@ import jdk.graal.compiler.api.replacements.Fold; /** - * The class that holds various build-time and runtime structures necessary for security providers. + * The class that holds various build-time and run-time structures necessary for security providers. */ public final class SecurityProvidersSupport { /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index d14b0837c6a1..fbc01b8ae8d5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -201,7 +201,7 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class service registerProviderForRuntimeResourceAccess(access.getApplicationClassLoader().getUnnamedModule(), serviceProvider.getName(), registeredProviders); } - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+14/src/java.base/share/classes/java/util/ServiceLoader.java#L745-L793") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+21/src/java.base/share/classes/java/util/ServiceLoader.java#L745-L793") public static void registerProviderForRuntimeReflectionAccess(DuringAnalysisAccess access, String provider, Set registeredProviders) { /* Make provider reflectively instantiable */ Class providerClass = access.findClassByName(provider); @@ -271,7 +271,7 @@ public static void registerProviderForRuntimeResourceAccess(Module module, Strin } } - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+14/src/java.base/share/classes/java/util/ServiceLoader.java#L620-L631") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+21/src/java.base/share/classes/java/util/ServiceLoader.java#L620-L631") private static Constructor findNullaryConstructor(Class providerClass) { Constructor nullaryConstructor = null; try { @@ -285,7 +285,7 @@ private static Constructor findNullaryConstructor(Class providerClass) { return nullaryConstructor; } - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+14/src/java.base/share/classes/java/util/ServiceLoader.java#L583-L612") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+21/src/java.base/share/classes/java/util/ServiceLoader.java#L583-L612") private static Method findProviderMethod(Class providerClass) { Method nullaryProviderMethod = null; try {