Skip to content

Commit

Permalink
Initialize security providers at run time.
Browse files Browse the repository at this point in the history
  • Loading branch information
jovanstevanovic committed Dec 3, 2024
1 parent 36c69ed commit 6456842
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 271 deletions.
3 changes: 3 additions & 0 deletions substratevm/mx.substratevm/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,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,129 @@
/*
* 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.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<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 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.");
}
}
}
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.lang.reflect.InvocationTargetException;
import java.net.URL;
Expand Down Expand Up @@ -69,6 +68,8 @@
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.SecurityConstants;

/*
Expand Down Expand Up @@ -328,6 +329,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 @@ -338,16 +340,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 @@ -365,15 +362,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 @@ -547,19 +535,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;
}
}

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

0 comments on commit 6456842

Please sign in to comment.