Skip to content

Commit bcc8d2e

Browse files
Initialize security providers at run time.
1 parent a9b0ed7 commit bcc8d2e

File tree

8 files changed

+394
-290
lines changed

8 files changed

+394
-290
lines changed

substratevm/mx.substratevm/suite.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,12 @@
296296
"sun.reflect.generics.reflectiveObjects",
297297
"sun.reflect.generics.repository",
298298
"sun.reflect.generics.tree",
299+
"sun.security.rsa",
299300
"sun.security.jca",
300301
"sun.security.ssl",
301302
"sun.security.util",
303+
"sun.security.provider",
304+
"com.sun.crypto.provider",
302305
"sun.text.spi",
303306
"sun.util",
304307
"sun.util.locale",
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.oracle.svm.core.jdk;
27+
28+
import java.lang.reflect.Constructor;
29+
import java.lang.reflect.InvocationTargetException;
30+
import java.security.Provider;
31+
import java.util.Collections;
32+
import java.util.HashMap;
33+
import java.util.HashSet;
34+
import java.util.List;
35+
import java.util.Map;
36+
import java.util.Set;
37+
38+
import org.graalvm.nativeimage.ImageSingletons;
39+
import org.graalvm.nativeimage.Platform;
40+
import org.graalvm.nativeimage.Platforms;
41+
42+
import com.oracle.svm.core.util.VMError;
43+
44+
import jdk.graal.compiler.api.replacements.Fold;
45+
46+
/**
47+
* The class that holds various build-time and runtime structures necessary for security providers.
48+
*/
49+
public final class SecurityProvidersSupport {
50+
/**
51+
* A set of providers to be loaded using the service-loading technique at runtime, but not
52+
* discoverable at build-time when processing services in the feature (see
53+
* ServiceLoaderFeature#handleServiceClassIsReachable). This occurs when the user does not
54+
* explicitly request a provider, but the provider is discovered via static analysis from a
55+
* JCA-compliant security service used by the user's code (see
56+
* SecurityServicesFeature#registerServiceReachabilityHandlers).
57+
*/
58+
@Platforms(Platform.HOSTED_ONLY.class)//
59+
private final Set<String> markedAsNotLoaded = Collections.synchronizedSet(new HashSet<>());
60+
61+
/** Set of fully qualified provider names, required for runtime resource access. */
62+
private final Set<String> userRequestedSecurityProviders = Collections.synchronizedSet(new HashSet<>());
63+
64+
/**
65+
* A map of providers, identified by their names (see {@link Provider#getName()}), and the
66+
* results of their verification (see javax.crypto.JceSecurity#getVerificationResult). This
67+
* structure is used instead of the (see javax.crypto.JceSecurity#verifyingProviders) map to
68+
* avoid keeping provider objects in the image heap.
69+
*/
70+
private final Map<String, Object> verifiedSecurityProviders = Collections.synchronizedMap(new HashMap<>());
71+
72+
private Constructor<?> sunECConstructor;
73+
74+
@Platforms(Platform.HOSTED_ONLY.class)
75+
public SecurityProvidersSupport(List<String> userRequestedSecurityProviders) {
76+
this.userRequestedSecurityProviders.addAll(userRequestedSecurityProviders);
77+
}
78+
79+
@Fold
80+
public static SecurityProvidersSupport singleton() {
81+
return ImageSingletons.lookup(SecurityProvidersSupport.class);
82+
}
83+
84+
@Platforms(Platform.HOSTED_ONLY.class)
85+
public void addVerifiedSecurityProvider(String key, Object value) {
86+
verifiedSecurityProviders.put(key, value);
87+
}
88+
89+
public Object getSecurityProviderVerificationResult(String key) {
90+
return verifiedSecurityProviders.get(key);
91+
}
92+
93+
@Platforms(Platform.HOSTED_ONLY.class)
94+
public void markSecurityProviderAsNotLoaded(String provider) {
95+
markedAsNotLoaded.add(provider);
96+
}
97+
98+
@Platforms(Platform.HOSTED_ONLY.class)
99+
public boolean isSecurityProviderNotLoaded(String provider) {
100+
return markedAsNotLoaded.contains(provider);
101+
}
102+
103+
@Platforms(Platform.HOSTED_ONLY.class)
104+
public boolean isUserRequestedSecurityProvider(String provider) {
105+
return userRequestedSecurityProviders.contains(provider);
106+
}
107+
108+
/**
109+
* Returns {@code true} if the provider, identified by either its name (e.g., SUN) or fully
110+
* qualified name (e.g., sun.security.provider.Sun), is either user-requested or reachable via a
111+
* security service.
112+
*/
113+
public boolean isSecurityProviderExpected(String providerName, String providerFQName) {
114+
return verifiedSecurityProviders.containsKey(providerName) || userRequestedSecurityProviders.contains(providerFQName);
115+
}
116+
117+
@Platforms(Platform.HOSTED_ONLY.class)
118+
public void setSunECConstructor(Constructor<?> sunECConstructor) {
119+
this.sunECConstructor = sunECConstructor;
120+
}
121+
122+
public Provider allocateSunECProvider() {
123+
try {
124+
return (Provider) sunECConstructor.newInstance();
125+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
126+
throw VMError.shouldNotReachHere("The SunEC constructor is not present.");
127+
}
128+
}
129+
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java

Lines changed: 115 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

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

29-
import java.lang.ref.ReferenceQueue;
3029
import java.lang.reflect.Constructor;
3130
import java.net.URL;
3231
import java.security.AccessControlContext;
@@ -70,6 +69,8 @@
7069
import com.oracle.svm.core.util.VMError;
7170
import com.oracle.svm.util.ReflectionUtil;
7271

72+
import jdk.graal.compiler.core.common.SuppressFBWarnings;
73+
import jdk.graal.compiler.serviceprovider.JavaVersionUtil;
7374
import sun.security.util.SecurityConstants;
7475

7576
/*
@@ -358,6 +359,7 @@ final class Target_javax_crypto_JceSecurity {
358359
// value == PROVIDER_VERIFIED is successfully verified
359360
// value is failure cause Exception in error case
360361
@Alias //
362+
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) //
361363
private static Map<Object, Object> verificationResults;
362364

363365
@Alias //
@@ -368,16 +370,11 @@ final class Target_javax_crypto_JceSecurity {
368370
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) //
369371
private static Map<Class<?>, URL> codeBaseCacheRef = new WeakHashMap<>();
370372

371-
@Alias //
372-
@TargetElement //
373-
private static ReferenceQueue<Object> queue;
374-
375373
@Substitute
376374
static Exception getVerificationResult(Provider p) {
377375
/* Start code block copied from original method. */
378376
/* The verification results map key is an identity wrapper object. */
379-
Object key = new Target_javax_crypto_JceSecurity_WeakIdentityWrapper(p, queue);
380-
Object o = verificationResults.get(key);
377+
Object o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p.getName());
381378
if (o == PROVIDER_VERIFIED) {
382379
return null;
383380
} else if (o != null) {
@@ -395,15 +392,6 @@ static Exception getVerificationResult(Provider p) {
395392
}
396393
}
397394

