KEYCLOAK-16520 X509 Auth: Add option to verify certificate policy

This commit is contained in:
Luca Leonardo Scorcia 2020-12-02 12:11:38 -05:00 committed by Marek Posolda
parent da8861686a
commit 9838a47662
8 changed files with 488 additions and 20 deletions

View file

@ -83,6 +83,10 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
public static final String CUSTOM_ATTRIBUTE_NAME = "x509-cert-auth.mapper-selection.user-attribute-name";
public static final String CERTIFICATE_KEY_USAGE = "x509-cert-auth.keyusage";
public static final String CERTIFICATE_EXTENDED_KEY_USAGE = "x509-cert-auth.extendedkeyusage";
public static final String CERTIFICATE_POLICY = "x509-cert-auth.certificate-policy";
public static final String CERTIFICATE_POLICY_MODE = "x509-cert-auth.certificate-policy-mode";
public static final String CERTIFICATE_POLICY_MODE_ALL = "All";
public static final String CERTIFICATE_POLICY_MODE_ANY = "Any";
static final String DEFAULT_MATCH_ALL_EXPRESSION = "(.*?)(?:$)";
public static final String CONFIRMATION_PAGE_DISALLOWED = "x509-cert-auth.confirmation-page-disallowed";
public static final String REVALIDATE_CERTIFICATE = "x509-cert-auth.revalidate-certificate-enabled";
@ -104,6 +108,9 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
.parse(config.getKeyUsage())
.extendedKeyUsage()
.parse(config.getExtendedKeyUsage())
.certificatePolicy()
.mode(config.getCertificatePolicyMode().getMode())
.parse(config.getCertificatePolicy())
.revocation()
.cRLEnabled(config.getCRLEnabled())
.cRLDPEnabled(config.getCRLDistributionPointEnabled())

View file

@ -87,6 +87,11 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
USERNAME_EMAIL_MAPPER
};
private static final String[] CERTIFICATE_POLICY_MODES = {
CERTIFICATE_POLICY_MODE_ALL,
CERTIFICATE_POLICY_MODE_ANY
};
protected static final List<ProviderConfigProperty> configProperties;
static {
List<String> mappingSourceTypes = new LinkedList<>();
@ -201,6 +206,22 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
extendedKeyUsage.setLabel("Validate Extended Key Usage");
extendedKeyUsage.setHelpText("Validates the extended purposes of the certificate's key using certificate's Extended Key Usage extension. Leaving the field blank will disable Extended Key Usage validation. See RFC 5280 for a detailed definition of X509 Extended Key Usage extension.");
ProviderConfigProperty certificatePolicy = new ProviderConfigProperty();
certificatePolicy.setType(STRING_TYPE);
certificatePolicy.setName(CERTIFICATE_POLICY);
certificatePolicy.setLabel("Validate Certificate Policy");
certificatePolicy.setHelpText("Validates the certificate policies of the certificate's key using certificate's Policy extension. Leaving the field blank will disable Certificate Policies validation. Multiple policies should be separated using a comma. See RFC 5280 for a detailed definition of X509 Certificate Policy extension.");
List<String> certificatePolicyModesOptions = new LinkedList<>();
Collections.addAll(certificatePolicyModesOptions, CERTIFICATE_POLICY_MODES);
ProviderConfigProperty certificatePolicyMode = new ProviderConfigProperty();
certificatePolicyMode.setType(ProviderConfigProperty.LIST_TYPE);
certificatePolicyMode.setName(CERTIFICATE_POLICY_MODE);
certificatePolicyMode.setLabel("Certificate Policy Validation Mode");
certificatePolicyMode.setHelpText("If Certificate Policy validation is specified, indicates whether it should match all or at least one of the specified policies.");
certificatePolicyMode.setDefaultValue(CERTIFICATE_POLICY_MODES[0]);
certificatePolicyMode.setOptions(certificatePolicyModesOptions);
ProviderConfigProperty identityConfirmationPageDisallowed = new ProviderConfigProperty();
identityConfirmationPageDisallowed.setType(BOOLEAN_TYPE);
identityConfirmationPageDisallowed.setName(CONFIRMATION_PAGE_DISALLOWED);
@ -229,7 +250,9 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
keyUsage,
extendedKeyUsage,
identityConfirmationPageDisallowed,
revalidateCertificateEnabled);
revalidateCertificateEnabled,
certificatePolicy,
certificatePolicyMode);
}
@Override

