From b653c645914a105163089470ef47d3dae124d730 Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 14 Sep 2022 14:22:38 +0200 Subject: [PATCH] Resteasy Rest Client: Fix truststore password issue with Vert.x The truststore password was being sent as empty ("") in the JksOptions. This caused the following exception: ``` Caused by: io.vertx.core.VertxException: java.security.UnrecoverableKeyException: Get Key failed: Given final block not properly padded. Such issues can arise if a bad key is used during decryption. [09:59:27.352] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.getContext(SSLHelper.java:480) [09:59:27.353] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.getContext(SSLHelper.java:469) [09:59:27.353] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.validate(SSLHelper.java:507) [09:59:27.353] [INFO] [client] at io.vertx.core.net.impl.NetClientImpl.(NetClientImpl.java:95) [09:59:27.353] [INFO] [client] at io.vertx.core.http.impl.HttpClientImpl.(HttpClientImpl.java:155) [09:59:27.354] [INFO] [client] at io.vertx.core.impl.VertxImpl.createHttpClient(VertxImpl.java:338) [09:59:27.354] [INFO] [client] at io.vertx.core.impl.VertxImpl.createHttpClient(VertxImpl.java:350) [09:59:27.354] [INFO] [client] at org.jboss.resteasy.reactive.client.impl.ClientImpl.(ClientImpl.java:170) [09:59:27.354] [INFO] [client] at org.jboss.resteasy.reactive.client.impl.ClientBuilderImpl.build(ClientBuilderImpl.java:244) [09:59:27.354] [INFO] [client] at io.quarkus.rest.client.reactive.runtime.RestClientBuilderImpl.build(RestClientBuilderImpl.java:332) [09:59:27.355] [INFO] [client] at io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilder.build(RestClientCDIDelegateBuilder.java:64) [09:59:27.355] [INFO] [client] at io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilder.createDelegate(RestClientCDIDelegateBuilder.java:42) [09:59:27.355] [INFO] [client] at io.quarkus.rest.client.reactive.runtime.RestClientReactiveCDIWrapperBase.(RestClientReactiveCDIWrapperBase.java:20) [09:59:27.355] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper.(Unknown Source) [09:59:27.356] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper_ClientProxy.(Unknown Source) [09:59:27.356] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper_Bean.proxy(Unknown Source) [09:59:27.356] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper_Bean.get(Unknown Source) [09:59:27.356] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper_Bean.get(Unknown Source) [09:59:27.357] [INFO] [client] ... 26 more [09:59:27.357] [INFO] [client] Caused by: java.security.UnrecoverableKeyException: Get Key failed: Given final block not properly padded. Such issues can arise if a bad key is used during decryption. [09:59:27.357] [INFO] [client] at java.base/sun.security.pkcs12.PKCS12KeyStore.engineGetKey(PKCS12KeyStore.java:446) [09:59:27.357] [INFO] [client] at java.base/sun.security.util.KeyStoreDelegator.engineGetKey(KeyStoreDelegator.java:90) [09:59:27.357] [INFO] [client] at java.base/java.security.KeyStore.getKey(KeyStore.java:1057) [09:59:27.357] [INFO] [client] at io.vertx.core.net.impl.KeyStoreHelper.(KeyStoreHelper.java:109) [09:59:27.358] [INFO] [client] at io.vertx.core.net.KeyStoreOptionsBase.getHelper(KeyStoreOptionsBase.java:187) [09:59:27.358] [INFO] [client] at io.vertx.core.net.KeyStoreOptionsBase.getTrustManagerFactory(KeyStoreOptionsBase.java:217) [09:59:27.358] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.getTrustMgrFactory(SSLHelper.java:327) [09:59:27.358] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.getContext(SSLHelper.java:478) [09:59:27.358] [INFO] [client] ... 43 more [09:59:27.359] [INFO] [client] Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption. [09:59:27.359] [INFO] [client] at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975) [09:59:27.359] [INFO] [client] at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056) [09:59:27.359] [INFO] [client] at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853) [09:59:27.359] [INFO] [client] at java.base/com.sun.crypto.provider.PKCS12PBECipherCore.implDoFinal(PKCS12PBECipherCore.java:408) [09:59:27.360] [INFO] [client] at java.base/com.sun.crypto.provider.PKCS12PBECipherCore$PBEWithSHA1AndDESede.engineDoFinal(PKCS12PBECipherCore.java:440) [09:59:27.360] [INFO] [client] at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202) [09:59:27.360] [INFO] [client] at java.base/sun.security.pkcs12.PKCS12KeyStore.lambda$engineGetKey$0(PKCS12KeyStore.java:387) [09:59:27.360] [INFO] [client] at java.base/sun.security.pkcs12.PKCS12KeyStore$RetryWithZero.run(PKCS12KeyStore.java:283) [09:59:27.360] [INFO] [client] at java.base/sun.security.pkcs12.PKCS12KeyStore.engineGetKey(PKCS12KeyStore.java:381) [09:59:27.361] [INFO] [client] ... 50 more ``` --- .../runtime/RestClientBuilderImpl.java | 5 ++++ .../runtime/RestClientCDIDelegateBuilder.java | 6 ++-- .../RestClientCDIDelegateBuilderTest.java | 4 +-- .../client/impl/ClientBuilderImpl.java | 10 +++++-- .../rest-client-reactive/pom.xml | 30 +++++++++++++++++++ .../client/main/ClientCallingResource.java | 7 +++++ .../selfsigned/ExternalSelfSignedClient.java | 15 ++++++++++ .../src/main/resources/application.properties | 5 +++- .../ExternalSelfSignedTestCase.java | 21 +++++++++++++ 9 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/selfsigned/ExternalSelfSignedClient.java create mode 100644 integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java index 12c830977b9c8..81c315c22837d 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java @@ -95,6 +95,11 @@ public RestClientBuilderImpl trustStore(KeyStore trustStore) { return this; } + public RestClientBuilderImpl trustStore(KeyStore trustStore, String trustStorePassword) { + clientBuilder.trustStore(trustStore, trustStorePassword.toCharArray()); + return this; + } + @Override public RestClientBuilderImpl keyStore(KeyStore keyStore, String keystorePassword) { clientBuilder.keyStore(keyStore, keystorePassword); diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java index 83e647ae666df..d5f998178a40c 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java @@ -182,7 +182,7 @@ private void configureShared(RestClientBuilder builder) { } } - private void configureSsl(RestClientBuilder builder) { + private void configureSsl(RestClientBuilderImpl builder) { Optional maybeTrustStore = oneOf(clientConfigByClassName().trustStore, clientConfigByConfigKey().trustStore, configRoot.trustStore); @@ -249,7 +249,7 @@ private void registerKeyStore(String keyStorePath, RestClientBuilder builder) { } } - private void registerTrustStore(String trustStorePath, RestClientBuilder builder) { + private void registerTrustStore(String trustStorePath, RestClientBuilderImpl builder) { Optional maybeTrustStorePassword = oneOf(clientConfigByClassName().trustStorePassword, clientConfigByConfigKey().trustStorePassword, configRoot.trustStorePassword); Optional maybeTrustStoreType = oneOf(clientConfigByClassName().trustStoreType, @@ -269,7 +269,7 @@ private void registerTrustStore(String trustStorePath, RestClientBuilder builder e); } - builder.trustStore(trustStore); + builder.trustStore(trustStore, password); } catch (KeyStoreException e) { throw new IllegalArgumentException("Failed to initialize trust store from " + trustStorePath, e); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java index 60f80120fb6a7..9a0ec4493673d 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java @@ -109,7 +109,7 @@ public void testClientSpecificConfigs() { Mockito.verify(restClientBuilderMock).register(MyResponseFilter1.class); Mockito.verify(restClientBuilderMock).queryParamStyle(QueryParamStyle.COMMA_SEPARATED); - Mockito.verify(restClientBuilderMock).trustStore(Mockito.any()); + Mockito.verify(restClientBuilderMock).trustStore(Mockito.any(), Mockito.anyString()); Mockito.verify(restClientBuilderMock).keyStore(Mockito.any(), Mockito.anyString()); } @@ -151,7 +151,7 @@ public void testGlobalConfigs() { Mockito.verify(restClientBuilderMock).register(MyResponseFilter2.class); Mockito.verify(restClientBuilderMock).queryParamStyle(QueryParamStyle.MULTI_PAIRS); - Mockito.verify(restClientBuilderMock).trustStore(Mockito.any()); + Mockito.verify(restClientBuilderMock).trustStore(Mockito.any(), Mockito.anyString()); Mockito.verify(restClientBuilderMock).keyStore(Mockito.any(), Mockito.anyString()); } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java index e8020ea6c1547..6afe4fb4d6e52 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java @@ -46,6 +46,7 @@ public class ClientBuilderImpl extends ClientBuilder { private char[] keystorePassword; private SSLContext sslContext; private KeyStore trustStore; + private char[] trustStorePassword; private String proxyHost; private int proxyPort; @@ -88,7 +89,12 @@ public ClientBuilder keyStore(KeyStore keyStore, char[] password) { @Override public ClientBuilder trustStore(KeyStore trustStore) { + return trustStore(trustStore, null); + } + + public ClientBuilder trustStore(KeyStore trustStore, char[] password) { this.trustStore = trustStore; + this.trustStorePassword = password; return this; } @@ -164,7 +170,7 @@ public ClientBuilder clientLogger(ClientLogger clientLogger) { @Override public ClientImpl build() { Buffer keyStore = asBuffer(this.keyStore, keystorePassword); - Buffer trustStore = asBuffer(this.trustStore, EMPTY_CHAR_ARARAY); + Buffer trustStore = asBuffer(this.trustStore, this.trustStorePassword); HttpClientOptions options = Optional.ofNullable(configuration.getFromContext(HttpClientOptions.class)) .orElseGet(HttpClientOptions::new); @@ -185,7 +191,7 @@ public ClientImpl build() { if (trustStore != null) { JksOptions jks = new JksOptions(); jks.setValue(trustStore); - jks.setPassword(""); + jks.setPassword(trustStorePassword == null ? "" : new String(trustStorePassword)); options.setTrustStoreOptions(jks); } } diff --git a/integration-tests/rest-client-reactive/pom.xml b/integration-tests/rest-client-reactive/pom.xml index 8889bbc39f7ad..d81583d01c89e 100644 --- a/integration-tests/rest-client-reactive/pom.xml +++ b/integration-tests/rest-client-reactive/pom.xml @@ -11,6 +11,11 @@ quarkus-integration-test-rest-client-reactive Quarkus - Integration Tests - REST Client Reactive + + ${project.build.directory}/self-signed.p12 + changeit + + @@ -165,6 +170,31 @@ + + + uk.co.automatictester + truststore-maven-plugin + ${truststore-maven-plugin.version} + + + self-signed-truststore + generate-test-resources + + generate-truststore + + + PKCS12 + ${self-signed.trust-store} + ${self-signed.trust-store-password} + + self-signed.badssl.com:443 + + true + LEAF + + + + diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java index 5b6252ce83a00..1e311fef17815 100644 --- a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java @@ -21,6 +21,7 @@ import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.quarkus.it.rest.client.main.MyResponseExceptionMapper.MyException; +import io.quarkus.it.rest.client.main.selfsigned.ExternalSelfSignedClient; import io.smallrye.mutiny.Uni; import io.vertx.core.Future; import io.vertx.core.json.Json; @@ -44,6 +45,9 @@ public class ClientCallingResource { @RestClient FaultToleranceOnInterfaceClient faultToleranceOnInterfaceClient; + @RestClient + ExternalSelfSignedClient externalSelfSignedClient; + @Inject InMemorySpanExporter inMemorySpanExporter; @@ -165,6 +169,9 @@ void init(@Observes Router router) { }); router.get("/with%20space").handler(rc -> rc.response().setStatusCode(200).end()); + + router.get("/self-signed").blockingHandler( + rc -> rc.response().setStatusCode(200).end(String.valueOf(externalSelfSignedClient.invoke().getStatus()))); } private Future success(RoutingContext rc, String body) { diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/selfsigned/ExternalSelfSignedClient.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/selfsigned/ExternalSelfSignedClient.java new file mode 100644 index 0000000000000..7d2610f75b4be --- /dev/null +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/selfsigned/ExternalSelfSignedClient.java @@ -0,0 +1,15 @@ +package io.quarkus.it.rest.client.main.selfsigned; + +import javax.ws.rs.GET; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(baseUri = "https://self-signed.badssl.com/", configKey = "self-signed") +public interface ExternalSelfSignedClient { + + @GET + @Retry(delay = 1000) + Response invoke(); +} diff --git a/integration-tests/rest-client-reactive/src/main/resources/application.properties b/integration-tests/rest-client-reactive/src/main/resources/application.properties index 392722ce0c912..03c013f85ce30 100644 --- a/integration-tests/rest-client-reactive/src/main/resources/application.properties +++ b/integration-tests/rest-client-reactive/src/main/resources/application.properties @@ -1,4 +1,7 @@ w-exception-mapper/mp-rest/url=${test.url} w-fault-tolerance/mp-rest/url=${test.url} io.quarkus.it.rest.client.main.ParamClient/mp-rest/url=${test.url} -io.quarkus.it.rest.client.multipart.MultipartClient/mp-rest/url=${test.url} \ No newline at end of file +io.quarkus.it.rest.client.multipart.MultipartClient/mp-rest/url=${test.url} +# HTTPS +quarkus.rest-client.self-signed.trust-store=${self-signed.trust-store} +quarkus.rest-client.self-signed.trust-store-password=${self-signed.trust-store-password} diff --git a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java new file mode 100644 index 0000000000000..1348f532290fc --- /dev/null +++ b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java @@ -0,0 +1,21 @@ +package io.quarkus.it.rest.client.selfsigned; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class ExternalSelfSignedTestCase { + + @Test + public void should_accept_self_signed_certs() { + when() + .get("/self-signed") + .then() + .statusCode(200) + .body(is("200")); + } +}