Skip to content

Commit bd3008c

Browse files
[GR-57827] Move the initialization of security providers from build time to run time and place it behind the --future-defaults flag.
PullRequest: graal/18846
2 parents a9e11af + af69098 commit bd3008c

File tree

11 files changed

+705
-203
lines changed

11 files changed

+705
-203
lines changed

docs/reference-manual/native-image/JCASecurityServices.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,20 @@ The report will detail all registered service classes, the API methods that trig
3636

3737
> Note: The `--enable-all-security-services` option is now deprecated and it will be removed in a future release.
3838
39+
## Provider Initialization
40+
41+
Currently security providers are initialized at build time.
42+
To move their initialization to run time, use the option `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk`.
43+
Provider verification will still occur at build time.
44+
Run-time initialization of security providers helps reduce image heap size.
45+
To move their initialization to run time, you can use the flag `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk`.
46+
3947
## Provider Registration
4048

4149
The `native-image` builder captures the list of providers and their preference order from the underlying JVM.
4250
The provider order is specified in the `java.security` file under `<java-home>/conf/security/java.security`.
43-
New security providers cannot be registered at run time; all providers must be statically configured at executable build time.
51+
New security providers cannot be registered at run time by default (see the section above); all providers must be statically configured at executable build time.
52+
If the user specifies `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk` to move providers initialization to run time, then a specific properties file can be used via the command line option `-Djava.security.properties=<path>`.
4453

4554
## Providers Reordering at Run Time
4655

@@ -52,6 +61,9 @@ Security.removeProvider("BC");
5261
Security.insertProviderAt(bcProvider, 1);
5362
```
5463

64+
If `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk` is enabled, the list of providers is constructed at run time.
65+
The same approach to manipulating providers can then be used.
66+
5567
## SecureRandom
5668

5769
The `SecureRandom` implementations open the `/dev/random` and `/dev/urandom` files which are used as sources.

substratevm/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ This changelog summarizes major changes to GraalVM Native Image.
2828
* (GR-47881) Remove the total number of loaded types, fields, and methods from the build output, deprecated these metrics in the build output schema, and removed already deprecated build output metrics.
2929
* (GR-64619) Missing registration errors are now subclasses of `LinkageError`
3030
* (GR-63591) Resource bundle registration is now included as part of the `"resources"` section of _reachability-metadata.json_. When this is the case, the bundle name is specified using the `"bundle"` field.
31+
* (GR-57827) Move the initialization of security providers from build time to runtime.
32+
* (GR-57827) Security providers can now be initialized at run time (instead of build time) when using the option `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk`.
33+
Run-time initialization of security providers helps reduce image heap size by avoiding unnecessary objects inclusion.
3134

3235
## GraalVM for JDK 24 (Internal Version 24.2.0)
3336
* (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.

substratevm/mx.substratevm/suite.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@
352352
],
353353
"requiresConcealed" : {
354354
"java.base" : [
355+
"com.sun.crypto.provider",
355356
"sun.invoke.util",
356357
"sun.net",
357358
"sun.net.www",
@@ -362,6 +363,8 @@
362363
"sun.reflect.generics.repository",
363364
"sun.reflect.generics.tree",
364365
"sun.security.jca",
366+
"sun.security.provider",
367+
"sun.security.rsa",
365368
"sun.security.ssl",
366369
"sun.security.util",
367370
"sun.text.spi",
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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.List;
32+
import java.util.Properties;
33+
import java.util.Set;
34+
import java.util.concurrent.ConcurrentHashMap;
35+
36+
import org.graalvm.collections.EconomicMap;
37+
import org.graalvm.collections.EconomicSet;
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.ImageHeapMap;
43+
import com.oracle.svm.core.util.VMError;
44+
45+
import jdk.graal.compiler.api.replacements.Fold;
46+
import sun.security.util.Debug;
47+
48+
/**
49+
* The class that holds various build-time and run-time structures necessary for security providers,
50+
* but only in case they are initialized at run time (see the <a href=
51+
* "../../../../../../../../../../../../docs/reference-manual/native-image/JCASecurityServices.md">
52+
* JCA Security Services documentation</a> for details).
53+
*/
54+
public final class SecurityProvidersSupport {
55+
/**
56+
* A set of providers to be loaded using the service-loading technique at runtime, but not
57+
* discoverable at build-time when processing services in the feature (see
58+
* ServiceLoaderFeature#handleServiceClassIsReachable). This occurs when the user does not
59+
* explicitly request a provider, but the provider is discovered via static analysis from a
60+
* JCA-compliant security service used by the user's code (see
61+
* SecurityServicesFeature#registerServiceReachabilityHandlers).
62+
*/
63+
@Platforms(Platform.HOSTED_ONLY.class)//
64+
private final Set<String> markedAsNotLoaded = ConcurrentHashMap.newKeySet();
65+
66+
/** Set of fully qualified provider names, required for runtime resource access. */
67+
private final EconomicSet<String> userRequestedSecurityProviders = EconomicSet.create();
68+
69+
/**
70+
* A map of providers, identified by their names (see {@link Provider#getName()}), and the
71+
* results of their verification (see javax.crypto.JceSecurity#getVerificationResult). This
72+
* structure is used instead of the (see javax.crypto.JceSecurity#verifyingProviders) map to
73+
* avoid keeping provider objects in the image heap.
74+
*/
75+
private final EconomicMap<String, Object> verifiedSecurityProviders = ImageHeapMap.create("verifiedSecurityProviders");
76+
77+
private Properties savedInitialSecurityProperties;
78+
79+
private Constructor<?> sunECConstructor;
80+
81+
@Platforms(Platform.HOSTED_ONLY.class)
82+
public SecurityProvidersSupport(List<String> userRequestedSecurityProviders) {
83+
this.userRequestedSecurityProviders.addAll(userRequestedSecurityProviders);
84+
}
85+
86+
@Fold
87+
public static SecurityProvidersSupport singleton() {
88+
return ImageSingletons.lookup(SecurityProvidersSupport.class);
89+
}
90+
91+
@Platforms(Platform.HOSTED_ONLY.class)
92+
public void addVerifiedSecurityProvider(String key, Object verificationResult) {
93+
verifiedSecurityProviders.put(key, verificationResult);
94+
}
95+
96+
public Object getSecurityProviderVerificationResult(String key) {
97+
return verifiedSecurityProviders.get(key);
98+
}
99+
100+
@Platforms(Platform.HOSTED_ONLY.class)
101+
public void markSecurityProviderAsNotLoaded(String provider) {
102+
markedAsNotLoaded.add(provider);
103+
}
104+
105+
@Platforms(Platform.HOSTED_ONLY.class)
106+
public boolean isSecurityProviderNotLoaded(String provider) {
107+
return markedAsNotLoaded.contains(provider);
108+
}
109+
110+
@Platforms(Platform.HOSTED_ONLY.class)
111+
public boolean isUserRequestedSecurityProvider(String provider) {
112+
return userRequestedSecurityProviders.contains(provider);
113+
}
114+
115+
/**
116+
* Returns {@code true} if the provider, identified by either its name (e.g., SUN) or fully
117+
* qualified name (e.g., sun.security.provider.Sun), is either user-requested or reachable via a
118+
* security service.
119+
*/
120+
public boolean isSecurityProviderRequested(String providerName, String providerFQName) {
121+
return verifiedSecurityProviders.containsKey(providerName) || userRequestedSecurityProviders.contains(providerFQName);
122+
}
123+
124+
@Platforms(Platform.HOSTED_ONLY.class)
125+
public void setSunECConstructor(Constructor<?> sunECConstructor) {
126+
this.sunECConstructor = sunECConstructor;
127+
}
128+
129+
public Provider allocateSunECProvider() {
130+
try {
131+
return (Provider) sunECConstructor.newInstance();
132+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
133+
throw VMError.shouldNotReachHere("The SunEC constructor is not present.");
134+
}
135+
}
136+
137+
@Platforms(Platform.HOSTED_ONLY.class)
138+
public void setSavedInitialSecurityProperties(Properties savedSecurityProperties) {
139+
this.savedInitialSecurityProperties = savedSecurityProperties;
140+
}
141+
142+
public Properties getSavedInitialSecurityProperties() {
143+
return savedInitialSecurityProperties;
144+
}
145+
146+
public Provider loadBuiltInProvider(String provName, Debug debug) {
147+
return switch (provName) {
148+
case "SUN", "sun.security.provider.Sun" ->
149+
isSecurityProviderRequested("SUN", "sun.security.provider.Sun") ? new sun.security.provider.Sun() : null;
150+
case "SunRsaSign", "sun.security.rsa.SunRsaSign" ->
151+
isSecurityProviderRequested("SunRsaSign", "sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : null;
152+
case "SunJCE", "com.sun.crypto.provider.SunJCE" ->
153+
isSecurityProviderRequested("SunJCE", "com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : null;
154+
case "SunJSSE" ->
155+
isSecurityProviderRequested("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null;
156+
case "SunEC" ->
157+
isSecurityProviderRequested("SunEC", "sun.security.ec.SunEC") ? allocateSunECProvider() : null;
158+
case "Apple", "apple.security.AppleProvider" -> {
159+
try {
160+
Class<?> c = Class.forName("apple.security.AppleProvider");
161+
if (Provider.class.isAssignableFrom(c)) {
162+
yield (Provider) c.getDeclaredConstructor().newInstance();
163+
}
164+
} catch (Exception ex) {
165+
if (debug != null) {
166+
debug.println("Error loading provider Apple");
167+
ex.printStackTrace();
168+
}
169+
}
170+
yield null;
171+
}
172+
default -> null;
173+
};
174+
}
175+
176+
public static boolean isBuiltInProvider(String provName) {
177+
return switch (provName) {
178+
case "SUN", "sun.security.provider.Sun",
179+
"SunRsaSign", "sun.security.rsa.SunRsaSign",
180+
"SunJCE", "com.sun.crypto.provider.SunJCE",
181+
"SunJSSE",
182+
"SunEC",
183+
"Apple", "apple.security.AppleProvider" ->
184+
true;
185+
default -> false;
186+
};
187+
}
188+
}

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

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import java.security.Policy;
3737
import java.security.ProtectionDomain;
3838
import java.security.Provider;
39-
import java.security.SecureRandom;
4039
import java.util.List;
4140
import java.util.Map;
4241
import java.util.Objects;
@@ -244,26 +243,28 @@ private static void setJavaHome(String newJavaHome) {
244243
}
245244
}
246245

247-
@TargetClass(className = "javax.crypto.JceSecurity")
246+
/**
247+
* The {@code javax.crypto.JceSecurity#verificationResults} cache is initialized by the
248+
* SecurityServicesFeature at build time, for all registered providers. The cache is used by
249+
* {@code javax.crypto.JceSecurity#canUseProvider} at run time to check whether a provider is
250+
* properly signed and can be used by JCE. It does that via jar verification which we cannot
251+
* support.
252+
*/
253+
@TargetClass(className = "javax.crypto.JceSecurity", onlyWith = JDKInitializedAtBuildTime.class)
248254
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+27/src/java.base/share/classes/javax/crypto/JceSecurity.java.template")
249255
@SuppressWarnings({"unused"})
250256
final class Target_javax_crypto_JceSecurity {
251257

252-
/*
253-
* The JceSecurity.verificationResults cache is initialized by the SecurityServicesFeature at
254-
* build time, for all registered providers. The cache is used by JceSecurity.canUseProvider()
255-
* at runtime to check whether a provider is properly signed and can be used by JCE. It does
256-
* that via jar verification which we cannot support.
257-
*/
258-
259258
// Checkstyle: stop
260259
@Alias //
261260
private static Object PROVIDER_VERIFIED;
262261
// Checkstyle: resume
263262

264-
// Map<Provider,?> of the providers we already have verified
265-
// value == PROVIDER_VERIFIED is successfully verified
266-
// value is failure cause Exception in error case
263+
/*
264+
* Map<Provider, ?> of providers that have already been verified. A value of PROVIDER_VERIFIED
265+
* indicates successful verification. Otherwise, the value is the Exception that caused the
266+
* verification to fail.
267+
*/
267268
@Alias //
268269
private static Map<Object, Object> verificationResults;
269270

@@ -281,7 +282,6 @@ final class Target_javax_crypto_JceSecurity {
281282

282283
@Substitute
283284
static Exception getVerificationResult(Provider p) {
284-
/* Start code block copied from original method. */
285285
/* The verification results map key is an identity wrapper object. */
286286
Object key = new Target_javax_crypto_JceSecurity_WeakIdentityWrapper(p, queue);
287287
Object o = verificationResults.get(key);
@@ -290,19 +290,20 @@ static Exception getVerificationResult(Provider p) {
290290
} else if (o != null) {
291291
return (Exception) o;
292292
}
293-
/* End code block copied from original method. */
294293
/*
295-
* If the verification result is not found in the verificationResults map JDK proceeds to
296-
* verify it. That requires accessing the code base which we don't support. The substitution
297-
* for getCodeBase() would be enough to take care of this too, but substituting
298-
* getVerificationResult() allows for a better error message.
294+
* If the verification result is not found in the verificationResults map, HotSpot will
295+
* attempt to verify the provider. This requires accessing the code base, which isn't
296+
* supported in Native Image, so we need to fail. We could either fail here or substitute
297+
* getCodeBase() and fail there, but handling it here is a cleaner approach.
299298
*/
300-
throw VMError.unsupportedFeature("Trying to verify a provider that was not registered at build time: " + p + ". " +
301-
"All providers must be registered and verified in the Native Image builder. ");
299+
throw new SecurityException(
300+
"Attempted to verify a provider that was not registered at build time: " + p + ". " +
301+
"All security providers must be registered and verified during native image generation. " +
302+
"Try adding the option: -H:AdditionalSecurityProviders=" + p + " and rebuild the image.");
302303
}
303304
}
304305

305-
@TargetClass(className = "javax.crypto.JceSecurity", innerClass = "WeakIdentityWrapper")
306+
@TargetClass(className = "javax.crypto.JceSecurity", innerClass = "WeakIdentityWrapper", onlyWith = JDKInitializedAtBuildTime.class)
306307
@SuppressWarnings({"unused"})
307308
final class Target_javax_crypto_JceSecurity_WeakIdentityWrapper {
308309

@@ -311,31 +312,6 @@ final class Target_javax_crypto_JceSecurity_WeakIdentityWrapper {
311312
}
312313
}
313314

314-
class JceSecurityAccessor {
315-
private static volatile SecureRandom RANDOM;
316-
317-
static SecureRandom get() {
318-
SecureRandom result = RANDOM;
319-
if (result == null) {
320-
/* Lazy initialization on first access. */
321-
result = initializeOnce();
322-
}
323-
return result;
324-
}
325-
326-
private static synchronized SecureRandom initializeOnce() {
327-
SecureRandom result = RANDOM;
328-
if (result != null) {
329-
/* Double-checked locking is OK because INSTANCE is volatile. */
330-
return result;
331-
}
332-
333-
result = new SecureRandom();
334-
RANDOM = result;
335-
return result;
336-
}
337-
}
338-
339315
/**
340316
* JDK 8 has the class `javax.crypto.JarVerifier`, but in JDK 11 and later that class is only
341317
* available in Oracle builds, and not in OpenJDK builds.
@@ -401,7 +377,7 @@ public boolean implies(ProtectionDomain domain, Permission permission) {
401377
}
402378
}
403379

404-
@TargetClass(className = "sun.security.jca.ProviderConfig")
380+
@TargetClass(className = "sun.security.jca.ProviderConfig", onlyWith = JDKInitializedAtBuildTime.class)
405381
@SuppressWarnings({"unused", "static-method"})
406382
final class Target_sun_security_jca_ProviderConfig {
407383

0 commit comments

Comments
 (0)