KEYCLOAK-8996: Provide a way to set a responder certificate in OCSP/X509 Authenticator
This commit is contained in:
parent
e843d84f6e
commit
231db059b2
12 changed files with 255 additions and 15 deletions
|
@ -378,7 +378,7 @@ public final class OCSPUtils {
|
||||||
}
|
}
|
||||||
if (signingCert != null) {
|
if (signingCert != null) {
|
||||||
if (signingCert.equals(issuerCertificate)) {
|
if (signingCert.equals(issuerCertificate)) {
|
||||||
logger.log(Level.INFO, "OCSP response is signed by the target\'s Issuing CA");
|
logger.log(Level.INFO, "OCSP response is signed by the target''s Issuing CA");
|
||||||
} else if (responderCertificate != null && signingCert.equals(responderCertificate)) {
|
} else if (responderCertificate != null && signingCert.equals(responderCertificate)) {
|
||||||
// https://www.ietf.org/rfc/rfc2560.txt
|
// https://www.ietf.org/rfc/rfc2560.txt
|
||||||
// 2.6 OCSP Signature Authority Delegation
|
// 2.6 OCSP Signature Authority Delegation
|
||||||
|
@ -390,7 +390,7 @@ public final class OCSPUtils {
|
||||||
// extension and is issued by the CA that issued the certificate in
|
// extension and is issued by the CA that issued the certificate in
|
||||||
// question."
|
// question."
|
||||||
if (!signingCert.getIssuerX500Principal().equals(issuerCertificate.getSubjectX500Principal())) {
|
if (!signingCert.getIssuerX500Principal().equals(issuerCertificate.getSubjectX500Principal())) {
|
||||||
logger.log(Level.INFO, "Signer certificate's Issuer: {0}\nIssuer certificate's Subject: {1}",
|
logger.log(Level.INFO, "Signer certificate''s Issuer: {0}\nIssuer certificate''s Subject: {1}",
|
||||||
new Object[] {signingCert.getIssuerX500Principal().getName(), issuerCertificate.getSubjectX500Principal().getName()});
|
new Object[] {signingCert.getIssuerX500Principal().getName(), issuerCertificate.getSubjectX500Principal().getName()});
|
||||||
throw new CertPathValidatorException("Responder\'s certificate is not authorized to sign OCSP responses");
|
throw new CertPathValidatorException("Responder\'s certificate is not authorized to sign OCSP responses");
|
||||||
}
|
}
|
||||||
|
@ -401,7 +401,7 @@ public final class OCSPUtils {
|
||||||
throw new CertPathValidatorException("Responder\'s certificate not valid for signing OCSP responses");
|
throw new CertPathValidatorException("Responder\'s certificate not valid for signing OCSP responses");
|
||||||
}
|
}
|
||||||
} catch (CertificateParsingException e) {
|
} catch (CertificateParsingException e) {
|
||||||
logger.log(Level.FINE, "Failed to get certificate's extended key usage extension\n{0}", e.getMessage());
|
logger.log(Level.FINE, "Failed to get certificate''s extended key usage extension\n{0}", e.getMessage());
|
||||||
}
|
}
|
||||||
if (date == null) {
|
if (date == null) {
|
||||||
signingCert.checkValidity();
|
signingCert.checkValidity();
|
||||||
|
|
|
@ -52,6 +52,11 @@ public class ProviderConfigProperty {
|
||||||
public static final String CLIENT_LIST_TYPE="ClientList";
|
public static final String CLIENT_LIST_TYPE="ClientList";
|
||||||
public static final String PASSWORD="Password";
|
public static final String PASSWORD="Password";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* textarea field
|
||||||
|
*/
|
||||||
|
public static final String TEXT_TYPE="Text";
|
||||||
|
|
||||||
protected String name;
|
protected String name;
|
||||||
protected String label;
|
protected String label;
|
||||||
protected String helpText;
|
protected String helpText;
|
||||||
|
|
|
@ -54,6 +54,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
public static final String ENABLE_CRLDP = "x509-cert-auth.crldp-checking-enabled";
|
public static final String ENABLE_CRLDP = "x509-cert-auth.crldp-checking-enabled";
|
||||||
public static final String CRL_RELATIVE_PATH = "x509-cert-auth.crl-relative-path";
|
public static final String CRL_RELATIVE_PATH = "x509-cert-auth.crl-relative-path";
|
||||||
public static final String OCSPRESPONDER_URI = "x509-cert-auth.ocsp-responder-uri";
|
public static final String OCSPRESPONDER_URI = "x509-cert-auth.ocsp-responder-uri";
|
||||||
|
public static final String OCSPRESPONDER_CERTIFICATE = "x509-cert-auth.ocsp-responder-certificate";
|
||||||
public static final String MAPPING_SOURCE_SELECTION = "x509-cert-auth.mapping-source-selection";
|
public static final String MAPPING_SOURCE_SELECTION = "x509-cert-auth.mapping-source-selection";
|
||||||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
|
public static final String MAPPING_SOURCE_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
|
||||||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
|
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
|
||||||
|
@ -93,6 +94,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
.cRLDPEnabled(config.getCRLDistributionPointEnabled())
|
.cRLDPEnabled(config.getCRLDistributionPointEnabled())
|
||||||
.cRLrelativePath(config.getCRLRelativePath())
|
.cRLrelativePath(config.getCRLRelativePath())
|
||||||
.oCSPEnabled(config.getOCSPEnabled())
|
.oCSPEnabled(config.getOCSPEnabled())
|
||||||
|
.oCSPResponseCertificate(config.getOCSPResponderCertificate())
|
||||||
.oCSPResponderURI(config.getOCSPResponder());
|
.oCSPResponderURI(config.getOCSPResponder());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_CN;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_CN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_SELECTION;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_SELECTION;
|
||||||
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.OCSPRESPONDER_CERTIFICATE;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.OCSPRESPONDER_URI;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.OCSPRESPONDER_URI;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.REGULAR_EXPRESSION;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.REGULAR_EXPRESSION;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.USERNAME_EMAIL_MAPPER;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.USERNAME_EMAIL_MAPPER;
|
||||||
|
@ -55,6 +56,7 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.USER_MAPPER_SELECTION;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.USER_MAPPER_SELECTION;
|
||||||
import static org.keycloak.provider.ProviderConfigProperty.BOOLEAN_TYPE;
|
import static org.keycloak.provider.ProviderConfigProperty.BOOLEAN_TYPE;
|
||||||
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
|
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
|
||||||
|
import static org.keycloak.provider.ProviderConfigProperty.TEXT_TYPE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
|
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
|
||||||
|
@ -155,6 +157,12 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
|
||||||
ocspResponderUri.setLabel("OCSP Responder Uri");
|
ocspResponderUri.setLabel("OCSP Responder Uri");
|
||||||
ocspResponderUri.setHelpText("Clients use OCSP Responder Uri to check certificate revocation status.");
|
ocspResponderUri.setHelpText("Clients use OCSP Responder Uri to check certificate revocation status.");
|
||||||
|
|
||||||
|
ProviderConfigProperty ocspResponderCert = new ProviderConfigProperty();
|
||||||
|
ocspResponderCert.setType(TEXT_TYPE);
|
||||||
|
ocspResponderCert.setName(OCSPRESPONDER_CERTIFICATE);
|
||||||
|
ocspResponderCert.setLabel("OCSP Responder Certificate");
|
||||||
|
ocspResponderCert.setHelpText("Optional certificate used by the responder to sign the responses. The certificate should be in PEM format without BEGIN and END tags. It is only used if the OCSP Responder URI is set. By default, the certificate of the OCSP responder is that of the issuer of the certificate being validated or one with the OCSPSigning extension and also issued by the same CA. This option identifies the certificate of the OCSP responder when the defaults do not apply.");
|
||||||
|
|
||||||
ProviderConfigProperty keyUsage = new ProviderConfigProperty();
|
ProviderConfigProperty keyUsage = new ProviderConfigProperty();
|
||||||
keyUsage.setType(STRING_TYPE);
|
keyUsage.setType(STRING_TYPE);
|
||||||
keyUsage.setName(CERTIFICATE_KEY_USAGE);
|
keyUsage.setName(CERTIFICATE_KEY_USAGE);
|
||||||
|
@ -182,6 +190,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
|
||||||
cRLRelativePath,
|
cRLRelativePath,
|
||||||
oCspCheckingEnabled,
|
oCspCheckingEnabled,
|
||||||
ocspResponderUri,
|
ocspResponderUri,
|
||||||
|
ocspResponderCert,
|
||||||
keyUsage,
|
keyUsage,
|
||||||
extendedKeyUsage,
|
extendedKeyUsage,
|
||||||
identityConfirmationPageDisallowed);
|
identityConfirmationPageDisallowed);
|
||||||
|
|
|
@ -51,6 +51,8 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
|
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
|
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
|
||||||
|
@ -149,8 +151,11 @@ public class CertificateValidator {
|
||||||
public static class BouncyCastleOCSPChecker extends OCSPChecker {
|
public static class BouncyCastleOCSPChecker extends OCSPChecker {
|
||||||
|
|
||||||
private final String responderUri;
|
private final String responderUri;
|
||||||
BouncyCastleOCSPChecker(String responderUri) {
|
private final X509Certificate responderCert;
|
||||||
|
|
||||||
|
BouncyCastleOCSPChecker(String responderUri, X509Certificate responderCert) {
|
||||||
this.responderUri = responderUri;
|
this.responderUri = responderUri;
|
||||||
|
this.responderCert = responderCert;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -177,12 +182,13 @@ public class CertificateValidator {
|
||||||
String message = String.format("Unable to check certificate revocation status using OCSP.\n%s", e.getMessage());
|
String message = String.format("Unable to check certificate revocation status using OCSP.\n%s", e.getMessage());
|
||||||
throw new CertPathValidatorException(message, e);
|
throw new CertPathValidatorException(message, e);
|
||||||
}
|
}
|
||||||
logger.tracef("Responder URI \"%s\" will be used to verify revocation status of the certificate using OCSP", uri.toString());
|
logger.tracef("Responder URI \"%s\" will be used to verify revocation status of the certificate using OCSP with responderCert=%s",
|
||||||
|
uri.toString(), responderCert);
|
||||||
// Obtains the revocation status of a certificate using OCSP.
|
// Obtains the revocation status of a certificate using OCSP.
|
||||||
// OCSP responder's certificate is assumed to be the issuer's certificate
|
// OCSP responder's certificate is assumed to be the issuer's certificate
|
||||||
// certificate.
|
// certificate.
|
||||||
// responderUri overrides the contents (if any) of the certificate's AIA extension
|
// responderUri overrides the contents (if any) of the certificate's AIA extension
|
||||||
ocspRevocationStatus = OCSPUtils.check(cert, issuerCertificate, uri, null, null);
|
ocspRevocationStatus = OCSPUtils.check(cert, issuerCertificate, uri, responderCert, null);
|
||||||
}
|
}
|
||||||
return ocspRevocationStatus;
|
return ocspRevocationStatus;
|
||||||
}
|
}
|
||||||
|
@ -529,6 +535,7 @@ public class CertificateValidator {
|
||||||
CRLLoaderImpl _crlLoader;
|
CRLLoaderImpl _crlLoader;
|
||||||
boolean _ocspEnabled;
|
boolean _ocspEnabled;
|
||||||
String _responderUri;
|
String _responderUri;
|
||||||
|
X509Certificate _responderCert;
|
||||||
|
|
||||||
public CertificateValidatorBuilder() {
|
public CertificateValidatorBuilder() {
|
||||||
_extendedKeyUsage = new LinkedList<>();
|
_extendedKeyUsage = new LinkedList<>();
|
||||||
|
@ -675,6 +682,21 @@ public class CertificateValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GotOCSP {
|
public class GotOCSP {
|
||||||
|
public GotOCSP oCSPResponseCertificate(String responderCert) {
|
||||||
|
if (responderCert != null && !responderCert.isEmpty()) {
|
||||||
|
try {
|
||||||
|
_responderCert = XMLSignatureUtil.getX509CertificateFromKeyInfoString(responderCert);
|
||||||
|
_responderCert.checkValidity();
|
||||||
|
} catch(CertificateException e) {
|
||||||
|
logger.warnf("Ignoring invalid certificate: %s", _responderCert);
|
||||||
|
_responderCert = null;
|
||||||
|
} catch (ProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new GotOCSP();
|
||||||
|
}
|
||||||
|
|
||||||
public CertificateValidatorBuilder oCSPResponderURI(String responderURI) {
|
public CertificateValidatorBuilder oCSPResponderURI(String responderURI) {
|
||||||
_responderUri = responderURI;
|
_responderUri = responderURI;
|
||||||
return _parent;
|
return _parent;
|
||||||
|
@ -699,7 +721,8 @@ public class CertificateValidator {
|
||||||
_crlLoader = new CRLFileLoader("");
|
_crlLoader = new CRLFileLoader("");
|
||||||
}
|
}
|
||||||
return new CertificateValidator(certs, _keyUsageBits, _extendedKeyUsage,
|
return new CertificateValidator(certs, _keyUsageBits, _extendedKeyUsage,
|
||||||
_crlCheckingEnabled, _crldpEnabled, _crlLoader, _ocspEnabled, new BouncyCastleOCSPChecker(_responderUri));
|
_crlCheckingEnabled, _crldpEnabled, _crlLoader, _ocspEnabled,
|
||||||
|
new BouncyCastleOCSPChecker(_responderUri, _responderCert));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,19 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getOCSPResponderCertificate() {
|
||||||
|
return getConfig().getOrDefault(OCSPRESPONDER_CERTIFICATE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509AuthenticatorConfigModel setOCSPResponderCertificate(String responderCert) {
|
||||||
|
if (responderCert != null) {
|
||||||
|
getConfig().put(OCSPRESPONDER_CERTIFICATE, responderCert);
|
||||||
|
} else {
|
||||||
|
getConfig().remove(OCSPRESPONDER_CERTIFICATE);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MappingSourceType getMappingSourceType() {
|
public MappingSourceType getMappingSourceType() {
|
||||||
return MappingSourceType.parse(getConfig().getOrDefault(MAPPING_SOURCE_SELECTION, MAPPING_SOURCE_CERT_SUBJECTDN));
|
return MappingSourceType.parse(getConfig().getOrDefault(MAPPING_SOURCE_SELECTION, MAPPING_SOURCE_CERT_SUBJECTDN));
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,9 +68,13 @@ import io.undertow.util.Headers;
|
||||||
|
|
||||||
final class OcspHandler implements HttpHandler {
|
final class OcspHandler implements HttpHandler {
|
||||||
|
|
||||||
private static final String OCSP_RESPONDER_CERT_PATH = "/client-auth-test/intermediate-ca.crt";
|
// certificates created by the same ca than the client certs
|
||||||
|
public static final String OCSP_RESPONDER_CERT_PATH = "/client-auth-test/intermediate-ca.crt";
|
||||||
|
public static final String OCSP_RESPONDER_KEYPAIR_PATH = "/client-auth-test/intermediate-ca.key";
|
||||||
|
|
||||||
private static final String OCSP_RESPONDER_KEYPAIR_PATH = "/client-auth-test/intermediate-ca.key";
|
// certificates specific for responderCert
|
||||||
|
public static final String OCSP_RESPONDER_CERT_PATH_SPECIFIC = "/client-auth-test/intermediate-ca-2.crt";
|
||||||
|
public static final String OCSP_RESPONDER_KEYPAIR_PATH_SPECIFIC = "/client-auth-test/intermediate-ca-2.key";
|
||||||
|
|
||||||
// add any certificates that the OCSP responder needs to know about in the tests here
|
// add any certificates that the OCSP responder needs to know about in the tests here
|
||||||
private static final Map<BigInteger, CertificateStatus> REVOKED_CERTIFICATES_STATUS = ImmutableMap
|
private static final Map<BigInteger, CertificateStatus> REVOKED_CERTIFICATES_STATUS = ImmutableMap
|
||||||
|
@ -82,9 +86,10 @@ final class OcspHandler implements HttpHandler {
|
||||||
|
|
||||||
private final AsymmetricKeyParameter privateKey;
|
private final AsymmetricKeyParameter privateKey;
|
||||||
|
|
||||||
OcspHandler() throws OperatorCreationException, GeneralSecurityException, IOException {
|
public OcspHandler(String responderCertPath, String responderKeyPath)
|
||||||
|
throws OperatorCreationException, GeneralSecurityException, IOException {
|
||||||
final Certificate certificate = CertificateFactory.getInstance("X509")
|
final Certificate certificate = CertificateFactory.getInstance("X509")
|
||||||
.generateCertificate(X509OCSPResponderTest.class.getResourceAsStream(OCSP_RESPONDER_CERT_PATH));
|
.generateCertificate(X509OCSPResponderTest.class.getResourceAsStream(responderCertPath));
|
||||||
|
|
||||||
chain = new X509CertificateHolder[] {new X509CertificateHolder(certificate.getEncoded())};
|
chain = new X509CertificateHolder[] {new X509CertificateHolder(certificate.getEncoded())};
|
||||||
|
|
||||||
|
@ -92,7 +97,7 @@ final class OcspHandler implements HttpHandler {
|
||||||
|
|
||||||
subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey);
|
subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey);
|
||||||
|
|
||||||
final InputStream keyPairStream = X509OCSPResponderTest.class.getResourceAsStream(OCSP_RESPONDER_KEYPAIR_PATH);
|
final InputStream keyPairStream = X509OCSPResponderTest.class.getResourceAsStream(responderKeyPath);
|
||||||
|
|
||||||
try (final PEMParser keyPairReader = new PEMParser(new InputStreamReader(keyPairStream))) {
|
try (final PEMParser keyPairReader = new PEMParser(new InputStreamReader(keyPairStream))) {
|
||||||
final PEMKeyPair keyPairPem = (PEMKeyPair) keyPairReader.readObject();
|
final PEMKeyPair keyPairPem = (PEMKeyPair) keyPairReader.readObject();
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Analytical Graphics, Inc. and/or its affiliates
|
||||||
|
* and other 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 org.keycloak.testsuite.x509;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel;
|
||||||
|
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
|
||||||
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USERNAME_EMAIL;
|
||||||
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL;
|
||||||
|
|
||||||
|
import io.undertow.Undertow;
|
||||||
|
import io.undertow.server.handlers.BlockingHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies Certificate revocation using OCSP responder but specifying specific
|
||||||
|
* responder cert.
|
||||||
|
* The tests rely on an OCSP responder service listening
|
||||||
|
* for OCSP requests on http://localhost:8888
|
||||||
|
* @author rmartinc
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
* @since 11/2/2016
|
||||||
|
*/
|
||||||
|
public class X509OCSPResponderSpecificCertTest extends AbstractX509AuthenticationTest {
|
||||||
|
|
||||||
|
private static final String OCSP_RESPONDER_HOST = "localhost";
|
||||||
|
|
||||||
|
private static final int OCSP_RESPONDER_PORT = 8888;
|
||||||
|
|
||||||
|
private Undertow ocspResponder;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginFailedInvalidResponderOnOCSPResponderRevocationCheck() throws Exception {
|
||||||
|
X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel()
|
||||||
|
.setOCSPEnabled(true)
|
||||||
|
.setMappingSourceType(SUBJECTDN_EMAIL)
|
||||||
|
.setUserIdentityMapperType(USERNAME_EMAIL);
|
||||||
|
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig());
|
||||||
|
String cfgId = createConfig(directGrantExecution.getId(), cfg);
|
||||||
|
Assert.assertNotNull(cfgId);
|
||||||
|
|
||||||
|
oauth.clientId("resource-owner");
|
||||||
|
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);
|
||||||
|
|
||||||
|
assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatusCode());
|
||||||
|
assertEquals("invalid_request", response.getError());
|
||||||
|
|
||||||
|
Assert.assertThat(response.getErrorDescription(), containsString("Responder's certificate is not authorized to sign OCSP responses"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginFailedOnOCSPResponderRevocationCheck() throws Exception {
|
||||||
|
X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel()
|
||||||
|
.setOCSPEnabled(true)
|
||||||
|
.setOCSPResponder("http://" + OCSP_RESPONDER_HOST + ":" + OCSP_RESPONDER_PORT + "/oscp")
|
||||||
|
.setOCSPResponderCertificate(
|
||||||
|
"MIIDSDCCAjACCQDutBlh01xKxDANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJV\n"
|
||||||
|
+ "UzELMAkGA1UECBMCTUExETAPBgNVBAcTCFdlc3R3b3JkMRAwDgYDVQQKEwdSZWQg\n"
|
||||||
|
+ "SGF0MREwDwYDVQQLEwhLZXljbG9hazESMBAGA1UEAxMJbG9jYWxob3N0MCAXDTE4\n"
|
||||||
|
+ "MTEyOTE1MzYxNFoYDzMwMTgwNDAxMTUzNjE0WjBkMQswCQYDVQQGEwJVUzELMAkG\n"
|
||||||
|
+ "A1UECAwCTUExDzANBgNVBAcMBkJvc3RvbjEQMA4GA1UECgwHUmVkIEhhdDERMA8G\n"
|
||||||
|
+ "A1UECwwIS2V5Y2xvYWsxEjAQBgNVBAMMCUtleWNsb2FrMjCCASIwDQYJKoZIhvcN\n"
|
||||||
|
+ "AQEBBQADggEPADCCAQoCggEBALxnRdlqot+p3fAZ8BPt/ZeytVy2ZFXd7zG8jVCu\n"
|
||||||
|
+ "j/u/IqrN9fE7esdaiZYEwMvaTPKG7pAxb5NlRgWsE8UfNNN9a0GCp3wPJsmj4Lfx\n"
|
||||||
|
+ "K7LmH9QLtq+K7Ap5UGXXRNU0BZqcDMznMaIz04N4DimX5uGAwFQCy/NM5yUP2iOa\n"
|
||||||
|
+ "dPlB6ECpLavHDT09rMSVl5RgLQLx/TeX7pT4IW7kbpdTVI02rzE90O72riK61c6P\n"
|
||||||
|
+ "Q9Zb6bGSaNZwfNGIVQ8u6AVimLJx66p3BNP+3kEfg7xvXkw6UcaXM5LlMVcxi7cr\n"
|
||||||
|
+ "Se1k2UR95gPwJC1AVVPUPqHVb3Ix/wLl7GGhHcuBLPODF0cCAwEAATANBgkqhkiG\n"
|
||||||
|
+ "9w0BAQsFAAOCAQEADX2znEyqJZHGWLazrSHFn9Rn1mREqH+OCRq38ymz3tCNyVhs\n"
|
||||||
|
+ "OTSO1t6Fo1PP4RvlxB6gd4BYH7/cSCsO00s/OjPf8ptqz59TQAmCIM0+dwQuyxKO\n"
|
||||||
|
+ "gq55nWpy5gbqf/zqQiWsMXW5nkMVEMUvf1qbKx6xYP61B83vZh+t65LZh7meG6S5\n"
|
||||||
|
+ "B5qT6nDGKN7C8AHuxHHJjpgvYL8kNb47fASTYdHzW57Yi92NrkmAq4PCEt6FQTkX\n"
|
||||||
|
+ "WybC/Il6hS0jPdR2ExNV9ykKJrNGGhiwg3C8sf97/Kf+qQgRK8wQIdT88g81aJaG\n"
|
||||||
|
+ "qpwfJXd9AZO7DdDJdZ75lR9N1YSnhSq3Ur6IJg==")
|
||||||
|
.setMappingSourceType(SUBJECTDN_EMAIL)
|
||||||
|
.setUserIdentityMapperType(USERNAME_EMAIL);
|
||||||
|
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig());
|
||||||
|
String cfgId = createConfig(directGrantExecution.getId(), cfg);
|
||||||
|
Assert.assertNotNull(cfgId);
|
||||||
|
|
||||||
|
oauth.clientId("resource-owner");
|
||||||
|
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);
|
||||||
|
|
||||||
|
assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatusCode());
|
||||||
|
assertEquals("invalid_request", response.getError());
|
||||||
|
|
||||||
|
Assert.assertThat(response.getErrorDescription(), containsString("Certificate's been revoked."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void startOCSPResponder() throws Exception {
|
||||||
|
ocspResponder = Undertow.builder().addHttpListener(OCSP_RESPONDER_PORT, OCSP_RESPONDER_HOST)
|
||||||
|
.setHandler(new BlockingHandler(new OcspHandler(
|
||||||
|
OcspHandler.OCSP_RESPONDER_CERT_PATH_SPECIFIC,
|
||||||
|
OcspHandler.OCSP_RESPONDER_KEYPAIR_PATH_SPECIFIC))
|
||||||
|
).build();
|
||||||
|
|
||||||
|
ocspResponder.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void stopOCSPResponder() {
|
||||||
|
ocspResponder.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -76,7 +76,9 @@ public class X509OCSPResponderTest extends AbstractX509AuthenticationTest {
|
||||||
@Before
|
@Before
|
||||||
public void startOCSPResponder() throws Exception {
|
public void startOCSPResponder() throws Exception {
|
||||||
ocspResponder = Undertow.builder().addHttpListener(OCSP_RESPONDER_PORT, OCSP_RESPONDER_HOST)
|
ocspResponder = Undertow.builder().addHttpListener(OCSP_RESPONDER_PORT, OCSP_RESPONDER_HOST)
|
||||||
.setHandler(new BlockingHandler(new OcspHandler())).build();
|
.setHandler(new BlockingHandler(
|
||||||
|
new OcspHandler(OcspHandler.OCSP_RESPONDER_CERT_PATH, OcspHandler.OCSP_RESPONDER_KEYPAIR_PATH))
|
||||||
|
).build();
|
||||||
|
|
||||||
ocspResponder.start();
|
ocspResponder.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDSDCCAjACCQDutBlh01xKxDANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJV
|
||||||
|
UzELMAkGA1UECBMCTUExETAPBgNVBAcTCFdlc3R3b3JkMRAwDgYDVQQKEwdSZWQg
|
||||||
|
SGF0MREwDwYDVQQLEwhLZXljbG9hazESMBAGA1UEAxMJbG9jYWxob3N0MCAXDTE4
|
||||||
|
MTEyOTE1MzYxNFoYDzMwMTgwNDAxMTUzNjE0WjBkMQswCQYDVQQGEwJVUzELMAkG
|
||||||
|
A1UECAwCTUExDzANBgNVBAcMBkJvc3RvbjEQMA4GA1UECgwHUmVkIEhhdDERMA8G
|
||||||
|
A1UECwwIS2V5Y2xvYWsxEjAQBgNVBAMMCUtleWNsb2FrMjCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBALxnRdlqot+p3fAZ8BPt/ZeytVy2ZFXd7zG8jVCu
|
||||||
|
j/u/IqrN9fE7esdaiZYEwMvaTPKG7pAxb5NlRgWsE8UfNNN9a0GCp3wPJsmj4Lfx
|
||||||
|
K7LmH9QLtq+K7Ap5UGXXRNU0BZqcDMznMaIz04N4DimX5uGAwFQCy/NM5yUP2iOa
|
||||||
|
dPlB6ECpLavHDT09rMSVl5RgLQLx/TeX7pT4IW7kbpdTVI02rzE90O72riK61c6P
|
||||||
|
Q9Zb6bGSaNZwfNGIVQ8u6AVimLJx66p3BNP+3kEfg7xvXkw6UcaXM5LlMVcxi7cr
|
||||||
|
Se1k2UR95gPwJC1AVVPUPqHVb3Ix/wLl7GGhHcuBLPODF0cCAwEAATANBgkqhkiG
|
||||||
|
9w0BAQsFAAOCAQEADX2znEyqJZHGWLazrSHFn9Rn1mREqH+OCRq38ymz3tCNyVhs
|
||||||
|
OTSO1t6Fo1PP4RvlxB6gd4BYH7/cSCsO00s/OjPf8ptqz59TQAmCIM0+dwQuyxKO
|
||||||
|
gq55nWpy5gbqf/zqQiWsMXW5nkMVEMUvf1qbKx6xYP61B83vZh+t65LZh7meG6S5
|
||||||
|
B5qT6nDGKN7C8AHuxHHJjpgvYL8kNb47fASTYdHzW57Yi92NrkmAq4PCEt6FQTkX
|
||||||
|
WybC/Il6hS0jPdR2ExNV9ykKJrNGGhiwg3C8sf97/Kf+qQgRK8wQIdT88g81aJaG
|
||||||
|
qpwfJXd9AZO7DdDJdZ75lR9N1YSnhSq3Ur6IJg==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpgIBAAKCAQEAvGdF2Wqi36nd8BnwE+39l7K1XLZkVd3vMbyNUK6P+78iqs31
|
||||||
|
8Tt6x1qJlgTAy9pM8obukDFvk2VGBawTxR80031rQYKnfA8myaPgt/ErsuYf1Au2
|
||||||
|
r4rsCnlQZddE1TQFmpwMzOcxojPTg3gOKZfm4YDAVALL80znJQ/aI5p0+UHoQKkt
|
||||||
|
q8cNPT2sxJWXlGAtAvH9N5fulPghbuRul1NUjTavMT3Q7vauIrrVzo9D1lvpsZJo
|
||||||
|
1nB80YhVDy7oBWKYsnHrqncE0/7eQR+DvG9eTDpRxpczkuUxVzGLtytJ7WTZRH3m
|
||||||
|
A/AkLUBVU9Q+odVvcjH/AuXsYaEdy4Es84MXRwIDAQABAoIBAQCTcxPUm8OK3qvm
|
||||||
|
zLX7MwiEoAWW4NxX45DfUR0cJvJi6W0dVoIEYTOvL+l8Mo4dPOV57iZpTXdwWRNb
|
||||||
|
Pxhd7xVt67t0kue3jTgjGZG3BBXaNoZ0cxJwAn7Hl7hXbAnf/o3gOf8+ojZTJtKE
|
||||||
|
EwKqhMrac+SSG3o5GVfRxr1HngusQxJvT5amKoG01jILMPJ5ihXKnxKLSxUQL18Z
|
||||||
|
UsCYiWBtcFpp06frjqke44E06YTDoUMMzuQJ1sLmJvbVkNgJNSZVAu719TZ5MNcu
|
||||||
|
4ioIxzpHVnjfSsDwzyO8/NV72WTwXqHppluGVtPU3iOECk3KUflQVe7qxDUmw0e7
|
||||||
|
vrIe9o5JAoGBAPB+Dl/ObNtMgAVE4rEGl4pUIumXCblc/odGdWxLQwCmtPYborTU
|
||||||
|
IZWLDmZ426P9iIhd95a/2T4XkioMcwbz/I8HKJn8dQ6yRSpHrUhruQFxK0FXIWMG
|
||||||
|
6e3BM9OE+0hyCiJysZ6SA8DEiyw33xHGyMoeFIRjisxfng+JIHC4J7gtAoGBAMiN
|
||||||
|
Wq7uTSnz74a4W7E4oJXDJLf330bqdeoxNXJZ/ToTxbpUzudsIUhCj8zhpPEugh8H
|
||||||
|
cMBOYzvY0oqZH4tFK68Moc6Swp3r54y3Lqg/ON2QaarIQReZA0/dCr0wUkp5Kla1
|
||||||
|
fKecUPf8iPopaOKqFmzeDHuh10ZYSVQM1LW3FCHDAoGBALnznkWyJTWD91EbrHTY
|
||||||
|
9f+9gqk/YgNzxlLuxgiQQSSVwkKxp6NOixFITkApnxs0BIf8sF5dxSpfq2Lb8W0k
|
||||||
|
5tno3RoDvLc6XISOm4vtz0UwLhP9vREG3YY650nb/zhLBU8RhAlqOYCp+DqekNr7
|
||||||
|
63FsxMyA82eHX33/t5ewrPWZAoGBAKuC762wkH8zZYHyQkBCbFsHo2xNaD5unt7k
|
||||||
|
rI+Z1A0KAcxDYRrbB2+TtQUDaBr2qkM9Crj7kLQKzqvHHgnt4tj2VZ6jNhr2N3TI
|
||||||
|
5/bs8oXRTfzG+5vhuMphACEhpQ6ZppK27N8uuLQL3V0Lcu59hWYfU8+bbc50DGn7
|
||||||
|
+yFYa7gfAoGBANatpQuu9CwP3/tKkDN8jzDo7n824celL8ANt6CflU0HGW3IKtRN
|
||||||
|
8E4UVfpuSjU1+KQ4a07wqXMHmY3lIdZa4UOut8Ug/os8+NydlhxuQufFjdtjPYP2
|
||||||
|
Mq7zrVhebbk+X+vuZdsV6VUr+Xx4n6BpxZ7nf3q0UtrVmHbDVg7QfKjR
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -2,7 +2,7 @@
|
||||||
<div data-ng-repeat="option in properties" class="form-group" data-ng-controller="ProviderConfigCtrl">
|
<div data-ng-repeat="option in properties" class="form-group" data-ng-controller="ProviderConfigCtrl">
|
||||||
<label class="col-md-2 control-label">{{:: option.label | translate}}</label>
|
<label class="col-md-2 control-label">{{:: option.label | translate}}</label>
|
||||||
|
|
||||||
<div class="col-md-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role' || option.type == 'ClientList' || option.type == 'Password' || option.type=='Script'">
|
<div class="col-md-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role' || option.type == 'ClientList' || option.type == 'Password' || option.type=='Script' || option.type=='Text'">
|
||||||
<input class="form-control" type="text" data-ng-model="config[ option.name ]" >
|
<input class="form-control" type="text" data-ng-model="config[ option.name ]" >
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6" data-ng-show="option.type == 'Password'">
|
<div class="col-md-6" data-ng-show="option.type == 'Password'">
|
||||||
|
@ -38,6 +38,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6" data-ng-show="option.type == 'Text'">
|
||||||
|
<textarea class="form-control" data-ng-model="config[ option.name ]"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<kc-tooltip>{{:: option.helpText | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: option.helpText | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue