Skip to content
Merged
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
14 changes: 13 additions & 1 deletion docs/reference-manual/native-image/JCASecurityServices.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,20 @@ The report will detail all registered service classes, the API methods that trig

> Note: The `--enable-all-security-services` option is now deprecated and it will be removed in a future release.

## Provider Initialization

Currently security providers are initialized at build time.
To move their initialization to run time, use the option `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk`.
Provider verification will still occur at build time.
Run-time initialization of security providers helps reduce image heap size.
To move their initialization to run time, you can use the flag `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk`.

## Provider Registration

The `native-image` builder captures the list of providers and their preference order from the underlying JVM.
The provider order is specified in the `java.security` file under `<java-home>/conf/security/java.security`.
New security providers cannot be registered at run time; all providers must be statically configured at executable build time.
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.
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>`.

## Providers Reordering at Run Time

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

If `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk` is enabled, the list of providers is constructed at run time.
The same approach to manipulating providers can then be used.

## SecureRandom

The `SecureRandom` implementations open the `/dev/random` and `/dev/urandom` files which are used as sources.
Expand Down
3 changes: 3 additions & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ This changelog summarizes major changes to GraalVM Native Image.
* (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.
* (GR-64619) Missing registration errors are now subclasses of `LinkageError`
* (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.
* (GR-57827) Move the initialization of security providers from build time to runtime.
* (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`.
Run-time initialization of security providers helps reduce image heap size by avoiding unnecessary objects inclusion.

## 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 @@ -352,6 +352,7 @@
],
"requiresConcealed" : {
"java.base" : [
"com.sun.crypto.provider",
"sun.invoke.util",
"sun.net",
"sun.net.www",
Expand All @@ -362,6 +363,8 @@
"sun.reflect.generics.repository",
"sun.reflect.generics.tree",
"sun.security.jca",
"sun.security.provider",
"sun.security.rsa",
"sun.security.ssl",
"sun.security.util",
"sun.text.spi",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* 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.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.EconomicSet;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;

import com.oracle.svm.core.util.ImageHeapMap;
import com.oracle.svm.core.util.VMError;

import jdk.graal.compiler.api.replacements.Fold;
import sun.security.util.Debug;

/**
* The class that holds various build-time and run-time structures necessary for security providers,
* but only in case they are initialized at run time (see the <a href=
* "../../../../../../../../../../../../docs/reference-manual/native-image/JCASecurityServices.md">
* JCA Security Services documentation</a> for details).
*/
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 = ConcurrentHashMap.newKeySet();

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

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 verificationResult) {
verifiedSecurityProviders.put(key, verificationResult);
}

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 isSecurityProviderRequested(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.");
}
}

@Platforms(Platform.HOSTED_ONLY.class)
public void setSavedInitialSecurityProperties(Properties savedSecurityProperties) {
this.savedInitialSecurityProperties = savedSecurityProperties;
}

public Properties getSavedInitialSecurityProperties() {
return savedInitialSecurityProperties;
}

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;
case "SunRsaSign", "sun.security.rsa.SunRsaSign" ->
isSecurityProviderRequested("SunRsaSign", "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;
case "SunJSSE" ->
isSecurityProviderRequested("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null;
case "SunEC" ->
isSecurityProviderRequested("SunEC", "sun.security.ec.SunEC") ? allocateSunECProvider() : null;
case "Apple", "apple.security.AppleProvider" -> {
try {
Class<?> c = Class.forName("apple.security.AppleProvider");
if (Provider.class.isAssignableFrom(c)) {
yield (Provider) c.getDeclaredConstructor().newInstance();
}
} catch (Exception ex) {
if (debug != null) {
debug.println("Error loading provider Apple");
ex.printStackTrace();
}
}
yield null;
}
default -> null;
};
}

public static boolean isBuiltInProvider(String provName) {
return switch (provName) {
case "SUN", "sun.security.provider.Sun",
"SunRsaSign", "sun.security.rsa.SunRsaSign",
"SunJCE", "com.sun.crypto.provider.SunJCE",
"SunJSSE",
"SunEC",
"Apple", "apple.security.AppleProvider" ->
true;
default -> false;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import java.security.Policy;
import java.security.ProtectionDomain;
import java.security.Provider;
import java.security.SecureRandom;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -244,26 +243,28 @@ private static void setJavaHome(String newJavaHome) {
}
}

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

/*
* The JceSecurity.verificationResults cache is initialized by the SecurityServicesFeature at
* build time, for all registered providers. The cache is used by JceSecurity.canUseProvider()
* at runtime to check whether a provider is properly signed and can be used by JCE. It does
* that via jar verification which we cannot support.
*/

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

// Map<Provider,?> of the providers we already have verified
// value == PROVIDER_VERIFIED is successfully verified
// value is failure cause Exception in error case
/*
* 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 //
private static Map<Object, Object> verificationResults;

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

@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);
Expand All @@ -290,19 +290,20 @@ static Exception getVerificationResult(Provider p) {
} else if (o != null) {
return (Exception) o;
}
/* End code block copied from original method. */
/*
* If the verification result is not found in the verificationResults map JDK proceeds to
* verify it. That requires accessing the code base which we don't support. The substitution
* for getCodeBase() would be enough to take care of this too, but substituting
* getVerificationResult() allows for a better error message.
* 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
* supported in Native Image, so we need to fail. We could either fail here or substitute
* getCodeBase() and fail there, but handling it here is a cleaner approach.
*/
throw VMError.unsupportedFeature("Trying to verify a provider that was not registered at build time: " + p + ". " +
"All providers must be registered and verified in the Native Image builder. ");
throw new SecurityException(
"Attempted to verify a provider that was not registered at build time: " + p + ". " +
"All security providers must be registered and verified during native image generation. " +
"Try adding the option: -H:AdditionalSecurityProviders=" + p + " and rebuild the image.");
}
}

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

Expand All @@ -311,31 +312,6 @@ final class Target_javax_crypto_JceSecurity_WeakIdentityWrapper {
}
}

class JceSecurityAccessor {
private static volatile SecureRandom RANDOM;

static SecureRandom get() {
SecureRandom result = RANDOM;
if (result == null) {
/* Lazy initialization on first access. */
result = initializeOnce();
}
return result;
}

private static synchronized SecureRandom initializeOnce() {
SecureRandom result = RANDOM;
if (result != null) {
/* Double-checked locking is OK because INSTANCE is volatile. */
return result;
}

result = new SecureRandom();
RANDOM = result;
return result;
}
}

/**
* JDK 8 has the class `javax.crypto.JarVerifier`, but in JDK 11 and later that class is only
* available in Oracle builds, and not in OpenJDK builds.
Expand Down Expand Up @@ -401,7 +377,7 @@ public boolean implies(ProtectionDomain domain, Permission permission) {
}
}

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

Expand Down
Loading