KEYCLOAK-16520 X509 Auth: Add option to verify certificate policy
This commit is contained in:
parent
da8861686a
commit
9838a47662
8 changed files with 488 additions and 20 deletions
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ public class X509ClientCertificateAuthenticator extends AbstractX509ClientCertif
|
|||
.validateTrust()
|
||||
.validateKeyUsage()
|
||||
.validateExtendedKeyUsage()
|
||||
.validatePolicy()
|
||||
.validateTimestamps();
|
||||
} catch(Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue