Skip to content

[GR-57827] Initialize security providers at run time. #10143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions substratevm/mx.substratevm/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 run-time 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<String> markedAsNotLoaded = Collections.synchronizedSet(new HashSet<>());

/** Set of fully qualified provider names, required for runtime resource access. */
private final Set<String> 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<String, Object> verifiedSecurityProviders = Collections.synchronizedMap(new HashMap<>());

private Properties savedInitialSecurityProperties;

private Constructor<?> sunECConstructor;

@Platforms(Platform.HOSTED_ONLY.class)
public SecurityProvidersSupport(List<String> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import static com.oracle.svm.core.snippets.KnownIntrinsics.readCallerStackPointer;

import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.security.CodeSource;
Expand All @@ -40,6 +39,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;
Expand All @@ -64,6 +64,7 @@
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.util.ReflectionUtil;

import jdk.graal.compiler.core.common.SuppressFBWarnings;
import sun.security.util.SecurityConstants;

/*
Expand Down Expand Up @@ -125,6 +126,22 @@ 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;
}

@TargetClass(value = java.security.Security.class, innerClass = "SecPropLoader")
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");
Expand Down Expand Up @@ -265,6 +282,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<Object, Object> verificationResults;

@Alias //
Expand All @@ -275,16 +293,11 @@ final class Target_javax_crypto_JceSecurity {
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) //
private static Map<Class<?>, URL> codeBaseCacheRef = new WeakHashMap<>();

@Alias //
@TargetElement //
private static ReferenceQueue<Object> 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) {
Expand All @@ -302,15 +315,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<Object> queue) {
}
}

class JceSecurityAccessor {
private static volatile SecureRandom RANDOM;

Expand Down Expand Up @@ -408,19 +412,110 @@ 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 + ".");
@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 "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 {
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;
}
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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}

Expand Down
Loading
Loading