View file

@ -76,6 +76,12 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.CertificatePolicies;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE_ANY;
/**
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
@ -391,6 +397,8 @@ public class CertificateValidator {
X509Certificate[] _certChain;
int _keyUsageBits;
List<String> _extendedKeyUsage;
List<String> _certificatePolicy;
String _certificatePolicyMode;
boolean _crlCheckingEnabled;
boolean _crldpEnabled;
CRLLoaderImpl _crlLoader;
@ -404,6 +412,7 @@ public class CertificateValidator {
}
protected CertificateValidator(X509Certificate[] certChain,
int keyUsageBits, List<String> extendedKeyUsage,
List<String> certificatePolicy, String certificatePolicyMode,
boolean cRLCheckingEnabled,
boolean cRLDPCheckingEnabled,
CRLLoaderImpl crlLoader,
@ -415,6 +424,8 @@ public class CertificateValidator {
_certChain = certChain;
_keyUsageBits = keyUsageBits;
_extendedKeyUsage = extendedKeyUsage;
_certificatePolicy = certificatePolicy;
_certificatePolicyMode = certificatePolicyMode;
_crlCheckingEnabled = cRLCheckingEnabled;
_crldpEnabled = cRLDPCheckingEnabled;
_crlLoader = crlLoader;
@ -495,6 +506,46 @@ public class CertificateValidator {
}
}
private static void validatePolicy(X509Certificate[] certs, List<String> expectedPolicies, String policyCheckMode) throws GeneralSecurityException {
if (expectedPolicies == null || expectedPolicies.size() == 0) {
logger.debug("Certificate Policy validation is not enabled.");
return;
}
Extensions certExtensions = new JcaX509CertificateHolder(certs[0]).getExtensions();
if (certExtensions == null)
throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate extensions were found");
CertificatePolicies policies = CertificatePolicies.fromExtensions(certExtensions);
if (policies == null)
throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate policy extensions were found");
List<String> policyList = new LinkedList<>();
Arrays.stream(policies.getPolicyInformation()).forEach(p -> policyList.add(p.getPolicyIdentifier().toString().toLowerCase()));
logger.debugf("Certificate policies found: %s", String.join(",", policyList));
if (policyCheckMode == CERTIFICATE_POLICY_MODE_ANY)
{
boolean hasMatch = expectedPolicies.stream().anyMatch(p -> policyList.contains(p.toLowerCase()));
if (!hasMatch) {
String message = String.format("Certificate Policy check failed: mode = ANY, found = \'%s\', expected = \'%s\'.",
String.join(",", policyList), String.join(",", expectedPolicies));
throw new GeneralSecurityException(message);
}
}
else
{
for (String policy : expectedPolicies) {
if (!policyList.contains(policy.toLowerCase())) {
String message = String.format("Certificate Policy check failed: mode = ALL, certificate policy \'%s\' is missing.", policy);
throw new GeneralSecurityException(message);
}
}
}
}
public CertificateValidator validateKeyUsage() throws GeneralSecurityException {
validateKeyUsage(_certChain, _keyUsageBits);
return this;
@ -505,6 +556,11 @@ public class CertificateValidator {
return this;
}
public CertificateValidator validatePolicy() throws GeneralSecurityException {
validatePolicy(_certChain, _certificatePolicy, _certificatePolicyMode);
return this;
}
public CertificateValidator validateTimestamps() throws GeneralSecurityException {
if (!_timestampValidationEnabled)
return this;
@ -730,6 +786,8 @@ public class CertificateValidator {
KeycloakSession session;
int _keyUsageBits;
List<String> _extendedKeyUsage;
List<String> _certificatePolicy;
String _certificatePolicyMode;
boolean _crlCheckingEnabled;
boolean _crldpEnabled;
CRLLoaderImpl _crlLoader;
@ -741,6 +799,7 @@ public class CertificateValidator {
public CertificateValidatorBuilder() {
_extendedKeyUsage = new LinkedList<>();
_certificatePolicy = new LinkedList<>();
_keyUsageBits = 0;
}
@ -844,6 +903,30 @@ public class CertificateValidator {
}
}
public class CertificatePolicyValidationBuilder {
CertificateValidatorBuilder _parent;
protected CertificatePolicyValidationBuilder(CertificateValidatorBuilder parent) {
_parent = parent;
}
public CertificatePolicyValidationBuilder mode(String mode) {
_certificatePolicyMode = mode;
return this;
}
public CertificateValidatorBuilder parse(String certificatePolicy) {
if (certificatePolicy == null || certificatePolicy.trim().length() == 0)
return _parent;
String[] strs = certificatePolicy.split("[,;:]");
for (String str : strs) {
_certificatePolicy.add(str.trim());
}
return _parent;
}
}
public class RevocationStatusCheckBuilder {
CertificateValidatorBuilder _parent;
@ -945,6 +1028,10 @@ public class CertificateValidator {
return new ExtendedKeyUsageValidationBuilder(this);
}
public CertificatePolicyValidationBuilder certificatePolicy() {
return new CertificatePolicyValidationBuilder(this);
}
public RevocationStatusCheckBuilder revocation() {
return new RevocationStatusCheckBuilder(this);
}
@ -962,6 +1049,7 @@ public class CertificateValidator {
_crlLoader = new CRLFileLoader(session, "");
}
return new CertificateValidator(certs, _keyUsageBits, _extendedKeyUsage,
_certificatePolicy, _certificatePolicyMode,
_crlCheckingEnabled, _crldpEnabled, _crlLoader, _ocspEnabled,
new BouncyCastleOCSPChecker(session, _responderUri, _responderCert), session, _timestampValidationEnabled, _trustValidationEnabled);
}

View file

@ -77,7 +77,8 @@ public class ValidateX509CertificateUsername extends AbstractX509ClientCertifica
.validateTrust()
.validateKeyUsage()
.validateExtendedKeyUsage()
.validateTimestamps();
.validateTimestamps()
.validatePolicy();
} catch(Exception e) {
logger.error(e.getMessage(), e);
// TODO use specific locale to load error messages

View file

@ -82,6 +82,27 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel {
}
}
public enum CertificatePolicyModeType {
ALL(CERTIFICATE_POLICY_MODE_ALL),
ANY(CERTIFICATE_POLICY_MODE_ANY);
private String mode;
CertificatePolicyModeType(String mode) {
this.mode = mode;
}
public String getMode() { return this.mode; }
public static CertificatePolicyModeType parse(String mode) throws IllegalArgumentException, IndexOutOfBoundsException {
if (mode == null || mode.trim().length() == 0)
throw new IllegalArgumentException("mode");
for (CertificatePolicyModeType value : CertificatePolicyModeType.values()) {
if (value.getMode().equalsIgnoreCase(mode))
return value;
}
throw new IndexOutOfBoundsException("mode");
}
}
public X509AuthenticatorConfigModel(AuthenticatorConfigModel model) {
this.setAlias(model.getAlias());
this.setId(model.getId());
@ -227,6 +248,28 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel {
return this;
}
public String getCertificatePolicy() {
return getConfig().getOrDefault(CERTIFICATE_POLICY, null);
}
public X509AuthenticatorConfigModel setCertificatePolicy(String value) {
if (value != null) {
getConfig().put(CERTIFICATE_POLICY, value);
} else {
getConfig().remove(CERTIFICATE_POLICY);
}
return this;
}
public CertificatePolicyModeType getCertificatePolicyMode() {
return CertificatePolicyModeType.parse(getConfig().getOrDefault(CERTIFICATE_POLICY_MODE, CERTIFICATE_POLICY_MODE_ALL));
}
public X509AuthenticatorConfigModel setCertificatePolicyMode(CertificatePolicyModeType value) {
getConfig().put(CERTIFICATE_POLICY_MODE, value.getMode());
return this;
}
public boolean getConfirmationPageDisallowed() {
return Boolean.parseBoolean(getConfig().get(CONFIRMATION_PAGE_DISALLOWED));
}

View file

@ -88,6 +88,7 @@ public class X509ClientCertificateAuthenticator extends AbstractX509ClientCertif
.validateTrust()
.validateKeyUsage()
.validateExtendedKeyUsage()
.validatePolicy()
.validateTimestamps();
} catch(Exception e) {
logger.error(e.getMessage(), e);

View file

@ -1,8 +1,13 @@
package org.keycloak.authentication.authenticators.x509;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.CertificatePolicies;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@ -14,6 +19,7 @@ import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
@ -22,6 +28,11 @@ import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE_ALL;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE_ANY;
/**
* author Pascal Knueppel <br>
@ -43,7 +54,7 @@ public class CertificateValidatorTest {
KeyPair keyPair = kpg.generateKeyPair();
X509Certificate certificate =
createCertificate("CN=keycloak-test", new Date(),
new Date(System.currentTimeMillis() + 1000L * 60), keyPair);
new Date(System.currentTimeMillis() + 1000L * 60), keyPair, null);
CertificateValidator.CertificateValidatorBuilder builder =
new CertificateValidator.CertificateValidatorBuilder();
@ -69,7 +80,7 @@ public class CertificateValidatorTest {
KeyPair keyPair = kpg.generateKeyPair();
X509Certificate certificate =
createCertificate("CN=keycloak-test", new Date(System.currentTimeMillis() + 1000L * 60),
new Date(System.currentTimeMillis() + 1000L * 60), keyPair);
new Date(System.currentTimeMillis() + 1000L * 60), keyPair, null);
CertificateValidator.CertificateValidatorBuilder builder =
new CertificateValidator.CertificateValidatorBuilder();
@ -96,7 +107,7 @@ public class CertificateValidatorTest {
KeyPair keyPair = kpg.generateKeyPair();
X509Certificate certificate =
createCertificate("CN=keycloak-test", new Date(System.currentTimeMillis() - 1000L * 60 * 2),
new Date(System.currentTimeMillis() - 1000L * 60), keyPair);
new Date(System.currentTimeMillis() - 1000L * 60), keyPair, null);
CertificateValidator.CertificateValidatorBuilder builder =
new CertificateValidator.CertificateValidatorBuilder();
@ -113,6 +124,266 @@ public class CertificateValidatorTest {
}
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ALL, no policies are requested and the cert does not contain any policy
*/
@Test
public void testCertificatePolicyModeAllNotRequestedAndNotPresent() throws GeneralSecurityException {
testCertificatePolicyValidation(null, CERTIFICATE_POLICY_MODE_ALL);
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ALL, no policies are requested and the cert does contains a policy
*/
@Test
public void testCertificatePolicyModeAllNotRequestedAndOnePresent() throws GeneralSecurityException {
testCertificatePolicyValidation(null, CERTIFICATE_POLICY_MODE_ALL, "1.3.76.16.2.1");
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ALL, no policies are requested and the cert contains two policies
*/
@Test
public void testCertificatePolicyModeAllNotRequestedAndTwoPresent() throws GeneralSecurityException {
testCertificatePolicyValidation(null, CERTIFICATE_POLICY_MODE_ALL, "1.3.76.16.2.1", "1.2.3.4.5.6");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ALL, one policy is requested and the cert does not contain any policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAllOneRequestedAndNotPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ALL, null);
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ALL, one policy is requested and the cert contains that policy
*/
@Test
public void testCertificatePolicyModeAllOneRequestedAndOnePresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ALL, "1.3.76.16.2.1");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ALL, one policy is requested and the cert contains a different policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAllOneRequestedAndOnePresentDifferent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ALL, "1.2.3.4.5.6");
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ALL, one policy is requested and the cert contains that policy and one more
*/
@Test
public void testCertificatePolicyModeAllOneRequestedAndTwoPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ALL, "1.3.76.16.2.1", "1.2.3.4.5.6");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ALL, one policy is requested and the cert contains a different policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAllOneRequestedAndTwoPresentDifferent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ALL, "1.2.3.4.5", "1.2.3.4.5.6");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ALL, two policies are requested and the cert does not contain any policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAllTwoRequestedAndNotPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1,1.2.3.4.5.6", CERTIFICATE_POLICY_MODE_ALL);
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ALL, two policies are requested and the cert contains one different policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAllTwoRequestedAndOnePresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1,1.2.3.4.5.6", CERTIFICATE_POLICY_MODE_ALL, "1.3.76.16.2.1");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ALL, two policies are requested and the cert contains one different policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAllTwoRequestedAndOnePresentDifferent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1,1.2.3.4.5.6", CERTIFICATE_POLICY_MODE_ALL, "1.2.3.4");
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ALL, two policies are requested and the cert contains those two policies
*/
@Test
public void testCertificatePolicyModeAllTwoRequestedAndTwoPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1,1.2.3.4.5.6", CERTIFICATE_POLICY_MODE_ALL, "1.3.76.16.2.1", "1.2.3.4.5.6");
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ANY, no policies are requested and the cert does not contain any policy
*/
@Test
public void testCertificatePolicyModeAnyNotRequestedAndNotPresent() throws GeneralSecurityException {
testCertificatePolicyValidation(null, CERTIFICATE_POLICY_MODE_ANY);
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ANY, no policies are requested and the cert does contains a policy
*/
@Test
public void testCertificatePolicyModeAnyNotRequestedAndOnePresent() throws GeneralSecurityException {
testCertificatePolicyValidation(null, CERTIFICATE_POLICY_MODE_ANY, "1.3.76.16.2.1");
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ANY, no policies are requested and the cert contains two policies
*/
@Test
public void testCertificatePolicyModeAnyNotRequestedAndTwoPresent() throws GeneralSecurityException {
testCertificatePolicyValidation(null, CERTIFICATE_POLICY_MODE_ANY, "1.3.76.16.2.1", "1.2.3.4.5.6");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ANY, one policy is requested and the cert does not contain any policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAnyOneRequestedAndNotPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ANY, null);
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ANY, one policy is requested and the cert contains that policy
*/
@Test
public void testCertificatePolicyModeAnyOneRequestedAndOnePresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ANY, "1.3.76.16.2.1");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ANY, one policy is requested and the cert contains a different policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAnyOneRequestedAndOnePresentDifferent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ANY, "1.2.3.4.5.6");
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ANY, one policy is requested and the cert contains that policy and one more
*/
@Test
public void testCertificatePolicyModeAnyOneRequestedAndTwoPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ANY, "1.3.76.16.2.1", "1.2.3.4.5.6");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ANY, one policy is requested and the cert contains a different policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAnyOneRequestedAndTwoPresentDifferent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1", CERTIFICATE_POLICY_MODE_ANY, "1.2.3.4.5", "1.2.3.4.5.6");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ANY, two policies are requested and the cert does not contain any policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAnyTwoRequestedAndNotPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1,1.2.3.4.5.6", CERTIFICATE_POLICY_MODE_ANY);
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ANY, two policies are requested and the cert contains one policy
*/
@Test
public void testCertificatePolicyModeAnyTwoRequestedAndOnePresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1,1.2.3.4.5.6", CERTIFICATE_POLICY_MODE_ANY, "1.3.76.16.2.1");
}
/**
* will validate that the certificate policy validation WILL throw exceptions
* if mode=ANY, two policies are requested and the cert contains one different policy
*/
@Test(expected = GeneralSecurityException.class)
public void testCertificatePolicyModeAnyTwoRequestedAndOnePresentDifferent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1,1.2.3.4.5.6", CERTIFICATE_POLICY_MODE_ANY, "1.2.3.4");
}
/**
* will validate that the certificate policy validation won't throw exceptions
* if mode=ANY, two policies are requested and the cert contains those two policies
*/
@Test
public void testCertificatePolicyModeAnyTwoRequestedAndTwoPresent() throws GeneralSecurityException {
testCertificatePolicyValidation("1.3.76.16.2.1,1.2.3.4.5.6", CERTIFICATE_POLICY_MODE_ANY, "1.3.76.16.2.1", "1.2.3.4.5.6");
}
// Helper to test various certificate policy validation combinations
private void testCertificatePolicyValidation(String expectedPolicy, String mode, String... certificatePolicyOid)
throws GeneralSecurityException
{
List<Extension> certificatePolicies = null;
if (certificatePolicyOid != null && certificatePolicyOid.length > 0)
{
certificatePolicies = new LinkedList<>();
List<PolicyInformation> policyInfoList = new LinkedList<>();
for (String oid: certificatePolicyOid)
{
policyInfoList.add(new PolicyInformation(new ASN1ObjectIdentifier(oid)));
}
CertificatePolicies policies = new CertificatePolicies(policyInfoList.toArray(new PolicyInformation[0]));
try {
boolean isCritical = false;
Extension extension = new Extension(Extension.certificatePolicies, isCritical, policies.getEncoded());
certificatePolicies.add(extension);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(512);
KeyPair keyPair = kpg.generateKeyPair();
X509Certificate certificate =
createCertificate("CN=keycloak-test", new Date(System.currentTimeMillis() - 1000L * 60 * 2),
new Date(System.currentTimeMillis() - 1000L * 60), keyPair, certificatePolicies);
CertificateValidator.CertificateValidatorBuilder builder =
new CertificateValidator.CertificateValidatorBuilder();
CertificateValidator validator = builder
.certificatePolicy()
.mode(mode)
.parse(expectedPolicy)
.build(new X509Certificate[] { certificate });
validator.validatePolicy();
}
/**
* will create a self-signed certificate
@ -121,36 +392,50 @@ public class CertificateValidatorTest {
* @param startDate startdate of the validity of the created certificate
* @param expiryDate expiration date of the created certificate
* @param keyPair the keypair that is used to create the certificate
* @param extensions optional list of extensions to include in the certificate
* @return a X509-Certificate in version 3
*/
public X509Certificate createCertificate(String dn,
Date startDate,
Date expiryDate,
KeyPair keyPair) {
KeyPair keyPair,
List<Extension> extensions) {
// Cert data
X500Name subjectDN = new X500Name(dn);
X500Name issuerDN = new X500Name(dn);
// @formatter:off
SubjectPublicKeyInfo subjPubKeyInfo = SubjectPublicKeyInfo.getInstance(
ASN1Sequence.getInstance(keyPair.getPublic().getEncoded()));
// @formatter:on
SubjectPublicKeyInfo subjPubKeyInfo = SubjectPublicKeyInfo.getInstance(
ASN1Sequence.getInstance(keyPair.getPublic().getEncoded()));
BigInteger serialNumber = new BigInteger(130, new SecureRandom());
// Build the certificate
X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(issuerDN, serialNumber, startDate, expiryDate,
subjectDN, subjPubKeyInfo);
ContentSigner contentSigner = null;
if (extensions != null)
{
try
{
for (Extension certExtension: extensions)
certGen.addExtension(certExtension);
} catch (CertIOException e) {
throw new IllegalStateException(e);
}
}
// Sign the cert with the private key
try {
// @formatter:off
contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider(BOUNCY_CASTLE_PROVIDER)
.build(keyPair.getPrivate());
X509Certificate x509Certificate = new JcaX509CertificateConverter()
.setProvider(BOUNCY_CASTLE_PROVIDER)
.getCertificate(certGen.build(contentSigner));
// @formatter:on
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider(BOUNCY_CASTLE_PROVIDER)
.build(keyPair.getPrivate());
X509Certificate x509Certificate = new JcaX509CertificateConverter()
.setProvider(BOUNCY_CASTLE_PROVIDER)
.getCertificate(certGen.build(contentSigner));
return x509Certificate;
} catch (CertificateException | OperatorCreationException e) {
throw new IllegalStateException(e);
}
}
}

View file

@ -20,4 +20,24 @@ public class X509AuthenticatorConfigModelTest {
Assert.assertNull(configModel.getConfig().get(AbstractX509ClientCertificateAuthenticator.TIMESTAMP_VALIDATION));
Assert.assertFalse(configModel.isCertValidationEnabled());
}
/**
* this test will verify that no exception occurs if no settings are stored for the certificate policy validation
*/
@Test
public void testCertificatePolicyValidationAttributeReturnsNull() {
X509AuthenticatorConfigModel configModel = new X509AuthenticatorConfigModel();
Assert.assertNull(configModel.getConfig().get(AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY));
Assert.assertNull(configModel.getCertificatePolicy());
}
/**
* this test will verify that no exception occurs and ALL will be returned if no settings are stored for the certificate policy mode setting
*/
@Test
public void testCertificatePolicyModeValidationAttributeReturnsAll() {
X509AuthenticatorConfigModel configModel = new X509AuthenticatorConfigModel();
Assert.assertNull(configModel.getConfig().get(AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE));
Assert.assertEquals(AbstractX509ClientCertificateAuthenticator.CERTIFICATE_POLICY_MODE_ALL, configModel.getCertificatePolicyMode().getMode());
}
}