-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[GR-32212] Add support for runtime configuration of default trusted KeyStore. #3652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
65 changes: 65 additions & 0 deletions
65
docs/reference-manual/native-image/CertificateManagement.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
@@ -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; | ||
|
|
@@ -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 { | ||
|
|
@@ -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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class queries "java.specification.version", but it's not strictly necessary to have it explicitly initialized at buildtime. Your call on whether to keep it here or not |
||
| } | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @teshull, I was wondering if this blacklisted certificates that are used to decide trust, should be additive. What I mean is, should users be able to specify additional certificates dynamically during runtime usage of the generated native application? If they are not allowed to do so, then a native application/image generated with a specific build time JDK will have to be regularly rebuilt, whenever any new blacklisted certs are added into the JDK itself, which may not always be feasible. I can understand why it would be a bad thing to completely override this blacklisted certificates file at runtime but "adding" new certificates perhaps should be possible? Of course, I haven't looked at the technical implementation aspects of this to know if this is feasible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @jaikiran, I think it is best to leave this as is and not allow the user to add additional blacklisted certificates. In OpenJDK, it is not possible for the user to specify the blacklist, so adding support for this to me seems like an unneeded deviation from OpenJDK.
Too, now that they can modify the truststore at runtime, that should be sufficient for updating certificate info without a rebuild