Skip to content

Commit 73c0243

Browse files
authored
fix(gax): register Conscrypt SSLContext SPI classes for GraalVM reflection (#14129)
Error: ``` JUnit Jupiter:ITBigQueryStorageTest ClassSource [className = 'com.google.cloud.bigquery.storage.v1beta2.it.ITBigQueryStorageTest', filePosition = null] => java.lang.IllegalStateException: Failed to initialize SSLSocketFactory. com.google.api.client.http.javanet.NetHttpTransport$Builder.createDefaultSslSocketFactory(NetHttpTransport.java:476) com.google.api.client.http.javanet.NetHttpTransport$Builder.resolveSslSocketFactory(NetHttpTransport.java:446) com.google.api.client.http.javanet.NetHttpTransport$Builder.build(NetHttpTransport.java:485) com.google.cloud.http.HttpTransportOptions$DefaultHttpTransportFactory.create(HttpTransportOptions.java:75) com.google.cloud.bigquery.spi.v2.HttpBigQueryRpc.<init>(HttpBigQueryRpc.java:108) [...] Caused by: java.security.NoSuchAlgorithmException: class configured for SSLContext (provider: Conscrypt) cannot be found. java.base@21.0.2/java.security.Provider$Service.getImplClass(Provider.java:2004) java.base@21.0.2/java.security.Provider$Service.getDefaultConstructor(Provider.java:2020) java.base@21.0.2/java.security.Provider$Service.newInstanceOf(Provider.java:1934) java.base@21.0.2/java.security.Provider$Service.newInstanceUtil(Provider.java:1942) java.base@21.0.2/java.security.Provider$Service.newInstance(Provider.java:1917) [...] Caused by: java.lang.ClassNotFoundException: org.conscrypt.OpenSSLContextImpl$TLSv13 java.base@21.0.2/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:52) java.base@21.0.2/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) java.base@21.0.2/java.lang.ClassLoader.loadClass(ClassLoader.java:121) java.base@21.0.2/java.security.Provider$Service.getImplClass(Provider.java:1991) [...] ```
1 parent 13a6ca9 commit 73c0243

4 files changed

Lines changed: 111 additions & 7 deletions

File tree

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/GoogleJsonClientFeature.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030

3131
package com.google.api.gax.nativeimage;
3232

33+
import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassForJni;
3334
import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassForReflection;
35+
import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassHierarchyForJni;
36+
import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassHierarchyForReflection;
3437

3538
import org.graalvm.nativeimage.hosted.Feature;
3639
import org.jspecify.annotations.NullMarked;
@@ -51,9 +54,65 @@ final class GoogleJsonClientFeature implements Feature {
5154
public void beforeAnalysis(BeforeAnalysisAccess access) {
5255
loadApiClient(access);
5356
loadHttpClient(access);
57+
loadConscrypt(access);
5458
loadMiscClasses(access);
5559
}
5660

61+
/**
62+
* Registers Conscrypt SSLContext and Security Provider SPI implementation classes (and their
63+
* nested protocol subclasses) for GraalVM reflection, as well as Conscrypt native JNI classes and
64+
* native C shared library resources when Conscrypt is present on the classpath.
65+
*
66+
* <p>When Conscrypt is configured as the security provider for HTTP/JSON transports, Java's JCA
67+
* framework reflectively instantiates provider implementation classes (e.g. {@code
68+
* OpenSSLContextImpl$TLSv13}) via String lookup in {@code SSLContext.getInstance("TLS",
69+
* provider)}. In GraalVM Native Image builds, these reflectively looked-up SPI classes are
70+
* stripped by static analysis unless explicitly registered for reflection, leading to {@code
71+
* ClassNotFoundException} / {@code NoSuchAlgorithmException} at runtime. Additionally, native JNI
72+
* methods and JDK classes accessed by Conscrypt's native C library must be registered for GraalVM
73+
* JNI linkage.
74+
*/
75+
private void loadConscrypt(BeforeAnalysisAccess access) {
76+
Class<?> conscryptClass = access.findClassByName("org.conscrypt.Conscrypt");
77+
if (conscryptClass != null) {
78+
registerClassHierarchyForReflection(access, "org.conscrypt.OpenSSLContextImpl");
79+
registerClassHierarchyForReflection(access, "org.conscrypt.OpenSSLProvider");
80+
registerClassHierarchyForReflection(access, "org.conscrypt.KeyManagerFactoryImpl");
81+
registerClassHierarchyForReflection(access, "org.conscrypt.TrustManagerFactoryImpl");
82+
83+
// Register Conscrypt native JNI bridge classes and hierarchy
84+
registerClassHierarchyForReflection(access, "org.conscrypt.NativeCrypto");
85+
registerClassHierarchyForReflection(access, "org.conscrypt.NativeCryptoJni");
86+
registerClassHierarchyForReflection(access, "org.conscrypt.CryptoUpcalls");
87+
registerClassHierarchyForReflection(access, "org.conscrypt.NativeRef");
88+
registerClassHierarchyForReflection(access, "org.conscrypt.OpenSSLBIOInputStream");
89+
90+
registerClassHierarchyForJni(access, "org.conscrypt.NativeCrypto");
91+
registerClassHierarchyForJni(access, "org.conscrypt.NativeCryptoJni");
92+
registerClassHierarchyForJni(access, "org.conscrypt.CryptoUpcalls");
93+
registerClassHierarchyForJni(access, "org.conscrypt.NativeRef");
94+
registerClassHierarchyForJni(access, "org.conscrypt.OpenSSLBIOInputStream");
95+
96+
// Register array types and JDK classes accessed via JNI by Conscrypt native C code
97+
registerClassForJni(Object[].class);
98+
registerClassForJni(byte[].class);
99+
registerClassForJni(byte[][].class);
100+
registerClassForJni(int[].class);
101+
102+
registerClassHierarchyForJni(access, "java.util.Calendar");
103+
registerClassHierarchyForJni(access, "java.io.InputStream");
104+
registerClassHierarchyForJni(access, "java.io.OutputStream");
105+
registerClassHierarchyForJni(access, "java.io.FileDescriptor");
106+
registerClassHierarchyForJni(access, "java.lang.Integer");
107+
registerClassHierarchyForJni(access, "java.lang.String");
108+
registerClassHierarchyForJni(access, "java.lang.Object");
109+
registerClassHierarchyForJni(access, "java.nio.Buffer");
110+
registerClassHierarchyForJni(access, "java.nio.ByteBuffer");
111+
registerClassHierarchyForJni(access, "java.security.PrivateKey");
112+
registerClassHierarchyForJni(access, "java.security.Key");
113+
}
114+
}
115+
57116
private void loadApiClient(BeforeAnalysisAccess access) {
58117
// For com.google.api-client:google-api-client
59118
Class<?> googleApiClientClass = access.findClassByName(GOOGLE_API_CLIENT_CLASS);

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/nativeimage/NativeImageUtils.java

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.logging.Level;
3737
import java.util.logging.Logger;
3838
import org.graalvm.nativeimage.hosted.Feature.FeatureAccess;
39+
import org.graalvm.nativeimage.hosted.RuntimeJNIAccess;
3940
import org.graalvm.nativeimage.hosted.RuntimeReflection;
4041
import org.jspecify.annotations.NullMarked;
4142

@@ -85,19 +86,62 @@ public static void registerConstructorsForReflection(FeatureAccess access, Strin
8586
}
8687
}
8788

89+
/** Registers an entire class for reflection use. */
90+
public static void registerClassForReflection(Class<?> clazz) {
91+
RuntimeReflection.register(clazz);
92+
RuntimeReflection.register(clazz.getDeclaredConstructors());
93+
RuntimeReflection.register(clazz.getDeclaredFields());
94+
RuntimeReflection.register(clazz.getDeclaredMethods());
95+
}
96+
8897
/** Registers an entire class for reflection use. */
8998
public static void registerClassForReflection(FeatureAccess access, String name) {
9099
Class<?> clazz = access.findClassByName(name);
91100
if (clazz != null) {
92-
RuntimeReflection.register(clazz);
93-
RuntimeReflection.register(clazz.getDeclaredConstructors());
94-
RuntimeReflection.register(clazz.getDeclaredFields());
95-
RuntimeReflection.register(clazz.getDeclaredMethods());
101+
registerClassForReflection(clazz);
96102
} else {
97103
LOGGER.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, name);
98104
}
99105
}
100106

107+
/** Registers an entire class for JNI use. */
108+
public static void registerClassForJni(Class<?> clazz) {
109+
RuntimeJNIAccess.register(clazz);
110+
RuntimeJNIAccess.register(clazz.getDeclaredConstructors());
111+
RuntimeJNIAccess.register(clazz.getDeclaredFields());
112+
RuntimeJNIAccess.register(clazz.getDeclaredMethods());
113+
}
114+
115+
/** Registers an entire class for JNI use. */
116+
public static void registerClassForJni(FeatureAccess access, String name) {
117+
Class<?> clazz = access.findClassByName(name);
118+
if (clazz != null) {
119+
registerClassForJni(clazz);
120+
} else {
121+
LOGGER.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, name);
122+
}
123+
}
124+
125+
/**
126+
* Registers the transitive class hierarchy of the provided {@code className} for JNI use.
127+
*
128+
* <p>The transitive class hierarchy contains the class itself and its transitive set of
129+
* *non-private* nested subclasses.
130+
*/
131+
public static void registerClassHierarchyForJni(FeatureAccess access, String className) {
132+
Class<?> clazz = access.findClassByName(className);
133+
if (clazz != null) {
134+
registerClassForJni(access, className);
135+
for (Class<?> nestedClass : clazz.getDeclaredClasses()) {
136+
if (!Modifier.isPrivate(nestedClass.getModifiers())) {
137+
registerClassHierarchyForJni(access, nestedClass.getName());
138+
}
139+
}
140+
} else {
141+
LOGGER.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, className);
142+
}
143+
}
144+
101145
/**
102146
* Registers the transitive class hierarchy of the provided {@code className} for reflection.
103147
*

sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/native-image.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Args = --enable-url-protocols=https,http \
2-
--initialize-at-build-time=org.conscrypt,\
3-
org.junit.platform.engine.TestTag,\
2+
--initialize-at-run-time=org.conscrypt \
3+
--initialize-at-build-time=org.junit.platform.engine.TestTag,\
44
com.google.api.gax.core.GaxProperties,\
55
com.google.common.base.Platform,\
66
com.google.common.base.Platform$JdkPatternCompiler,\

sdk-platform-java/gax-java/gax/src/main/resources/META-INF/native-image/com.google.api/gax/resource-config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{"pattern":"\\Qdependencies.properties\\E"},
55
{"pattern":"\\Qcom/google/api/client/googleapis/google-api-client.properties\\E"},
66
{"pattern":"\\Qcom/google/api/client/googleapis/google.p12\\E"},
7-
{"pattern":"\\Qcom/google/api/client/http/google-http-client.properties\\E"}],
7+
{"pattern":"\\Qcom/google/api/client/http/google-http-client.properties\\E"},
8+
{"pattern":".*conscrypt_openjdk_jni.*"}],
89
"bundles":[]
910
}

0 commit comments

Comments
 (0)