diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java index e8571d34e4..bbaa98b5b7 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java @@ -29,16 +29,22 @@ import javax.ws.rs.core.Response; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.util.encoders.Hex; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.Authenticator; import org.keycloak.events.Details; import org.keycloak.forms.login.LoginFormsProvider; +import org.keycloak.jose.jws.crypto.HashUtils; +import org.keycloak.crypto.HashException; +import org.keycloak.crypto.JavaAlgorithm; +import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.services.ServicesLogger; import org.keycloak.services.x509.X509ClientCertificateLookup; + /** * @author Peter Nalyvayko * @version $Revision: 1 $ @@ -55,6 +61,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth public static final String ENABLE_OCSP = "x509-cert-auth.ocsp-checking-enabled"; public static final String ENABLE_CRLDP = "x509-cert-auth.crldp-checking-enabled"; public static final String CANONICAL_DN = "x509-cert-auth.canonical-dn-enabled"; + public static final String SERIALNUMBER_HEX = "x509-cert-auth.serialnumber-hex-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"; @@ -65,9 +72,9 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth public static final String MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME = "Subject's Alternative Name otherName (UPN)"; public static final String MAPPING_SOURCE_CERT_SUBJECTDN_CN = "Subject's Common Name"; public static final String MAPPING_SOURCE_CERT_ISSUERDN = "Match IssuerDN using regular expression"; - public static final String MAPPING_SOURCE_CERT_ISSUERDN_EMAIL = "Issuer's e-mail"; - public static final String MAPPING_SOURCE_CERT_ISSUERDN_CN = "Issuer's Common Name"; public static final String MAPPING_SOURCE_CERT_SERIALNUMBER = "Certificate Serial Number"; + public static final String MAPPING_SOURCE_CERT_SHA256_THUMBPRINT = "SHA-256 Thumbprint"; + public static final String MAPPING_SOURCE_CERT_SERIALNUMBER_ISSUERDN = "Certificate Serial Number and IssuerDN"; public static final String MAPPING_SOURCE_CERT_CERTIFICATE_PEM = "Full Certificate in PEM format"; public static final String USER_MAPPER_SELECTION = "x509-cert-auth.mapper-selection"; public static final String USER_ATTRIBUTE_MAPPER = "Custom Attribute Mapper"; @@ -129,6 +136,18 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth } return null; }; + + private static final Function getSerialnumberFunc(X509AuthenticatorConfigModel config) { + return config.isSerialnumberHex() ? + certs -> Hex.toHexString(certs[0].getSerialNumber().toByteArray()) : + certs -> certs[0].getSerialNumber().toString(); + } + + private static final Function getIssuerDNFunc(X509AuthenticatorConfigModel config) { + return config.isCanonicalDnEnabled() ? + certs -> certs[0].getIssuerX500Principal().getName(X500Principal.CANONICAL) : + certs -> certs[0].getIssuerDN().getName(); + } static UserIdentityExtractor fromConfig(X509AuthenticatorConfigModel config) { @@ -146,13 +165,24 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth extractor = UserIdentityExtractor.getPatternIdentityExtractor(pattern, func); break; case ISSUERDN: - func = config.isCanonicalDnEnabled() ? - certs -> certs[0].getIssuerX500Principal().getName(X500Principal.CANONICAL) : - certs -> certs[0].getIssuerDN().getName(); - extractor = UserIdentityExtractor.getPatternIdentityExtractor(pattern, func); + extractor = UserIdentityExtractor.getPatternIdentityExtractor(pattern, getIssuerDNFunc(config)); break; case SERIALNUMBER: - extractor = UserIdentityExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, certs -> certs[0].getSerialNumber().toString()); + extractor = UserIdentityExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, getSerialnumberFunc(config)); + break; + case SHA256_THUMBPRINT: + extractor = UserIdentityExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, certs -> { + try { + return Hex.toHexString(HashUtils.hash(JavaAlgorithm.SHA256, certs[0].getEncoded())); + } catch (CertificateEncodingException | HashException e) { + logger.warn("Unable to get certificate's thumbprint", e); + } + return null; + }); + break; + case SERIALNUMBER_ISSUERDN: + func = certs -> getSerialnumberFunc(config).apply(certs) + Constants.CFG_DELIMITER + getIssuerDNFunc(config).apply(certs); + extractor = UserIdentityExtractor.getPatternIdentityExtractor(DEFAULT_MATCH_ALL_EXPRESSION, func); break; case SUBJECTDN_CN: extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, subject); @@ -168,14 +198,6 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth case SUBJECTALTNAME_OTHERNAME: extractor = UserIdentityExtractor.getSubjectAltNameExtractor(0); break; - case ISSUERDN_CN: - extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, issuer); - break; - case ISSUERDN_EMAIL: - extractor = UserIdentityExtractor - .either(UserIdentityExtractor.getX500NameExtractor(BCStyle.EmailAddress, issuer)) - .or(UserIdentityExtractor.getX500NameExtractor(BCStyle.E, issuer)); - break; case CERTIFICATE_PEM: extractor = UserIdentityExtractor.getCertificatePemIdentityExtractor(config); break; diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java index e729472765..e62d04b85a 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java @@ -40,8 +40,6 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.ENABLE_CRLDP; import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.ENABLE_OCSP; import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN; -import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_CN; -import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_EMAIL; import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SERIALNUMBER; import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL; import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME; @@ -77,9 +75,9 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME, MAPPING_SOURCE_CERT_SUBJECTDN_CN, MAPPING_SOURCE_CERT_ISSUERDN, - MAPPING_SOURCE_CERT_ISSUERDN_EMAIL, - MAPPING_SOURCE_CERT_ISSUERDN_CN, MAPPING_SOURCE_CERT_SERIALNUMBER, + MAPPING_SOURCE_CERT_SERIALNUMBER_ISSUERDN, + MAPPING_SOURCE_CERT_SHA256_THUMBPRINT, MAPPING_SOURCE_CERT_CERTIFICATE_PEM }; @@ -109,6 +107,14 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen canonicalDn.setDefaultValue(false); canonicalDn.setHelpText("Use the canonical format to determine the distinguished name. This option is relevant for authenticators using a distinguished name."); + ProviderConfigProperty serialnumberHex = new ProviderConfigProperty(); + serialnumberHex.setType(BOOLEAN_TYPE); + serialnumberHex.setName(SERIALNUMBER_HEX); + serialnumberHex.setLabel("Enable Serial Number hexadecimal representation"); + serialnumberHex.setDefaultValue(false); + serialnumberHex.setHelpText("Use the hex representation of the serial number. This option is relevant for authenticators using serial number."); + + ProviderConfigProperty regExp = new ProviderConfigProperty(); regExp.setType(STRING_TYPE); regExp.setName(REGULAR_EXPRESSION); @@ -130,11 +136,12 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen userMapperList.setOptions(mapperTypes); ProviderConfigProperty attributeOrPropertyValue = new ProviderConfigProperty(); - attributeOrPropertyValue.setType(STRING_TYPE); + attributeOrPropertyValue.setType(MULTIVALUED_STRING_TYPE); attributeOrPropertyValue.setName(CUSTOM_ATTRIBUTE_NAME); attributeOrPropertyValue.setDefaultValue(DEFAULT_ATTRIBUTE_NAME); attributeOrPropertyValue.setLabel("A name of user attribute"); - attributeOrPropertyValue.setHelpText("A name of user attribute to map the extracted user identity to existing user. The name must be a valid, existing user attribute if User Mapping Method is set to Custom Attribute Mapper."); + attributeOrPropertyValue.setHelpText("A name of user attribute to map the extracted user identity to existing user. The name must be a valid, existing user attribute if User Mapping Method is set to Custom Attribute Mapper. " + + "Multiple values are relevant when attribute mapping is related to multiple values, e.g. 'Certificate Serial Number and IssuerDN'"); ProviderConfigProperty crlCheckingEnabled = new ProviderConfigProperty(); crlCheckingEnabled.setType(BOOLEAN_TYPE); @@ -198,6 +205,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen configProperties = asList(mappingMethodList, canonicalDn, + serialnumberHex, regExp, userMapperList, attributeOrPropertyValue, diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityExtractor.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityExtractor.java index 0fb92e1a01..1362b6b5a4 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityExtractor.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityExtractor.java @@ -37,6 +37,7 @@ import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -222,7 +223,7 @@ public abstract class UserIdentityExtractor { @Override public Object extractUserIdentity(X509Certificate[] certs) { - String value = _f.apply(certs); + String value = Optional.ofNullable(_f.apply(certs)).orElseThrow(IllegalArgumentException::new); Pattern r = Pattern.compile(_pattern, Pattern.CASE_INSENSITIVE); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityToModelMapper.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityToModelMapper.java index e86e1c25da..6457090697 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityToModelMapper.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/UserIdentityToModelMapper.java @@ -18,9 +18,12 @@ package org.keycloak.authentication.authenticators.x509; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.UserModel; @@ -45,16 +48,27 @@ public abstract class UserIdentityToModelMapper { } static class UserIdentityToCustomAttributeMapper extends UserIdentityToModelMapper { - - private String _customAttribute; - UserIdentityToCustomAttributeMapper(String customAttribute) { - _customAttribute = customAttribute; + private List _customAttributes; + UserIdentityToCustomAttributeMapper(String customAttributes) { + _customAttributes = Arrays.asList(Constants.CFG_DELIMITER_PATTERN.split(customAttributes)); } @Override public UserModel find(AuthenticationFlowContext context, Object userIdentity) throws Exception { KeycloakSession session = context.getSession(); - List users = session.users().searchForUserByUserAttribute(_customAttribute, userIdentity.toString(), context.getRealm()); + List userIdentityValues = Arrays.asList(Constants.CFG_DELIMITER_PATTERN.split(userIdentity.toString())); + + if (_customAttributes.isEmpty() || userIdentityValues.isEmpty() || (_customAttributes.size() != userIdentityValues.size())) { + return null; + } + List users = session.users().searchForUserByUserAttribute(_customAttributes.get(0), userIdentityValues.get(0), context.getRealm()); + + for (int i = 1; i <_customAttributes.size(); ++i) { + String customAttribute = _customAttributes.get(i); + String userIdentityValue = userIdentityValues.get(i); + + users = users.stream().filter(user -> user.getFirstAttribute(customAttribute).equals(userIdentityValue)).collect(Collectors.toList()); + } if (users != null && users.size() > 1) { throw new ModelDuplicateException(); } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java index 80e7dd6ddf..5826d634fc 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java @@ -55,16 +55,16 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel { public enum MappingSourceType { SERIALNUMBER(MAPPING_SOURCE_CERT_SERIALNUMBER), - ISSUERDN_CN(MAPPING_SOURCE_CERT_ISSUERDN_CN), - ISSUERDN_EMAIL(MAPPING_SOURCE_CERT_ISSUERDN_EMAIL), ISSUERDN(MAPPING_SOURCE_CERT_ISSUERDN), SUBJECTDN_CN(MAPPING_SOURCE_CERT_SUBJECTDN_CN), SUBJECTDN_EMAIL(MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL), SUBJECTALTNAME_EMAIL(MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL), SUBJECTALTNAME_OTHERNAME(MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME), SUBJECTDN(MAPPING_SOURCE_CERT_SUBJECTDN), + SHA256_THUMBPRINT(MAPPING_SOURCE_CERT_SHA256_THUMBPRINT), + SERIALNUMBER_ISSUERDN(MAPPING_SOURCE_CERT_SERIALNUMBER_ISSUERDN), CERTIFICATE_PEM(MAPPING_SOURCE_CERT_CERTIFICATE_PEM); - + private String name; MappingSourceType(String name) { this.name = name; @@ -253,4 +253,13 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel { getConfig().put(CANONICAL_DN, Boolean.toString(value)); return this; } + + public boolean isSerialnumberHex() { + return Boolean.parseBoolean(getConfig().get(SERIALNUMBER_HEX)); + } + + public X509AuthenticatorConfigModel setSerialnumberHex(boolean value) { + getConfig().put(SERIALNUMBER_HEX, Boolean.toString(value)); + return this; + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java index 5ff3ff454f..d0e98a67ce 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java @@ -76,7 +76,6 @@ import java.util.Map; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USERNAME_EMAIL; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USER_ATTRIBUTE; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN; -import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN_CN; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTALTNAME_EMAIL; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTALTNAME_OTHERNAME; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java index cee4569384..111ef7e5e3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java @@ -40,8 +40,8 @@ import static org.hamcrest.Matchers.startsWith; 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.IdentityMapperType.USER_ATTRIBUTE; -import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN_CN; -import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN_EMAIL; +import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SERIALNUMBER_ISSUERDN; +import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SHA256_THUMBPRINT; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SERIALNUMBER; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL; @@ -146,9 +146,34 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest { } @Test - public void loginAsUserFromCertIssuerCNMappedToUserAttribute() { - x509BrowserLogin(createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(ISSUERDN_CN, "x509_issuer_identity"), - userId2, "keycloak", "Keycloak Intermediate CA"); + public void loginAsUserFromCertSerialnumberAndIssuerDNMappedToUserAttribute() { + UserRepresentation user = testRealm().users().get(userId2).toRepresentation(); + Assert.assertNotNull(user); + + user.singleAttribute("x509_certificate_serialnumber", "4105"); + user.singleAttribute("x509_issuer_dn", "EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US"); + this.updateUser(user); + + events.clear(); + + x509BrowserLogin(createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(SERIALNUMBER_ISSUERDN, "x509_certificate_serialnumber##x509_issuer_dn"), + userId2, "keycloak", "4105##EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US"); + } + + @Test + public void loginAsUserFromHexCertSerialnumberAndIssuerDNMappedToUserAttribute() { + UserRepresentation user = testRealm().users().get(userId2).toRepresentation(); + Assert.assertNotNull(user); + + user.singleAttribute("x509_certificate_serialnumber", "1009"); + user.singleAttribute("x509_issuer_dn", "EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US"); + this.updateUser(user); + + events.clear(); + + X509AuthenticatorConfigModel config = createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(SERIALNUMBER_ISSUERDN, "x509_certificate_serialnumber##x509_issuer_dn"); + config.setSerialnumberHex(true); + x509BrowserLogin(config, userId2, "keycloak", "1009##EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US"); } @Test @@ -167,18 +192,18 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest { @Test - public void loginAsUserFromCertIssuerEmailMappedToUserAttribute() { + public void loginAsUserFromCertSHA256MappedToUserAttribute() { UserRepresentation user = testRealm().users().get(userId2).toRepresentation(); Assert.assertNotNull(user); - user.singleAttribute("x509_issuer_identity", "contact@keycloak.org"); + user.singleAttribute("x509_cert_sha256thumbprint", "71237a14c118a90cc8406f14d039ed3431c9065f68e535293ee919d4c33b5e15"); this.updateUser(user); events.clear(); - x509BrowserLogin(createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(ISSUERDN_EMAIL, "x509_issuer_identity"), - userId2, "keycloak", "contact@keycloak.org"); + x509BrowserLogin(createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(SHA256_THUMBPRINT, "x509_cert_sha256thumbprint"), + userId2, "keycloak", "71237a14c118a90cc8406f14d039ed3431c9065f68e535293ee919d4c33b5e15"); } @@ -196,6 +221,22 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest { x509BrowserLogin(createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(SERIALNUMBER, "x509_serial_number"), userId2, "keycloak", "4105"); } + + @Test + public void loginAsUserFromHexCertSerialNumberMappedToUserAttribute() { + + UserRepresentation user = testRealm().users().get(userId2).toRepresentation(); + Assert.assertNotNull(user); + + user.singleAttribute("x509_serial_number", "1009"); + this.updateUser(user); + + events.clear(); + + X509AuthenticatorConfigModel config = createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(SERIALNUMBER, "x509_serial_number"); + config.setSerialnumberHex(true); + x509BrowserLogin(config, userId2, "keycloak", "1009"); + } @Test