From 6d6db1f3e52ff7cd22e076e1bbfaeae328743d3b Mon Sep 17 00:00:00 2001 From: rmartinc Date: Tue, 9 Jul 2019 17:48:32 +0200 Subject: [PATCH] KEYCLOAK-10345: OCSP validation fails if there is no intermediate CA in the client certificate --- .../org/keycloak/common/util/OCSPUtils.java | 2 +- .../x509/CertificateValidator.java | 47 +++++-- .../jboss/common/keystore/client-ca.jks | Bin 0 -> 4079 bytes .../certs/clients/test-user-ca@localhost.csr | 28 ++++ .../clients/test-user-ca@localhost.key.pem | 51 +++++++ .../certs/clients/test-user-ca@localhost.p12 | Bin 0 -> 4333 bytes .../certs/clients/test-user-ca@localhost.pem | 128 ++++++++++++++++++ .../jboss/common/pki/root/ca/index.txt | 1 + .../common/pki/root/ca/index.txt.attr.old | 1 + .../jboss/common/pki/root/ca/index.txt.old | 1 + .../common/pki/root/ca/newcerts/1001.pem | 128 ++++++++++++++++++ .../jboss/common/pki/root/ca/serial | 2 +- .../jboss/common/pki/root/ca/serial.old | 2 +- .../servers/auth-server/jboss/pom.xml | 1 + .../keycloak/testsuite/util/OAuthClient.java | 49 ++++--- .../testsuite/x509/X509OCSPResponderTest.java | 74 ++++++++++ 16 files changed, 484 insertions(+), 31 deletions(-) create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/client-ca.jks create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.csr create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.key.pem create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.p12 create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.pem create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/index.txt.attr.old create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/newcerts/1001.pem diff --git a/common/src/main/java/org/keycloak/common/util/OCSPUtils.java b/common/src/main/java/org/keycloak/common/util/OCSPUtils.java index 6a6e00c404..d2e05b5dfe 100644 --- a/common/src/main/java/org/keycloak/common/util/OCSPUtils.java +++ b/common/src/main/java/org/keycloak/common/util/OCSPUtils.java @@ -396,7 +396,7 @@ public final class OCSPUtils { } try { List purposes = signingCert.getExtendedKeyUsage(); - if (purposes != null && !purposes.contains(KeyPurposeId.id_kp_OCSPSigning.getId())) { + if (purposes == null || !purposes.contains(KeyPurposeId.id_kp_OCSPSigning.getId())) { logger.log(Level.INFO, "OCSPSigning extended usage is not set"); throw new CertPathValidatorException("Responder\'s certificate not valid for signing OCSP responses"); } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java index 900e4217df..7a7ed30e77 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java @@ -54,10 +54,13 @@ import java.util.List; import java.util.Set; import java.util.LinkedList; import java.util.ArrayList; +import java.util.Map; import java.util.stream.Collectors; +import javax.security.auth.x500.X500Principal; import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.processing.core.util.XMLSignatureUtil; +import org.keycloak.truststore.TruststoreProvider; /** * @author Peter Nalyvayko @@ -464,20 +467,48 @@ public class CertificateValidator { validateExtendedKeyUsage(_certChain, _extendedKeyUsage); return this; } + + private X509Certificate findCAInTruststore(X500Principal issuer) throws GeneralSecurityException { + TruststoreProvider truststoreProvider = session.getProvider(TruststoreProvider.class); + if (truststoreProvider == null || truststoreProvider.getTruststore() == null) { + return null; + } + Map rootCerts = truststoreProvider.getRootCertificates(); + X509Certificate ca = rootCerts.get(issuer); + if (ca != null) { + ca.checkValidity(); + } + return ca; + } + private void checkRevocationUsingOCSP(X509Certificate[] certs) throws GeneralSecurityException { - if (certs.length < 2) { - // OCSP requires a responder certificate to verify OCSP - // signed response. - String message = "OCSP requires a responder certificate. OCSP cannot be used to verify the revocation status of self-signed certificates."; - throw new GeneralSecurityException(message); + if (logger.isDebugEnabled() && certs != null) { + for (X509Certificate cert : certs) { + logger.debugf("Certificate: %s", cert.getSubjectDN().getName()); + } } - for (X509Certificate cert : certs) { - logger.debugf("Certificate: %s", cert.getSubjectDN().getName()); + X509Certificate cert = null; + X509Certificate issuer = null; + + if (certs == null || certs.length == 0) { + throw new GeneralSecurityException("No certificates sent"); + } else if (certs.length > 1) { + cert = certs[0]; + issuer = certs[1]; + } else { + // only one cert => find the CA certificate using the truststore SPI + cert = certs[0]; + issuer = findCAInTruststore(cert.getIssuerX500Principal()); + if (issuer == null) { + throw new GeneralSecurityException( + String.format("No trusted CA in certificate found: %s. Add it to truststore SPI if valid.", + cert.getIssuerX500Principal())); + } } - OCSPUtils.OCSPRevocationStatus rs = ocspChecker.check(certs[0], certs[1]); + OCSPUtils.OCSPRevocationStatus rs = ocspChecker.check(cert, issuer); if (rs == null) { throw new GeneralSecurityException("Unable to check client revocation status using OCSP"); diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/client-ca.jks b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/client-ca.jks new file mode 100644 index 0000000000000000000000000000000000000000..674cd57347eda498486f2df57e72f48812f50f3f GIT binary patch literal 4079 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 literal 0 HcmV?d00001 diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.csr b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.csr new file mode 100644 index 0000000000..925fa37533 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.csr @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEzjCCArYCAQAwgYgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNQTEPMA0GA1UE +BwwGQm9zdG9uMRAwDgYDVQQKDAdSZWQgSGF0MREwDwYDVQQLDAhLZXljbG9hazES +MBAGA1UEAwwJdGVzdC11c2VyMSIwIAYJKoZIhvcNAQkBFhN0ZXN0LXVzZXJAbG9j +YWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwxZN9ePdOvlI +x1xbtxYXov/9PoqFtuc9gk9jGuQrpjeIV6rVw9SKfRpPdTcPRyRLgLTu20G8qjHP +WnalUpdZf9g1FTgEuqWqyN+wf/+l2xai8tFLsjcRMHsZAbFrM9iU8StSlLdSn+f5 +4OVi798PtmMgH6fLcmK0vxP3Ux6sxxF6M8zq/0UyiYN1fkT6pQ91A3C8JM900xb6 +ST/zBIcu//e2cqfvvLrsuflYdZVdpNJlasGcBjx7/qJwkpvZJmXXYf3eNu+dMKqq +yqZWFde8rwfL/BeqNVpQc3eBojCnIV4cIfhm4etviFsufaRAEXhDcEdh3PokbNkk +dV3CZRKiQukWrJrdr7hMO7FZe77E8NbwJtAauUcna6GrvwJy6YpmFpVRORqeOxKW ++A+m/JCHCL5MTjKeEDh43gVP0LxfKdnUiHmyFEL53GUzd3ogk+15n8pgpetbqBgy +sKFXAJ3IXClgHpLat2nBqFZsSBZhzD9og8puO4v9ioGe1QBxaXngN5ajGlw15/kg +Xr7rcoBCk5c9DBFuKYmv68OJYhAfAYJbb3lq8Gb2YGRMBOGeo1m0jpUjlTtEb/8Z +cUGU6ug70q1wIa3HPPHPUx/nO7d4uO6QqUwmTrTTF9vDBy4GB1BBNIR3NTdgvVhq +oPaoo2XVBPLuFu53w92ap9VHz0fk0JUCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IC +AQCPSe8gh9BkehsdIEtPw6FVNusofuxG2fvEQOBI2lRBfXJ5LdTKOFzXbKxn8wSs +MNidFbpmyDMzhyLBTTCbGSRMUsZfPfOWJG9HknHttcwX1k8bI/a1gEVK6L/e6rRm +yqKF9pRus3EkbRCKEVMGh7CbVVwxujN2xKbuX00QLyni4OiYhpRzCJuIUkzU+Dmf +jWzrhjDhI3C6D0Mlpl1Anor5qlThwJap4Ak/fS6WlWgBA+XsPujNLu/fdutWUatn +bEuVSQuP/4LH1l86OT5aP095JQWLYTo0xrMyT6+K5U5zPjLzKDK0CoAHfpbiauRN +iPSGlEGtICtm66fVAHhbH448aRnWB+6O0GBrSO5STI8XJUNg5IyyCF8ihbDUC/9G +m9wksO0Xc0M27Ir9yuPe53f/giP/0gTw6Z01hCz3NZB6tNppHHd270c5edPI8ewf +pEpqXvvx08vgyUbW5OgK4mpjP7w/Xv6zV5hrw/mXbg7maPHA4EsgbEUTWQupjSZ7 +FFHTmVVnnnBrFHvSnb5OM+5/s80beaniDHp7zemj1+nxabeUWuDn6rTxiCFrF1+F +TCJDFdHsl3wcLVroygCaVg8TDq2UmUygQuuSn36oHCAM+jDOf4AAi/vgL/WLtJQx +rWYtLhB+Ilaoh7+lH6SepgFuQzDMa7BOrB7AQ3zsOtgsTg== +-----END CERTIFICATE REQUEST----- diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.key.pem b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.key.pem new file mode 100644 index 0000000000..8ce8b92f98 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.p12 b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-ca@localhost.p12 new file mode 100644 index 0000000000000000000000000000000000000000..f1d93e2d6da4bb0fd5729b4fd06353af6c8a90d0 GIT binary patch literal 4333 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.jks keycloak.truststore client.jks + client-ca.jks ca.crt client.crt client.key diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java index 782003de4a..6f14f099d4 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java @@ -267,36 +267,45 @@ public class OAuthClient { return this; } + public Supplier getHttpClient() { + return httpClient; + } + public static CloseableHttpClient newCloseableHttpClient() { if (sslRequired) { - KeyStore keystore = null; - // load the keystore containing the client certificate - keystore type is probably jks or pkcs12 String keyStorePath = System.getProperty("client.certificate.keystore"); String keyStorePassword = System.getProperty("client.certificate.keystore.passphrase"); - try { - keystore = KeystoreUtil.loadKeyStore(keyStorePath, keyStorePassword); - } catch (Exception e) { - e.printStackTrace(); - } - - // load the trustore - KeyStore truststore = null; String trustStorePath = System.getProperty("client.truststore"); String trustStorePassword = System.getProperty("client.truststore.passphrase"); - try { - truststore = KeystoreUtil.loadKeyStore(trustStorePath, trustStorePassword); - } catch(Exception e) { - e.printStackTrace(); - } - return (CloseableHttpClient) new org.keycloak.adapters.HttpClientBuilder() - .keyStore(keystore, keyStorePassword) - .trustStore(truststore) - .hostnameVerification(org.keycloak.adapters.HttpClientBuilder.HostnameVerificationPolicy.ANY) - .build(); + return newCloseableHttpClientSSL(keyStorePath, keyStorePassword, trustStorePath, trustStorePassword); } return HttpClientBuilder.create().build(); } + public static CloseableHttpClient newCloseableHttpClientSSL(String keyStorePath, + String keyStorePassword, String trustStorePath, String trustStorePassword) { + KeyStore keystore = null; + // load the keystore containing the client certificate - keystore type is probably jks or pkcs12 + try { + keystore = KeystoreUtil.loadKeyStore(keyStorePath, keyStorePassword); + } catch (Exception e) { + e.printStackTrace(); + } + + // load the trustore + KeyStore truststore = null; + try { + truststore = KeystoreUtil.loadKeyStore(trustStorePath, trustStorePassword); + } catch (Exception e) { + e.printStackTrace(); + } + return (CloseableHttpClient) new org.keycloak.adapters.HttpClientBuilder() + .keyStore(keystore, keyStorePassword) + .trustStore(truststore) + .hostnameVerification(org.keycloak.adapters.HttpClientBuilder.HostnameVerificationPolicy.ANY) + .build(); + } + public CloseableHttpResponse doPreflightRequest() { try (CloseableHttpClient client = httpClient.get()) { HttpOptions options = new HttpOptions(getAccessTokenUrl()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509OCSPResponderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509OCSPResponderTest.java index 3573efefa6..95a3628b40 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509OCSPResponderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509OCSPResponderTest.java @@ -18,6 +18,7 @@ package org.keycloak.testsuite.x509; +import com.google.common.base.Charsets; import org.jboss.arquillian.drone.api.annotation.Drone; import org.junit.After; import org.junit.Assert; @@ -38,6 +39,12 @@ import io.undertow.Undertow; import io.undertow.server.handlers.BlockingHandler; import org.keycloak.testsuite.util.PhantomJSBrowser; import org.openqa.selenium.WebDriver; +import java.nio.file.Paths; +import java.util.function.Supplier; +import org.apache.commons.io.IOUtils; +import org.apache.http.impl.client.CloseableHttpClient; +import org.keycloak.testsuite.util.PhantomJSBrowser; +import org.openqa.selenium.WebDriver; /** * Verifies Certificate revocation using OCSP responder. @@ -85,6 +92,73 @@ public class X509OCSPResponderTest extends AbstractX509AuthenticationTest { Assert.assertThat(response.getErrorDescription(), containsString("Certificate's been revoked.")); } + @Test + public void loginFailedOnOCSPResponderRevocationCheckWithoutCA() throws Exception { + X509AuthenticatorConfigModel config = + new X509AuthenticatorConfigModel() + .setOCSPEnabled(true) + .setMappingSourceType(SUBJECTDN_EMAIL) + .setOCSPResponder("http://" + OCSP_RESPONDER_HOST + ":" + OCSP_RESPONDER_PORT + "/oscp") + .setUserIdentityMapperType(USERNAME_EMAIL); + AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig()); + String cfgId = createConfig(directGrantExecution.getId(), cfg); + Assert.assertNotNull(cfgId); + + String keyStorePath = Paths.get(System.getProperty("client.certificate.keystore")) + .getParent().resolve("client-ca.jks").toString(); + String keyStorePassword = System.getProperty("client.certificate.keystore.passphrase"); + String trustStorePath = System.getProperty("client.truststore"); + String trustStorePassword = System.getProperty("client.truststore.passphrase"); + Supplier previous = oauth.getHttpClient(); + try { + oauth.clientId("resource-owner"); + oauth.httpClient(() -> OAuthClient.newCloseableHttpClientSSL(keyStorePath, keyStorePassword, trustStorePath, trustStorePassword)); + OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null); + + assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatusCode()); + assertEquals("invalid_request", response.getError()); + + // the ocsp signer cert is issued by the same CA but no OCSP-Signing extension so error + Assert.assertThat(response.getErrorDescription(), containsString("Responder's certificate not valid for signing OCSP responses")); + } finally { + oauth.httpClient(previous); + } + } + + @Test + public void loginOKOnOCSPResponderRevocationCheckWithoutCA() throws Exception { + X509AuthenticatorConfigModel config = + new X509AuthenticatorConfigModel() + .setOCSPEnabled(true) + .setMappingSourceType(SUBJECTDN_EMAIL) + .setOCSPResponder("http://" + OCSP_RESPONDER_HOST + ":" + OCSP_RESPONDER_PORT + "/oscp") + .setOCSPResponderCertificate( + IOUtils.toString(this.getClass().getResourceAsStream(OcspHandler.OCSP_RESPONDER_CERT_PATH), Charsets.UTF_8) + .replace("-----BEGIN CERTIFICATE-----\n", "") + .replace("\n-----END CERTIFICATE-----", "")) + .setUserIdentityMapperType(USERNAME_EMAIL); + AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig()); + String cfgId = createConfig(directGrantExecution.getId(), cfg); + Assert.assertNotNull(cfgId); + + String keyStorePath = Paths.get(System.getProperty("client.certificate.keystore")) + .getParent().resolve("client-ca.jks").toString(); + String keyStorePassword = System.getProperty("client.certificate.keystore.passphrase"); + String trustStorePath = System.getProperty("client.truststore"); + String trustStorePassword = System.getProperty("client.truststore.passphrase"); + Supplier previous = oauth.getHttpClient(); + try { + oauth.clientId("resource-owner"); + oauth.httpClient(() -> OAuthClient.newCloseableHttpClientSSL(keyStorePath, keyStorePassword, trustStorePath, trustStorePassword)); + OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null); + + // now it's OK because the certificate is fixed + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode()); + } finally { + oauth.httpClient(previous); + } + } + @Before public void startOCSPResponder() throws Exception { ocspResponder = Undertow.builder().addHttpListener(OCSP_RESPONDER_PORT, OCSP_RESPONDER_HOST)