com.oracle.svm.hosted.SecurityServicesFeature Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of svm Show documentation
Show all versions of svm Show documentation
SubstrateVM image builder components
/*
* Copyright (c) 2018, 2018, 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.hosted;
// Checkstyle: allow reflection
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Provider;
import java.security.Provider.Service;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
import org.graalvm.nativeimage.Feature;
import org.graalvm.nativeimage.RuntimeClassInitialization;
import org.graalvm.nativeimage.RuntimeReflection;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.annotate.AutomaticFeature;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.jni.JNIRuntimeAccess;
import sun.security.jca.Providers;
import sun.security.provider.NativePRNG;
import sun.security.x509.OIDMap;
@AutomaticFeature
public class SecurityServicesFeature implements Feature {
static class Options {
@Option(help = "Enable the feature that provides support for security services.")//
public static final HostedOptionKey EnableSecurityServicesFeature = new HostedOptionKey<>(true);
@Option(help = "Enable trace logging for the security services feature.")//
static final HostedOptionKey TraceSecurityServices = new HostedOptionKey<>(false);
}
/*
* The providers names are defined in Java Cryptography Architecture Oracle Providers
* Documentation:
* https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html
*/
private static final String SUN_PROVIDER = "SUN";
/*
* The security services names are defined in Java Cryptography Architecture Standard Algorithm
* Name Documentation:
* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html.
*/
private static final String SECURE_RANDOM_SERVICE = "SecureRandom";
private static final String MESSAGE_DIGEST_SERVICE = "MessageDigest";
private static final String SIGNATURE_SERVICE = "Signature";
private static final String CIPHER_SERVICE = "Cipher";
private static final String KEY_AGREEMENT_SERVICE = "KeyAgreement";
@Override
public boolean isInConfiguration(IsInConfigurationAccess access) {
return Options.EnableSecurityServicesFeature.getValue();
}
@Override
public void duringSetup(DuringSetupAccess access) {
/*
* The SecureRandom implementations open the /dev/random and /dev/urandom files which are
* used as sources for entropy. These files are opened in the static initializers. That's
* why we rerun the static initializers at runtime. We cannot completely delay the static
* initializers execution to runtime because the SecureRandom classes are needed by the
* native image generator too, e.g., by Files.createTempDirectory().
*/
RuntimeClassInitialization.rerunClassInitialization(NativePRNG.class);
RuntimeClassInitialization.rerunClassInitialization(NativePRNG.Blocking.class);
RuntimeClassInitialization.rerunClassInitialization(NativePRNG.NonBlocking.class);
RuntimeClassInitialization.rerunClassInitialization(access.findClassByName("sun.security.provider.SeedGenerator"));
RuntimeClassInitialization.rerunClassInitialization(access.findClassByName("sun.security.provider.SecureRandom$SeederHolder"));
if (!JavaVersionUtil.Java8OrEarlier) {
RuntimeClassInitialization.rerunClassInitialization(access.findClassByName("sun.security.provider.FileInputStreamPool"));
}
/* java.util.UUID$Holder has a static final SecureRandom field. */
RuntimeClassInitialization.rerunClassInitialization(access.findClassByName("java.util.UUID$Holder"));
/*
* The classes bellow have a static final SecureRandom field. Note that if the classes are
* not found as reachable by the analysis registering them form class initialization rerun
* doesn't have any effect.
*/
RuntimeClassInitialization.rerunClassInitialization(access.findClassByName("sun.security.jca.JCAUtil$CachedSecureRandomHolder"));
RuntimeClassInitialization.rerunClassInitialization(access.findClassByName("com.sun.crypto.provider.SunJCE$SecureRandomHolder"));
RuntimeClassInitialization.rerunClassInitialization(access.findClassByName("sun.security.krb5.Confounder"));
RuntimeClassInitialization.rerunClassInitialization(javax.net.ssl.SSLContext.class);
if (SubstrateOptions.EnableAllSecurityServices.getValue()) {
/* Prepare SunEC native library access. */
prepareSunEC();
}
}
private static void prepareSunEC() {
// @formatter:off
/* Registering byte[] is needed because Java_sun_security_ec_ECKeyPairGenerator_generateECKeyPair looks for it:
* baCls = env->FindClass("[B");
* if (baCls == NULL) {
* goto cleanup;
* }
* If the byte[] is not registered it just silently fails.
*/
// @formatter:on
JNIRuntimeAccess.register(byte[].class);
}
/** Get the list of configured providers. The SUN provider is returned by default. */
private static List getProviders(boolean enableAllSecurityServices) {
if (enableAllSecurityServices) {
/* Parse and instantiate all providers. */
return Providers.getProviderList().providers();
} else {
/*
* Get only the SUN provider. Avoids parsing the entire providers list and instantiating
* unused providers.
*/
Provider sunProvider = Providers.getSunProvider();
assert isSunProvider(sunProvider);
return Collections.singletonList(sunProvider);
}
}
@Override
@SuppressWarnings("unchecked")
public void beforeAnalysis(BeforeAnalysisAccess access) {
boolean enableAllSecurityServices = SubstrateOptions.EnableAllSecurityServices.getValue();
Function> consParamClassAccessor = getConsParamClassAccessor(access);
trace("Registering security services...");
for (Provider provider : getProviders(enableAllSecurityServices)) {
register(provider);
for (Service service : provider.getServices()) {
if (enableAllSecurityServices || isMessageDigest(service) || isSecureRandom(service)) {
/* SecureRandom and MessageDigest SUN services are registered by default. */
register(access, service, consParamClassAccessor);
}
}
}
if (enableAllSecurityServices) {
/*
* Register the default JavaKeyStore, JKS. It is not returned by the
* provider.getServices() enumeration.
*/
Class> javaKeyStoreJks = access.findClassByName("sun.security.provider.JavaKeyStore$JKS");
registerForReflection(javaKeyStoreJks);
trace("Class registered for reflection: " + javaKeyStoreJks);
try {
/* Register the x509 certificate extension classes for reflection. */
trace("Registering X.509 certificate extensions...");
Field extensionMapField = OIDMap.class.getDeclaredField("nameMap");
extensionMapField.setAccessible(true);
/*
* The OIDInfo class which represents the values in the map is not visible. Get the
* list of extension names through reflection, i.e., the keys in the map, and use
* the OIDMap.getClass(name) API to get the extension classes.
*/
Map map = (Map) extensionMapField.get(null);
for (String name : map.keySet()) {
Class> extensionClass = OIDMap.getClass(name);
assert sun.security.x509.Extension.class.isAssignableFrom(extensionClass);
registerForReflection(extensionClass);
trace("Class registered for reflection: " + extensionClass);
}
} catch (NoSuchFieldException | CertificateException | IllegalAccessException e) {
VMError.shouldNotReachHere(e);
}
}
}
/**
* Return a Function which given the serviceType as a String will return the corresponding
* constructor parameter Class, or null.
*/
@SuppressWarnings("unchecked")
private static Function> getConsParamClassAccessor(BeforeAnalysisAccess access) {
try {
Field knownEnginesField = Provider.class.getDeclaredField("knownEngines");
knownEnginesField.setAccessible(true);
Class> engineDescriptionClass = access.findClassByName("java.security.Provider$EngineDescription");
Field consParamClassNameField = engineDescriptionClass.getDeclaredField("constructorParameterClassName");
consParamClassNameField.setAccessible(true);
Map knownEngines = (Map) knownEnginesField.get(null);
/*
* The returned lambda captures the value of the Provider.knownEngines map retrieved
* above and it uses it to find the parameterClass corresponding to the serviceType
* parameter.
*/
return (serviceType) -> {
try {
/*
* Access the Provider.knownEngines map and extract the EngineDescription
* corresponding to the serviceType. From the EngineDescription object extract
* the value of the constructorParameterClassName field then, if the class name
* is not null, get the corresponding Class> object and return it.
*/
/* EngineDescription */Object engineDescription = knownEngines.get(serviceType);
String constrParamClassName = (String) consParamClassNameField.get(engineDescription);
if (constrParamClassName != null) {
return access.findClassByName(constrParamClassName);
}
} catch (IllegalAccessException e) {
VMError.shouldNotReachHere(e);
}
return null;
};
} catch (NoSuchFieldException | IllegalAccessException e) {
throw VMError.shouldNotReachHere(e);
}
}
private static void register(Provider provider) {
registerForReflection(provider.getClass());
try {
// Checkstyle: stop
Method getVerificationResult = Class.forName("javax.crypto.JceSecurity").getDeclaredMethod("getVerificationResult", Provider.class);
// Checkstyle: resume
getVerificationResult.setAccessible(true);
/*
* Trigger initialization of JceSecurity.verificationResults 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. See also Target_javax_crypto_JceSecurity.
*/
getVerificationResult.invoke(null, provider);
} catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
VMError.shouldNotReachHere(e);
}
}
private static void register(BeforeAnalysisAccess access, Service service, Function> consParamClassAccessor) {
Class> serviceClass = access.findClassByName(service.getClassName());
if (serviceClass != null) {
registerForReflection(serviceClass);
Class> consParamClass = consParamClassAccessor.apply(service.getType());
if (consParamClass != null) {
registerForReflection(consParamClass);
trace("Parameter class registered: " + consParamClass);
}
if (isSignature(service) || isCipher(service) || isKeyAgreement(service)) {
for (String keyClassName : getSupportedKeyClasses(service)) {
Class> keyClass = access.findClassByName(keyClassName);
if (keyClass != null) {
registerForReflection(keyClass);
}
}
}
trace("Service registered: " + asString(service));
} else {
trace("Service registration failed: " + asString(service) + ". Cause: class not found " + service.getClassName());
}
}
private static void registerForReflection(Class> clazz) {
RuntimeReflection.register(clazz);
RuntimeReflection.register(clazz.getConstructors());
}
private static boolean isSunProvider(Provider provider) {
return provider.getName().equals(SUN_PROVIDER);
}
private static boolean isSecureRandom(Service s) {
return s.getType().equals(SECURE_RANDOM_SERVICE);
}
private static boolean isMessageDigest(Service s) {
return s.getType().equals(MESSAGE_DIGEST_SERVICE);
}
private static boolean isSignature(Service s) {
return s.getType().equals(SIGNATURE_SERVICE);
}
private static boolean isCipher(Service s) {
return s.getType().equals(CIPHER_SERVICE);
}
private static boolean isKeyAgreement(Service s) {
return s.getType().equals(KEY_AGREEMENT_SERVICE);
}
private static final String[] emptyStringArray = new String[0];
private static String[] getSupportedKeyClasses(Service s) {
assert isSignature(s) || isCipher(s) || isKeyAgreement(s);
String supportedKeyClasses = s.getAttribute("SupportedKeyClasses");
if (supportedKeyClasses != null) {
return supportedKeyClasses.split("\\|");
}
return emptyStringArray;
}
// Checkstyle issue: illegal space before a comma
// Checkstyle: stop
private static final String SEP = " , ";
// Checkstyle: resume
private static String asString(Service s) {
String str = "Provider = " + s.getProvider().getName() + SEP;
str += "Type = " + s.getType() + SEP;
str += "Algorithm = " + s.getAlgorithm() + SEP;
str += "Class = " + s.getClassName();
if (isSignature(s) || isCipher(s) || isKeyAgreement(s)) {
str += SEP + "SupportedKeyClasses = " + Arrays.toString(getSupportedKeyClasses(s));
}
return str;
}
private static void trace(String trace) {
if (Options.TraceSecurityServices.getValue()) {
// Checkstyle: stop
System.out.println(trace);
// Checkstyle: resume
}
}
}