From 7f58c1c570e3960f9f9e099da99bc6f736e06a1f Mon Sep 17 00:00:00 2001 From: Youssef El Houti Date: Thu, 1 Sep 2022 14:26:46 +0200 Subject: [PATCH] KEYCLOAK-19138 nginx x509 client trusted certificate lookup --- docs/guides/src/main/server/reverseproxy.adoc | 3 + ...roxySslClientCertificateLookupFactory.java | 24 +++++- ...nxProxyTrustedClientCertificateLookup.java | 79 +++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/x509/NginxProxyTrustedClientCertificateLookup.java diff --git a/docs/guides/src/main/server/reverseproxy.adoc b/docs/guides/src/main/server/reverseproxy.adoc index 2924c70d3c2..7d7fc3d3fe0 100644 --- a/docs/guides/src/main/server/reverseproxy.adoc +++ b/docs/guides/src/main/server/reverseproxy.adoc @@ -165,6 +165,9 @@ to load additional certificates from headers `CERT_CHAIN_0` to `CERT_CHAIN_9` if |certificate-chain-length | The maximum length of the certificate chain. + +|trust-proxy-verification +| Enable trusting NGINX proxy certificate verification, instead of forwarding the certificate to keycloak and verifying it in keycloak. |=== ==== Configuring the NGINX provider diff --git a/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookupFactory.java b/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookupFactory.java index 9a4a742637b..b2bdd249c9f 100644 --- a/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookupFactory.java +++ b/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookupFactory.java @@ -1,5 +1,7 @@ package org.keycloak.services.x509; +import org.jboss.logging.Logger; +import org.keycloak.Config; import org.keycloak.models.KeycloakSession; /** @@ -13,12 +15,30 @@ import org.keycloak.models.KeycloakSession; public class NginxProxySslClientCertificateLookupFactory extends AbstractClientCertificateFromHttpHeadersLookupFactory { + private final static Logger logger = Logger.getLogger(NginxProxySslClientCertificateLookupFactory.class); + private final static String PROVIDER = "nginx"; + protected final static String TRUST_PROXY_VERIFICATION = "trust-proxy-verification"; + + protected boolean trustProxyVerification = false; + + @Override + public void init(Config.Scope config) { + super.init(config); + trustProxyVerification = config.getBoolean(TRUST_PROXY_VERIFICATION, false); + logger.tracev("{0}: ''{1}''", TRUST_PROXY_VERIFICATION, trustProxyVerification); + } + @Override public X509ClientCertificateLookup create(KeycloakSession session) { - return new NginxProxySslClientCertificateLookup(sslClientCertHttpHeader, - sslChainHttpHeaderPrefix, certificateChainLength, session); + if (trustProxyVerification) { + return new NginxProxyTrustedClientCertificateLookup(sslClientCertHttpHeader, + sslChainHttpHeaderPrefix, certificateChainLength); + } else { + return new NginxProxySslClientCertificateLookup(sslClientCertHttpHeader, + sslChainHttpHeaderPrefix, certificateChainLength, session); + } } @Override diff --git a/services/src/main/java/org/keycloak/services/x509/NginxProxyTrustedClientCertificateLookup.java b/services/src/main/java/org/keycloak/services/x509/NginxProxyTrustedClientCertificateLookup.java new file mode 100644 index 00000000000..3438e4a94ef --- /dev/null +++ b/services/src/main/java/org/keycloak/services/x509/NginxProxyTrustedClientCertificateLookup.java @@ -0,0 +1,79 @@ +package org.keycloak.services.x509; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.spi.HttpRequest; +import org.keycloak.common.util.PemException; +import org.keycloak.common.util.PemUtils; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; + +/** + * The NGINX Trusted Provider verify extract end user X.509 certificate sent during TLS mutual authentication, + * verifies it against provided CA the and forwarded in an HTTP header along with a new header ssl-client-verify: SUCCESS. + * + * NGINX configuration must have : + * + * server { + * ... + * ssl_client_certificate path-to-trusted-ca.crt; + * ssl_verify_client on|optional; + * ssl_verify_depth 2; + * ... + * location / { + * ... + * proxy_set_header ssl-client-cert $ssl_client_escaped_cert; + * ... + * } + * + * + * Note that $ssl_client_cert is deprecated, use only $ssl_client_escaped_cert with this implementation + * + * @author Youssef El Houti + * @version $Revision: 1 $ + * @since 01/09/2022 + */ + +public class NginxProxyTrustedClientCertificateLookup extends AbstractClientCertificateFromHttpHeadersLookup { + + private static final Logger log = Logger.getLogger(NginxProxyTrustedClientCertificateLookup.class); + + public NginxProxyTrustedClientCertificateLookup(String sslCientCertHttpHeader, + String sslCertChainHttpHeaderPrefix, + int certificateChainLength) { + super(sslCientCertHttpHeader, sslCertChainHttpHeaderPrefix, certificateChainLength); + } + + @Override + protected X509Certificate getCertificateFromHttpHeader(HttpRequest request, String httpHeader) throws GeneralSecurityException { + X509Certificate certificate = super.getCertificateFromHttpHeader(request, httpHeader); + if (certificate == null) { + return null; + } + String validCertificateResult = getHeaderValue(request, "ssl-client-verify"); + if ("SUCCESS".equals(validCertificateResult)) { + return certificate; + } else { + log.warn("nginx could not verify the certificate: ssl-client-verify: " + validCertificateResult); + return null; + } + } + + @Override + protected X509Certificate decodeCertificateFromPem(String pem) throws PemException { + + if (pem == null) { + log.warn("End user TLS Certificate is NULL! "); + return null; + } + try { + pem = java.net.URLDecoder.decode(pem, "UTF-8"); + } catch (UnsupportedEncodingException e) { + log.error("Cannot URL decode the end user TLS Certificate : " + pem,e); + } + + return PemUtils.decodeCertificate(pem); + } + +}