398-
@TargetClass(className = "javax.crypto.JceSecurity", innerClass = "WeakIdentityWrapper")
399-
@SuppressWarnings({"unused"})
400-
final class Target_javax_crypto_JceSecurity_WeakIdentityWrapper {
401-
402-
@Alias //
403-
Target_javax_crypto_JceSecurity_WeakIdentityWrapper(Provider obj, ReferenceQueue<Object> queue) {
404-
}
405-
}
406-
407395
class JceSecurityAccessor {
408396
private static volatile SecureRandom RANDOM;
409397

@@ -577,19 +565,120 @@ final class Target_sun_security_jca_ProviderConfig {
577565
@Alias //
578566
private String provName;
579567

568+
@Alias//
569+
private static sun.security.util.Debug debug;
570+
571+
@Alias//
572+
private Provider provider;
573+
574+
@Alias//
575+
private boolean isLoading;
576+
577+
@Alias//
578+
private int tries;
579+
580+
@Alias
581+
private native Provider doLoadProvider();
582+
583+
@Alias
584+
private native boolean shouldLoad();
585+
580586
/**
581-
* All security providers used in a native-image must be registered during image build time. At
582-
* runtime, we shouldn't have a call to doLoadProvider. However, this method is still reachable
583-
* at runtime, and transitively includes other types in the image, among which is
584-
* sun.security.jca.ProviderConfig.ProviderLoader. This class contains a static field with a
585-
* cache of providers loaded during the image build. The contents of this cache can vary even
586-
* when building the same image due to the way services are loaded on Java 11. This cache can
587-
* increase the final image size substantially (if it contains, for example,
588-
* {@code org.jcp.xml.dsig.internal.dom.XMLDSigRI}.
587+
* The `entrypoint` for allocating security providers at runtime. The implementation is copied
588+
* from the JDK with a small tweak to filter out providers that are neither user-requested nor
589+
* reachable via a security service.
589590
*/
590591
@Substitute
591-
private Provider doLoadProvider() {
592-
throw VMError.unsupportedFeature("Cannot load new security provider at runtime: " + provName + ".");
592+
@SuppressWarnings("fallthrough")
593+
@SuppressFBWarnings(value = "DC_DOUBLECHECK", justification = "This double-check is implemented correctly and is intentional.")
594+
Provider getProvider() {
595+
// volatile variable load
596+
Provider p = provider;
597+
if (p != null) {
598+
return p;
599+
}
600+
// DCL
601+
synchronized (this) {
602+
p = provider;
603+
if (p != null) {
604+
return p;
605+
}
606+
if (!shouldLoad()) {
607+
return null;
608+
}
609+
610+
// Create providers which are in java.base directly
611+
SecurityProvidersSupport support = SecurityProvidersSupport.singleton();
612+
switch (provName) {
613+
case "SUN", "sun.security.provider.Sun": {
614+
p = support.isSecurityProviderExpected("SUN", "sun.security.provider.Sun") ? new sun.security.provider.Sun() : null;
615+
break;
616+
}
617+
case "SunRsaSign", "sun.security.rsa.SunRsaSign": {
618+
p = support.isSecurityProviderExpected("SunRsaSign", "sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : null;
619+
break;
620+
}
621+
case "SunJCE", "com.sun.crypto.provider.SunJCE": {
622+
p = support.isSecurityProviderExpected("SunJCE", "com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : null;
623+
break;
624+
}
625+
case "SunJSSE": {
626+
p = support.isSecurityProviderExpected("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null;
627+
break;
628+
}
629+
case "Apple", "apple.security.AppleProvider": {
630+
// need to use reflection since this class only exists on MacOsx
631+
try {
632+
Class<?> c = Class.forName("apple.security.AppleProvider");
633+
if (Provider.class.isAssignableFrom(c)) {
634+
@SuppressWarnings("deprecation")
635+
Object newInstance = c.newInstance();
636+
p = (Provider) newInstance;
637+
}
638+
} catch (Exception ex) {
639+
if (debug != null) {
640+
debug.println("Error loading provider Apple");
641+
ex.printStackTrace();
642+
}
643+
}
644+
break;
645+
}
646+
case "SunEC": {
647+
if (JavaVersionUtil.JAVA_SPEC > 21) {
648+
// Constructor inside method and then allocate. ModuleSupport to open.
649+
p = support.isSecurityProviderExpected("SunEC", "sun.security.ec.SunEC") ? support.allocateSunECProvider() : null;
650+
break;
651+
}
652+
/*
653+
* On older JDK versions, SunEC was part of the `jdk.crypto.ec` module and was
654+
* allocated via the service loading mechanism, so this fallthrough is
655+
* intentional. On newer JDK versions, SunEC is part of `java.base` and is
656+
* allocated directly.
657+
*/
658+
}
659+
// fall through
660+
default: {
661+
if (isLoading) {
662+
// because this method is synchronized, this can only
663+
// happen if there is recursion.
664+
if (debug != null) {
665+
debug.println("Recursion loading provider: " + this);
666+
new Exception("Call trace").printStackTrace();
667+
}
668+
return null;
669+
}
670+
try {
671+
isLoading = true;
672+
tries++;
673+
p = doLoadProvider();
674+
} finally {
675+
isLoading = false;
676+
}
677+
}
678+
}
679+
provider = p;
680+
}
681+
return p;
593682
}
594683
}
595684

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,6 @@ public void afterRegistration(AfterRegistrationAccess access) {
8989
*/
9090
RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class);
9191
rci.initializeAtBuildTime("sun.security.util.UntrustedCertificates", "Required for TrustStoreManager");
92-
/*
93-
* All security providers must be registered (and initialized) at buildtime (see
94-
* SecuritySubstitutions.java). XMLDSigRI is used for validating XML Signatures from
95-
* certificate files while generating X509Certificates.
96-
*/
97-
rci.initializeAtBuildTime("org.jcp.xml.dsig.internal.dom.XMLDSigRI", "Required for TrustStoreManager");
98-
rci.initializeAtBuildTime("org.jcp.xml.dsig.internal.dom.XMLDSigRI$ProviderService", "Required for TrustStoreManager");
9992
}
10093
}
10194

0 commit comments

Comments
 (0)