From 75f17cf5aa233c8b85a40533f119f4f97114c99b Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Mon, 4 May 2020 11:57:33 -0300 Subject: [PATCH] [fixes #8508] - mTLS client authentication support --- bom/runtime/pom.xml | 2 +- .../deployment/SecurityProcessor.java | 2 + .../runtime/X509IdentityProvider.java | 30 +++++++ .../deployment/HttpSecurityProcessor.java | 27 +++++- .../vertx/http/security/MtlsRequestTest.java | 63 ++++++++++++++ .../vertx/http/security/MtlsRequiredTest.java | 69 +++++++++++++++ .../resources/conf/mtls/client-keystore.jks | Bin 0 -> 2214 bytes .../resources/conf/mtls/client-truststore.jks | Bin 0 -> 1674 bytes .../test/resources/conf/mtls/mtls-jks.conf | 5 ++ .../resources/conf/mtls/mtls-no-auth-jks.conf | 7 ++ .../resources/conf/mtls/server-keystore.jks | Bin 0 -> 2423 bytes .../resources/conf/mtls/server-truststore.jks | Bin 0 -> 925 bytes .../http/runtime/HttpBuildTimeConfig.java | 8 ++ .../security/HttpCredentialTransport.java | 6 +- .../security/HttpSecurityRecorder.java | 9 ++ .../security/MtlsAuthenticationMechanism.java | 81 ++++++++++++++++++ 16 files changed, 305 insertions(+), 4 deletions(-) create mode 100644 extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java create mode 100644 extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/MtlsRequestTest.java create mode 100644 extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/MtlsRequiredTest.java create mode 100644 extensions/vertx-http/deployment/src/test/resources/conf/mtls/client-keystore.jks create mode 100644 extensions/vertx-http/deployment/src/test/resources/conf/mtls/client-truststore.jks create mode 100644 extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-jks.conf create mode 100644 extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-no-auth-jks.conf create mode 100644 extensions/vertx-http/deployment/src/test/resources/conf/mtls/server-keystore.jks create mode 100644 extensions/vertx-http/deployment/src/test/resources/conf/mtls/server-truststore.jks create mode 100644 extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/MtlsAuthenticationMechanism.java diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index b23933870f049..aa62d991663ab 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -183,7 +183,7 @@ 3.3.3 5.3.1 4.7.2 - 1.1.1.Final + 1.1.2.Final-SNAPSHOT 9.0.2 1.14.0 0.1.55 diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java index 8ba803d5b416d..ee76775ce8a71 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java @@ -51,6 +51,7 @@ import io.quarkus.security.runtime.SecurityBuildTimeConfig; import io.quarkus.security.runtime.SecurityIdentityAssociation; import io.quarkus.security.runtime.SecurityIdentityProxy; +import io.quarkus.security.runtime.X509IdentityProvider; import io.quarkus.security.runtime.interceptor.AuthenticatedInterceptor; import io.quarkus.security.runtime.interceptor.DenyAllInterceptor; import io.quarkus.security.runtime.interceptor.PermitAllInterceptor; @@ -354,5 +355,6 @@ void registerAdditionalBeans(BuildProducer beans) { beans.produce(AdditionalBeanBuildItem.unremovableOf(SecurityIdentityAssociation.class)); beans.produce(AdditionalBeanBuildItem.unremovableOf(IdentityProviderManagerCreator.class)); beans.produce(AdditionalBeanBuildItem.unremovableOf(SecurityIdentityProxy.class)); + beans.produce(AdditionalBeanBuildItem.unremovableOf(X509IdentityProvider.class)); } } diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java new file mode 100644 index 0000000000000..37e990aa4b60b --- /dev/null +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java @@ -0,0 +1,30 @@ +package io.quarkus.security.runtime; + +import java.security.cert.X509Certificate; + +import javax.inject.Singleton; + +import io.quarkus.security.identity.AuthenticationRequestContext; +import io.quarkus.security.identity.IdentityProvider; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.identity.request.CertificateAuthenticationRequest; +import io.smallrye.mutiny.Uni; + +@Singleton +public class X509IdentityProvider implements IdentityProvider { + + @Override + public Class getRequestType() { + return CertificateAuthenticationRequest.class; + } + + @Override + public Uni authenticate(CertificateAuthenticationRequest request, AuthenticationRequestContext context) { + X509Certificate certificate = request.getCertificate().getCertificate(); + + return Uni.createFrom().item(QuarkusSecurityIdentity.builder() + .setPrincipal(certificate.getSubjectX500Principal()) + .addCredential(request.getCertificate()) + .build()); + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java index 55ac5491c20d0..2611f7f5d3c4c 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java @@ -27,10 +27,12 @@ import io.quarkus.vertx.http.runtime.security.HttpAuthorizer; import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy; import io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder; +import io.quarkus.vertx.http.runtime.security.MtlsAuthenticationMechanism; import io.quarkus.vertx.http.runtime.security.PathMatchingHttpSecurityPolicy; import io.quarkus.vertx.http.runtime.security.PermitSecurityPolicy; import io.quarkus.vertx.http.runtime.security.RolesAllowedHttpSecurityPolicy; import io.quarkus.vertx.http.runtime.security.SupplierImpl; +import io.vertx.core.http.ClientAuth; public class HttpSecurityProcessor { @@ -67,12 +69,28 @@ SyntheticBeanBuildItem initFormAuth( return null; } + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + SyntheticBeanBuildItem initMtlsClientAuth( + HttpSecurityRecorder recorder, + HttpBuildTimeConfig buildTimeConfig) { + if (isMtlsClientAuthenticationEnabled(buildTimeConfig)) { + return SyntheticBeanBuildItem.configure(MtlsAuthenticationMechanism.class) + .types(HttpAuthenticationMechanism.class) + .setRuntimeInit() + .scope(Singleton.class) + .supplier(recorder.setupMtlsClientAuth()).done(); + } + return null; + } + @BuildStep @Record(ExecutionTime.RUNTIME_INIT) SyntheticBeanBuildItem initBasicAuth( HttpSecurityRecorder recorder, HttpBuildTimeConfig buildTimeConfig) { - if (buildTimeConfig.auth.form.enabled && !buildTimeConfig.auth.basic) { + if ((buildTimeConfig.auth.form.enabled || isMtlsClientAuthenticationEnabled(buildTimeConfig)) + && !buildTimeConfig.auth.basic) { //if form auth is enabled and we are not then we don't install return null; } @@ -82,7 +100,8 @@ SyntheticBeanBuildItem initBasicAuth( .setRuntimeInit() .scope(Singleton.class) .supplier(recorder.setupBasicAuth(buildTimeConfig)); - if (!buildTimeConfig.auth.form.enabled && !buildTimeConfig.auth.basic) { + if (!buildTimeConfig.auth.form.enabled && !isMtlsClientAuthenticationEnabled(buildTimeConfig) + && !buildTimeConfig.auth.basic) { //if not explicitly enabled we make this a default bean, so it is the fallback if nothing else is defined configurator.defaultBean(); } @@ -134,4 +153,8 @@ void setupAuthenticationMechanisms( } } } + + private boolean isMtlsClientAuthenticationEnabled(HttpBuildTimeConfig buildTimeConfig) { + return !ClientAuth.NONE.equals(buildTimeConfig.clientAuth); + } } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/MtlsRequestTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/MtlsRequestTest.java new file mode 100644 index 0000000000000..50da1c7295a51 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/MtlsRequestTest.java @@ -0,0 +1,63 @@ +package io.quarkus.vertx.http.security; + +import static org.hamcrest.Matchers.is; + +import java.io.File; +import java.net.URL; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; + +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.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; +import io.restassured.RestAssured; +import io.vertx.ext.web.Router; + +public class MtlsRequestTest { + + @TestHTTPResource(value = "/mtls", ssl = true) + URL url; + + @TestHTTPResource(value = "/mtls", ssl = false) + URL urlNoTls; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(MyBean.class) + .addAsResource(new File("src/test/resources/conf/mtls/mtls-no-auth-jks.conf"), "application.properties") + .addAsResource(new File("src/test/resources/conf/mtls/server-keystore.jks"), "server-keystore.jks") + .addAsResource(new File("src/test/resources/conf/mtls/server-truststore.jks"), "server-truststore.jks")); + + @Test + public void testClientAuthentication() { + RestAssured.given() + .keyStore(new File("src/test/resources/conf/mtls/client-keystore.jks"), "password") + .trustStore(new File("src/test/resources/conf/mtls/client-truststore.jks"), "password") + .get(url).then().statusCode(200).body(is("CN=client,OU=cert,O=quarkus,L=city,ST=state,C=AU")); + } + + @Test + public void testNoClientCert() { + RestAssured.given() + .trustStore(new File("src/test/resources/conf/mtls/client-truststore.jks"), "password") + .get(url).then().statusCode(401); + } + + @ApplicationScoped + static class MyBean { + + public void register(@Observes Router router) { + router.get("/mtls").handler(rc -> { + rc.response().end(QuarkusHttpUser.class.cast(rc.user()).getSecurityIdentity().getPrincipal().getName()); + }); + } + + } +} diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/MtlsRequiredTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/MtlsRequiredTest.java new file mode 100644 index 0000000000000..667f067b79a00 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/MtlsRequiredTest.java @@ -0,0 +1,69 @@ +package io.quarkus.vertx.http.security; + +import static org.hamcrest.Matchers.is; + +import java.io.File; +import java.net.URL; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; + +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.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; +import io.restassured.RestAssured; +import io.vertx.ext.web.Router; + +public class MtlsRequiredTest { + + @TestHTTPResource(value = "/mtls", ssl = true) + URL url; + + @TestHTTPResource(value = "/mtls", ssl = false) + URL urlNoTls; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(MyBean.class) + .addAsResource(new File("src/test/resources/conf/mtls/mtls-jks.conf"), "application.properties") + .addAsResource(new File("src/test/resources/conf/mtls/server-keystore.jks"), "server-keystore.jks") + .addAsResource(new File("src/test/resources/conf/mtls/server-truststore.jks"), "server-truststore.jks")); + + @Test + public void testClientAuthentication() { + RestAssured.given() + .keyStore(new File("src/test/resources/conf/mtls/client-keystore.jks"), "password") + .trustStore(new File("src/test/resources/conf/mtls/client-truststore.jks"), "password") + .get(url).then().statusCode(200).body(is("CN=client,OU=cert,O=quarkus,L=city,ST=state,C=AU")); + } + + @Test + public void testNoSslServerWithJKS() { + RestAssured.given() + .get(urlNoTls).then().statusCode(401); + } + + @Test + public void testNoClientCert() { + RestAssured.given() + .trustStore(new File("src/test/resources/conf/mtls/client-truststore.jks"), "password") + .get(url).then().statusCode(401); + } + + @ApplicationScoped + static class MyBean { + + public void register(@Observes Router router) { + router.get("/mtls").handler(rc -> { + rc.response().end(QuarkusHttpUser.class.cast(rc.user()).getSecurityIdentity().getPrincipal().getName()); + }); + } + + } +} diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/mtls/client-keystore.jks b/extensions/vertx-http/deployment/src/test/resources/conf/mtls/client-keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..cf6d6ba454864d18322799afac37f520673193d6 GIT binary patch literal 2214 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 literal 0 HcmV?d00001 diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-jks.conf b/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-jks.conf new file mode 100644 index 0000000000000..1219f0623d7ed --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-jks.conf @@ -0,0 +1,5 @@ +quarkus.http.ssl.certificate.key-store-file=server-keystore.jks +quarkus.http.ssl.certificate.key-store-password=secret +quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks +quarkus.http.ssl.certificate.trust-store-password=password +quarkus.http.ssl.client-auth=REQUIRED \ No newline at end of file diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-no-auth-jks.conf b/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-no-auth-jks.conf new file mode 100644 index 0000000000000..e1f95f0d7b8f5 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-no-auth-jks.conf @@ -0,0 +1,7 @@ +quarkus.http.ssl.certificate.key-store-file=server-keystore.jks +quarkus.http.ssl.certificate.key-store-password=secret +quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks +quarkus.http.ssl.certificate.trust-store-password=password +quarkus.http.ssl.client-auth=REQUEST +quarkus.http.auth.permission.all.paths=/* +quarkus.http.auth.permission.all.policy=authenticated \ No newline at end of file diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/mtls/server-keystore.jks b/extensions/vertx-http/deployment/src/test/resources/conf/mtls/server-keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..da33e8e7a16683d421c7a541bf0013521efb605e GIT binary patch literal 2423 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 aH~3Ca%|*I@e88To>+D>~J`lt~rv3-)J!2gJ literal 0 HcmV?d00001 diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/mtls/server-truststore.jks b/extensions/vertx-http/deployment/src/test/resources/conf/mtls/server-truststore.jks new file mode 100644 index 0000000000000000000000000000000000000000..8ec8e126507b61e0e602b71d9f67b3d7e3c7cae3 GIT binary patch literal 925 zcmezO_TO6u1_mY|W(3o$xs}0Ue&dE&8D>0B0 z=QXr6G&C?YG&M3ZGLI7HHL^4`0AdK2=Jq!+Dj|EBk(GhDiIJbdpox)-sfm%1VUF3s zo}9*=J}DCyv6d_>miqhX+0BER7vzr=weLB0SI~-EN4Tx;+oAX3sZu+AreC;W+rDov z-#5*V51%D8KX(20xaE+MMC5UqlCZ}&*8kY~jW=TIFRK(mcF!wJ^TT7)vd&p~1Uy)$ z`pW;@Uk~HZlKvCjci1|P$j&@8Uwi5_z2paTO5a=hS@rqWe0&n}OLbn_nKBuLad&4PoF%w@U!d89 z+LCrv&mDH_wWWUYJ`36D%^-U7h-9RXpo^<=ZFc0HX`&2UWMlL9lv%uVR}CpmJg{;8 zx_~1U?x&fU85tNCD;mff$O0oymXAe@MTGhL(rFKW9uTVSSw3Zn;O1#zMYmj$0}+^R zfPu)!z}8XzuI^>`BBZaSb80!?+r7pc#`09np_Lozt4zJ}{d+W?|#aPK1E8H9tc&p84 z9$uW3|Ks-G6^q-888qdu-^`O)?fLIsY>e9J3v8=pk6rP))@~NVdhf7^xr%i0hj$v? zKacmOG&aO636*{EblS9x!(U?iHAU{$t+Riy#c;{YELD{*se2PT}_AU?Y+Xxvefqh07{ov?f?J) literal 0 HcmV?d00001 diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java index 41147163da72f..390a85a92672f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java @@ -3,6 +3,7 @@ import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; +import io.vertx.core.http.ClientAuth; @ConfigRoot(name = "http", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) public class HttpBuildTimeConfig { @@ -15,6 +16,13 @@ public class HttpBuildTimeConfig { public AuthConfig auth; + /** + * Configures the engine to require/request client authentication. + * NONE, REQUEST, REQUIRED + */ + @ConfigItem(name = "ssl.client-auth", defaultValue = "NONE") + public ClientAuth clientAuth; + /** * If this is true then only a virtual channel will be set up for vertx web. * We have this switch for testing purposes. diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpCredentialTransport.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpCredentialTransport.java index 78b89a527f72e..41575c3ed9c9f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpCredentialTransport.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpCredentialTransport.java @@ -39,7 +39,11 @@ public enum Type { /** * A post request, target is the POST URI */ - POST + POST, + /** + * X509 + */ + X509 } @Override diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java index 6cdf9daf03725..50eb322b54404 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java @@ -209,4 +209,13 @@ public BasicAuthenticationMechanism get() { } }; } + + public Supplier setupMtlsClientAuth() { + return new Supplier() { + @Override + public MtlsAuthenticationMechanism get() { + return new MtlsAuthenticationMechanism(); + } + }; + } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/MtlsAuthenticationMechanism.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/MtlsAuthenticationMechanism.java new file mode 100644 index 0000000000000..ebafd1c5cc000 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/MtlsAuthenticationMechanism.java @@ -0,0 +1,81 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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.quarkus.vertx.http.runtime.security; + +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import javax.net.ssl.SSLPeerUnverifiedException; + +import io.netty.handler.codec.http.HttpResponseStatus; +import io.quarkus.security.credential.CertificateCredential; +import io.quarkus.security.identity.IdentityProviderManager; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.identity.request.AuthenticationRequest; +import io.quarkus.security.identity.request.CertificateAuthenticationRequest; +import io.smallrye.mutiny.Uni; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.ext.web.RoutingContext; + +/** + * The authentication handler responsible for mTLS client authentication + */ +public class MtlsAuthenticationMechanism implements HttpAuthenticationMechanism { + + @Override + public Uni authenticate(RoutingContext context, + IdentityProviderManager identityProviderManager) { + HttpServerRequest request = context.request(); + + if (!request.isSSL()) { + // we don't handle insecure requests + return Uni.createFrom().failure(new AuthenticationCompletionException()); + } + + Certificate certificate; + + try { + certificate = request.sslSession().getPeerCertificates()[0]; + } catch (SSLPeerUnverifiedException e) { + return Uni.createFrom().optional(Optional.empty()); + } + + return identityProviderManager + .authenticate(new CertificateAuthenticationRequest( + new CertificateCredential(X509Certificate.class.cast(certificate)))); + } + + @Override + public Uni getChallenge(RoutingContext context) { + return Uni.createFrom().item(new ChallengeData(HttpResponseStatus.UNAUTHORIZED.code(), + null, null)); + } + + @Override + public Set> getCredentialTypes() { + return Collections.singleton(CertificateAuthenticationRequest.class); + } + + @Override + public HttpCredentialTransport getCredentialTransport() { + return new HttpCredentialTransport(HttpCredentialTransport.Type.X509, "X509"); + } +}