-
-
Notifications
You must be signed in to change notification settings - Fork 16.3k
Improve SSL context support using BouncyCastlePemReader in native images #14826
Description
BC provider is instantiated in BouncyCastlePemReader even when it is registered as Security provider. When using JVM it doesn't make a difference whether Netty instantiates the provider itself or take the provider instance from java.security.Security#getProvider. However when debugging quarkusio/quarkus#46279 I have mentioned that provider instantiated during at the build time has more services then the one created during the first BouncyCastlePemReader usage. Registering every required BC class for reflection can be difficult task. Would it be possible to try Security.getProvider("BC") (Security.getProvider("BCFIPS") respectively) before instantiating classes using reflection API? Alternatively maybe allow users to specify BC provider themselves?
I'd appreciate any change that would allow me to use official Netty API rather than working around my issues. Thank you
Details
I am using 4.1.118.Final.
Steps to reproduce
Clone git@github.com:michalvavrik/netty-ssl-context-bouncycastle-native-issue-reproducer.git, run mvn clean verify -Dnative. My SslContextBuilder.forClient().keyManager(certificate, privateKey).build() works with JVM:
2025-02-16 15:28:15,409 DEBUG [io.net.han.ssl.BouncyCastlePemReader] (executor-thread-1) Bouncy Castle provider available
2025-02-16 15:28:15,421 DEBUG [io.net.han.ssl.BouncyCastlePemReader] (executor-thread-1) Parsed PEM object of type org.bouncycastle.openssl.PEMKeyPair and assume key is not encrypted
2025-02-16 15:28:15,490 DEBUG [io.net.han.ssl.OpenSsl] (executor-thread-1) netty-tcnative not in the classpath; OpenSslEngine will be unavailable.
2025-02-16 15:28:15,510 DEBUG [io.net.han.ssl.JdkSslContext] (executor-thread-1) Default protocols (JDK): [TLSv1.3, TLSv1.2]
2025-02-16 15:28:15,510 DEBUG [io.net.han.ssl.JdkSslContext] (executor-thread-1) Default cipher suites (JDK): [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384]
SSL context for certificate java.io.ByteArrayInputStream@7e2e2176 is io.netty.handler.ssl.JdkSslClientContext@4b6addbd
2025-02-16 15:28:15,634 DEBUG [io.net.han.ssl.BouncyCastlePemReader] (executor-thread-1) Parsed PEM object of type org.bouncycastle.asn1.pkcs.PrivateKeyInfo and assume key is not encrypted
but failing when using the native image:
2025-02-16 15:29:56,391 DEBUG [io.net.han.ssl.BouncyCastlePemReader] (executor-thread-1) Bouncy Castle provider available
2025-02-16 15:29:56,391 DEBUG [io.net.han.ssl.BouncyCastlePemReader] (executor-thread-1) Parsed PEM object of type org.bouncycastle.openssl.PEMKeyPair and assume key is not encrypted
2025-02-16 15:29:56,391 DEBUG [io.net.han.ssl.BouncyCastlePemReader] (executor-thread-1) Unable to extract private key: org.bouncycastle.openssl.PEMException: unable to convert key pair: no such algorithm: EC for provider BC
at org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.getKeyPair(Unknown Source)
at io.netty.handler.ssl.BouncyCastlePemReader.getPrivateKey(BouncyCastlePemReader.java:177)
at io.netty.handler.ssl.BouncyCastlePemReader.getPrivateKey(BouncyCastlePemReader.java:124)
at io.netty.handler.ssl.SslContext.toPrivateKey(SslContext.java:1204)
at io.netty.handler.ssl.SslContextBuilder.keyManager(SslContextBuilder.java:421)
at io.netty.handler.ssl.SslContextBuilder.keyManager(SslContextBuilder.java:348)
at org.acme.BouncyCastleEndpoint.pkcs1(BouncyCastleEndpoint.java:39)
at org.acme.BouncyCastleEndpoint$quarkusrestinvoker$pkcs1_b8002ac5f80c85fa5587227252617383662986bc.invoke(Unknown Source)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$15.runWith(VertxCoreRecorder.java:639)
at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2675)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2654)
at org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1627)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1594)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base@21.0.6/java.lang.Thread.runWith(Thread.java:1596)
at java.base@21.0.6/java.lang.Thread.run(Thread.java:1583)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:896)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:872)
Caused by: java.security.NoSuchAlgorithmException: no such algorithm: EC for provider BC
at java.base@21.0.6/sun.security.jca.GetInstance.getService(GetInstance.java:101)
at java.base@21.0.6/sun.security.jca.GetInstance.getInstance(GetInstance.java:218)
at java.base@21.0.6/java.security.KeyFactory.getInstance(KeyFactory.java:266)
at org.bouncycastle.jcajce.util.ProviderJcaJceHelper.createKeyFactory(Unknown Source)
at org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.getKeyFactory(Unknown Source)
... 23 more
Workaround
I have tried using BC provider registered as Security provider and it worked, but I don't want this to keep in place (Netty can easily change implementation):
@com.oracle.svm.core.annotate.TargetClass(className = "io.netty.handler.ssl.BouncyCastlePemReader", onlyWith = NettySslBountyCastleSupportRequired.class)
final class Target_io_netty_handler_ssl_BouncyCastlePemReader {
@Alias
private static volatile Provider bcProvider;
@Alias
private static volatile boolean attemptedLoading;
@Alias
private static volatile Throwable unavailabilityCause;
@com.oracle.svm.core.annotate.Substitute
public static boolean isAvailable() {
if (!attemptedLoading) {
bcProvider = Security.getProvider(SecurityProviderUtils.BOUNCYCASTLE_PROVIDER_NAME);
if (bcProvider == null) {
bcProvider = Security.getProvider(SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_NAME);
}
if (bcProvider == null) {
tryLoading();
} else {
attemptedLoading = true;
}
}
return unavailabilityCause == null;
}
@Alias
private static void tryLoading() {
}
}