Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
sanjerai opened this issue Oct 24, 2024 · 3 comments
Labels

Comments

@sanjerai
Copy link

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
Copy link
Contributor

sbordet commented Oct 24, 2024

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
Copy link
Author

sanjerai commented Nov 5, 2024

@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
Copy link
Contributor

sbordet commented Nov 5, 2024

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: No status
Development

No branches or pull requests

2 participants