KEYCLOAK-18852 Prevent NPE in case of missing truststore
even though the "return null" at the top of the method is called if no truststore is set, the finally block is still executed. And since the keystore is not there an NPE is thrown when calling the remove method.
This commit is contained in:
parent
d29d945cc4
commit
843bbf1bb3
1 changed files with 105 additions and 100 deletions
|
@ -34,10 +34,10 @@ import org.keycloak.truststore.TruststoreProviderFactory;
|
||||||
/**
|
/**
|
||||||
* The NGINX Provider extract end user X.509 certificate send during TLS mutual authentication,
|
* The NGINX Provider extract end user X.509 certificate send during TLS mutual authentication,
|
||||||
* and forwarded in an http header.
|
* and forwarded in an http header.
|
||||||
*
|
*
|
||||||
* NGINX configuration must have :
|
* NGINX configuration must have :
|
||||||
* <code>
|
* <code>
|
||||||
* server {
|
* server {
|
||||||
* ...
|
* ...
|
||||||
* ssl_client_certificate path-to-my-trustyed-cas-for-client-auth.pem;
|
* ssl_client_certificate path-to-my-trustyed-cas-for-client-auth.pem;
|
||||||
* ssl_verify_client on|optional_no_ca;
|
* ssl_verify_client on|optional_no_ca;
|
||||||
|
@ -49,9 +49,9 @@ import org.keycloak.truststore.TruststoreProviderFactory;
|
||||||
* ...
|
* ...
|
||||||
* }
|
* }
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
* Note that $ssl_client_cert is deprecated, use only $ssl_client_escaped_cert with this implementation
|
* Note that $ssl_client_cert is deprecated, use only $ssl_client_escaped_cert with this implementation
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:arnault.michel@toad-consulting.com">Arnault MICHEL</a>
|
* @author <a href="mailto:arnault.michel@toad-consulting.com">Arnault MICHEL</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
* @since 10/09/2018
|
* @since 10/09/2018
|
||||||
|
@ -59,30 +59,30 @@ import org.keycloak.truststore.TruststoreProviderFactory;
|
||||||
|
|
||||||
public class NginxProxySslClientCertificateLookup extends AbstractClientCertificateFromHttpHeadersLookup {
|
public class NginxProxySslClientCertificateLookup extends AbstractClientCertificateFromHttpHeadersLookup {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(NginxProxySslClientCertificateLookup.class);
|
private static final Logger log = Logger.getLogger(NginxProxySslClientCertificateLookup.class);
|
||||||
|
|
||||||
|
private static boolean isTruststoreLoaded = false;
|
||||||
|
|
||||||
|
private static KeyStore truststore = null;
|
||||||
|
private static Set<X509Certificate> trustedRootCerts = null;
|
||||||
|
private static Set<X509Certificate> intermediateCerts = null;
|
||||||
|
|
||||||
|
|
||||||
private static boolean isTruststoreLoaded = false;
|
|
||||||
|
|
||||||
private static KeyStore truststore = null;
|
|
||||||
private static Set<X509Certificate> trustedRootCerts = null;
|
|
||||||
private static Set<X509Certificate> intermediateCerts = null;
|
|
||||||
|
|
||||||
|
|
||||||
public NginxProxySslClientCertificateLookup(String sslCientCertHttpHeader,
|
public NginxProxySslClientCertificateLookup(String sslCientCertHttpHeader,
|
||||||
String sslCertChainHttpHeaderPrefix,
|
String sslCertChainHttpHeaderPrefix,
|
||||||
int certificateChainLength,
|
int certificateChainLength,
|
||||||
KeycloakSession kcsession) {
|
KeycloakSession kcsession) {
|
||||||
super(sslCientCertHttpHeader, sslCertChainHttpHeaderPrefix, certificateChainLength);
|
super(sslCientCertHttpHeader, sslCertChainHttpHeaderPrefix, certificateChainLength);
|
||||||
|
|
||||||
if (!loadKeycloakTrustStore(kcsession)) {
|
if (!loadKeycloakTrustStore(kcsession)) {
|
||||||
log.warn("Keycloak Truststore is null or empty, but it's required for NGINX x509cert-lookup provider");
|
log.warn("Keycloak Truststore is null or empty, but it's required for NGINX x509cert-lookup provider");
|
||||||
log.warn(" see Keycloak documentation here : https://www.keycloak.org/docs/latest/server_installation/index.html#_truststore");
|
log.warn(" see Keycloak documentation here : https://www.keycloak.org/docs/latest/server_installation/index.html#_truststore");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removing PEM Headers and end of lines
|
* Removing PEM Headers and end of lines
|
||||||
*
|
*
|
||||||
* @param pem
|
* @param pem
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
@ -101,15 +101,15 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific
|
||||||
protected X509Certificate decodeCertificateFromPem(String pem) throws PemException {
|
protected X509Certificate decodeCertificateFromPem(String pem) throws PemException {
|
||||||
|
|
||||||
if (pem == null) {
|
if (pem == null) {
|
||||||
log.warn("End user TLS Certificate is NULL! ");
|
log.warn("End user TLS Certificate is NULL! ");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
pem = java.net.URLDecoder.decode(pem, "UTF-8");
|
pem = java.net.URLDecoder.decode(pem, "UTF-8");
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
log.error("Cannot URL decode the end user TLS Certificate : " + pem,e);
|
log.error("Cannot URL decode the end user TLS Certificate : " + pem,e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pem.startsWith("-----BEGIN CERTIFICATE-----")) {
|
if (pem.startsWith("-----BEGIN CERTIFICATE-----")) {
|
||||||
pem = removeBeginEnd(pem);
|
pem = removeBeginEnd(pem);
|
||||||
}
|
}
|
||||||
|
@ -127,16 +127,16 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific
|
||||||
if (clientCert != null) {
|
if (clientCert != null) {
|
||||||
log.debugf("End user certificate found : Subject DN=[%s] SerialNumber=[%s]", clientCert.getSubjectDN(), clientCert.getSerialNumber());
|
log.debugf("End user certificate found : Subject DN=[%s] SerialNumber=[%s]", clientCert.getSubjectDN(), clientCert.getSerialNumber());
|
||||||
|
|
||||||
// Rebuilding the end user certificate chain using Keycloak Truststore
|
// Rebuilding the end user certificate chain using Keycloak Truststore
|
||||||
X509Certificate[] certChain = buildChain(clientCert);
|
X509Certificate[] certChain = buildChain(clientCert);
|
||||||
if ( certChain == null || certChain.length == 0 ) {
|
if (certChain == null || certChain.length == 0) {
|
||||||
log.info("Impossible to rebuild end user cert chain : client certificate authentication will fail." );
|
log.info("Impossible to rebuild end user cert chain : client certificate authentication will fail." );
|
||||||
chain.add(clientCert);
|
chain.add(clientCert);
|
||||||
} else {
|
} else {
|
||||||
for (X509Certificate cacert : certChain) {
|
for (X509Certificate cacert : certChain) {
|
||||||
chain.add(cacert);
|
chain.add(cacert);
|
||||||
log.debugf("Rebuilded user cert chain DN : %s", cacert.getSubjectDN().toString() );
|
log.debugf("Rebuilded user cert chain DN : %s", cacert.getSubjectDN().toString() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return chain.toArray(new X509Certificate[0]);
|
return chain.toArray(new X509Certificate[0]);
|
||||||
|
@ -150,14 +150,14 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific
|
||||||
* @param end_user_auth_cert
|
* @param end_user_auth_cert
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public X509Certificate[] buildChain(X509Certificate end_user_auth_cert) {
|
public X509Certificate[] buildChain(X509Certificate end_user_auth_cert) {
|
||||||
|
|
||||||
X509Certificate[] user_cert_chain = null;
|
X509Certificate[] user_cert_chain = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// No truststore : no way!
|
// No truststore : no way!
|
||||||
if (truststore == null) {
|
if (isTruststoreLoaded == false) {
|
||||||
log.warn("Keycloak Truststore is null, but it is required !");
|
log.warn("Keycloak Truststore is null, but it is required !");
|
||||||
log.warn(" see https://www.keycloak.org/docs/latest/server_installation/index.html#_truststore");
|
log.warn(" see https://www.keycloak.org/docs/latest/server_installation/index.html#_truststore");
|
||||||
return null;
|
return null;
|
||||||
|
@ -174,14 +174,14 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific
|
||||||
}
|
}
|
||||||
// Configure the PKIX certificate builder algorithm parameters
|
// Configure the PKIX certificate builder algorithm parameters
|
||||||
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters( trustAnchors, selector);
|
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters( trustAnchors, selector);
|
||||||
|
|
||||||
// Disable CRL checks, as it's possibly done after depending on Keycloak settings
|
// Disable CRL checks, as it's possibly done after depending on Keycloak settings
|
||||||
pkixParams.setRevocationEnabled(false);
|
pkixParams.setRevocationEnabled(false);
|
||||||
pkixParams.setExplicitPolicyRequired(false);
|
pkixParams.setExplicitPolicyRequired(false);
|
||||||
pkixParams.setAnyPolicyInhibited(false);
|
pkixParams.setAnyPolicyInhibited(false);
|
||||||
pkixParams.setPolicyQualifiersRejected(false);
|
pkixParams.setPolicyQualifiersRejected(false);
|
||||||
pkixParams.setMaxPathLength(certificateChainLength);
|
pkixParams.setMaxPathLength(certificateChainLength);
|
||||||
|
|
||||||
// Adding the list of intermediate certificates + end user certificate
|
// Adding the list of intermediate certificates + end user certificate
|
||||||
intermediateCerts.add(end_user_auth_cert);
|
intermediateCerts.add(end_user_auth_cert);
|
||||||
CollectionCertStoreParameters intermediateCA_userCert = new CollectionCertStoreParameters(intermediateCerts);
|
CollectionCertStoreParameters intermediateCA_userCert = new CollectionCertStoreParameters(intermediateCerts);
|
||||||
|
@ -192,70 +192,75 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific
|
||||||
CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX","BC");
|
CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX","BC");
|
||||||
CertPath certPath = certPathBuilder.build(pkixParams).getCertPath();
|
CertPath certPath = certPathBuilder.build(pkixParams).getCertPath();
|
||||||
log.debug("Certification path building OK, and contains " + certPath.getCertificates().size() + " X509 Certificates");
|
log.debug("Certification path building OK, and contains " + certPath.getCertificates().size() + " X509 Certificates");
|
||||||
|
|
||||||
user_cert_chain = convertCertPathtoX509CertArray( certPath );
|
user_cert_chain = convertCertPathtoX509CertArray( certPath );
|
||||||
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
log.error(e.getLocalizedMessage(),e);
|
log.error(e.getLocalizedMessage(),e);
|
||||||
} catch (CertPathBuilderException e) {
|
} catch (CertPathBuilderException e) {
|
||||||
if ( log.isEnabled(Level.TRACE) )
|
if (log.isEnabled(Level.TRACE)) {
|
||||||
log.debug(e.getLocalizedMessage(),e);
|
log.debug(e.getLocalizedMessage(),e);
|
||||||
else
|
} else {
|
||||||
log.warn(e.getLocalizedMessage());
|
log.warn(e.getLocalizedMessage());
|
||||||
|
}
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
log.error(e.getLocalizedMessage(),e);
|
log.error(e.getLocalizedMessage(),e);
|
||||||
} catch (NoSuchProviderException e) {
|
} catch (NoSuchProviderException e) {
|
||||||
log.error(e.getLocalizedMessage(),e);
|
log.error(e.getLocalizedMessage(),e);
|
||||||
} finally {
|
} finally {
|
||||||
//Remove end user certificate
|
if (isTruststoreLoaded) {
|
||||||
intermediateCerts.remove(end_user_auth_cert);
|
//Remove end user certificate
|
||||||
}
|
intermediateCerts.remove(end_user_auth_cert);
|
||||||
|
}
|
||||||
return user_cert_chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public X509Certificate[] convertCertPathtoX509CertArray( CertPath certPath ) {
|
|
||||||
|
|
||||||
X509Certificate[] x509certchain = null;
|
|
||||||
|
|
||||||
if (certPath!=null) {
|
|
||||||
List<X509Certificate> trustedX509Chain = new ArrayList<X509Certificate>();
|
|
||||||
for (Certificate certificate : certPath.getCertificates() )
|
|
||||||
if ( certificate instanceof X509Certificate )
|
|
||||||
trustedX509Chain.add((X509Certificate)certificate);
|
|
||||||
x509certchain = trustedX509Chain.toArray(new X509Certificate[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509certchain;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Loading truststore @ first login
|
|
||||||
*
|
|
||||||
* @param kcsession
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean loadKeycloakTrustStore(KeycloakSession kcsession) {
|
|
||||||
|
|
||||||
if (!isTruststoreLoaded) {
|
|
||||||
log.debug(" Loading Keycloak truststore ...");
|
|
||||||
KeycloakSessionFactory factory = kcsession.getKeycloakSessionFactory();
|
|
||||||
TruststoreProviderFactory truststoreFactory = (TruststoreProviderFactory) factory.getProviderFactory(TruststoreProvider.class, "file");
|
|
||||||
|
|
||||||
TruststoreProvider provider = truststoreFactory.create(kcsession);
|
|
||||||
|
|
||||||
if ( provider != null && provider.getTruststore() != null ) {
|
|
||||||
truststore = provider.getTruststore();
|
|
||||||
trustedRootCerts = new HashSet<>(provider.getRootCertificates().values());
|
|
||||||
intermediateCerts = new HashSet<>(provider.getIntermediateCertificates().values());
|
|
||||||
log.debug("Keycloak truststore loaded for NGINX x509cert-lookup provider.");
|
|
||||||
|
|
||||||
isTruststoreLoaded = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isTruststoreLoaded;
|
return user_cert_chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public X509Certificate[] convertCertPathtoX509CertArray( CertPath certPath ) {
|
||||||
|
|
||||||
|
X509Certificate[] x509certchain = null;
|
||||||
|
|
||||||
|
if (certPath != null) {
|
||||||
|
List<X509Certificate> trustedX509Chain = new ArrayList<X509Certificate>();
|
||||||
|
for (Certificate certificate : certPath.getCertificates()) {
|
||||||
|
if (certificate instanceof X509Certificate) {
|
||||||
|
trustedX509Chain.add((X509Certificate) certificate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x509certchain = trustedX509Chain.toArray(new X509Certificate[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509certchain;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Loading truststore @ first login
|
||||||
|
*
|
||||||
|
* @param kcsession
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean loadKeycloakTrustStore(KeycloakSession kcsession) {
|
||||||
|
|
||||||
|
if (!isTruststoreLoaded) {
|
||||||
|
log.debug(" Loading Keycloak truststore ...");
|
||||||
|
KeycloakSessionFactory factory = kcsession.getKeycloakSessionFactory();
|
||||||
|
TruststoreProviderFactory truststoreFactory = (TruststoreProviderFactory) factory.getProviderFactory(TruststoreProvider.class, "file");
|
||||||
|
|
||||||
|
TruststoreProvider provider = truststoreFactory.create(kcsession);
|
||||||
|
|
||||||
|
if (provider != null && provider.getTruststore() != null) {
|
||||||
|
truststore = provider.getTruststore();
|
||||||
|
trustedRootCerts = new HashSet<>(provider.getRootCertificates().values());
|
||||||
|
intermediateCerts = new HashSet<>(provider.getIntermediateCertificates().values());
|
||||||
|
log.debug("Keycloak truststore loaded for NGINX x509cert-lookup provider.");
|
||||||
|
|
||||||
|
isTruststoreLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isTruststoreLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue