Skip to content

Improve SSL context support using BouncyCastlePemReader in native images #14826

@michalvavrik

Description

@michalvavrik

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() {

    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions