diff --git a/web-modules/jersey-2/pom.xml b/web-modules/jersey-2/pom.xml index 1cac59a9312d..8d24f67c06ff 100644 --- a/web-modules/jersey-2/pom.xml +++ b/web-modules/jersey-2/pom.xml @@ -115,6 +115,12 @@ jakarta.xml.bind-api ${jaxb-runtime.version} + + org.wiremock + wiremock + ${wiremock.version} + test + @@ -144,6 +150,7 @@ 3.0.1 5.8.2 4.5.1 + 3.13.0 diff --git a/web-modules/jersey-2/src/test/java/com/baeldung/jersey/https/JerseyHttpsClientManualTest.java b/web-modules/jersey-2/src/test/java/com/baeldung/jersey/https/JerseyHttpsClientManualTest.java new file mode 100644 index 000000000000..cfc1ebe1c00b --- /dev/null +++ b/web-modules/jersey-2/src/test/java/com/baeldung/jersey/https/JerseyHttpsClientManualTest.java @@ -0,0 +1,130 @@ +package com.baeldung.jersey.https; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Paths; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.SSLContext; + +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.Response; + +/** + * 1. run {@code gen-keys.sh api.service} to generate server certificate/stores + * 2. specify system properties with the directory of the certificates and password + * 3. create api.service entries for 127.0.0.1 in /etc/hosts + * 4. run this class + */ +class JerseyHttpsClientManualTest { + + static final String MOCK_HOST = "api.service"; + static final String CERTS_DIR = System.getProperty("certs.dir"); + static final String PASSWORD = System.getProperty("certs.password"); + + WireMockServer api; + + void setup(boolean mTls) { + api = mockHttpsServer(mTls); + api.stubFor(get(urlEqualTo("/test")).willReturn(aResponse().withHeader("Content-Type", "text/plain") + .withStatus(200) + .withBody("ok"))); + api.start(); + + System.out.println(">> Mock server started on HTTPS port: " + api.httpsPort()); + } + + @AfterEach + void teardown() { + if (api != null) { + api.stop(); + } + } + + @Test + void whenUsingJVMParameters_thenCorrectCertificateUsed() { + setup(false); + + System.setProperty("javax.net.ssl.trustStore", CERTS_DIR + "/trust." + MOCK_HOST + ".p12"); + System.setProperty("javax.net.ssl.trustStorePassword", PASSWORD); + + Response response = ClientBuilder.newClient().target(String.format("https://%s:%d/test", api.getOptions().bindAddress(), api.httpsPort())) + .request() + .get(); + + assertEquals(200, response.getStatus()); + } + + @Test + void whenUsingCustomSSLContext_thenCorrectCertificateUsed() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { + setup(false); + + SSLContext sslContext = SSLContextBuilder.create() + .loadTrustMaterial(Paths.get(CERTS_DIR + "/trust." + MOCK_HOST + ".p12"), PASSWORD.toCharArray()) + .build(); + + Client client = ClientBuilder.newBuilder() + .sslContext(sslContext) + .build(); + + Response response = client.target(String.format("https://%s:%d/test", api.getOptions().bindAddress(), api.httpsPort())) + .request() + .get(); + assertEquals(200, response.getStatus()); + } + + @Test + void whenUsingMutualTLS_thenCorrectCertificateUsed() throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { + setup(true); + + char[] password = PASSWORD.toCharArray(); + + SSLContext sslContext = SSLContextBuilder.create() + .loadTrustMaterial(Paths.get(CERTS_DIR + "/trust." + MOCK_HOST + ".p12"), password) + .loadKeyMaterial(Paths.get(CERTS_DIR + "/client." + MOCK_HOST + ".p12"), password, password) + .build(); + + Client client = ClientBuilder.newBuilder() + .sslContext(sslContext) + .build(); + + Response response = client.target(String.format("https://%s:%d/test", api.getOptions().bindAddress(), api.httpsPort())) + .request() + .get(); + assertEquals(200, response.getStatus()); + } + + private static WireMockServer mockHttpsServer(boolean mTls) { + WireMockConfiguration config = WireMockConfiguration.options() + .bindAddress(MOCK_HOST) + .dynamicPort() + .dynamicHttpsPort() + .keystorePath(CERTS_DIR + "/server." + MOCK_HOST + ".p12") + .keystorePassword(PASSWORD) + .keyManagerPassword(PASSWORD); + + if (mTls) { + config.trustStorePath(CERTS_DIR + "/trust." + MOCK_HOST + ".p12") + .trustStorePassword(PASSWORD) + .needClientAuth(true); + } + + return new WireMockServer(config); + } +} diff --git a/web-modules/jersey-2/src/test/resources/https/gen-keys.sh b/web-modules/jersey-2/src/test/resources/https/gen-keys.sh new file mode 100755 index 000000000000..2bddb1922876 --- /dev/null +++ b/web-modules/jersey-2/src/test/resources/https/gen-keys.sh @@ -0,0 +1,49 @@ +#!/bin/bash -e +# @see JerseyHttpsClientManualTest +# generates a CA, signed client/server keys and trust stores for the provided host. +# e.g.: gen-keys.sh api.service +MYSELF="$(readlink -f $0)" +MYDIR="${MYSELF%/*}" + +HOST=${1} +if [[ -z "$HOST" ]]; then + echo "arg 1 should be the host/alias" + exit 1 +fi + +PASSWORD="${2}" +if [[ -z "$PASSWORD" ]]; then + echo "arg 2 should be the desired password" + exit 1 +fi + +VALIDITY_DAYS=1 +CERTS_DIR=$MYDIR/keystore + +mkdir -p "$CERTS_DIR" + +keytool=$JAVA_HOME/bin/keytool +$JAVA_HOME/bin/java -version + +echo '1. creating certificate authority (CA)' +openssl genrsa -out $CERTS_DIR/ca.${HOST}.key 2048 +openssl req -x509 -new -nodes -key $CERTS_DIR/ca.${HOST}.key -sha256 -days $VALIDITY_DAYS -out $CERTS_DIR/ca.${HOST}.crt -subj "/CN=${HOST}" + +echo '2. generating server key and CSR' +openssl genrsa -out $CERTS_DIR/server.${HOST}.key 2048 +openssl req -new -key $CERTS_DIR/server.${HOST}.key -out $CERTS_DIR/server.${HOST}.csr -subj "/CN=${HOST}" +openssl x509 -req -in $CERTS_DIR/server.${HOST}.csr -CA $CERTS_DIR/ca.${HOST}.crt -CAkey $CERTS_DIR/ca.${HOST}.key -CAcreateserial -out $CERTS_DIR/server.${HOST}.crt -days $VALIDITY_DAYS -sha256 + +echo '3. generating client key and CSR' +openssl genrsa -out $CERTS_DIR/client.${HOST}.key 2048 +openssl req -new -key $CERTS_DIR/client.${HOST}.key -out $CERTS_DIR/client.${HOST}.csr -subj "/CN=${HOST}" +openssl x509 -req -in $CERTS_DIR/client.${HOST}.csr -CA $CERTS_DIR/ca.${HOST}.crt -CAkey $CERTS_DIR/ca.${HOST}.key -CAcreateserial -out $CERTS_DIR/client.${HOST}.crt -days $VALIDITY_DAYS -sha256 + +echo '4. creating PKCS12 keystores' +openssl pkcs12 -export -out $CERTS_DIR/server.${HOST}.p12 -inkey $CERTS_DIR/server.${HOST}.key -in $CERTS_DIR/server.${HOST}.crt -certfile $CERTS_DIR/ca.${HOST}.crt -name ${HOST} -passout pass:$PASSWORD +openssl pkcs12 -export -out $CERTS_DIR/client.${HOST}.p12 -inkey $CERTS_DIR/client.${HOST}.key -in $CERTS_DIR/client.${HOST}.crt -certfile $CERTS_DIR/ca.${HOST}.crt -name ${HOST} -passout pass:$PASSWORD + +echo '5. creating truststore' +$keytool -importcert -keystore $CERTS_DIR/trust.${HOST}.p12 -storetype PKCS12 -storepass $PASSWORD -alias ca.${HOST} -file $CERTS_DIR/ca.${HOST}.crt -noprompt + +echo done