diff --git a/.github/actions/build-and-run-jvm-tests/action.yml b/.github/actions/build-and-run-jvm-tests/action.yml index f610312f7..bb3d8a455 100644 --- a/.github/actions/build-and-run-jvm-tests/action.yml +++ b/.github/actions/build-and-run-jvm-tests/action.yml @@ -42,10 +42,10 @@ runs: run: ./mvnw -B formatter:validate install -fae -ntp # Same as the previous but only JVM tests and different default ConduitFactory - - name: QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY=VertxHttpClientHTTPConduitFactory mvn -B test + - name: QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY=URLConnectionHTTPConduitFactory mvn -B test shell: bash env: - QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY: VertxHttpClientHTTPConduitFactory + QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY: URLConnectionHTTPConduitFactory run: ./mvnw -B clean install -fae -ntp - name: 'Upload generated Antora docs site' diff --git a/.github/actions/run-native-test/action.yml b/.github/actions/run-native-test/action.yml index fdac7ae67..e56d8a4a7 100644 --- a/.github/actions/run-native-test/action.yml +++ b/.github/actions/run-native-test/action.yml @@ -49,10 +49,10 @@ runs: fi # Same as the previous but different default ConduitFactory - - name: Run integration test ${{ inputs.test-module-spec }} with QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY=VertxHttpClientHTTPConduitFactory + - name: Run integration test ${{ inputs.test-module-spec }} with QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY=URLConnectionHTTPConduitFactory shell: bash env: - QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY: VertxHttpClientHTTPConduitFactory + QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY: URLConnectionHTTPConduitFactory # Skip native tests for CodeQL Security Scans if: "${{ env.SKIP_NATIVE_TESTS != 'true' }}" run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf16459b0..9abe51efe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,9 +83,9 @@ jobs: -fae # Same as the previous but different default ConduitFactory - - name: mvn test + - name: QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY=URLConnectionHTTPConduitFactory mvn test env: - QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY: VertxHttpClientHTTPConduitFactory + QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY: URLConnectionHTTPConduitFactory shell: bash run: | ./mvnw -B clean test \ diff --git a/bom/pom.xml b/bom/pom.xml index f85265353..b79716be6 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -260,11 +260,6 @@ quarkus-cxf-services-sts-deployment ${quarkus-cxf.version} - - io.quarkiverse.cxf - quarkus-cxf-vertx-client - ${quarkus-cxf.version} - io.quarkiverse.cxf quarkus-cxf-woodstox diff --git a/docs/modules/ROOT/examples/ws-security-policy/application.properties b/docs/modules/ROOT/examples/ws-security-policy/application.properties index 74b3e6f68..534649d4c 100644 --- a/docs/modules/ROOT/examples/ws-security-policy/application.properties +++ b/docs/modules/ROOT/examples/ws-security-policy/application.properties @@ -154,6 +154,14 @@ quarkus.cxf.client.helloAllowAll.trust-store = client-truststore.pkcs12 quarkus.cxf.client.helloAllowAll.trust-store-password = client-truststore-password quarkus.cxf.client.helloAllowAll.hostname-verifier = AllowAllHostnameVerifier + +quarkus.tls.helloAllowAll.trust-store.p12.path = client-truststore.pkcs12 +quarkus.tls.helloAllowAll.trust-store.p12.password = client-truststore-password +quarkus.tls.helloAllowAll.hostname-verification-algorithm = NONE +quarkus.cxf.client.helloAllowAllTlsConfig.client-endpoint-url = https://127.0.0.1:${quarkus.http.test-ssl-port}/services/hello +quarkus.cxf.client.helloAllowAllTlsConfig.service-interface = io.quarkiverse.cxf.it.security.policy.HelloService +quarkus.cxf.client.helloAllowAllTlsConfig.tls-configuration-name = helloAllowAll + quarkus.cxf.client.helloCustomHostnameVerifier.client-endpoint-url = https://127.0.0.1:${quarkus.http.test-ssl-port}/services/hello quarkus.cxf.client.helloCustomHostnameVerifier.service-interface = io.quarkiverse.cxf.it.security.policy.HelloService quarkus.cxf.client.helloCustomHostnameVerifier.trust-store = client-truststore.pkcs12 diff --git a/docs/modules/ROOT/pages/reference/extensions/quarkus-cxf.adoc b/docs/modules/ROOT/pages/reference/extensions/quarkus-cxf.adoc index 54c53bb57..05527c2a1 100644 --- a/docs/modules/ROOT/pages/reference/extensions/quarkus-cxf.adoc +++ b/docs/modules/ROOT/pages/reference/extensions/quarkus-cxf.adoc @@ -2002,12 +2002,25 @@ instead. - One of the well known values: `AllowAllHostnameVerifier`, `HttpsURLConnectionDefaultHostnameVerifier` - A fully qualified class name implementing `javax.net.ssl.HostnameVerifier` to look up in the CDI container. -- A bean name prefixed with `++#++` that will be looked up in the CDI container; example: `++#++myHostnameVerifier` If -not specified, then the creation of the `HostnameVerifier` is delegated to CXF, which boils down to +- A bean name prefixed with `++#++` that will be looked up in the CDI container; example: `++#++myHostnameVerifier` + +If not specified, then the creation of the `HostnameVerifier` is delegated to CXF, which boils down to `org.apache.cxf.transport.https.httpclient.DefaultHostnameVerifier` with the default `org.apache.cxf.transport.https.httpclient.PublicSuffixMatcherLoader` as returned from `PublicSuffixMatcherLoader.getDefault()`. +[IMPORTANT] +==== +Setting this option when the conduit factory of this client is set to `VertxHttpClientHTTPConduitFactory` +(default since {quarkus-cxf-project-name} 3.16.0) leads to an exception at runtime. +The `AllowAllHostnameVerifier` value of this option can be replaced by using a +xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus-cxf-client-client-name-tls-configuration-name[named TLS +configuration] +with +`{link-quarkus-docs-base}/tls-registry-reference#trusting-all-certificates-and-hostname-verification[hostname-verification-algorithm]` +set to `NONE`. Otherwise, there is no way to implement custom hostname verification for Vert.x HTTP client. +==== + *Environment variable*: `+++QUARKUS_CXF_CLIENT__CLIENT_NAME__HOSTNAME_VERIFIER+++` + *Since Quarkus CXF*: 2.5.0 diff --git a/docs/modules/ROOT/pages/release-notes/3.16.0.adoc b/docs/modules/ROOT/pages/release-notes/3.16.0.adoc new file mode 100644 index 000000000..fa5affe13 --- /dev/null +++ b/docs/modules/ROOT/pages/release-notes/3.16.0.adoc @@ -0,0 +1,44 @@ += {quarkus-cxf-project-name} 3.16.0 LTS release notes + +This is the first release in the new 3.16 https://quarkus.io/blog/lts-releases/[LTS stream]. +As usual, where we plan to produce patch releases with bug and security fixes for 12 months. + +== Important dependency upgrades + +* Quarkus 3.15.x -> 3.16.0 - https://quarkus.io/blog/quarkus-3-16-0-released/[release notes] + +== New and noteworthy in {quarkus-cxf-project-name} + +=== Vert.x HttpClient based HTTP Conduit is the new default + +The `VertxHttpClientHTTPConduitFactory` was introduced in {quarkus-cxf-project-name} 3.13.0. +Since then, it went through some improvements and testing +so that we are confident enough to make it the default for the xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus-cxf-client-client-name-http-conduit-factory[quarkus.cxf.client."client-name".http-conduit-factory] and +xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus-cxf-http-conduit-factory[quarkus.cxf.http-conduit-factory] options. + +==== SSL/TLS configuration + +Prefer the new named TLS configurations for CXF clients. + +==== Hostname verifiers not supported in combination with `VertxHttpClientHTTPConduitFactory` + +Setting `xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus-cxf-client-client-name-hostname-verifier[quarkus.cxf.client."client-name".hostname-verifier]` together with `VertxHttpClientHTTPConduitFactory` leads to an exception at runtime. + +The `AllowAllHostnameVerifier` value of that option can be replaced by using a +xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus-cxf-client-client-name-tls-configuration-name[named TLS configuration] +with `{link-quarkus-docs-base}/tls-registry-reference#trusting-all-certificates-and-hostname-verification[hostname-verification-algorithm]` +set to `NONE`. + +Otherwise, there is no way to implement custom hostname verification for Vert.x HTTP client. + +==== Force the old default + +There are three options how you can get back to the old default: + +* Set the `QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY` environment variable to `URLConnectionHTTPConduitFactory` +* Set the global xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus-cxf-http-conduit-factory[quarkus.cxf.http-conduit-factory] +* Set the per client xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus-cxf-client-client-name-http-conduit-factory[quarkus.cxf.client."client-name".http-conduit-factory] option to `URLConnectionHTTPConduitFactory` + +== Full changelog + +https://github.com/quarkiverse/quarkus-cxf/compare/3.15.0+++...+++3.16.0 diff --git a/docs/src/test/java/io/quarkiverse/cxf/doc/it/AntoraTest.java b/docs/src/test/java/io/quarkiverse/cxf/doc/it/AntoraTest.java index 4d34eb600..69bb99d2f 100644 --- a/docs/src/test/java/io/quarkiverse/cxf/doc/it/AntoraTest.java +++ b/docs/src/test/java/io/quarkiverse/cxf/doc/it/AntoraTest.java @@ -1,6 +1,9 @@ package io.quarkiverse.cxf.doc.it; import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.TimeoutException; @@ -29,12 +32,19 @@ public void antoraSite() throws TimeoutException, IOException, InterruptedExcept @Test public void externalLinks() { - Set ignorables = Set.of( + Set ignorables1 = Set.of( /* known issue https://github.com/quarkiverse/antora-ui-quarkiverse/issues/86 */ "http://quarkus.io/training", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role", "http://schemas.xmlsoap.org/ws/2005/02/sc/dk/p_sha1", "http://www.w3.org/2009/xmlenc11#aes256-gcm"); + final Set ignorables = new LinkedHashSet<>(ignorables1); + + final ZonedDateTime deadline_2_16_0 = ZonedDateTime.parse("2024-10-31T23:59:59+01:00[Europe/Paris]"); + if (ZonedDateTime.now(ZoneId.of("Europe/Paris")).isBefore(deadline_2_16_0)) { + ignorables.add("https://quarkus.io/blog/quarkus-3-16-0-released/"); + ignorables.add("https://github.com/quarkiverse/quarkus-cxf/compare/3.15.0...3.16.0"); + } AntoraTestUtils.assertExternalLinksValid(err -> ignorables.contains(err.uri())); } diff --git a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java index e571fdb1b..511c14f2e 100644 --- a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java +++ b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java @@ -37,10 +37,10 @@ import io.quarkiverse.cxf.CXFClientInfo; import io.quarkiverse.cxf.CXFRecorder; import io.quarkiverse.cxf.ClientInjectionPoint; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; import io.quarkiverse.cxf.CxfClientProducer; import io.quarkiverse.cxf.CxfFixedConfig; import io.quarkiverse.cxf.CxfFixedConfig.ClientFixedConfig; +import io.quarkiverse.cxf.HTTPConduitImpl; import io.quarkiverse.cxf.HttpClientHTTPConduitFactory; import io.quarkiverse.cxf.annotation.CXFClient; import io.quarkiverse.cxf.graal.QuarkusCxfFeature; diff --git a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/BadHostnameTest.java b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/BadHostnameTest.java new file mode 100644 index 000000000..a7c96c85c --- /dev/null +++ b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/BadHostnameTest.java @@ -0,0 +1,116 @@ +package io.quarkiverse.cxf.deployment.test; + +import jakarta.inject.Inject; +import jakarta.jws.WebMethod; +import jakarta.jws.WebService; + +import org.assertj.core.api.Assertions; +import org.jboss.logging.Logger; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkiverse.cxf.annotation.CXFClient; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.certs.Format; +import io.smallrye.certs.junit5.Certificate; +import io.smallrye.certs.junit5.Certificates; + +@Certificates(baseDir = "target/classes", // + certificates = @Certificate( // + name = "fake-host", // + password = "secret", // + cn = "fake-host", // + formats = { Format.PKCS12 })) +public class BadHostnameTest { + + @RegisterExtension + public static final QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloService.class, HelloServiceImpl.class)) + + /* Server TLS */ + .overrideConfigKey("quarkus.tls.key-store.p12.path", "fake-host-keystore.p12") + .overrideConfigKey("quarkus.tls.key-store.p12.password", "secret") + .overrideConfigKey("quarkus.http.insecure-requests", "disabled") + /* Service */ + .overrideConfigKey("quarkus.cxf.endpoint.\"/hello\".implementor", + HelloServiceImpl.class.getName()) + .overrideConfigKey("quarkus.cxf.endpoint.\"/hello\".logging.enabled", "true") + + /* Named TLS configuration for the clients */ + .overrideConfigKey("quarkus.tls.client-pkcs12.trust-store.p12.path", "fake-host-truststore.p12") + .overrideConfigKey("quarkus.tls.client-pkcs12.trust-store.p12.password", "secret") + + /* Client with VertxHttpClientHTTPConduitFactory */ + .overrideConfigKey("quarkus.cxf.client.helloVertx.client-endpoint-url", "https://localhost:8444/services/hello") + .overrideConfigKey("quarkus.cxf.client.helloVertx.logging.enabled", "true") + .overrideConfigKey("quarkus.cxf.client.helloVertx.service-interface", HelloService.class.getName()) + .overrideConfigKey("quarkus.cxf.client.helloVertx.http-conduit-factory", "VertxHttpClientHTTPConduitFactory") + .overrideConfigKey("quarkus.cxf.client.helloVertx.tls-configuration-name", "client-pkcs12") + + /* Client with HttpClientHTTPConduitFactory */ + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.client-endpoint-url", + "https://localhost:8444/services/hello") + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.logging.enabled", "true") + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.service-interface", HelloService.class.getName()) + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.http-conduit-factory", "HttpClientHTTPConduitFactory") + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.tls-configuration-name", "client-pkcs12") + + /* Client with URLConnectionHTTPConduitFactory */ + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.client-endpoint-url", + "https://localhost:8444/services/hello") + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.logging.enabled", "true") + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.service-interface", HelloService.class.getName()) + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.http-conduit-factory", "URLConnectionHTTPConduitFactory") + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.tls-configuration-name", "client-pkcs12"); + + @CXFClient("helloVertx") + HelloService helloVertx; + + @CXFClient("helloHttpClient") + HelloService helloHttpClient; + + @CXFClient("helloUrlConnection") + HelloService helloUrlConnection; + + @Inject + Logger logger; + + @Test + void vertx() { + Assertions.assertThatThrownBy(() -> helloVertx.hello("Doe")).hasRootCauseMessage( + "No subject alternative DNS name matching localhost found."); + } + + @Test + void httpClient() { + Assertions.assertThatThrownBy(() -> helloHttpClient.hello("Doe")).hasRootCauseMessage( + "No name matching localhost found"); + } + + @Test + void urlConnection() { + Assertions.assertThatThrownBy(() -> helloUrlConnection.hello("Doe")).hasRootCauseMessage( + "The https URL hostname does not match the Common Name (CN) on the server certificate in the client's truststore. Make sure server certificate is correct, or to disable this check (NOT recommended for production) set the CXF client TLS configuration property \"disableCNCheck\" to true."); + } + + @WebService + public interface HelloService { + + @WebMethod + String hello(String person); + + } + + @WebService(serviceName = "HelloService") + public static class HelloServiceImpl implements HelloService { + + @Override + public String hello(String person) { + return "Hello " + person; + } + } + +} diff --git a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientConduitFactoryTest.java b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientConduitFactoryTest.java index e9d22f98c..f7ceff1f0 100644 --- a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientConduitFactoryTest.java +++ b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientConduitFactoryTest.java @@ -8,7 +8,6 @@ import org.apache.cxf.BusFactory; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; -import org.apache.cxf.transport.http.HTTPConduitFactory; import org.apache.cxf.transport.http.HttpClientHTTPConduit; import org.apache.cxf.transport.http.URLConnectionHTTPConduit; import org.assertj.core.api.Assertions; @@ -18,12 +17,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; -import io.quarkiverse.cxf.URLConnectionHTTPConduitFactory; +import io.quarkiverse.cxf.HTTPConduitImpl; +import io.quarkiverse.cxf.HTTPConduitSpec; import io.quarkiverse.cxf.annotation.CXFClient; -import io.quarkiverse.cxf.deployment.test.GlobalConduitFactoryTest.HelloService; import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduit; -import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduitFactory; import io.quarkus.test.QuarkusUnitTest; public class ClientConduitFactoryTest { @@ -73,11 +70,10 @@ public class ClientConduitFactoryTest { @Test void conduitFactory() { final Bus bus = BusFactory.getDefaultBus(); - final HTTPConduitFactory factory = bus.getExtension(HTTPConduitFactory.class); - final HTTPConduitImpl impl = HTTPConduitImpl.findDefaultHTTPConduitImpl(); - Assertions.assertThat(factory).isInstanceOf(impl.newHTTPConduitFactory().getClass()); + final HTTPConduitSpec registeredImpl = bus.getExtension(HTTPConduitSpec.class); + HTTPConduitImpl defaultImpl = io.quarkiverse.cxf.HTTPConduitImpl.findDefaultHTTPConduitImpl(); + Assertions.assertThat(registeredImpl.resolveDefault()).isEqualTo(defaultImpl); - HTTPConduitImpl defaultImpl = io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl.findDefaultHTTPConduitImpl(); { final HelloService service = helloDefault; final Client client = ClientProxy.getClient(service); diff --git a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientHttpPolicyDefaultsTest.java b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientHttpPolicyDefaultsTest.java index 0cc76cc9f..da2c3db75 100644 --- a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientHttpPolicyDefaultsTest.java +++ b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientHttpPolicyDefaultsTest.java @@ -14,7 +14,6 @@ import org.apache.cxf.BusFactory; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; -import org.apache.cxf.transport.http.HTTPConduitFactory; import org.apache.cxf.transport.http.URLConnectionHTTPConduit; import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; import org.assertj.core.api.Assertions; @@ -27,10 +26,10 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkiverse.cxf.CxfClientConfig; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; +import io.quarkiverse.cxf.HTTPConduitImpl; +import io.quarkiverse.cxf.HTTPConduitSpec; import io.quarkiverse.cxf.annotation.CXFClient; import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduit; -import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduitFactory; import io.quarkus.test.QuarkusUnitTest; public class ClientHttpPolicyDefaultsTest { @@ -85,9 +84,9 @@ public void defaults() throws IllegalArgumentException, IllegalAccessException, @Test void defaultConduitFactory() { final Bus bus = BusFactory.getDefaultBus(); - final HTTPConduitFactory factory = bus.getExtension(HTTPConduitFactory.class); - HTTPConduitImpl defaultImpl = io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl.findDefaultHTTPConduitImpl(); - Assertions.assertThat(factory).isInstanceOf(defaultImpl.newHTTPConduitFactory().getClass()); + final HTTPConduitSpec registeredImpl = bus.getExtension(HTTPConduitSpec.class); + HTTPConduitImpl defaultImpl = io.quarkiverse.cxf.HTTPConduitImpl.findDefaultHTTPConduitImpl(); + Assertions.assertThat(registeredImpl.resolveDefault()).isEqualTo(defaultImpl); final Client client = ClientProxy.getClient(helloService); switch (defaultImpl) { diff --git a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientReceiveTimeoutTest.java b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientReceiveTimeoutTest.java index 99e9fa5d6..33ebcde6e 100644 --- a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientReceiveTimeoutTest.java +++ b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/ClientReceiveTimeoutTest.java @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; +import io.quarkiverse.cxf.HTTPConduitImpl; import io.quarkiverse.cxf.HttpClientHTTPConduitFactory; import io.quarkiverse.cxf.URLConnectionHTTPConduitFactory; import io.quarkiverse.cxf.annotation.CXFClient; diff --git a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/GlobalConduitFactoryTest.java b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/GlobalConduitFactoryTest.java index 64fd958d9..50bcf8b96 100644 --- a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/GlobalConduitFactoryTest.java +++ b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/GlobalConduitFactoryTest.java @@ -18,7 +18,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; +import io.quarkiverse.cxf.HTTPConduitImpl; import io.quarkiverse.cxf.HttpClientHTTPConduitFactory; import io.quarkiverse.cxf.URLConnectionHTTPConduitFactory; import io.quarkiverse.cxf.annotation.CXFClient; @@ -66,7 +66,7 @@ public class GlobalConduitFactoryTest { void conduitFactory() { final Bus bus = BusFactory.getDefaultBus(); final HTTPConduitFactory factory = bus.getExtension(HTTPConduitFactory.class); - Assertions.assertThat(factory).isInstanceOf(URLConnectionHTTPConduitFactory.class); + Assertions.assertThat(factory).isNull(); { final Client client = ClientProxy.getClient(helloGlobal); diff --git a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/HostnameVerifierTest.java b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/HostnameVerifierTest.java index 972fb7adb..1dca7d64d 100644 --- a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/HostnameVerifierTest.java +++ b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/HostnameVerifierTest.java @@ -116,11 +116,8 @@ void customHostnameVerifierVertx() { Assertions.assertThat(customHostnameVerifier.getCheckedHostNames()).isEmpty(); customHostnameVerifier.setReturnVal(false); Assertions.assertThatThrownBy(() -> helloVertx.hello("Doe")).hasRootCauseMessage( - "The https URL hostname localhost does not match the Common Name (CN) on the server certificate in the client's truststore. Make sure server certificate is correct, or to disable this check (NOT recommended for production) set the CXF client TLS configuration property \"disableCNCheck\" to true."); - Assertions.assertThat(customHostnameVerifier.getCheckedHostNames()).containsExactly("localhost"); - customHostnameVerifier.setReturnVal(true); - Assertions.assertThat(helloVertx.hello("Joe")).isEqualTo("Hello Joe"); - Assertions.assertThat(customHostnameVerifier.getCheckedHostNames()).containsExactly("localhost", "localhost"); + "http-conduit-factory = VertxHttpClientHTTPConduitFactory does not support setting a hostname verifier. AllowAllHostnameVerifier can be replaced by using a named TLS configuration with hostname-verification-algorithm set to NONE"); + Assertions.assertThat(customHostnameVerifier.getCheckedHostNames()).isEmpty(); } @Test diff --git a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/TlsConfigurationTest.java b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/TlsConfigurationTest.java new file mode 100644 index 000000000..1b12fc363 --- /dev/null +++ b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/deployment/test/TlsConfigurationTest.java @@ -0,0 +1,151 @@ +package io.quarkiverse.cxf.deployment.test; + +import jakarta.inject.Inject; +import jakarta.jws.WebMethod; +import jakarta.jws.WebService; + +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.endpoint.Client; +import org.apache.cxf.frontend.ClientProxy; +import org.apache.cxf.transport.http.HTTPConduit; +import org.assertj.core.api.Assertions; +import org.jboss.logging.Logger; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkiverse.cxf.QuarkusTLSClientParameters; +import io.quarkiverse.cxf.annotation.CXFClient; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.certs.Format; +import io.smallrye.certs.junit5.Certificate; +import io.smallrye.certs.junit5.Certificates; + +@Certificates(baseDir = "target/classes", // + certificates = @Certificate( // + name = "localhost", // + password = "secret", // + formats = { Format.PKCS12 })) +public class TlsConfigurationTest { + + @RegisterExtension + public static final QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloService.class, HelloServiceImpl.class)) + + /* Server TLS */ + .overrideConfigKey("quarkus.tls.localhost-pkcs12.key-store.p12.path", "localhost-keystore.p12") + .overrideConfigKey("quarkus.tls.localhost-pkcs12.key-store.p12.password", "secret") + .overrideConfigKey("quarkus.http.tls-configuration-name", "localhost-pkcs12") + .overrideConfigKey("quarkus.http.insecure-requests", "disabled") + /* Service */ + .overrideConfigKey("quarkus.cxf.endpoint.\"/hello\".implementor", + HelloServiceImpl.class.getName()) + .overrideConfigKey("quarkus.cxf.endpoint.\"/hello\".logging.enabled", "true") + + /* Named TLS configuration for the clients */ + .overrideConfigKey("quarkus.tls.client-pkcs12.trust-store.p12.path", "target/classes/localhost-truststore.p12") + .overrideConfigKey("quarkus.tls.client-pkcs12.trust-store.p12.password", "secret") + + /* Client with VertxHttpClientHTTPConduitFactory */ + .overrideConfigKey("quarkus.cxf.client.helloVertx.client-endpoint-url", "https://localhost:8444/services/hello") + .overrideConfigKey("quarkus.cxf.client.helloVertx.logging.enabled", "true") + .overrideConfigKey("quarkus.cxf.client.helloVertx.service-interface", HelloService.class.getName()) + .overrideConfigKey("quarkus.cxf.client.helloVertx.http-conduit-factory", "VertxHttpClientHTTPConduitFactory") + .overrideConfigKey("quarkus.cxf.client.helloVertx.tls-configuration-name", "client-pkcs12") + + .overrideConfigKey("quarkus.cxf.client.helloVertx2.client-endpoint-url", "https://localhost:8444/services/hello") + .overrideConfigKey("quarkus.cxf.client.helloVertx2.logging.enabled", "true") + .overrideConfigKey("quarkus.cxf.client.helloVertx2.service-interface", HelloService.class.getName()) + .overrideConfigKey("quarkus.cxf.client.helloVertx2.http-conduit-factory", "VertxHttpClientHTTPConduitFactory") + .overrideConfigKey("quarkus.cxf.client.helloVertx2.tls-configuration-name", "client-pkcs12") + + /* Client with HttpClientHTTPConduitFactory */ + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.client-endpoint-url", + "https://localhost:8444/services/hello") + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.logging.enabled", "true") + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.service-interface", HelloService.class.getName()) + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.http-conduit-factory", "HttpClientHTTPConduitFactory") + .overrideConfigKey("quarkus.cxf.client.helloHttpClient.tls-configuration-name", "client-pkcs12") + + /* Client with URLConnectionHTTPConduitFactory */ + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.client-endpoint-url", + "https://localhost:8444/services/hello") + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.logging.enabled", "true") + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.service-interface", HelloService.class.getName()) + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.http-conduit-factory", "URLConnectionHTTPConduitFactory") + .overrideConfigKey("quarkus.cxf.client.helloUrlConnection.tls-configuration-name", "client-pkcs12"); + + @CXFClient("helloVertx") + HelloService helloVertx; + + @CXFClient("helloVertx2") + HelloService helloVertx2; + + @CXFClient("helloHttpClient") + HelloService helloHttpClient; + + @CXFClient("helloUrlConnection") + HelloService helloUrlConnection; + + @Inject + Logger logger; + + @Test + void vertx() { + Assertions.assertThat(helloVertx.hello("Doe")).isEqualTo("Hello Doe"); + } + + @Test + void httpClient() { + Assertions.assertThat(helloHttpClient.hello("Doe")).isEqualTo("Hello Doe"); + } + + @Test + void urlConnection() { + Assertions.assertThat(helloUrlConnection.hello("Doe")).isEqualTo("Hello Doe"); + } + + @Test + void sameTlsConfiguration() { + + /* + * The TlsConfigurations must be the same instance, otherwise the identity based caching in HttpClientPool would not + * work + */ + + TLSClientParameters p1 = getTLSClientParameters(helloVertx2); + TLSClientParameters p2 = getTLSClientParameters(helloVertx); + + Assertions.assertThat(p1).isNotNull().isInstanceOf(QuarkusTLSClientParameters.class); + Assertions.assertThat(p2).isNotNull().isInstanceOf(QuarkusTLSClientParameters.class); + Assertions.assertThat(((QuarkusTLSClientParameters) p1).getTlsConfiguration()) + .isSameAs(((QuarkusTLSClientParameters) p2).getTlsConfiguration()); + + } + + static TLSClientParameters getTLSClientParameters(HelloService cl) { + final Client client = ClientProxy.getClient(cl); + HTTPConduit httpConduit = (HTTPConduit) client.getConduit(); + return httpConduit.getTlsClientParameters(); + } + + @WebService + public interface HelloService { + + @WebMethod + String hello(String person); + + } + + @WebService(serviceName = "HelloService") + public static class HelloServiceImpl implements HelloService { + + @Override + public String hello(String person) { + return "Hello " + person; + } + } + +} diff --git a/extensions/core/vertx-client/src/test/java/io/quarkiverse/cxf/vertx/http/client/RequestBodyOutputStreamTest.java b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/vertx/http/client/RequestBodyOutputStreamTest.java similarity index 100% rename from extensions/core/vertx-client/src/test/java/io/quarkiverse/cxf/vertx/http/client/RequestBodyOutputStreamTest.java rename to extensions/core/deployment/src/test/java/io/quarkiverse/cxf/vertx/http/client/RequestBodyOutputStreamTest.java diff --git a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/vertx/http/client/deployment/VertxWebClientConduitFactoryTest.java b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/vertx/http/client/deployment/VertxWebClientConduitFactoryTest.java index 70f47ffe4..1d7d10e45 100644 --- a/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/vertx/http/client/deployment/VertxWebClientConduitFactoryTest.java +++ b/extensions/core/deployment/src/test/java/io/quarkiverse/cxf/vertx/http/client/deployment/VertxWebClientConduitFactoryTest.java @@ -8,7 +8,6 @@ import org.apache.cxf.BusFactory; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; -import org.apache.cxf.transport.http.HTTPConduitFactory; import org.apache.cxf.transport.http.URLConnectionHTTPConduit; import org.assertj.core.api.Assertions; import org.jboss.logging.Logger; @@ -17,10 +16,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; +import io.quarkiverse.cxf.HTTPConduitImpl; +import io.quarkiverse.cxf.HTTPConduitSpec; import io.quarkiverse.cxf.annotation.CXFClient; import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduit; -import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduitFactory; import io.quarkus.test.QuarkusUnitTest; public class VertxWebClientConduitFactoryTest { @@ -45,9 +44,9 @@ public class VertxWebClientConduitFactoryTest { @Test void conduitFactory() { final Bus bus = BusFactory.getDefaultBus(); - final HTTPConduitFactory factory = bus.getExtension(HTTPConduitFactory.class); - HTTPConduitImpl defaultImpl = io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl.findDefaultHTTPConduitImpl(); - Assertions.assertThat(factory).isInstanceOf(defaultImpl.newHTTPConduitFactory().getClass()); + final HTTPConduitSpec registeredImpl = bus.getExtension(HTTPConduitSpec.class); + HTTPConduitImpl defaultImpl = io.quarkiverse.cxf.HTTPConduitImpl.findDefaultHTTPConduitImpl(); + Assertions.assertThat(registeredImpl.resolveDefault()).isEqualTo(defaultImpl); final Client client = ClientProxy.getClient(helloService); switch (defaultImpl) { diff --git a/extensions/core/pom.xml b/extensions/core/pom.xml index e242c277d..3c37fcbf8 100644 --- a/extensions/core/pom.xml +++ b/extensions/core/pom.xml @@ -14,7 +14,6 @@ axiom-api-stub - vertx-client runtime deployment diff --git a/extensions/core/runtime/pom.xml b/extensions/core/runtime/pom.xml index 443bfdf71..d6485a5a0 100644 --- a/extensions/core/runtime/pom.xml +++ b/extensions/core/runtime/pom.xml @@ -38,10 +38,6 @@ io.quarkiverse.cxf quarkus-cxf-axiom-api-stub - - io.quarkiverse.cxf - quarkus-cxf-vertx-client - io.quarkiverse.cxf quarkus-cxf-woodstox diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java index bfe2f4849..6e610536b 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java @@ -12,7 +12,6 @@ import org.apache.cxf.transports.http.configuration.ConnectionType; import org.apache.cxf.transports.http.configuration.ProxyServerType; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; import io.quarkus.arc.Arc; import io.quarkus.arc.Unremovable; import io.quarkus.tls.TlsConfiguration; @@ -303,7 +302,11 @@ static TlsConfiguration tlsConfiguration(Vertx vertx, CxfClientConfig config, St trustOptions = null; trustStore = null; } - final CxfTlsConfiguration cxfTlsConfiguration = new CxfTlsConfiguration(keyStoreOptions, keyStore, trustOptions, + + final CxfTlsConfiguration cxfTlsConfiguration = new CxfTlsConfiguration( + keyStoreOptions, + keyStore, + trustOptions, trustStore); tlsRegistry.register(registryKey, cxfTlsConfiguration); return cxfTlsConfiguration; diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java index 3cab16cd8..51b735688 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java @@ -7,11 +7,9 @@ import java.util.function.Consumer; import org.apache.cxf.Bus; -import org.apache.cxf.transport.http.HTTPConduitFactory; import org.jboss.logging.Logger; import io.netty.util.internal.shaded.org.jctools.queues.MessagePassingQueue.Supplier; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; import io.quarkiverse.cxf.annotation.CXFEndpoint; import io.quarkiverse.cxf.transport.CxfHandler; import io.quarkiverse.cxf.transport.VertxDestinationFactory; @@ -281,7 +279,7 @@ public static boolean isHc5Present() { } public RuntimeValue> setBusHTTPConduitFactory(HTTPConduitImpl factory) { - return new RuntimeValue<>(bus -> bus.setExtension(factory.newHTTPConduitFactory(), HTTPConduitFactory.class)); + return new RuntimeValue<>(bus -> bus.setExtension(factory, HTTPConduitSpec.class)); } } diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java index 194948d84..9cfacc7d7 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java @@ -5,13 +5,11 @@ import org.apache.cxf.annotations.SchemaValidation.SchemaValidationType; import org.apache.cxf.configuration.jsse.TLSClientParameters; -import org.apache.cxf.transport.http.HTTPConduitFactory; import org.apache.cxf.transports.http.configuration.ConnectionType; import org.apache.cxf.transports.http.configuration.ProxyServerType; import io.quarkiverse.cxf.LoggingConfig.PerClientOrServiceLoggingConfig; import io.quarkus.runtime.annotations.ConfigDocEnum; -import io.quarkus.runtime.annotations.ConfigDocEnumValue; import io.quarkus.runtime.annotations.ConfigGroup; import io.smallrye.config.WithConverter; import io.smallrye.config.WithDefault; @@ -532,12 +530,25 @@ public interface CxfClientConfig { * * - One of the well known values: `AllowAllHostnameVerifier`, `HttpsURLConnectionDefaultHostnameVerifier` * - A fully qualified class name implementing `javax.net.ssl.HostnameVerifier` to look up in the CDI container. - * - A bean name prefixed with `++#++` that will be looked up in the CDI container; example: `++#++myHostnameVerifier` If - * not specified, then the creation of the `HostnameVerifier` is delegated to CXF, which boils down to + * - A bean name prefixed with `++#++` that will be looked up in the CDI container; example: `++#++myHostnameVerifier` + * + * If not specified, then the creation of the `HostnameVerifier` is delegated to CXF, which boils down to * `org.apache.cxf.transport.https.httpclient.DefaultHostnameVerifier` with the default * `org.apache.cxf.transport.https.httpclient.PublicSuffixMatcherLoader` as returned from * `PublicSuffixMatcherLoader.getDefault()`. * + * [IMPORTANT] + * ==== + * Setting this option when the conduit factory of this client is set to `VertxHttpClientHTTPConduitFactory` + * (default since {quarkus-cxf-project-name} 3.16.0) leads to an exception at runtime. + * The `AllowAllHostnameVerifier` value of this option can be replaced by using a + * xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus-cxf-client-client-name-tls-configuration-name[named TLS + * configuration] + * with + * `{link-quarkus-docs-base}/tls-registry-reference#trusting-all-certificates-and-hostname-verification[hostname-verification-algorithm]` + * set to `NONE`. Otherwise, there is no way to implement custom hostname verification for Vert.x HTTP client. + * ==== + * * @asciidoclet * @since 2.5.0 */ @@ -554,59 +565,6 @@ public interface CxfClientConfig { @WithName("schema-validation.enabled-for") public Optional schemaValidationEnabledFor(); - public enum HTTPConduitImpl { - - @ConfigDocEnumValue("QuarkusCXFDefault") - QuarkusCXFDefault { - @Override - public HTTPConduitFactory newHTTPConduitFactory() { - final HTTPConduitImpl impl = findDefaultHTTPConduitImpl(); - return impl.newHTTPConduitFactory(); - } - - }, - @ConfigDocEnumValue("CXFDefault") - CXFDefault { - @Override - public HTTPConduitFactory newHTTPConduitFactory() { - return null; - } - }, - @ConfigDocEnumValue("VertxHttpClientHTTPConduitFactory") - VertxHttpClientHTTPConduitFactory { - @Override - public HTTPConduitFactory newHTTPConduitFactory() { - return new io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduitFactory(); - } - }, - @ConfigDocEnumValue("HttpClientHTTPConduitFactory") - HttpClientHTTPConduitFactory { - @Override - public HTTPConduitFactory newHTTPConduitFactory() { - return new HttpClientHTTPConduitFactory(); - } - }, - @ConfigDocEnumValue("URLConnectionHTTPConduitFactory") - URLConnectionHTTPConduitFactory { - @Override - public HTTPConduitFactory newHTTPConduitFactory() { - return new URLConnectionHTTPConduitFactory(); - } - }; - - public abstract HTTPConduitFactory newHTTPConduitFactory(); - - public static HTTPConduitImpl findDefaultHTTPConduitImpl() { - if (QuarkusHTTPConduitFactory.defaultHTTPConduitImpl == null) { - final String defaultName = System.getenv(QuarkusHTTPConduitFactory.QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY); - QuarkusHTTPConduitFactory.defaultHTTPConduitImpl = defaultName == null || defaultName.isEmpty() - ? URLConnectionHTTPConduitFactory - : valueOf(defaultName); - } - return QuarkusHTTPConduitFactory.defaultHTTPConduitImpl; - } - } - public enum WellKnownHostnameVerifier { AllowAllHostnameVerifier { diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java index 14def6163..f4d80e01f 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java @@ -183,16 +183,16 @@ private Object produceCxfClient(CXFClientInfo cxfClientInfo) { customizers.forEach(customizer -> customizer.customize(cxfClientInfo, factory)); final Bus bus = BusFactory.getDefaultBus(); - final HTTPConduitFactory origConduitFactory = bus.getExtension(HTTPConduitFactory.class); + final HTTPConduitSpec origConduitImpl = bus.getExtension(HTTPConduitSpec.class); final QuarkusHTTPConduitFactory conduitFactory = new QuarkusHTTPConduitFactory( - httpClientPool, fixedConfig, cxfClientInfo, - origConduitFactory, + origConduitImpl, authorizationPolicy, vertx); props.put(HTTPConduitFactory.class.getName(), conduitFactory); Object result; + final HTTPConduitFactory origConduitFactory = bus.getExtension(HTTPConduitFactory.class); try { /* * Workaround for https://github.com/quarkiverse/quarkus-cxf/issues/1264 diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfFixedConfig.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfFixedConfig.java index 8d666aafa..e49a408fb 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfFixedConfig.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfFixedConfig.java @@ -3,7 +3,6 @@ import java.util.Map; import java.util.Optional; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigPhase; diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfTlsConfiguration.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfTlsConfiguration.java index a83c41f6b..5a1a4dda3 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfTlsConfiguration.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfTlsConfiguration.java @@ -5,18 +5,30 @@ import javax.net.ssl.SSLContext; +import org.apache.cxf.configuration.jsse.TLSClientParameters; + +import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduit; import io.quarkus.tls.TlsConfiguration; import io.vertx.core.net.KeyCertOptions; import io.vertx.core.net.SSLOptions; import io.vertx.core.net.TrustOptions; +/** + * A {@link TlsConfiguration} implementation used in case the user uses the legacy way of implementing TLS + * via {@code .trust-store*} and {@code .key-store*} family of options rather than via {@code .tls-configuration-name}. + * {@link CxfTlsConfiguration} is only used for configuring {@link VertxHttpClientHTTPConduit}. + * Other kinds of conduits are configured only via {@link TLSClientParameters}. + */ public class CxfTlsConfiguration implements TlsConfiguration { private final TrustOptions trustOptions; private final KeyStore trustStore; private final KeyCertOptions keyStoreOptions; private final KeyStore keyStore; - public CxfTlsConfiguration(KeyCertOptions keyStoreOptions, KeyStore keyStore, TrustOptions trustOptions, + public CxfTlsConfiguration( + KeyCertOptions keyStoreOptions, + KeyStore keyStore, + TrustOptions trustOptions, KeyStore trustStore) { this.keyStoreOptions = keyStoreOptions; this.keyStore = keyStore; @@ -50,23 +62,44 @@ public SSLContext createSSLContext() throws Exception { } @Override - public SSLOptions getSSLOptions() { - throw new UnsupportedOperationException(getClass().getName() + ".getSSLOptions() is not supported"); + public synchronized SSLOptions getSSLOptions() { + SSLOptions options = new SSLOptions(); + options.setKeyCertOptions(getKeyStoreOptions()); + options.setTrustOptions(getTrustStoreOptions()); + // options.setUseAlpn(config().alpn()); + // options.setSslHandshakeTimeoutUnit(TimeUnit.SECONDS); + // options.setSslHandshakeTimeout(config().handshakeTimeout().toSeconds()); + // options.setEnabledSecureTransportProtocols(config().protocols()); + // + // for (Buffer buffer : crls) { + // options.addCrlValue(buffer); + // } + // + // for (String cipher : config().cipherSuites().orElse(Collections.emptyList())) { + // options.addEnabledCipherSuite(cipher); + // } + + return options; } @Override public boolean isTrustAll() { - throw new UnsupportedOperationException(getClass().getName() + ".isTrustAll() is not supported"); + return false; } @Override public Optional getHostnameVerificationAlgorithm() { - throw new UnsupportedOperationException(getClass().getName() + ".getHostnameVerificationAlgorithm() is not supported"); + /* + * If the user does not use .tls-configuration-name (which is the case when CxfTlsConfiguration is used) + * then there is no way for him to set the hostnameVerificationAlgorithm. + */ + return Optional.empty(); } @Override public boolean usesSni() { - throw new UnsupportedOperationException(getClass().getName() + ".usesSni() is not supported"); + /* There is no way to configure SNI via TLSClientParameters */ + return false; } @Override diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/HTTPConduitImpl.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/HTTPConduitImpl.java new file mode 100644 index 000000000..c9a20e9c7 --- /dev/null +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/HTTPConduitImpl.java @@ -0,0 +1,111 @@ +package io.quarkiverse.cxf; + +import java.io.IOException; +import java.util.Objects; + +import org.apache.cxf.Bus; +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.service.model.EndpointInfo; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transport.http.HTTPTransportFactory; +import org.apache.cxf.transport.http.HttpClientHTTPConduit; +import org.apache.cxf.transport.http.URLConnectionHTTPConduit; +import org.apache.cxf.ws.addressing.EndpointReferenceType; + +import io.quarkiverse.cxf.vertx.http.client.HttpClientPool; +import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduit; +import io.quarkus.runtime.annotations.ConfigDocEnumValue; + +public enum HTTPConduitImpl implements HTTPConduitSpec { + + @ConfigDocEnumValue("QuarkusCXFDefault") + QuarkusCXFDefault { + + private HTTPConduitImpl defaultHTTPConduitImpl; + + @Override + public HTTPConduitImpl resolveDefault() { + HTTPConduitImpl result; + if ((result = defaultHTTPConduitImpl) == null) { + defaultHTTPConduitImpl = result = findDefaultHTTPConduitImpl(); + } + return result; + } + }, + @ConfigDocEnumValue("CXFDefault") + CXFDefault { + @Override + public HTTPConduitImpl resolveDefault() { + /* + * Mimic what is done in org.apache.cxf.transport.http.HTTPTransportFactory.getConduit(EndpointInfo, + * EndpointReferenceType, Bus) + */ + if (Boolean.getBoolean("org.apache.cxf.transport.http.forceURLConnection")) { + return URLConnectionHTTPConduitFactory; + } else { + return HttpClientHTTPConduitFactory; + } + } + }, + @ConfigDocEnumValue("VertxHttpClientHTTPConduitFactory") + VertxHttpClientHTTPConduitFactory { + @Override + public HTTPConduit createConduit(HTTPTransportFactory f, Bus b, EndpointInfo localInfo, + EndpointReferenceType target) throws IOException { + final HttpClientPool httpClientPool = Objects.requireNonNull(b.getExtension(HttpClientPool.class), + "HttpClientPool in org.apache.cxf.Bus"); + return new VertxHttpClientHTTPConduit(b, localInfo, target, httpClientPool); + } + + @Override + public TLSClientParameters createTLSClientParameters(CXFClientInfo cxfClientInfo) { + if (cxfClientInfo.getHostnameVerifier() != null) { + throw new IllegalStateException( + getConduitDescription() + " does not support setting a hostname verifier." + + " AllowAllHostnameVerifier can be replaced by using a named TLS configuration" + + " with hostname-verification-algorithm set to NONE"); + } + return new QuarkusTLSClientParameters(cxfClientInfo.getTlsConfiguration()); + } + + }, + @ConfigDocEnumValue("HttpClientHTTPConduitFactory") + HttpClientHTTPConduitFactory { + @Override + public HTTPConduit createConduit(HTTPTransportFactory f, Bus b, EndpointInfo localInfo, + EndpointReferenceType target) throws IOException { + return new HttpClientHTTPConduit(b, localInfo, target); + } + }, + @ConfigDocEnumValue("URLConnectionHTTPConduitFactory") + URLConnectionHTTPConduitFactory { + @Override + public HTTPConduit createConduit(HTTPTransportFactory f, Bus b, EndpointInfo localInfo, + EndpointReferenceType target) throws IOException { + return new URLConnectionHTTPConduit(b, localInfo, target); + } + }; + + public static HTTPConduitImpl findDefaultHTTPConduitImpl() { + if (QuarkusHTTPConduitFactory.defaultHTTPConduitImpl == null) { + final String defaultName = System.getenv(QuarkusHTTPConduitFactory.QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY); + QuarkusHTTPConduitFactory.defaultHTTPConduitImpl = defaultName == null || defaultName.isEmpty() + ? VertxHttpClientHTTPConduitFactory + : valueOf(defaultName); + } + return QuarkusHTTPConduitFactory.defaultHTTPConduitImpl; + } + + @Override + public HTTPConduit createConduit(HTTPTransportFactory f, Bus b, EndpointInfo localInfo, EndpointReferenceType target) + throws IOException { + throw new IllegalStateException( + "Call " + HTTPConduitImpl.class.getName() + ".resolveDefault() before calling createConduit()"); + } + + @Override + public String getConduitDescription() { + return "http-conduit-factory = " + name(); + } + +} \ No newline at end of file diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/HTTPConduitSpec.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/HTTPConduitSpec.java new file mode 100644 index 000000000..7c1c8e0b0 --- /dev/null +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/HTTPConduitSpec.java @@ -0,0 +1,111 @@ +package io.quarkiverse.cxf; + +import java.io.IOException; +import java.util.Optional; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.cxf.Bus; +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.service.model.EndpointInfo; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transport.http.HTTPTransportFactory; +import org.apache.cxf.ws.addressing.EndpointReferenceType; + +import io.quarkiverse.cxf.CxfClientConfig.WellKnownHostnameVerifier; +import io.quarkus.tls.TlsConfiguration; +import io.quarkus.tls.runtime.VertxCertificateHolder; +import io.quarkus.tls.runtime.config.TlsBucketConfig; +import io.vertx.core.Vertx; +import io.vertx.core.net.KeyCertOptions; +import io.vertx.core.net.KeyStoreOptionsBase; + +public interface HTTPConduitSpec { + default HTTPConduitSpec resolveDefault() { + return this; + } + + HTTPConduit createConduit(HTTPTransportFactory f, Bus b, EndpointInfo localInfo, EndpointReferenceType target) + throws IOException; + + default Optional tlsClientParameters(CXFClientInfo cxfClientInfo, Vertx vertx) { + final String hostnameVerifierName = cxfClientInfo.getHostnameVerifier(); + final TlsConfiguration tlsConfig = cxfClientInfo.getTlsConfiguration(); + if (hostnameVerifierName != null || tlsConfig != null) { + TLSClientParameters tlsCP = createTLSClientParameters(cxfClientInfo); + if (hostnameVerifierName != null) { + final Optional wellKnownHostNameVerifierName = WellKnownHostnameVerifier + .of(hostnameVerifierName); + if (wellKnownHostNameVerifierName.isPresent()) { + wellKnownHostNameVerifierName.get().configure(tlsCP); + } else { + final HostnameVerifier hostnameVerifier = CXFRuntimeUtils.getInstance(hostnameVerifierName, true); + if (hostnameVerifier == null) { + throw new RuntimeException("Could not find or instantiate " + hostnameVerifierName); + } + tlsCP.setHostnameVerifier(hostnameVerifier); + } + } + + if (tlsConfig != null) { + final KeyCertOptions keyStoreOptions = tlsConfig.getKeyStoreOptions(); + if (keyStoreOptions != null) { + try { + final KeyManagerFactory kmf = keyStoreOptions.getKeyManagerFactory(vertx); + tlsCP.setKeyManagers(kmf.getKeyManagers()); + } catch (Exception e) { + throw new RuntimeException("Could not set up key manager factory", e); + } + if (keyStoreOptions instanceof KeyStoreOptionsBase) { + final KeyStoreOptionsBase keyStoreOptionsBase = (KeyStoreOptionsBase) keyStoreOptions; + if (keyStoreOptionsBase.getAlias() != null) { + tlsCP.setCertAlias(keyStoreOptionsBase.getAlias()); + } + } + } + + if (tlsConfig.getTrustStoreOptions() != null) { + try { + final TrustManagerFactory tmf = tlsConfig.getTrustStoreOptions().getTrustManagerFactory(vertx); + tlsCP.setTrustManagers(tmf.getTrustManagers()); + } catch (Exception e) { + throw new RuntimeException("Could not set up trust manager factory", e); + } + } + + } + return Optional.of(tlsCP); + } + return Optional.empty(); + } + + default TLSClientParameters createTLSClientParameters(CXFClientInfo cxfClientInfo) { + TlsConfiguration tlsConfig = cxfClientInfo.getTlsConfiguration(); + TLSClientParameters tlsCP = new TLSClientParameters(); + if (tlsConfig instanceof VertxCertificateHolder) { + final VertxCertificateHolder vertxCertificateHOlder = (VertxCertificateHolder) tlsConfig; + final TlsBucketConfig bucketConfig = vertxCertificateHOlder.config(); + bucketConfig.cipherSuites().ifPresent(tlsCP::setCipherSuites); + + /* + * We are not able to transfer some TLS config options from VertxCertificateHolder + * to the legacy conduit implementations + */ + if (tlsConfig.isTrustAll()) { + throw new IllegalStateException( + this.getClass().getName().replace("Factory", "") + + " does not support quarkus.tls.trust-all = true."); + } + if (tlsConfig.getHostnameVerificationAlgorithm().isPresent()) { + throw new IllegalStateException(getConduitDescription() + + " does not support quarkus.tls.hostname-verification-algorithm. Use quarkus.cxf.client.\"client-name\".hostname-verifier instead."); + } + } + return tlsCP; + } + + String getConduitDescription(); + +} \ No newline at end of file diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusHTTPConduitFactory.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusHTTPConduitFactory.java index 9f2e0c788..313cec731 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusHTTPConduitFactory.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusHTTPConduitFactory.java @@ -1,35 +1,19 @@ package io.quarkiverse.cxf; import java.io.IOException; -import java.util.Optional; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.TrustManagerFactory; import org.apache.cxf.Bus; -import org.apache.cxf.configuration.jsse.TLSClientParameters; import org.apache.cxf.configuration.security.AuthorizationPolicy; import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy; import org.apache.cxf.service.model.EndpointInfo; import org.apache.cxf.transport.http.HTTPConduit; import org.apache.cxf.transport.http.HTTPConduitFactory; import org.apache.cxf.transport.http.HTTPTransportFactory; -import org.apache.cxf.transport.http.HttpClientHTTPConduit; -import org.apache.cxf.transport.http.URLConnectionHTTPConduit; import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; import org.apache.cxf.ws.addressing.EndpointReferenceType; import org.jboss.logging.Logger; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; -import io.quarkiverse.cxf.CxfClientConfig.WellKnownHostnameVerifier; -import io.quarkiverse.cxf.vertx.http.client.HttpClientPool; -import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduit; -import io.quarkus.tls.TlsConfiguration; -import io.quarkus.tls.runtime.VertxCertificateHolder; -import io.quarkus.tls.runtime.config.TlsBucketConfig; import io.vertx.core.Vertx; -import io.vertx.core.net.KeyCertOptions; /** * A HTTPConduitFactory with some client specific configuration, such as timeouts and SSL. @@ -45,152 +29,50 @@ public class QuarkusHTTPConduitFactory implements HTTPConduitFactory { public static final String QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY = "QUARKUS_CXF_DEFAULT_HTTP_CONDUIT_FACTORY"; static HTTPConduitImpl defaultHTTPConduitImpl; - private final HttpClientPool httpClientPool; private final CxfFixedConfig cxFixedConfig; private final CXFClientInfo cxfClientInfo; - private final HTTPConduitFactory busHTTPConduitFactory; + private final HTTPConduitSpec busHTTPConduitImpl; private final AuthorizationPolicy authorizationPolicy; - private final HTTPConduitImpl defaultHTTPConduitFactory; private final Vertx vertx; public QuarkusHTTPConduitFactory( - HttpClientPool httpClientPool, CxfFixedConfig cxFixedConfig, CXFClientInfo cxfClientInfo, - HTTPConduitFactory busHTTPConduitFactory, + HTTPConduitSpec busHTTPConduitImpl, AuthorizationPolicy authorizationPolicy, Vertx vertx) { super(); - this.httpClientPool = httpClientPool; this.cxFixedConfig = cxFixedConfig; this.cxfClientInfo = cxfClientInfo; - this.busHTTPConduitFactory = busHTTPConduitFactory; + this.busHTTPConduitImpl = busHTTPConduitImpl; this.authorizationPolicy = authorizationPolicy; - this.defaultHTTPConduitFactory = HTTPConduitImpl.findDefaultHTTPConduitImpl(); this.vertx = vertx; } @Override public HTTPConduit createConduit(HTTPTransportFactory f, Bus b, EndpointInfo localInfo, EndpointReferenceType target) throws IOException { - HTTPConduitImpl httpConduitImpl = cxfClientInfo.getHttpConduitImpl(); + HTTPConduitSpec httpConduitImpl = cxfClientInfo.getHttpConduitImpl(); if (httpConduitImpl == null) { httpConduitImpl = cxFixedConfig.httpConduitFactory().orElse(null); } if (httpConduitImpl == null && (CXFRecorder.isHc5Present()) - && busHTTPConduitFactory != null) { - return configure( - busHTTPConduitFactory.createConduit(f, b, localInfo, target), - cxfClientInfo); + && busHTTPConduitImpl != null) { + return configure(f, busHTTPConduitImpl.resolveDefault(), cxfClientInfo, b, localInfo, target); } if (httpConduitImpl == null) { httpConduitImpl = HTTPConduitImpl.QuarkusCXFDefault; } - - final HTTPConduit result; - switch (httpConduitImpl) { - case CXFDefault: { - /* - * Mimic what is done in org.apache.cxf.transport.http.HTTPTransportFactory.getConduit(EndpointInfo, - * EndpointReferenceType, Bus) - */ - if (Boolean.getBoolean("org.apache.cxf.transport.http.forceURLConnection")) { - result = new URLConnectionHTTPConduit(b, localInfo, target); - } else { - result = new HttpClientHTTPConduit(b, localInfo, target); - } - break; - } - case QuarkusCXFDefault: - switch (defaultHTTPConduitFactory) { - case VertxHttpClientHTTPConduitFactory: { - result = new VertxHttpClientHTTPConduit(b, localInfo, target, httpClientPool); - break; - } - case URLConnectionHTTPConduitFactory: { - result = new URLConnectionHTTPConduit(b, localInfo, target); - break; - } - case HttpClientHTTPConduitFactory: { - result = new HttpClientHTTPConduit(b, localInfo, target); - break; - } - default: - throw new IllegalStateException("Unexpected " + HTTPConduitImpl.class.getSimpleName() + " value: " - + defaultHTTPConduitFactory); - } - break; - case VertxHttpClientHTTPConduitFactory: { - result = new VertxHttpClientHTTPConduit(b, localInfo, target, httpClientPool); - break; - } - case URLConnectionHTTPConduitFactory: { - result = new URLConnectionHTTPConduit(b, localInfo, target); - break; - } - case HttpClientHTTPConduitFactory: { - result = new HttpClientHTTPConduit(b, localInfo, target); - break; - } - default: - throw new IllegalStateException("Unexpected " + HTTPConduitImpl.class.getSimpleName() + " value: " - + httpConduitImpl); - } - return configure(result, cxfClientInfo); + return configure(f, httpConduitImpl.resolveDefault(), cxfClientInfo, b, localInfo, target); } - private HTTPConduit configure(HTTPConduit httpConduit, CXFClientInfo cxfClientInfo) throws IOException { - final String hostnameVerifierName = cxfClientInfo.getHostnameVerifier(); - final TlsConfiguration tlsConfig = cxfClientInfo.getTlsConfiguration(); - if (hostnameVerifierName != null || tlsConfig != null) { - TLSClientParameters tlsCP = new TLSClientParameters(); - - if (hostnameVerifierName != null) { - final Optional wellKnownHostNameVerifierName = WellKnownHostnameVerifier - .of(hostnameVerifierName); - if (wellKnownHostNameVerifierName.isPresent()) { - wellKnownHostNameVerifierName.get().configure(tlsCP); - } else { - final HostnameVerifier hostnameVerifier = CXFRuntimeUtils.getInstance(hostnameVerifierName, true); - if (hostnameVerifier == null) { - throw new RuntimeException("Could not find or instantiate " + hostnameVerifierName); - } - tlsCP.setHostnameVerifier(hostnameVerifier); - } - } - - if (tlsConfig != null) { - final KeyCertOptions keyStoreOptions = tlsConfig.getKeyStoreOptions(); - if (keyStoreOptions != null) { - try { - final KeyManagerFactory kmf = keyStoreOptions.getKeyManagerFactory(vertx); - tlsCP.setKeyManagers(kmf.getKeyManagers()); - } catch (Exception e) { - throw new RuntimeException("Could not set up key manager factory", e); - } - } - - if (tlsConfig.getTrustStoreOptions() != null) { - try { - final TrustManagerFactory tmf = tlsConfig.getTrustStoreOptions().getTrustManagerFactory(vertx); - tlsCP.setTrustManagers(tmf.getTrustManagers()); - } catch (Exception e) { - throw new RuntimeException("Could not set up trust manager factory", e); - } - } - - if (tlsConfig instanceof VertxCertificateHolder) { - final VertxCertificateHolder vertxCertificateHOlder = (VertxCertificateHolder) tlsConfig; - final TlsBucketConfig bucketConfig = vertxCertificateHOlder.config(); - bucketConfig.cipherSuites().ifPresent(tlsCP::setCipherSuites); - } - } - - httpConduit.setTlsClientParameters(tlsCP); - } - + private HTTPConduit configure(HTTPTransportFactory f, HTTPConduitSpec httpConduitImpl, CXFClientInfo cxfClientInfo, Bus b, + EndpointInfo localInfo, + EndpointReferenceType target) throws IOException { + final HTTPConduit httpConduit = httpConduitImpl.createConduit(f, b, localInfo, target); + httpConduitImpl.tlsClientParameters(cxfClientInfo, vertx).ifPresent(httpConduit::setTlsClientParameters); final HTTPClientPolicy policy = new HTTPClientPolicy(); httpConduit.setClient(policy); policy.setConnectionTimeout(cxfClientInfo.getConnectionTimeout()); diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusTLSClientParameters.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusTLSClientParameters.java new file mode 100644 index 000000000..d3306c523 --- /dev/null +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusTLSClientParameters.java @@ -0,0 +1,33 @@ +package io.quarkiverse.cxf; + +import java.util.List; + +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.configuration.security.FiltersType; + +import io.quarkus.tls.TlsConfiguration; + +public class QuarkusTLSClientParameters extends TLSClientParameters { + + private final TlsConfiguration tlsConfiguration; + + public QuarkusTLSClientParameters(TlsConfiguration tlsConfiguration) { + super(); + this.tlsConfiguration = tlsConfiguration; + } + + public TlsConfiguration getTlsConfiguration() { + return tlsConfiguration; + } + + @Override + public List getCipherSuites() { + return super.getCipherSuites(); + } + + @Override + public FiltersType getCipherSuitesFilter() { + return super.getCipherSuitesFilter(); + } + +} diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/vertx/http/client/HttpClientPool.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/vertx/http/client/HttpClientPool.java new file mode 100644 index 000000000..f750f22cf --- /dev/null +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/vertx/http/client/HttpClientPool.java @@ -0,0 +1,87 @@ +package io.quarkiverse.cxf.vertx.http.client; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.transport.http.HTTPConduit; + +import io.quarkus.tls.TlsConfiguration; +import io.quarkus.tls.runtime.config.TlsConfigUtils; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpClient; +import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.http.HttpVersion; + +public class HttpClientPool { + private final Map clients = new ConcurrentHashMap<>(); + private final Vertx vertx; + + public HttpClientPool(Vertx vertx) { + super(); + this.vertx = vertx; + } + + public HttpClient getClient(ClientSpec spec) { + return clients.computeIfAbsent(spec, v -> { + final HttpClientOptions opts = new HttpClientOptions() + .setProtocolVersion(spec.getVersion()); + if (spec.isSsl()) { + TlsConfigUtils.configure(opts, spec.tlsConfiguration); + } + return vertx.createHttpClient(opts); + }); + } + + public static class ClientSpec { + protected static final Logger LOG = LogUtils.getL7dLogger(HTTPConduit.class); + private final HttpVersion httpVersion; + private final TlsConfiguration tlsConfiguration; + + private final int hashCode; + + public ClientSpec( + HttpVersion version, + TlsConfiguration tlsConfiguration) { + + this.httpVersion = version; + this.tlsConfiguration = tlsConfiguration; + int h = 31 + httpVersion.hashCode(); + if (this.tlsConfiguration != null) { + h = 31 * h + this.tlsConfiguration.hashCode(); + } + + this.hashCode = h; + } + + public boolean isSsl() { + return tlsConfiguration != null; + } + + public HttpVersion getVersion() { + return httpVersion; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ClientSpec other = (ClientSpec) obj; + return httpVersion == other.httpVersion + && Objects.equals(tlsConfiguration, other.tlsConfiguration); + } + + @Override + public int hashCode() { + return hashCode; + } + + } + +} diff --git a/extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpClientHTTPConduit.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpClientHTTPConduit.java similarity index 96% rename from extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpClientHTTPConduit.java rename to extensions/core/runtime/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpClientHTTPConduit.java index 5b19e0a47..24ba711b5 100644 --- a/extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpClientHTTPConduit.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpClientHTTPConduit.java @@ -66,6 +66,7 @@ import org.apache.cxf.version.Version; import org.apache.cxf.ws.addressing.EndpointReferenceType; +import io.quarkiverse.cxf.QuarkusTLSClientParameters; import io.quarkiverse.cxf.vertx.http.client.HttpClientPool.ClientSpec; import io.quarkiverse.cxf.vertx.http.client.VertxHttpClientHTTPConduit.RequestBodyEvent.RequestBodyEventType; import io.vertx.core.AsyncResult; @@ -112,7 +113,7 @@ protected void setupConnection(Message message, Address address, HTTPClientPolic final HttpVersion version = getVersion(message, csPolicy); final boolean isHttps = "https".equals(uri.getScheme()); - final TLSClientParameters clientParameters; + final QuarkusTLSClientParameters clientParameters; if (isHttps) { clientParameters = findTLSClientParameters(message); if (clientParameters.getSSLSocketFactory() != null) { @@ -173,7 +174,7 @@ protected void setupConnection(Message message, Address address, HTTPClientPolic final RequestContext requestContext = new RequestContext( uri, requestOptions, - new ClientSpec(version, clientParameters), + new ClientSpec(version, clientParameters != null ? clientParameters.getTlsConfiguration() : null), determineReceiveTimeout(message, csPolicy)); message.put(RequestContext.class, requestContext); @@ -252,15 +253,36 @@ static HttpMethod getMethod(Message message) { return method; } - TLSClientParameters findTLSClientParameters(Message message) { + QuarkusTLSClientParameters findTLSClientParameters(Message message) { TLSClientParameters clientParameters = message.get(TLSClientParameters.class); if (clientParameters == null) { clientParameters = this.tlsClientParameters; } if (clientParameters == null) { - clientParameters = new TLSClientParameters(); + clientParameters = new QuarkusTLSClientParameters(null); } - return clientParameters; + + if (clientParameters.getHostnameVerifier() != null) { + throw new IllegalStateException( + getConduitName() + " does not support setting a hostname verifier." + + " AllowAllHostnameVerifier can be replaced by using a named TLS configuration" + + " with hostname-verification-algorithm set to NONE"); + } + + if (clientParameters instanceof QuarkusTLSClientParameters) { + return (QuarkusTLSClientParameters) clientParameters; + } + throw new IllegalStateException( + VertxHttpClientHTTPConduit.class.getName() + " accepts only " + QuarkusTLSClientParameters.class.getName()); + } + + @Override + public void setTlsClientParameters(TLSClientParameters params) { + if (params != null && !(params instanceof QuarkusTLSClientParameters)) { + throw new IllegalStateException( + VertxHttpClientHTTPConduit.class.getName() + " accepts only " + QuarkusTLSClientParameters.class.getName()); + } + super.setTlsClientParameters(params); } static record RequestContext( diff --git a/extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpException.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpException.java similarity index 100% rename from extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpException.java rename to extensions/core/runtime/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpException.java diff --git a/extensions/core/vertx-client/pom.xml b/extensions/core/vertx-client/pom.xml deleted file mode 100644 index 8a8ee9506..000000000 --- a/extensions/core/vertx-client/pom.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - 4.0.0 - - io.quarkiverse.cxf - quarkus-cxf-core - 3.16.0-SNAPSHOT - ../pom.xml - - - quarkus-cxf-vertx-client - Quarkus CXF - Vert.x Web client - A CXF conduit using Vert.x Web client and an underlying HTTP client - - - - io.vertx - vertx-core - - - org.apache.cxf - cxf-rt-transports-http - - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - ${assertj.version} - test - - - org.jboss.logmanager - jboss-logmanager - test - - - - - diff --git a/extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/HttpClientPool.java b/extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/HttpClientPool.java deleted file mode 100644 index 9ce6b9b3a..000000000 --- a/extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/HttpClientPool.java +++ /dev/null @@ -1,682 +0,0 @@ -package io.quarkiverse.cxf.vertx.http.client; - -import java.net.Socket; -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.security.Provider; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; -import java.util.stream.Stream; - -import javax.net.ssl.ExtendedSSLSession; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.KeyManagerFactorySpi; -import javax.net.ssl.ManagerFactoryParameters; -import javax.net.ssl.SNIHostName; -import javax.net.ssl.SNIServerName; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLEngineResult.HandshakeStatus; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSessionContext; -import javax.net.ssl.StandardConstants; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.TrustManagerFactorySpi; -import javax.net.ssl.X509ExtendedTrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.cxf.common.logging.LogUtils; -import org.apache.cxf.configuration.jsse.SSLUtils; -import org.apache.cxf.configuration.jsse.TLSClientParameters; -import org.apache.cxf.transport.http.HTTPConduit; -import org.apache.cxf.transport.https.httpclient.DefaultHostnameVerifier; -import org.apache.cxf.transport.https.httpclient.PublicSuffixMatcherLoader; - -import io.netty.handler.codec.http2.Http2SecurityUtil; -import io.netty.handler.ssl.ClientAuth; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslProvider; -import io.vertx.core.Vertx; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.http.HttpVersion; -import io.vertx.core.net.SSLEngineOptions; -import io.vertx.core.spi.tls.DefaultSslContextFactory; -import io.vertx.core.spi.tls.SslContextFactory; - -public class HttpClientPool { - private final Map clients = new ConcurrentHashMap<>(); - private final Vertx vertx; - - public HttpClientPool(Vertx vertx) { - super(); - this.vertx = vertx; - } - - public HttpClient getClient(ClientSpec spec) { - return clients.computeIfAbsent(spec, v -> { - final HttpClientOptions opts = new HttpClientOptions() - .setProtocolVersion(spec.getVersion()); - if (spec.isSsl()) { - opts - .setSsl(spec.isSsl()) - .setTrustAll(true) // - .setSslEngineOptions(spec.createSslEngineOptions()); - } - return vertx.createHttpClient(opts); - }); - } - - public static class ClientSpec { - protected static final Logger LOG = LogUtils.getL7dLogger(HTTPConduit.class); - private final HttpVersion httpVersion; - private final KeyManager[] keyManagers; - private final TrustManager[] trustManagers; - private final Set cipherSuites; - private final HostnameVerifier hostNameVerifier; - private final boolean ssl; - - private final int hashCode; - - public ClientSpec( - HttpVersion version, - TLSClientParameters params) { - this.httpVersion = version; - int h = 31 + httpVersion.hashCode(); - if (params != null) { - KeyManager[] kms = params.getKeyManagers(); - if (kms == null) { - kms = SSLUtils.getDefaultKeyStoreManagers(LOG); - } - try { - kms = org.apache.cxf.transport.https.SSLUtils.configureKeyManagersWithCertAlias(params, kms); - } catch (GeneralSecurityException e) { - throw new VertxHttpException(e); - } - this.keyManagers = kms; - - TrustManager[] tms = params.getTrustManagers(); - if (tms == null) { - tms = SSLUtils.getDefaultTrustStoreManagers(LOG); - } - this.hostNameVerifier = getHostnameVerifier((TLSClientParameters) params); - this.trustManagers = Stream.of(tms) - .map(t -> new X509TrustManagerWrapper((X509ExtendedTrustManager) t, hostNameVerifier)) - .toArray(size -> new TrustManager[size]); - - String[] css; - try { - css = SSLUtils.getCiphersuitesToInclude( - params.getCipherSuites(), - params.getCipherSuitesFilter(), - SSLContext.getDefault().getDefaultSSLParameters().getCipherSuites(), - Http2SecurityUtil.CIPHERS.toArray(new String[] {}), - LOG); - } catch (NoSuchAlgorithmException e) { - throw new VertxHttpException(e); - } - this.cipherSuites = new LinkedHashSet<>(Arrays.asList(css)); - this.ssl = true; - - h = 31 * h + Arrays.hashCode(keyManagers); - h = 31 * h + Arrays.hashCode(tms); - h = 31 * h + hostNameVerifier.hashCode(); - h = 31 * h + cipherSuites.hashCode(); - h = 31 * h + (ssl ? 1231 : 1237); - } else { - this.keyManagers = null; - this.trustManagers = null; - this.hostNameVerifier = null; - this.cipherSuites = null; - this.ssl = false; - } - - this.hashCode = h; - } - - public HttpVersion getVersion() { - return httpVersion; - } - - public SSLEngineOptions createSslEngineOptions() { - return new CxfSSLEngineOptions(new CxfSslContextFactory(toSslContex())); - } - - SslContext toSslContex() { - - final SslContextFactory builder = new DefaultSslContextFactory(SslProvider.JDK, true) - .forClient(true); - - try { - return builder - // .applicationProtocols(Arrays.asList(params.getApplicationProtocols())) - .enabledCipherSuites(cipherSuites) - // .serverName(null) - .useAlpn(false) - .trustManagerFactory(new CxfTrustManagerFactory(trustManagers)) - .keyMananagerFactory(new CxfKeyManagerFactory(keyManagers)) - .clientAuth(ClientAuth.REQUIRE) // TODO do we need to make this configurable? - .create(); - } catch (SSLException e) { - throw new VertxHttpException(e); - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ClientSpec other = (ClientSpec) obj; - return httpVersion == other.httpVersion - && Objects.equals(cipherSuites, other.cipherSuites) - && Arrays.equals(keyManagers, other.keyManagers) - && Arrays.equals(trustManagers, other.trustManagers); - } - - @Override - public int hashCode() { - return hashCode; - } - - public boolean isSsl() { - return ssl; - } - - static HostnameVerifier getHostnameVerifier(TLSClientParameters tlsClientParameters) { - if (tlsClientParameters.getHostnameVerifier() != null) { - return tlsClientParameters.getHostnameVerifier(); - } else if (tlsClientParameters.isUseHttpsURLConnectionDefaultHostnameVerifier()) { - return HttpsURLConnection.getDefaultHostnameVerifier(); - } else if (tlsClientParameters.isDisableCNCheck()) { - return ALLOW_ALL_HOSTNAME_VERIFIER; - } else { - return DEFAULT_HOSTNAME_VERIFIER; - } - } - - private static final HostnameVerifier DEFAULT_HOSTNAME_VERIFIER = new DefaultHostnameVerifier( - PublicSuffixMatcherLoader.getDefault()); - private static final HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER; - static { - final TLSClientParameters params = new TLSClientParameters(); - params.setDisableCNCheck(true); - ALLOW_ALL_HOSTNAME_VERIFIER = org.apache.cxf.transport.https.SSLUtils.getHostnameVerifier(params); - } - - } - - public record CxfSslContextFactory(SslContext create) implements SslContextFactory { - } - - public static class CxfSSLEngineOptions extends SSLEngineOptions { - - private final SslContextFactory sslContextFactory; - - public CxfSSLEngineOptions(SslContextFactory sslContextFactory) { - super(); - this.sslContextFactory = sslContextFactory; - } - - @Override - public SSLEngineOptions copy() { - return new CxfSSLEngineOptions(sslContextFactory); - } - - @Override - public SslContextFactory sslContextFactory() { - return sslContextFactory; - } - - } - - private static final Provider PROVIDER = new Provider("", "0.0", "") { - }; - - static class CxfTrustManagerFactory extends TrustManagerFactory { - - CxfTrustManagerFactory(TrustManager... tm) { - super(new TrustManagerFactorySpi() { - @Override - protected void engineInit(KeyStore keyStore) throws KeyStoreException { - } - - @Override - protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { - } - - @Override - protected TrustManager[] engineGetTrustManagers() { - return tm; - } - }, PROVIDER, ""); - } - } - - /* - * The classes below are used by the HttpClient implementation to allow use of the - * HostNameVerifier that is configured. HttpClient does not provide a hook or - * anything to call into the HostNameVerifier after the certs are verified. It - * prefers that the Hostname is verified at the same time as the certificates - * but the only option for hostname is the global on/off system property. Thus, - * we have to provide a X509TrustManagerWrapper that would turn off the - * EndpointIdentificationAlgorithm and then handle the hostname verification - * directly. However, since the peer certs are not yet verified, we also need to wrapper - * the session so the HostnameVerifier things they are. - */ - static class X509TrustManagerWrapper extends X509ExtendedTrustManager { - - private final X509TrustManager delegate; - private final X509ExtendedTrustManager extendedDelegate; - private final HostnameVerifier verifier; - - X509TrustManagerWrapper(X509TrustManager delegate, HostnameVerifier hnv) { - this.delegate = delegate; - this.verifier = hnv; - this.extendedDelegate = delegate instanceof X509ExtendedTrustManager - ? (X509ExtendedTrustManager) delegate - : null; - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String s) throws CertificateException { - delegate.checkClientTrusted(chain, s); - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String s, Socket socket) - throws CertificateException { - if (extendedDelegate != null) { - extendedDelegate.checkClientTrusted(chain, s, socket); - } else { - delegate.checkClientTrusted(chain, s); - } - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String s, SSLEngine sslEngine) - throws CertificateException { - if (extendedDelegate != null) { - extendedDelegate.checkClientTrusted(chain, s, sslEngine); - } else { - delegate.checkClientTrusted(chain, s); - } - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String s) throws CertificateException { - System.out.println("cst1: " + s); - delegate.checkServerTrusted(chain, s); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String s, Socket socket) - throws CertificateException { - System.out.println("cst2: " + s); - if (extendedDelegate != null) { - extendedDelegate.checkServerTrusted(chain, s, socket); - } else { - delegate.checkServerTrusted(chain, s); - } - } - - private String getHostName(List names) { - if (names == null) { - return null; - } - for (SNIServerName n : names) { - if (n.getType() != StandardConstants.SNI_HOST_NAME) { - continue; - } - if (n instanceof SNIHostName) { - SNIHostName hostname = (SNIHostName) n; - return hostname.getAsciiName(); - } - } - return null; - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String s, SSLEngine engine) - throws CertificateException { - if (extendedDelegate != null) { - extendedDelegate.checkServerTrusted(chain, s, new SSLEngineWrapper(engine)); - //certificates are valid, now check hostnames - SSLSession session = engine.getHandshakeSession(); - List names = null; - if (session instanceof ExtendedSSLSession) { - ExtendedSSLSession extSession = (ExtendedSSLSession) session; - names = extSession.getRequestedServerNames(); - } - - boolean identifiable = false; - String peerHost = session.getPeerHost(); - String hostname = getHostName(names); - session = new SSLSessionWrapper(session, chain); - if (hostname != null && verifier.verify(hostname, session)) { - identifiable = true; - } - if (!identifiable && !verifier.verify(peerHost, session)) { - throw new CertificateException( - "The https URL hostname " + peerHost + " does not match the " - + "Common Name (CN) on the server certificate in the client's truststore. " - + "Make sure server certificate is correct, or to disable this check " - + "(NOT recommended for production) set the CXF client TLS " - + "configuration property \"disableCNCheck\" to true."); - } - } else { - delegate.checkServerTrusted(chain, s); - } - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return delegate.getAcceptedIssuers(); - } - } - - static class SSLEngineWrapper extends SSLEngine { - final SSLEngine delegate; - - SSLEngineWrapper(SSLEngine delegate) { - this.delegate = delegate; - } - - @Override - public SSLParameters getSSLParameters() { - //make sure the hostname verification is not done in the default X509 stuff - //so we can do it later - SSLParameters params = delegate.getSSLParameters(); - params.setEndpointIdentificationAlgorithm(null); - return params; - } - - @Override - public SSLSession getHandshakeSession() { - return delegate.getHandshakeSession(); - } - - @Override - public void beginHandshake() throws SSLException { - delegate.beginHandshake(); - } - - @Override - public void closeInbound() throws SSLException { - delegate.closeInbound(); - } - - @Override - public void closeOutbound() { - delegate.closeOutbound(); - } - - @Override - public Runnable getDelegatedTask() { - return delegate.getDelegatedTask(); - } - - @Override - public boolean getEnableSessionCreation() { - return delegate.getEnableSessionCreation(); - } - - @Override - public String[] getEnabledCipherSuites() { - return delegate.getEnabledCipherSuites(); - } - - @Override - public String[] getEnabledProtocols() { - return delegate.getEnabledProtocols(); - } - - @Override - public HandshakeStatus getHandshakeStatus() { - return delegate.getHandshakeStatus(); - } - - @Override - public boolean getNeedClientAuth() { - return delegate.getNeedClientAuth(); - } - - @Override - public SSLSession getSession() { - return delegate.getSession(); - } - - @Override - public String[] getSupportedCipherSuites() { - return delegate.getSupportedCipherSuites(); - } - - @Override - public String[] getSupportedProtocols() { - return delegate.getSupportedProtocols(); - } - - @Override - public boolean getUseClientMode() { - return delegate.getUseClientMode(); - } - - @Override - public boolean getWantClientAuth() { - return delegate.getWantClientAuth(); - } - - @Override - public boolean isInboundDone() { - return delegate.isInboundDone(); - } - - @Override - public boolean isOutboundDone() { - return delegate.isInboundDone(); - } - - @Override - public void setEnableSessionCreation(boolean arg0) { - delegate.setEnableSessionCreation(arg0); - } - - @Override - public void setEnabledCipherSuites(String[] arg0) { - delegate.setEnabledCipherSuites(arg0); - } - - @Override - public void setEnabledProtocols(String[] arg0) { - delegate.setEnabledProtocols(arg0); - } - - @Override - public void setNeedClientAuth(boolean arg0) { - delegate.setNeedClientAuth(arg0); - } - - @Override - public void setUseClientMode(boolean arg0) { - delegate.setUseClientMode(arg0); - } - - @Override - public void setWantClientAuth(boolean arg0) { - delegate.setWantClientAuth(arg0); - } - - @Override - public SSLEngineResult unwrap(ByteBuffer arg0, ByteBuffer[] arg1, int arg2, int arg3) - throws SSLException { - return null; - } - - @Override - public SSLEngineResult wrap(ByteBuffer[] arg0, int arg1, int arg2, ByteBuffer arg3) - throws SSLException { - return null; - } - - } - - static class SSLSessionWrapper implements SSLSession { - SSLSession session; - Certificate[] certificates; - - SSLSessionWrapper(SSLSession s, Certificate[] certs) { - this.certificates = certs; - this.session = s; - } - - @Override - public byte[] getId() { - return session.getId(); - } - - @Override - public SSLSessionContext getSessionContext() { - return session.getSessionContext(); - } - - @Override - public long getCreationTime() { - return session.getCreationTime(); - } - - @Override - public long getLastAccessedTime() { - return session.getLastAccessedTime(); - } - - @Override - public void invalidate() { - session.invalidate(); - } - - @Override - public boolean isValid() { - return session.isValid(); - } - - @Override - public void putValue(String s, Object o) { - session.putValue(s, o); - } - - @Override - public Object getValue(String s) { - return session.getValue(s); - } - - @Override - public void removeValue(String s) { - session.removeValue(s); - } - - @Override - public String[] getValueNames() { - return session.getValueNames(); - } - - @Override - public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { - return certificates; - } - - @Override - public Certificate[] getLocalCertificates() { - return session.getLocalCertificates(); - } - - @Override - public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - return session.getPeerPrincipal(); - } - - @Override - public Principal getLocalPrincipal() { - return session.getLocalPrincipal(); - } - - @Override - public String getCipherSuite() { - return session.getCipherSuite(); - } - - @Override - public String getProtocol() { - return session.getProtocol(); - } - - @Override - public String getPeerHost() { - return session.getPeerHost(); - } - - @Override - public int getPeerPort() { - return session.getPeerPort(); - } - - @Override - public int getPacketBufferSize() { - return session.getPacketBufferSize(); - } - - @Override - public int getApplicationBufferSize() { - return session.getApplicationBufferSize(); - } - - @SuppressWarnings("removal") - @Override - public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { - return session.getPeerCertificateChain(); - } - } - - static class CxfKeyManagerFactory extends KeyManagerFactory { - - CxfKeyManagerFactory(KeyManager... km) { - super(new KeyManagerFactorySpi() { - @Override - protected void engineInit(KeyStore keyStore, char[] pwd) throws KeyStoreException { - } - - @Override - protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { - } - - @Override - protected KeyManager[] engineGetKeyManagers() { - return km; - } - }, PROVIDER, ""); - } - } -} diff --git a/extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpClientHTTPConduitFactory.java b/extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpClientHTTPConduitFactory.java deleted file mode 100644 index 807f85886..000000000 --- a/extensions/core/vertx-client/src/main/java/io/quarkiverse/cxf/vertx/http/client/VertxHttpClientHTTPConduitFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package io.quarkiverse.cxf.vertx.http.client; - -import java.io.IOException; -import java.util.Objects; - -import org.apache.cxf.Bus; -import org.apache.cxf.common.injection.NoJSR250Annotations; -import org.apache.cxf.service.model.EndpointInfo; -import org.apache.cxf.transport.http.HTTPConduit; -import org.apache.cxf.transport.http.HTTPConduitFactory; -import org.apache.cxf.transport.http.HTTPTransportFactory; -import org.apache.cxf.ws.addressing.EndpointReferenceType; - -/** - * - */ -@NoJSR250Annotations -public class VertxHttpClientHTTPConduitFactory implements HTTPConduitFactory { - - public VertxHttpClientHTTPConduitFactory() { - super(); - } - - @Override - public HTTPConduit createConduit(HTTPTransportFactory f, Bus b, EndpointInfo localInfo, EndpointReferenceType target) - throws IOException { - final HttpClientPool httpClientPool = Objects.requireNonNull(b.getExtension(HttpClientPool.class), - "HttpClientPool in org.apache.cxf.Bus"); - return new VertxHttpClientHTTPConduit(b, localInfo, target, httpClientPool); - } - -} diff --git a/extensions/transports-http-hc5/deployment/src/test/java/io/quarkiverse/cxf/transport/http/hc5/deployment/Hc5ConduitFactoryTest.java b/extensions/transports-http-hc5/deployment/src/test/java/io/quarkiverse/cxf/transport/http/hc5/deployment/Hc5ConduitFactoryTest.java index 3b424ba6f..c8cf209e5 100644 --- a/extensions/transports-http-hc5/deployment/src/test/java/io/quarkiverse/cxf/transport/http/hc5/deployment/Hc5ConduitFactoryTest.java +++ b/extensions/transports-http-hc5/deployment/src/test/java/io/quarkiverse/cxf/transport/http/hc5/deployment/Hc5ConduitFactoryTest.java @@ -4,13 +4,9 @@ import jakarta.jws.WebMethod; import jakarta.jws.WebService; -import org.apache.cxf.Bus; -import org.apache.cxf.BusFactory; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; -import org.apache.cxf.transport.http.HTTPConduitFactory; import org.apache.cxf.transport.http.asyncclient.hc5.AsyncHTTPConduit; -import org.apache.cxf.transport.http.asyncclient.hc5.AsyncHTTPConduitFactory; import org.assertj.core.api.Assertions; import org.jboss.logging.Logger; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -40,9 +36,9 @@ public class Hc5ConduitFactoryTest { @Test void conduitFactory() { - final Bus bus = BusFactory.getDefaultBus(); - final HTTPConduitFactory factory = bus.getExtension(HTTPConduitFactory.class); - Assertions.assertThat(factory).isInstanceOf(AsyncHTTPConduitFactory.class); + // final Bus bus = BusFactory.getDefaultBus(); + // final HTTPConduitFactory factory = bus.getExtension(HTTPConduitFactory.class); + // Assertions.assertThat(factory).isInstanceOf(AsyncHTTPConduitFactory.class); final Client client = ClientProxy.getClient(helloService); Assertions.assertThat(client.getConduit()).isInstanceOf(AsyncHTTPConduit.class); diff --git a/extensions/transports-http-hc5/runtime/src/main/java/io/quarkiverse/cxf/transport/http/hc5/Hc5Recorder.java b/extensions/transports-http-hc5/runtime/src/main/java/io/quarkiverse/cxf/transport/http/hc5/Hc5Recorder.java index e9fa31d46..65c3a08de 100644 --- a/extensions/transports-http-hc5/runtime/src/main/java/io/quarkiverse/cxf/transport/http/hc5/Hc5Recorder.java +++ b/extensions/transports-http-hc5/runtime/src/main/java/io/quarkiverse/cxf/transport/http/hc5/Hc5Recorder.java @@ -1,13 +1,20 @@ package io.quarkiverse.cxf.transport.http.hc5; +import java.io.IOException; import java.util.function.Consumer; import org.apache.cxf.Bus; +import org.apache.cxf.service.model.EndpointInfo; +import org.apache.cxf.transport.http.HTTPConduit; import org.apache.cxf.transport.http.HTTPConduitFactory; +import org.apache.cxf.transport.http.HTTPTransportFactory; +import org.apache.cxf.transport.http.asyncclient.hc5.AsyncHTTPConduitFactory; import org.apache.cxf.transport.http.asyncclient.hc5.AsyncHttpResponseWrapperFactory; import org.apache.cxf.workqueue.WorkQueueManager; +import org.apache.cxf.ws.addressing.EndpointReferenceType; import org.eclipse.microprofile.context.ManagedExecutor; +import io.quarkiverse.cxf.HTTPConduitSpec; import io.quarkus.arc.Arc; import io.quarkus.arc.InstanceHandle; import io.quarkus.runtime.RuntimeValue; @@ -25,6 +32,7 @@ public class Hc5Recorder { public RuntimeValue> customizeBus() { return new RuntimeValue<>(bus -> { + bus.setExtension(new Hc5HTTPConduitImpl(), HTTPConduitSpec.class); final WorkQueueManager wqm = bus.getExtension(WorkQueueManager.class); InstanceHandle managedExecutorInst = Arc.container().instance(ManagedExecutor.class); if (managedExecutorInst.isAvailable()) { @@ -35,4 +43,24 @@ public RuntimeValue> customizeBus() { bus.setExtension(new QuarkusAsyncHttpResponseWrapperFactory(), AsyncHttpResponseWrapperFactory.class); }); } + + public static class Hc5HTTPConduitImpl implements HTTPConduitSpec { + private AsyncHTTPConduitFactory asyncHTTPConduitFactory; + + @Override + public HTTPConduit createConduit(HTTPTransportFactory f, Bus b, EndpointInfo localInfo, EndpointReferenceType target) + throws IOException { + AsyncHTTPConduitFactory factory; + if ((factory = asyncHTTPConduitFactory) == null) { + factory = asyncHTTPConduitFactory = new AsyncHTTPConduitFactory(b); + } + return factory.createConduit(f, b, localInfo, target); + } + + @Override + public String getConduitDescription() { + return "io.quarkiverse.cxf:quarkus-cxf-rt-transports-http-hc5"; + } + + } } diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkiverse/cxf/opentelemetry/it/OpenTelemetryTest.java b/integration-tests/opentelemetry/src/test/java/io/quarkiverse/cxf/opentelemetry/it/OpenTelemetryTest.java index f2d78ab7e..47d6e6735 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkiverse/cxf/opentelemetry/it/OpenTelemetryTest.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkiverse/cxf/opentelemetry/it/OpenTelemetryTest.java @@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test; import io.opentelemetry.api.trace.SpanKind; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; +import io.quarkiverse.cxf.HTTPConduitImpl; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; import io.restassured.common.mapper.TypeRef; diff --git a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SecurityPolicyResource.java b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SecurityPolicyResource.java index f0e66690c..b3e0d5c93 100644 --- a/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SecurityPolicyResource.java +++ b/integration-tests/ws-security-policy/src/main/java/io/quarkiverse/cxf/it/security/policy/SecurityPolicyResource.java @@ -30,6 +30,9 @@ public class SecurityPolicyResource { @CXFClient("helloAllowAll") HelloService helloAllowAll; + @CXFClient("helloAllowAllTlsConfig") + HelloService helloAllowAllTlsConfig; + @CXFClient("helloIp") HelloService helloIp; @@ -120,6 +123,9 @@ public Response hello(@PathParam("client") String client, String body) { case "helloAllowAll": service = helloAllowAll; break; + case "helloAllowAllTlsConfig": + service = helloAllowAllTlsConfig; + break; case "helloCustomHostnameVerifier": service = helloCustomHostnameVerifier; break; diff --git a/integration-tests/ws-security-policy/src/main/resources/application.properties b/integration-tests/ws-security-policy/src/main/resources/application.properties index d0c2f61bf..f96beaa83 100644 --- a/integration-tests/ws-security-policy/src/main/resources/application.properties +++ b/integration-tests/ws-security-policy/src/main/resources/application.properties @@ -154,6 +154,14 @@ quarkus.cxf.client.helloAllowAll.trust-store = client-truststore.${keystore.type quarkus.cxf.client.helloAllowAll.trust-store-password = client-truststore-password quarkus.cxf.client.helloAllowAll.hostname-verifier = AllowAllHostnameVerifier + +quarkus.tls.helloAllowAll.trust-store.${keystore.type.short}.path = client-truststore.${keystore.type} +quarkus.tls.helloAllowAll.trust-store.${keystore.type.short}.password = client-truststore-password +quarkus.tls.helloAllowAll.hostname-verification-algorithm = NONE +quarkus.cxf.client.helloAllowAllTlsConfig.client-endpoint-url = https://127.0.0.1:${quarkus.http.test-ssl-port}/services/hello +quarkus.cxf.client.helloAllowAllTlsConfig.service-interface = io.quarkiverse.cxf.it.security.policy.HelloService +quarkus.cxf.client.helloAllowAllTlsConfig.tls-configuration-name = helloAllowAll + quarkus.cxf.client.helloCustomHostnameVerifier.client-endpoint-url = https://127.0.0.1:${quarkus.http.test-ssl-port}/services/hello quarkus.cxf.client.helloCustomHostnameVerifier.service-interface = io.quarkiverse.cxf.it.security.policy.HelloService quarkus.cxf.client.helloCustomHostnameVerifier.trust-store = client-truststore.${keystore.type} diff --git a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/TransportPolicyTest.java b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/TransportPolicyTest.java index 9fde08985..2d5a0e572 100644 --- a/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/TransportPolicyTest.java +++ b/integration-tests/ws-security-policy/src/test/java/io/quarkiverse/cxf/it/security/policy/TransportPolicyTest.java @@ -12,7 +12,7 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; -import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl; +import io.quarkiverse.cxf.HTTPConduitImpl; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; @@ -33,32 +33,94 @@ void hello() { @Test void helloAllowAll() { - /* - * client calling a service having no policy via https://127.0.0.1 - * Should pass thanks to hostname-verifier = AllowAllHostnameVerifier - */ - RestAssured.given() - .config(PolicyTestUtils.restAssuredConfig()) - .body("Frank") - .post("/cxf/security-policy/helloAllowAll") - .then() - .statusCode(200) - .body(is("Hello Frank!")); + HTTPConduitImpl defaultImpl = HTTPConduitImpl.findDefaultHTTPConduitImpl(); + switch (defaultImpl) { + case VertxHttpClientHTTPConduitFactory: + /* + * client calling a service having no policy via https://127.0.0.1 + * hostname-verifier is not supported by VertxHttpClientHTTPConduitFactory + */ + RestAssured.given() + .config(PolicyTestUtils.restAssuredConfig()) + .body("Frank") + .post("/cxf/security-policy/helloAllowAll") + .then() + .statusCode(500) + .body(Matchers.containsString( + "http-conduit-factory = VertxHttpClientHTTPConduitFactory does not support setting a hostname verifier.")); + /* + * But the same works when the hostname verification is disabled via + * quarkus.tls.helloAllowAll.trust-store.hostname-verification-algorithm = NONE + */ + RestAssured.given() + .config(PolicyTestUtils.restAssuredConfig()) + .body("Frank") + .post("/cxf/security-policy/helloAllowAllTlsConfig") + .then() + .statusCode(200) + .body(is("Hello Frank!")); + break; + case URLConnectionHTTPConduitFactory: + /* + * client calling a service having no policy via https://127.0.0.1 + * Should pass thanks to hostname-verifier = AllowAllHostnameVerifier + */ + RestAssured.given() + .config(PolicyTestUtils.restAssuredConfig()) + .body("Frank") + .post("/cxf/security-policy/helloAllowAll") + .then() + .statusCode(200) + .body(is("Hello Frank!")); + + RestAssured.given() + .config(PolicyTestUtils.restAssuredConfig()) + .body("Frank") + .post("/cxf/security-policy/helloAllowAllTlsConfig") + .then() + .statusCode(500) + .body(Matchers.containsString( + "http-conduit-factory = URLConnectionHTTPConduitFactory does not support quarkus.tls.hostname-verification-algorithm. Use quarkus.cxf.client.\"client-name\".hostname-verifier instead.")); + break; + default: + throw new IllegalArgumentException("Unexpected value: " + defaultImpl); + } } @Test void helloCustomHostnameVerifier() { - /* - * client calling a service having no policy via https://127.0.0.1 - * Should pass thanks to hostname-verifier = io.quarkiverse.cxf.it.security.policy.NoopHostnameVerifier - */ - RestAssured.given() - .config(PolicyTestUtils.restAssuredConfig()) - .body("Frank") - .post("/cxf/security-policy/helloCustomHostnameVerifier") - .then() - .statusCode(200) - .body(is("Hello Frank!")); + HTTPConduitImpl defaultImpl = HTTPConduitImpl.findDefaultHTTPConduitImpl(); + switch (defaultImpl) { + case VertxHttpClientHTTPConduitFactory: + /* + * client calling a service having no policy via https://127.0.0.1 + * hostname-verifier is not supported by VertxHttpClientHTTPConduitFactory + */ + RestAssured.given() + .config(PolicyTestUtils.restAssuredConfig()) + .body("Frank") + .post("/cxf/security-policy/helloCustomHostnameVerifier") + .then() + .statusCode(500) + .body(Matchers.containsString( + "http-conduit-factory = VertxHttpClientHTTPConduitFactory does not support setting a hostname verifier. AllowAllHostnameVerifier can be replaced by using a named TLS configuration with hostname-verification-algorithm set to NONE")); + break; + case URLConnectionHTTPConduitFactory: + /* + * client calling a service having no policy via https://127.0.0.1 + * Should pass thanks to hostname-verifier = io.quarkiverse.cxf.it.security.policy.NoopHostnameVerifier + */ + RestAssured.given() + .config(PolicyTestUtils.restAssuredConfig()) + .body("Frank") + .post("/cxf/security-policy/helloCustomHostnameVerifier") + .then() + .statusCode(200) + .body(is("Hello Frank!")); + break; + default: + throw new IllegalArgumentException("Unexpected value: " + defaultImpl); + } } @Test @@ -164,7 +226,7 @@ void helloIp() { HTTPConduitImpl defaultImpl = HTTPConduitImpl.findDefaultHTTPConduitImpl(); switch (defaultImpl) { case VertxHttpClientHTTPConduitFactory: - expectedMessage = "The https URL hostname 127.0.0.1 does not match the Common Name (CN) on the server certificate in the client's truststore"; + expectedMessage = "No subject alternative names present"; break; case URLConnectionHTTPConduitFactory: expectedMessage = "The https URL hostname does not match the Common Name (CN) on the server certificate in the client's truststore";