jetty / jetty.project

Eclipse Jetty® - Web Container & Clients - supports HTTP/2, HTTP/1.1, HTTP/1.0, websocket, servlets, and more
https://eclipse.dev/jetty
Other
3.86k stars 1.91k forks source link

No ALPNProcessor for org.bouncycastle.jsse.provider.ProvSSLEngine error with jetty http2 client #12428

Open sanjerai opened 2 weeks ago

sanjerai commented 2 weeks ago

Jetty Version 11.0.20 Jetty Environment

Java Version JDK 17

Question I am trying to use jetty client in a spring boot app injected into spring webclient to make TLS1.3 over HTTP2 requests. Also i am using bouncycastle tls library as a security provider as i have a use case to retrieve master secret after TLS handshake which i plan to do using BCTLS.

my pom.xml snippet

<properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bctls-jdk18on</artifactId>
            <version>1.78.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-reactive-httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.http2</groupId>
            <artifactId>http2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.http2</groupId>
            <artifactId>http2-http-client-transport</artifactId>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.http2</groupId>
            <artifactId>http2-common</artifactId>
        </dependency>

my bean configurations

    @PostConstruct
    public void setupProvider() {
        Security.insertProviderAt(new BouncyCastleJsseProvider(),1);
        Security.insertProviderAt(new BouncyCastleProvider(),2);
    }   

    @Bean
    public SSLContext sslContext() throws NoSuchAlgorithmException, NoSuchProviderException{
        return SSLContext.getInstance("TLS", "BCJSSE");
    }

    @Bean
    public WebClient webClient() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        HTTP2Client http2Client = new HTTP2Client();
        http2Client.setMaxConcurrentPushedStreams(256);

        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        try (InputStream trustStoreStream = getClass()
                .getClassLoader().getResourceAsStream("client-truststore.jks")) {
            if (trustStoreStream == null) {
                throw new IllegalArgumentException("Truststore not found in classpath");
            }
            trustStore.load(trustStoreStream, "changeit".toCharArray());
        }

        HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(http2Client);
        transport.setUseALPN(true);
        SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(true);
        sslContextFactory.setProvider("BCJSSE");
        sslContextFactory.setIncludeProtocols("TLSv1.3");
        sslContextFactory.setTrustStore(trustStore);
        sslContextFactory.setTrustStorePassword("changeit");

        ClientConnector connector = new ClientConnector();
        connector.setSslContextFactory(sslContextFactory);
        transport.updateBean(transport.getBean(ClientConnector.class), connector);

        HttpClient httpClient = new HttpClient(transport);
        httpClient.setConnectTimeout(30000);
        httpClient.setIdleTimeout(60000);
        httpClient.setMaxRequestsQueuedPerDestination(1024);
        httpClient.setMaxConnectionsPerDestination(200);

        ClientHttpConnector httpConnector = new JettyClientHttpConnector(httpClient);

        return WebClient.builder().clientConnector(httpConnector).build();

on triggering the call I am facing below issue always and am not able to understand how to resolve this issue. help with this will be appreciated.

Setting idle timeout 60000 -> 60000 on SocketChannelEndPoint@5d764658[{l=/127.0.0.1:61168,r=localhost/127.0.0.1:8444,OPEN,fill=-,flush=-,to=12/60000}{io=0/0,kio=0,kro=8}]->[<null>]
protocols: [h2]
processors: [org.eclipse.jetty.alpn.java.client.JDK9ClientALPNProcessor@231a05be]
Could not create EndPoint java.nio.channels.SocketChannel[closed]: java.lang.IllegalStateException: No ALPNProcessor for org.bouncycastle.jsse.provider.ProvSSLEngine_9@4ae7c717

java.lang.IllegalStateException: No ALPNProcessor for org.bouncycastle.jsse.provider.ProvSSLEngine_9@4ae7c717
    at org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory.newConnection(ALPNClientConnectionFactory.java:106)
    at org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2.newConnection(HttpClientTransportOverHTTP2.java:165)
    at org.eclipse.jetty.io.ssl.SslClientConnectionFactory.newConnection(SslClientConnectionFactory.java:125)
    at org.eclipse.jetty.io.ClientConnector$Configurator.newConnection(ClientConnector.java:647)
    at org.eclipse.jetty.io.ClientConnector.newConnection(ClientConnector.java:531)
    at org.eclipse.jetty.io.ClientConnector$ClientSelectorManager.newConnection(ClientConnector.java:563)
    at org.eclipse.jetty.io.ManagedSelector.createEndPoint(ManagedSelector.java:385)
    at org.eclipse.jetty.io.ManagedSelector$CreateEndPoint.run(ManagedSelector.java:1078)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
    at java.base/java.lang.Thread.run(Thread.java:842)
Could not connect to localhost/127.0.0.1:8444
Connecting non-blocking to localhost/[0:0:0:0:0:0:0:1]:8444
Queued change lazy=false Connect@2db20e2b{java.nio.channels.SocketChannel[connection-pending remote=localhost/[0:0:0:0:0:0:0:1]:8444],{org.eclipse.jetty.client.destination=HttpDestination[Origin@81a1b08d[https://localhost:8444,tag=null,protocol=Protocol@198cd[proto=[h2],nego=false]]]@2d26680d,state=STARTED,queue=1,pool=MultiplexConnectionPool@76895a2c[s=STARTED,c=1/1/200,a=0,i=0,q=1,p=@5b0bf7f9[inUse=0,size=1,max=200,closed=false]],stale=false,idle=-1, org.eclipse.jetty.client.connector.applicationProtocols=[h2], org.eclipse.jetty.client.connection.promise=org.eclipse.jetty.client.HttpClient$1$1@65ddd85e, org.eclipse.jetty.client=HttpClient@a0f53fc{STARTED}, org.eclipse.jetty.client.http2=HTTP2Client@77eb5790{STARTED}, org.eclipse.jetty.client.connector.remoteSocketAddress=localhost/127.0.0.1:8444, org.eclipse.jetty.client.http2.sessionPromise=org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2$SessionListenerPromise@6f071967, org.eclipse.jetty.client.connector.clientConnectionFactory=org.eclipse.jetty.io.ssl.SslClientConnectionFactory@7392252c, org.eclipse.jetty.client.ssl.engine=org.bouncycastle.jsse.provider.ProvSSLEngine_9@4ae7c717, org.eclipse.jetty.client.http2.sessionListener=org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2$SessionListenerPromise@6f071967, org.eclipse.jetty.client.connector=ClientConnector@602ae7b6{STARTED}, org.eclipse.jetty.client.connector.connectionPromise=org.eclipse.jetty.util.Promise$1@9870ca9}} on ManagedSelector@5e820eca{STARTED} id=0 keys=1 selected=0 updates=0
Wakeup on submit ManagedSelector@5e820eca{STARTED} id=0 keys=1 selected=0 updates=1
Selector sun.nio.ch.WEPollSelectorImpl@5f87928a woken with none selected
ran CreateEndPoint@7052827e{Connect@1411e359{java.nio.channels.SocketChannel[closed],{org.eclipse.jetty.client.destination=HttpDestination[Origin@81a1b08d[https://localhost:8444,tag=null,protocol=Protocol@198cd[proto=[h2],nego=false]]]@2d26680d,state=STARTED,queue=1,pool=MultiplexConnectionPool@76895a2c[s=STARTED,c=1/1/200,a=0,i=0,q=1,p=@5b0bf7f9[inUse=0,size=1,max=200,closed=false]],stale=false,idle=-1, org.eclipse.jetty.client.connector.applicationProtocols=[h2], org.eclipse.jetty.client.connection.promise=org.eclipse.jetty.client.HttpClient$1$1@65ddd85e, org.eclipse.jetty.client=HttpClient@a0f53fc{STARTED}, org.eclipse.jetty.client.http2=HTTP2Client@77eb5790{STARTED}, org.eclipse.jetty.client.connector.remoteSocketAddress=localhost/127.0.0.1:8444, org.eclipse.jetty.client.http2.sessionPromise=org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2$SessionListenerPromise@6f071967, org.eclipse.jetty.client.connector.clientConnectionFactory=org.eclipse.jetty.io.ssl.SslClientConnectionFactory@7392252c, org.eclipse.jetty.client.ssl.engine=org.bouncycastle.jsse.provider.ProvSSLEngine_9@4ae7c717, org.eclipse.jetty.client.http2.sessionListener=org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2$SessionListenerPromise@6f071967, org.eclipse.jetty.client.connector=ClientConnector@602ae7b6{STARTED}, org.eclipse.jetty.client.connector.connectionPromise=org.eclipse.jetty.util.Promise$1@9870ca9}}} in QueuedThreadPool[HttpClient@a0f53fc]@7feaa5fb{STARTED,8<=8<=200,i=5,r=-1,t=59869ms,q=0}[ReservedThreadExecutor@56c22180{reserved=0/8,pending=0}]
sbordet commented 2 weeks ago

Jetty 11 is at end of community support, see:

Having said that, we do not have an implementation of ALPNProcessor for BouncyCastle, but probably we should.

In any case, if this feature is contributed (by you?) or implemented by us, it will be done in Jetty 12.

sanjerai commented 3 days ago

@sbordet

i was able to write a custom implementation following the Conscrypt implementation and run BCJSSE with jetty.

import java.security.Security;
import javax.net.ssl.SSLEngine;

import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.eclipse.jetty.alpn.client.ALPNClientConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.ssl.ALPNProcessor;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;

public class BouncyCastleClientALPNProcessor implements ALPNProcessor.Client {

    @Override
    public void init() {
        if (Security.getProvider("BCJSSE") == null) {
            Security.addProvider(new BouncyCastleJsseProvider());
            System.out.println("Added BouncyCastle JSSE provider");
        }
    }

    @Override
    public boolean appliesTo(SSLEngine sslEngine) {
        return sslEngine.getClass().getName().startsWith("org.bouncycastle.jsse.provider.");
    }

    @Override
    public void configure(SSLEngine sslEngine, Connection connection) {
        try {
            ALPNClientConnection alpn = (ALPNClientConnection) connection;
            String[] protocols = alpn.getProtocols().toArray(new String[0]);
            sslEngine.setHandshakeApplicationProtocolSelector((engine, protocolsList) -> {
                for (String protocol : protocolsList) {
                    for (String supported : protocols) {
                        if (supported.equals(protocol)) {
                            return protocol;
                        }
                    }
                }
                return null;
            });
            ((SslConnection.DecryptedEndPoint) connection.getEndPoint()).getSslConnection()
                    .addHandshakeListener(new ALPNListener(alpn));
        } catch (RuntimeException x) {
            throw x;
        } catch (Exception x) {
            throw new RuntimeException(x);
        }
    }

    private final class ALPNListener implements SslHandshakeListener {
        private final ALPNClientConnection alpnConnection;

        private ALPNListener(ALPNClientConnection connection) {
            alpnConnection = connection;
        }

        @Override
        public void handshakeSucceeded(Event event) {
            System.out.println("Entering handshakeSucceeded");
            try {
                SSLEngine sslEngine = alpnConnection.getSSLEngine();
                String protocol = sslEngine.getApplicationProtocol();               
                System.out.println("Selected "+ protocol + " for " + alpnConnection);
                alpnConnection.selected(protocol);  
            } catch (Throwable e) {
                System.out.println("Unable to process BouncyCastle ApplicationProtocol for "+ alpnConnection);
                System.out.println("handshakeSucceeded exception " + e);
                alpnConnection.selected(null);
            }
        }
    }
}
sbordet commented 2 days ago

@sanjerai if you want to write also a server-side implementation, and make a PR against the jetty-12.0.x branch, we could accept your contribution.

Please read: https://github.com/jetty/jetty.project/blob/jetty-12.0.x/CONTRIBUTING.md