Skip to content

Commit

Permalink
KEYCLOAK-8996: Provide a way to set a responder certificate in OCSP/X…
Browse files Browse the repository at this point in the history
…509 Authenticator
  • Loading branch information
rmartinc authored and mposolda committed Mar 7, 2019
1 parent e843d84 commit 231db05
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 15 deletions.
6 changes: 3 additions & 3 deletions common/src/main/java/org/keycloak/common/util/OCSPUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ private static void verifyResponse(BasicOCSPResp basicOcspResponse, X509Certific
}
if (signingCert != null) {
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)) {
// https://www.ietf.org/rfc/rfc2560.txt
// 2.6 OCSP Signature Authority Delegation
Expand All @@ -390,7 +390,7 @@ private static void verifyResponse(BasicOCSPResp basicOcspResponse, X509Certific
// extension and is issued by the CA that issued the certificate in
// question."
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()});
throw new CertPathValidatorException("Responder\'s certificate is not authorized to sign OCSP responses");
}
Expand All @@ -401,7 +401,7 @@ private static void verifyResponse(BasicOCSPResp basicOcspResponse, X509Certific
throw new CertPathValidatorException("Responder\'s certificate not valid for signing OCSP responses");
}
} 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) {
signingCert.checkValidity();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public class ProviderConfigProperty {
public static final String CLIENT_LIST_TYPE="ClientList";
public static final String PASSWORD="Password";

/**
* textarea field
*/
public static final String TEXT_TYPE="Text";

protected String name;
protected String label;
protected String helpText;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 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_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_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
Expand Down Expand Up @@ -93,6 +94,7 @@ static CertificateValidator.CertificateValidatorBuilder fromConfig(X509Authentic
.cRLDPEnabled(config.getCRLDistributionPointEnabled())
.cRLrelativePath(config.getCRLRelativePath())
.oCSPEnabled(config.getOCSPEnabled())
.oCSPResponseCertificate(config.getOCSPResponderCertificate())
.oCSPResponderURI(config.getOCSPResponder());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@
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_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.REGULAR_EXPRESSION;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.USERNAME_EMAIL_MAPPER;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.USER_ATTRIBUTE_MAPPER;
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.STRING_TYPE;
import static org.keycloak.provider.ProviderConfigProperty.TEXT_TYPE;

/**
* @author <a href="mailto:[email protected]">Peter Nalyvayko</a>
Expand Down Expand Up @@ -155,6 +157,12 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
ocspResponderUri.setLabel("OCSP Responder Uri");
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();
keyUsage.setType(STRING_TYPE);
keyUsage.setName(CERTIFICATE_KEY_USAGE);
Expand Down Expand Up @@ -182,6 +190,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
cRLRelativePath,
oCspCheckingEnabled,
ocspResponderUri,
ocspResponderCert,
keyUsage,
extendedKeyUsage,
identityConfirmationPageDisallowed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
import java.util.Set;
import java.util.LinkedList;
import java.util.ArrayList;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;

/**
* @author <a href="mailto:[email protected]">Peter Nalyvayko</a>
Expand Down Expand Up @@ -149,8 +151,11 @@ public static abstract class CRLLoaderImpl {
public static class BouncyCastleOCSPChecker extends OCSPChecker {

private final String responderUri;
BouncyCastleOCSPChecker(String responderUri) {
private final X509Certificate responderCert;

BouncyCastleOCSPChecker(String responderUri, X509Certificate responderCert) {
this.responderUri = responderUri;
this.responderCert = responderCert;
}

@Override
Expand All @@ -177,12 +182,13 @@ public OCSPUtils.OCSPRevocationStatus check(X509Certificate cert, X509Certificat
String message = String.format("Unable to check certificate revocation status using OCSP.\n%s", e.getMessage());
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.
// OCSP responder's certificate is assumed to be the issuer's certificate
// certificate.
// 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;
}
Expand Down Expand Up @@ -529,6 +535,7 @@ public static class CertificateValidatorBuilder {
CRLLoaderImpl _crlLoader;
boolean _ocspEnabled;
String _responderUri;
X509Certificate _responderCert;

public CertificateValidatorBuilder() {
_extendedKeyUsage = new LinkedList<>();
Expand Down Expand Up @@ -675,6 +682,21 @@ public GotCRLRelativePath cRLLoader(CRLLoaderImpl cRLLoader) {
}

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) {
_responderUri = responderURI;
return _parent;
Expand All @@ -699,7 +721,8 @@ public CertificateValidator build(X509Certificate[] certs) {
_crlLoader = new CRLFileLoader("");
}
return new CertificateValidator(certs, _keyUsageBits, _extendedKeyUsage,
_crlCheckingEnabled, _crldpEnabled, _crlLoader, _ocspEnabled, new BouncyCastleOCSPChecker(_responderUri));
_crlCheckingEnabled, _crldpEnabled, _crlLoader, _ocspEnabled,
new BouncyCastleOCSPChecker(_responderUri, _responderCert));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,19 @@ public X509AuthenticatorConfigModel setOCSPResponder(String responderUri) {
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() {
return MappingSourceType.parse(getConfig().getOrDefault(MAPPING_SOURCE_SELECTION, MAPPING_SOURCE_CERT_SUBJECTDN));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@

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
private static final Map<BigInteger, CertificateStatus> REVOKED_CERTIFICATES_STATUS = ImmutableMap
Expand All @@ -82,17 +86,18 @@ final class OcspHandler implements HttpHandler {

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")
.generateCertificate(X509OCSPResponderTest.class.getResourceAsStream(OCSP_RESPONDER_CERT_PATH));
.generateCertificate(X509OCSPResponderTest.class.getResourceAsStream(responderCertPath));

chain = new X509CertificateHolder[] {new X509CertificateHolder(certificate.getEncoded())};

final AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(certificate.getPublicKey().getEncoded());

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))) {
final PEMKeyPair keyPairPem = (PEMKeyPair) keyPairReader.readObject();
Expand Down
Loading

0 comments on commit 231db05

Please sign in to comment.