mutual authentication is a common way to authentication your client or integrate with upstream/downstream services. This post will demostrate how to enable mutual authentication, or so-call mTLS in SSL, in Jetty server.
The overall source code is stored at https://github.com/lawrenceching/jetty-mtls-example.
Change the password "changeit" in below commands if you generate a real certificate in production.
Run below commans to generate server and client certificates. It's notable that when terminal prompts your to input your first name and last name, input your domain name or ip address. It's mandatory that your hostname match your certificate.
I assume that you generate certs in src/main/resources
where we normally do.
# Generate server keystorekeytool -genkey -alias server -keyalg RSA -keypass changeit -storepass changeit -keystore server.jks# Export server certificate from server keystore and import it into truststore.# Client need it to validate server certificate in mTLS.keytool -export -alias server -storepass changeit -file server.cer -keystore server.jkskeytool -import -file ./server.cer -alias server -keystore client_truststore.jks# ---# Generate client keystorekeytool -genkey -alias client -keyalg RSA -keypass changeit -storepass changeit -keystore client.jks# Export client certificate and import in into truststore.# Server need it to validate client certificate in mTLS.keytool -export -alias client -storepass changeit -file client.cer -keystore client.jkskeytool -import -file ./client.cer -alias client -keystore server_truststore.jks# You you can convert them to PKCS12 or PEM formatkeytool -importkeystore -srckeystore client.jks -destkeystore client.p12 -srcalias client -srcstoretype jks -deststoretype pkcs12openssl pkcs12 -in client.p12 -nokeys -out client.crtopenssl pkcs12 -in client.p12 -nocerts -nodes -out client.key
The key to Jetty is to configurate the SslContextFactory. Things are pretty straightforward. You set the KeyStore and KeyStore password, and then set the TrustStore and TrustStore password. You need to call setNeedClientAuth(true)
to enable mTLS.
// https://github.com/lawrenceching/jetty-mtls-example/blob/master/src/main/java/me/imlc/JettyMtlsServer.java#L25-L32SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();sslContextFactory.setKeyStorePath(this.getClass().getResource("/server.jks").toExternalForm());sslContextFactory.setKeyStorePassword("changeit");sslContextFactory.setTrustStorePath(this.getClass().getResource("/server_truststore.jks").toExternalForm());sslContextFactory.setTrustStorePassword("changeit");sslContextFactory.setNeedClientAuth(true);
We usually use curl
to be a test client. However curl
doesn't know .jks file. So we need some convertion.
# Convert KeyStore to pkcs12 formatkeytool -importkeystore -srckeystore client.jks -destkeystore client.p12 -srcalias client -srcstoretype jks -deststoretype pkcs12# Convert private key and certificate to .key and .crt in PEM formatopenssl pkcs12 -in client.p12 -nokeys -out client.crtopenssl pkcs12 -in client.p12 -nocerts -nodes -out client.key# curl it!curl -v -k https://localhost:8443 \--cert ./client.crt \--key ./client.key# Orcurl -v -k --cert-type=P12 --cert ./client.p12 \https://localhost:8443
Or you may want to call mTLS server in Java client. Here is an example using Jetty http client. You can find similar configuration in OkHttp or Apacha Http Client.
https://github.com/lawrenceching/jetty-mtls-example/blob/master/src/test/java/me/imlc/JettyMtlsServerTest.java#L26-L46@Testpublic void canAuthenticateClient() throws Exception {SslContextFactory sslContextFactory = new SslContextFactory.Client();sslContextFactory.setKeyStorePath(this.getClass().getResource("/client.jks").toExternalForm());sslContextFactory.setKeyStorePassword("changeit");sslContextFactory.setTrustAll(true);sslContextFactory.setTrustStorePath(this.getClass().getResource("/client_truststore.jks").toExternalForm());sslContextFactory.setTrustStorePassword("changeit");HttpClient httpClient = new HttpClient(sslContextFactory);httpClient.start();ContentResponse response = httpClient.newRequest("https://localhost:8443").send();assertThat(200, equalTo(response.getStatus()));assertThat("Hello, world", equalTo(response.getContentAsString()));httpClient.stop();}