Skip to content

Commit

Permalink
[GR-32212] Add support for runtime configuration of default TrustStore.
Browse files Browse the repository at this point in the history
PullRequest: graal/9502
  • Loading branch information
teshull committed Aug 23, 2021
2 parents c6fd250 + 32fcac3 commit e98ae39
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 26 deletions.
65 changes: 65 additions & 0 deletions docs/reference-manual/native-image/CertificateManagement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
layout: docs
toc_group: native-image
link_title: Certificate Management in Native Image
permalink: /reference-manual/native-image/CertificateManagement/
---
# Certificate Management in Native Image

Native-image provides multiple ways to specify the certificate file used to
define the default TrustStore. In the following sections we describe the
available buildtime and runtime options. Note the default behavior for
native-image is to capture and use the default TrustStore from the buildtime
host environment.

## Buildtime Options

During the image building process, native-image captures the host environment's
default TrustStore and embeds it into the native image. This TrustStore is
by default created from the root certificate file provided within the JDK, but
can be changed to use a different certificate file by setting the buildtime
system property "javax.net.ssl.trustStore" (see [Properties](Properties.md) for
how to do so).

Since the contents of the buildtime certificate file is embedded into the image
executable, the file itself does not need to present in the target environment.

## Runtime Options

The certificate file can also be changed dynamically at runtime via setting
the "javax.net.ssl.trustStore\*" system properties.

If any of the following system properties are set during image execution,
native-image also requires "javax.net.ssl.trustStore" to be set and for it
to point to an accessible certificate file:
- javax.net.ssl.trustStore
- javax.net.ssl.trustStoreType
- javax.net.ssl.trustStoreProvider
- javax.net.ssl.trustStorePassword

If any of these properties are set and "javax.net.ssl.trustStore" does not point
to an accessible file, then an UnsupportedFeatureError will be thrown.

Note that this behavior is different than OpenJDK. When the
"javax.net.ssl.trustStore" system property is unset/invalid, OpenJDK will
fallback to using a certificate file shipped within the JDK; however, such
files will not be present alongside the image executable and hence cannot be
used as a fallback.

During the execution, it also possible to dynamically change the
"javax.net.ssl.trustStore\*" properties and for the default TrustStore to be
updated accordingly.

