Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,15 @@ public final class SecurityProvidersSupport {
private final EconomicSet<String> userRequestedSecurityProviders = EconomicSet.create();

/**
* 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.
* A map of providers, identified by their implementation classes, 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 EconomicMap<String, Object> verifiedSecurityProviders = ImageHeapMap.create("verifiedSecurityProviders");
private final EconomicMap<Class<?>, Object> verifiedSecurityProviders = ImageHeapMap.create("verifiedSecurityProviders");

/** Set of verified provider class names, used when providers are requested by name. */
private final EconomicSet<String> verifiedSecurityProviderClassNames = EconomicSet.create();

private Properties savedInitialSecurityProperties;

Expand All @@ -95,12 +98,13 @@ public static SecurityProvidersSupport singleton() {
}

@Platforms(Platform.HOSTED_ONLY.class)
public void addVerifiedSecurityProvider(String key, Object verificationResult) {
verifiedSecurityProviders.put(key, verificationResult);
public void addVerifiedSecurityProvider(Class<?> providerClass, Object verificationResult) {
verifiedSecurityProviders.put(providerClass, verificationResult);
verifiedSecurityProviderClassNames.add(providerClass.getName());
}

public Object getSecurityProviderVerificationResult(String key) {
return verifiedSecurityProviders.get(key);
public Object getSecurityProviderVerificationResult(Class<?> providerClass) {
return verifiedSecurityProviders.get(providerClass);
}

@Platforms(Platform.HOSTED_ONLY.class)
Expand All @@ -119,12 +123,11 @@ public boolean isUserRequestedSecurityProvider(String 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.
* Returns {@code true} if the provider, identified by its fully qualified name (e.g.,
* sun.security.provider.Sun), is either user-requested or reachable via a security service.
*/
public boolean isSecurityProviderRequested(String providerName, String providerFQName) {
return verifiedSecurityProviders.containsKey(providerName) || userRequestedSecurityProviders.contains(providerFQName);
public boolean isSecurityProviderRequested(String providerFQName) {
return verifiedSecurityProviderClassNames.contains(providerFQName) || userRequestedSecurityProviders.contains(providerFQName);
}

@Platforms(Platform.HOSTED_ONLY.class)
Expand Down Expand Up @@ -176,7 +179,7 @@ public static String getBuiltInProviderClassName(String provName) {
public boolean isMissingBuiltInProvider(String provName) {
String providerName = getBuiltInProviderName(provName);
String providerFQName = getBuiltInProviderClassName(provName);
return providerName != null && !isSecurityProviderRequested(providerName, providerFQName);
return providerName != null && !isSecurityProviderRequested(providerFQName);
}

public static SecurityException missingBuiltInProvider(String provName) {
Expand All @@ -194,15 +197,15 @@ public static SecurityException missingBuiltInProvider(String provName) {
public Provider loadBuiltInProvider(String provName, Debug debug) {
return switch (provName) {
case "SUN", "sun.security.provider.Sun" ->
isSecurityProviderRequested("SUN", "sun.security.provider.Sun") ? new sun.security.provider.Sun() : null;
isSecurityProviderRequested("sun.security.provider.Sun") ? new sun.security.provider.Sun() : null;
case "SunRsaSign", "sun.security.rsa.SunRsaSign" ->
isSecurityProviderRequested("SunRsaSign", "sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : null;
isSecurityProviderRequested("sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : null;
case "SunJCE", "com.sun.crypto.provider.SunJCE" ->
isSecurityProviderRequested("SunJCE", "com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : null;
isSecurityProviderRequested("com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : null;
case "SunJSSE", "sun.security.ssl.SunJSSE" ->
isSecurityProviderRequested("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null;
isSecurityProviderRequested("sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null;
case "SunEC", "sun.security.ec.SunEC" ->
isSecurityProviderRequested("SunEC", "sun.security.ec.SunEC") ? allocateSunECProvider() : null;
isSecurityProviderRequested("sun.security.ec.SunEC") ? allocateSunECProvider() : null;
case "Apple", "apple.security.AppleProvider" -> {
try {
Class<?> c = Class.forName("apple.security.AppleProvider");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,29 @@
package com.oracle.svm.core.jdk.runtimeinit;

import java.net.URL;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetElement;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.hub.RuntimeClassLoading;
import com.oracle.svm.core.jdk.SecurityProvidersInitializedAtRunTime;
import com.oracle.svm.core.jdk.SecurityProvidersSupport;
import com.oracle.svm.core.jdk.UnsupportedFeatureError;
import com.oracle.svm.shared.util.BasedOnJDKFile;

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

@TargetClass(value = java.security.Security.class, onlyWith = SecurityProvidersInitializedAtRunTime.class)
final class Target_java_security_Security {
Expand Down Expand Up @@ -74,32 +83,60 @@ private static void loadMaster() {
@SuppressWarnings({"unused"})
final class Target_javax_crypto_JceSecurity {

// Checkstyle: stop
@Alias //
static Object PROVIDER_VERIFIED;
// Checkstyle: resume

@Alias //
static Debug debug;

/*
* Map<Provider, ?> of providers that have already been verified. A value of PROVIDER_VERIFIED
* indicates successful verification. Otherwise, the value is the Exception that caused the
* verification to fail.
*/
@Alias //
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) //
private static Map<Object, Object> verificationResults;
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClass = ConcurrentHashMap.class) //
static Map<Object, Object> verificationResults;

@Alias //
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) //
private static Map<Provider, Object> verifyingProviders;
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClass = IdentityHashMap.class) //
static Map<Provider, Object> verifyingProviders;

@Alias //
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) //
private static Map<Class<?>, URL> codeBaseCacheRef = new WeakHashMap<>();

@Alias //
@TargetElement //
static java.lang.ref.ReferenceQueue<Object> queue;

@Alias //
static native void expungeStaleWrappers();

@Alias //
static native URL getCodeBase(Class<?> clazz);

@Alias //
static native void verifyProvider(URL codeBase, Provider p) throws Exception;

@Substitute
static Exception getVerificationResult(Provider p) {
/* The verification results map key is an identity wrapper object. */
Object o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p.getName());
/* Provider verification is tied to the provider implementation class. */
Object o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p.getClass());
if (o == Boolean.TRUE) {
return null;
} else if (o != null) {
return (Exception) o;
}
if (RuntimeClassLoading.isSupported() && DynamicHub.fromClass(p.getClass()).isRuntimeLoaded()) {
/*
* Providers loaded by Crema at run time cannot have a build-time verification result,
* so they use the JDK verifier path and its provider-identity cache.
*/
return JceSecurityRuntimeLoadedProviderVerifier.getVerificationResult(p);
}
/*
* If the verification result is not found in the verificationResults map, HotSpot will
* attempt to verify the provider. This requires accessing the code base, which isn't
Expand All @@ -114,6 +151,62 @@ static Exception getVerificationResult(Provider p) {
}
}

final class JceSecurityRuntimeLoadedProviderVerifier {

private JceSecurityRuntimeLoadedProviderVerifier() {
}

/** Verifies runtime-loaded providers using the JDK verifier algorithm. */
static Exception getVerificationResult(Provider p) {
Target_javax_crypto_JceSecurity.expungeStaleWrappers();
Object pKey = new Target_javax_crypto_JceSecurity_WeakIdentityWrapper(p, Target_javax_crypto_JceSecurity.queue);
try {
Object o = Target_javax_crypto_JceSecurity.verificationResults.computeIfAbsent(pKey, new Function<>() {
@Override
public Object apply(Object key) {
if (Target_javax_crypto_JceSecurity.verifyingProviders.get(p) != null) {
throw new IllegalStateException();
}
Object result;
try {
Target_javax_crypto_JceSecurity.verifyingProviders.put(p, Boolean.FALSE);
URL providerURL = Target_javax_crypto_JceSecurity.getCodeBase(p.getClass());
Target_javax_crypto_JceSecurity.verifyProvider(providerURL, p);
result = Target_javax_crypto_JceSecurity.PROVIDER_VERIFIED;
} catch (UnsupportedFeatureError e) {
/*
* OracleJDK can route provider verification through JarVerifier, which is
* intentionally unsupported in Native Image. OpenJDK's provider verifier is
* open for this case, so runtime-loaded providers fall back to that behavior.
*/
result = Target_javax_crypto_JceSecurity.PROVIDER_VERIFIED;
} catch (Exception e) {
result = e;
} finally {
Target_javax_crypto_JceSecurity.verifyingProviders.remove(p);
}
if (Target_javax_crypto_JceSecurity.debug != null) {
Target_javax_crypto_JceSecurity.debug.println("Provider " + p.getName() + " verification result: " + result);
}
return result;
}
});
return o == Target_javax_crypto_JceSecurity.PROVIDER_VERIFIED ? null : (Exception) o;
} catch (IllegalStateException ise) {
return new NoSuchProviderException("Recursion during verification");
}
}
}

@TargetClass(className = "javax.crypto.JceSecurity", innerClass = "WeakIdentityWrapper", onlyWith = SecurityProvidersInitializedAtRunTime.class)
@SuppressWarnings({"unused"})
final class Target_javax_crypto_JceSecurity_WeakIdentityWrapper {

@Alias //
Target_javax_crypto_JceSecurity_WeakIdentityWrapper(Provider obj, java.lang.ref.ReferenceQueue<Object> queue) {
}
}

@TargetClass(className = "sun.security.jca.ProviderConfig", onlyWith = SecurityProvidersInitializedAtRunTime.class)
@SuppressWarnings({"unused", "static-method"})
final class Target_sun_security_jca_ProviderConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,16 +844,15 @@ private void registerProvider(DuringAnalysisAccess access, Provider provider) {
* Exception, in case of failure. Null is interpreted as Boolean.TRUE at
* runtime, signifying successful verification.
*/
String providerName = provider.getName();
String providerFQName = provider.getClass().getName();
SecurityProvidersSupport support = SecurityProvidersSupport.singleton();
support.addVerifiedSecurityProvider(providerName, result instanceof Exception ? result : Boolean.TRUE);
support.addVerifiedSecurityProvider(provider.getClass(), 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<String> registeredProviders = new HashSet<>(); // noEconomicSet(unimplemented)
ServiceLoaderFeature.registerProviderForRuntimeReflectionAccess(access, providerFQName, registeredProviders);
Expand Down
Loading