Finally, whenever all of the "javax.net.ssl.trustStore\*" system properties
listed above are unset, the default TrustStore will be the one captured during
buildtime, as described in the [prior section](#buildtime-options).

## Untrusted Certificates

During the image building process, a list of untrusted certificates is loaded
from the file <java.home>/lib/security/blacklisted.certs. This file is used
when validating certificates at both buildtime and runtime. In other words,
when a new certificate file is specified at runtime via setting the
"javax.net.ssl.trustStore\*" system properties, the new certificates will still
be checked against the <java.home>/lib/security/blacklisted.certs loaded at
image buildtime.
9 changes: 9 additions & 0 deletions docs/security/security-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ This can either result in sensitive data ending up in the snapshot or fixing ini

Developers can request static initializers that process sensitive information to be instead executed at runtime by either specifying the `--initialize-at-run-time` CLI parameter when building a native image, or making use of the `RuntimeClassInitialization` API.

Native-image provides multiple ways to specify the certificate file used to
define the default TrustStore. While the default behavior for native-image is
to capture and use the default TrustStore from the buildtime host environment,
this can be changed at runtime by setting the "javax.net.ssl.trustStore\*"
system properties. Please see the
[documentation](/reference-manual/native-image/CertificateManagement/) for more
details.


In addition, developers can run the native image builder in a dedicated environment, such as a container, that does not contain any sensitive information in the first place.

### Serialization in Native Image
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021, 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
Expand All @@ -24,6 +24,7 @@
*/
package com.oracle.svm.core.jdk;

import java.io.File;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Set;
Expand All @@ -32,25 +33,34 @@
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;

import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.AutomaticFeature;
import com.oracle.svm.core.annotate.Delete;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.util.ReflectionUtil;

// Checkstyle: stop
import sun.security.ssl.SSLLogger;
// Checkstyle: resume

/**
* Native image uses the principle of "immutable security" for the root certificates: They are fixed
* at image build time based on the the certificate configuration used for the image generator. This
* avoids shipping a `cacerts` file or requiring to set a system property to set up root
* certificates that are provided by the OS where the image runs.
* Root certificates in native image are fixed/embedded into the image, at image build time, based
* on the certificate configuration used for the image generator. This avoids the need to ship a
* `cacerts` file alongside the image executable.
*
* As a consequence, system properties such as `javax.net.ssl.trustStore` do not have an effect at
* run time. They need to be provided at image build time.
* <p>
* Users are also allowed to override the embedded root certificate at run time by setting the
* `javax.net.ssl.trustStore*` system properties. For more details about both buildtime and runtime
* certificate management, please refer to <a href=
* "https://www.graalvm.org/reference-manual/native-image/CertificateManagement/">CertificateManagement.md</a>.
*
* The implementation "freezes" the return values of TrustStoreManager managers by invoking them at
* image build time (using reflection because the class is non-public) and returning the frozen
* values using a substitution.
* <p>
* For embedding the build time root certificates, the implementation "freezes" the return values of
* TrustStoreManager managers by invoking them at image build time (using reflection because the
* class is non-public) and returning the frozen values using a substitution.
*/
@AutomaticFeature
final class TrustStoreManagerFeature implements Feature {
Expand All @@ -73,48 +83,164 @@ public void afterRegistration(AfterRegistrationAccess access) {
/*
* The class initializer of UntrustedCertificates loads the file
* lib/security/blacklisted.certs, so this class must be initialized at image build time.
* This is the default anyway for code JDK classes, but since this this class is relevant
* for security we spell it out explicitly.
* This is the default anyway for code JDK classes, but since this class is relevant for
* security we spell it out explicitly.
*
* Note when a runtime certificate file is specified, we still honor/use the build time
* lib/security/blacklisted.certs file
*/
RuntimeClassInitialization.initializeAtBuildTime(sun.security.util.UntrustedCertificates.class);
RuntimeClassInitialization.initializeAtBuildTime(org.jcp.xml.dsig.internal.dom.XMLDSigRI.class);
}
}

final class TrustStoreManagerSupport {
final Set<X509Certificate> trustedCerts;
final KeyStore trustedKeyStore;

TrustStoreManagerSupport(Set<X509Certificate> trustedCerts, KeyStore trustedKeyStore) {
this.trustedCerts = trustedCerts;
this.trustedKeyStore = trustedKeyStore;
final Set<X509Certificate> buildtimeTrustedCerts;
final KeyStore buildtimeTrustedKeyStore;

TrustStoreManagerSupport(Set<X509Certificate> buildtimeTrustedCerts, KeyStore buildtimeTrustedKeyStore) {
this.buildtimeTrustedCerts = buildtimeTrustedCerts;
this.buildtimeTrustedKeyStore = buildtimeTrustedKeyStore;
}

/**
* This method creates a TrustStoreDescriptor if any of the "javax.net.ssl.trustStore*"
* properties are set, or otherwise returns null.
*/
static Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor getRuntimeTrustStoreDescriptor() {
/* First read current system properties. */
String storePropName = System.getProperty("javax.net.ssl.trustStore");
String storePropType = System.getProperty("javax.net.ssl.trustStoreType");
String storePropProvider = System.getProperty("javax.net.ssl.trustStoreProvider");
String storePropPassword = System.getProperty("javax.net.ssl.trustStorePassword");

/*
* Check if any of the properties are set. If not, then should not attempt to create a trust
* store descriptor.
*/
if (storePropName == null && storePropType == null && storePropProvider == null && storePropPassword == null) {
return null;
}

if (storePropName == null) {
throw VMError.unsupportedFeature(
"System property javax.net.ssl.trustStore must be also set if any of javax.net.ssl.trustStore(Type|Provider|Password) are set." +
"See https://www.graalvm.org/reference-manual/native-image/CertificateManagement#runtime-options for more details about runtime certificate management.");
}

/* Setting remaining properties to defaults if unset. */
if (storePropType == null) {
storePropType = KeyStore.getDefaultType();
}
if (storePropProvider == null) {
storePropProvider = "";
}
if (storePropPassword == null) {
storePropPassword = "";
}

/* Creating TrustStoreDescriptor. */
Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor descriptor = createTrustStoreDescriptor(storePropName, storePropType, storePropProvider,
storePropPassword);

/*
* Checking if TrustStoreDescriptor was able to find a valid trust store.
*/
if (descriptor == null) {
throw VMError.unsupportedFeature("Inaccessible trust store: " + storePropName +
" See https://www.graalvm.org/reference-manual/native-image/CertificateManagement#runtime-options for more details about runtime certificate management.");
}

return descriptor;
}

/**
* Creates a new TrustStoreDescriptor object.
*
* @return A new TrustStoreDescriptor or {@code null} if an appropriate descriptor for the
* provided parameters could not be found.
*/
private static Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor createTrustStoreDescriptor(String storePropName, String storePropType, String storePropProvider,
String storePropPassword) {
/* This code is largely taken from the JDK. */
String temporaryName = "";
File temporaryFile = null;
long temporaryTime = 0L;
if (!"NONE".equals(storePropName)) {
File f = new File(storePropName);
if (f.isFile() && f.canRead()) {
temporaryName = storePropName;
temporaryFile = f;
temporaryTime = f.lastModified();
} else {
// The file is inaccessible.
if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) {
SSLLogger.fine("Inaccessible trust store: " + storePropName);
}

return null;
}
} else {
temporaryName = storePropName;
}

return new Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor(
temporaryName, storePropType, storePropProvider,
storePropPassword, temporaryFile, temporaryTime);
}

}

@TargetClass(className = TrustStoreManagerFeature.TRUST_STORE_MANAGER_CLASS_NAME)
final class Target_sun_security_ssl_TrustStoreManager {
/*
* This singleton object caches the last retrieved trusted KeyStore and set of trusted
* certificates.
*/
@Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = TrustStoreManagerFeature.TRUST_STORE_MANAGER_CLASS_NAME +
"$TrustAnchorManager") private static Target_sun_security_ssl_TrustStoreManager_TrustAnchorManager tam;

@Substitute
private static Set<X509Certificate> getTrustedCerts() throws Exception {
return ImageSingletons.lookup(TrustStoreManagerSupport.class).trustedCerts;
Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor runtimeDescriptor = TrustStoreManagerSupport.getRuntimeTrustStoreDescriptor();
if (runtimeDescriptor == null) {
return ImageSingletons.lookup(TrustStoreManagerSupport.class).buildtimeTrustedCerts;
}
return tam.getTrustedCerts(runtimeDescriptor);
}

@Substitute
private static KeyStore getTrustedKeyStore() throws Exception {
return ImageSingletons.lookup(TrustStoreManagerSupport.class).trustedKeyStore;
Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor runtimeDescriptor = TrustStoreManagerSupport.getRuntimeTrustStoreDescriptor();
if (runtimeDescriptor == null) {
return ImageSingletons.lookup(TrustStoreManagerSupport.class).buildtimeTrustedKeyStore;
}
return tam.getKeyStore(runtimeDescriptor);
}
}

/*
* The internal classes to describe and load root certificates must not be reachable at run time.
*/

@Delete
@TargetClass(className = TrustStoreManagerFeature.TRUST_STORE_MANAGER_CLASS_NAME, innerClass = "TrustStoreDescriptor")
final class Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor {
@Delete private static String defaultStorePath;
@Delete private static String defaultStore;
@Delete private static String jsseDefaultStore;

@Delete
static native Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor createInstance();

@Alias
@SuppressWarnings("unused")
Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor(String storeName, String storeType, String storeProvider, String storePassword, File storeFile, long lastModified) {
throw VMError.shouldNotReachHere("This is an alias to the original constructor in the target class, so this code is unreachable");
}
}

@Delete
@TargetClass(className = TrustStoreManagerFeature.TRUST_STORE_MANAGER_CLASS_NAME, innerClass = "TrustAnchorManager")
final class Target_sun_security_ssl_TrustStoreManager_TrustAnchorManager {

@Alias
native Set<X509Certificate> getTrustedCerts(Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor descriptor) throws Exception;

@Alias
native KeyStore getKeyStore(Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor descriptor) throws Exception;
}

0 comments on commit e98ae39

Please sign in to comment.