KEYCLOAK-10785 X.509 Authenticator - Update user identity source mappers
Update user identity sources and the way how X.509 certificates are mapped to the user to: 1. Include "Serial number + Issuer DN" as described in RFC 5280 2. Include "Certificate's SHA256-Thumbprint" 3. Exclude "Issuer DN" 4. Exclude "Issuer Email" Add an option to represent serial number in hexadecimal format. Documentation PR created: https://github.com/keycloak/keycloak-documentation/pull/714 KEYCLOAK-10785 - Documentation for new user identity source mappers
This commit is contained in:
parent
75d2ec8ff6
commit
411ea331f6
7 changed files with 134 additions and 40 deletions
|
@ -29,16 +29,22 @@ import javax.ws.rs.core.Response;
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.forms.login.LoginFormsProvider;
|
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.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.x509.X509ClientCertificateLookup;
|
import org.keycloak.services.x509.X509ClientCertificateLookup;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
|
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
|
||||||
* @version $Revision: 1 $
|
* @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_OCSP = "x509-cert-auth.ocsp-checking-enabled";
|
||||||
public static final String ENABLE_CRLDP = "x509-cert-auth.crldp-checking-enabled";
|
public static final String ENABLE_CRLDP = "x509-cert-auth.crldp-checking-enabled";
|
||||||
public static final String CANONICAL_DN = "x509-cert-auth.canonical-dn-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 CRL_RELATIVE_PATH = "x509-cert-auth.crl-relative-path";
|
||||||
public static final String OCSPRESPONDER_URI = "x509-cert-auth.ocsp-responder-uri";
|
public static final String OCSPRESPONDER_URI = "x509-cert-auth.ocsp-responder-uri";
|
||||||
public static final String OCSPRESPONDER_CERTIFICATE = "x509-cert-auth.ocsp-responder-certificate";
|
public static final String 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_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_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 = "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_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 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_MAPPER_SELECTION = "x509-cert-auth.mapper-selection";
|
||||||
public static final String USER_ATTRIBUTE_MAPPER = "Custom Attribute Mapper";
|
public static final String USER_ATTRIBUTE_MAPPER = "Custom Attribute Mapper";
|
||||||
|
@ -130,6 +137,18 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static final Function<X509Certificate[], String> getSerialnumberFunc(X509AuthenticatorConfigModel config) {
|
||||||
|
return config.isSerialnumberHex() ?
|
||||||
|
certs -> Hex.toHexString(certs[0].getSerialNumber().toByteArray()) :
|
||||||
|
certs -> certs[0].getSerialNumber().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Function<X509Certificate[], String> getIssuerDNFunc(X509AuthenticatorConfigModel config) {
|
||||||
|
return config.isCanonicalDnEnabled() ?
|
||||||
|
certs -> certs[0].getIssuerX500Principal().getName(X500Principal.CANONICAL) :
|
||||||
|
certs -> certs[0].getIssuerDN().getName();
|
||||||
|
}
|
||||||
|
|
||||||
static UserIdentityExtractor fromConfig(X509AuthenticatorConfigModel config) {
|
static UserIdentityExtractor fromConfig(X509AuthenticatorConfigModel config) {
|
||||||
|
|
||||||
X509AuthenticatorConfigModel.MappingSourceType userIdentitySource = config.getMappingSourceType();
|
X509AuthenticatorConfigModel.MappingSourceType userIdentitySource = config.getMappingSourceType();
|
||||||
|
@ -146,13 +165,24 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
extractor = UserIdentityExtractor.getPatternIdentityExtractor(pattern, func);
|
extractor = UserIdentityExtractor.getPatternIdentityExtractor(pattern, func);
|
||||||
break;
|
break;
|
||||||
case ISSUERDN:
|
case ISSUERDN:
|
||||||
func = config.isCanonicalDnEnabled() ?
|
extractor = UserIdentityExtractor.getPatternIdentityExtractor(pattern, getIssuerDNFunc(config));
|
||||||
certs -> certs[0].getIssuerX500Principal().getName(X500Principal.CANONICAL) :
|
|
||||||
certs -> certs[0].getIssuerDN().getName();
|
|
||||||
extractor = UserIdentityExtractor.getPatternIdentityExtractor(pattern, func);
|
|
||||||
break;
|
break;
|
||||||
case SERIALNUMBER:
|
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;
|
break;
|
||||||
case SUBJECTDN_CN:
|
case SUBJECTDN_CN:
|
||||||
extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, subject);
|
extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, subject);
|
||||||
|
@ -168,14 +198,6 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
case SUBJECTALTNAME_OTHERNAME:
|
case SUBJECTALTNAME_OTHERNAME:
|
||||||
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(0);
|
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(0);
|
||||||
break;
|
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:
|
case CERTIFICATE_PEM:
|
||||||
extractor = UserIdentityExtractor.getCertificatePemIdentityExtractor(config);
|
extractor = UserIdentityExtractor.getCertificatePemIdentityExtractor(config);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -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_CRLDP;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.ENABLE_OCSP;
|
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;
|
||||||
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_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_EMAIL;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME;
|
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_SUBJECTALTNAME_OTHERNAME,
|
||||||
MAPPING_SOURCE_CERT_SUBJECTDN_CN,
|
MAPPING_SOURCE_CERT_SUBJECTDN_CN,
|
||||||
MAPPING_SOURCE_CERT_ISSUERDN,
|
MAPPING_SOURCE_CERT_ISSUERDN,
|
||||||
MAPPING_SOURCE_CERT_ISSUERDN_EMAIL,
|
|
||||||
MAPPING_SOURCE_CERT_ISSUERDN_CN,
|
|
||||||
MAPPING_SOURCE_CERT_SERIALNUMBER,
|
MAPPING_SOURCE_CERT_SERIALNUMBER,
|
||||||
|
MAPPING_SOURCE_CERT_SERIALNUMBER_ISSUERDN,
|
||||||
|
MAPPING_SOURCE_CERT_SHA256_THUMBPRINT,
|
||||||
MAPPING_SOURCE_CERT_CERTIFICATE_PEM
|
MAPPING_SOURCE_CERT_CERTIFICATE_PEM
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,6 +107,14 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
|
||||||
canonicalDn.setDefaultValue(false);
|
canonicalDn.setDefaultValue(false);
|
||||||
canonicalDn.setHelpText("Use the canonical format to determine the distinguished name. This option is relevant for authenticators using a distinguished name.");
|
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();
|
ProviderConfigProperty regExp = new ProviderConfigProperty();
|
||||||
regExp.setType(STRING_TYPE);
|
regExp.setType(STRING_TYPE);
|
||||||
regExp.setName(REGULAR_EXPRESSION);
|
regExp.setName(REGULAR_EXPRESSION);
|
||||||
|
@ -130,11 +136,12 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
|
||||||
userMapperList.setOptions(mapperTypes);
|
userMapperList.setOptions(mapperTypes);
|
||||||
|
|
||||||
ProviderConfigProperty attributeOrPropertyValue = new ProviderConfigProperty();
|
ProviderConfigProperty attributeOrPropertyValue = new ProviderConfigProperty();
|
||||||
attributeOrPropertyValue.setType(STRING_TYPE);
|
attributeOrPropertyValue.setType(MULTIVALUED_STRING_TYPE);
|
||||||
attributeOrPropertyValue.setName(CUSTOM_ATTRIBUTE_NAME);
|
attributeOrPropertyValue.setName(CUSTOM_ATTRIBUTE_NAME);
|
||||||
attributeOrPropertyValue.setDefaultValue(DEFAULT_ATTRIBUTE_NAME);
|
attributeOrPropertyValue.setDefaultValue(DEFAULT_ATTRIBUTE_NAME);
|
||||||
attributeOrPropertyValue.setLabel("A name of user attribute");
|
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();
|
ProviderConfigProperty crlCheckingEnabled = new ProviderConfigProperty();
|
||||||
crlCheckingEnabled.setType(BOOLEAN_TYPE);
|
crlCheckingEnabled.setType(BOOLEAN_TYPE);
|
||||||
|
@ -198,6 +205,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
|
||||||
|
|
||||||
configProperties = asList(mappingMethodList,
|
configProperties = asList(mappingMethodList,
|
||||||
canonicalDn,
|
canonicalDn,
|
||||||
|
serialnumberHex,
|
||||||
regExp,
|
regExp,
|
||||||
userMapperList,
|
userMapperList,
|
||||||
attributeOrPropertyValue,
|
attributeOrPropertyValue,
|
||||||
|
|
|
@ -37,6 +37,7 @@ import java.security.cert.X509Certificate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -222,7 +223,7 @@ public abstract class UserIdentityExtractor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object extractUserIdentity(X509Certificate[] certs) {
|
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);
|
Pattern r = Pattern.compile(_pattern, Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,12 @@
|
||||||
|
|
||||||
package org.keycloak.authentication.authenticators.x509;
|
package org.keycloak.authentication.authenticators.x509;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -45,16 +48,27 @@ public abstract class UserIdentityToModelMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static class UserIdentityToCustomAttributeMapper extends UserIdentityToModelMapper {
|
static class UserIdentityToCustomAttributeMapper extends UserIdentityToModelMapper {
|
||||||
|
private List<String> _customAttributes;
|
||||||
private String _customAttribute;
|
UserIdentityToCustomAttributeMapper(String customAttributes) {
|
||||||
UserIdentityToCustomAttributeMapper(String customAttribute) {
|
_customAttributes = Arrays.asList(Constants.CFG_DELIMITER_PATTERN.split(customAttributes));
|
||||||
_customAttribute = customAttribute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel find(AuthenticationFlowContext context, Object userIdentity) throws Exception {
|
public UserModel find(AuthenticationFlowContext context, Object userIdentity) throws Exception {
|
||||||
KeycloakSession session = context.getSession();
|
KeycloakSession session = context.getSession();
|
||||||
List<UserModel> users = session.users().searchForUserByUserAttribute(_customAttribute, userIdentity.toString(), context.getRealm());
|
List<String> userIdentityValues = Arrays.asList(Constants.CFG_DELIMITER_PATTERN.split(userIdentity.toString()));
|
||||||
|
|
||||||
|
if (_customAttributes.isEmpty() || userIdentityValues.isEmpty() || (_customAttributes.size() != userIdentityValues.size())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<UserModel> 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) {
|
if (users != null && users.size() > 1) {
|
||||||
throw new ModelDuplicateException();
|
throw new ModelDuplicateException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,14 +55,14 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel {
|
||||||
|
|
||||||
public enum MappingSourceType {
|
public enum MappingSourceType {
|
||||||
SERIALNUMBER(MAPPING_SOURCE_CERT_SERIALNUMBER),
|
SERIALNUMBER(MAPPING_SOURCE_CERT_SERIALNUMBER),
|
||||||
ISSUERDN_CN(MAPPING_SOURCE_CERT_ISSUERDN_CN),
|
|
||||||
ISSUERDN_EMAIL(MAPPING_SOURCE_CERT_ISSUERDN_EMAIL),
|
|
||||||
ISSUERDN(MAPPING_SOURCE_CERT_ISSUERDN),
|
ISSUERDN(MAPPING_SOURCE_CERT_ISSUERDN),
|
||||||
SUBJECTDN_CN(MAPPING_SOURCE_CERT_SUBJECTDN_CN),
|
SUBJECTDN_CN(MAPPING_SOURCE_CERT_SUBJECTDN_CN),
|
||||||
SUBJECTDN_EMAIL(MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL),
|
SUBJECTDN_EMAIL(MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL),
|
||||||
SUBJECTALTNAME_EMAIL(MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL),
|
SUBJECTALTNAME_EMAIL(MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL),
|
||||||
SUBJECTALTNAME_OTHERNAME(MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME),
|
SUBJECTALTNAME_OTHERNAME(MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME),
|
||||||
SUBJECTDN(MAPPING_SOURCE_CERT_SUBJECTDN),
|
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);
|
CERTIFICATE_PEM(MAPPING_SOURCE_CERT_CERTIFICATE_PEM);
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
@ -253,4 +253,13 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel {
|
||||||
getConfig().put(CANONICAL_DN, Boolean.toString(value));
|
getConfig().put(CANONICAL_DN, Boolean.toString(value));
|
||||||
return this;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.USERNAME_EMAIL;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USER_ATTRIBUTE;
|
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;
|
||||||
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_EMAIL;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTALTNAME_OTHERNAME;
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTALTNAME_OTHERNAME;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN;
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN;
|
||||||
|
|
|
@ -40,8 +40,8 @@ import static org.hamcrest.Matchers.startsWith;
|
||||||
import static org.junit.Assert.assertEquals;
|
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.USERNAME_EMAIL;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USER_ATTRIBUTE;
|
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.SERIALNUMBER_ISSUERDN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN_EMAIL;
|
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.SERIALNUMBER;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN;
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL;
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL;
|
||||||
|
@ -146,9 +146,34 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loginAsUserFromCertIssuerCNMappedToUserAttribute() {
|
public void loginAsUserFromCertSerialnumberAndIssuerDNMappedToUserAttribute() {
|
||||||
x509BrowserLogin(createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(ISSUERDN_CN, "x509_issuer_identity"),
|
UserRepresentation user = testRealm().users().get(userId2).toRepresentation();
|
||||||
userId2, "keycloak", "Keycloak Intermediate CA");
|
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
|
@Test
|
||||||
|
@ -167,18 +192,18 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loginAsUserFromCertIssuerEmailMappedToUserAttribute() {
|
public void loginAsUserFromCertSHA256MappedToUserAttribute() {
|
||||||
|
|
||||||
UserRepresentation user = testRealm().users().get(userId2).toRepresentation();
|
UserRepresentation user = testRealm().users().get(userId2).toRepresentation();
|
||||||
Assert.assertNotNull(user);
|
Assert.assertNotNull(user);
|
||||||
|
|
||||||
user.singleAttribute("x509_issuer_identity", "contact@keycloak.org");
|
user.singleAttribute("x509_cert_sha256thumbprint", "71237a14c118a90cc8406f14d039ed3431c9065f68e535293ee919d4c33b5e15");
|
||||||
this.updateUser(user);
|
this.updateUser(user);
|
||||||
|
|
||||||
events.clear();
|
events.clear();
|
||||||
|
|
||||||
x509BrowserLogin(createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(ISSUERDN_EMAIL, "x509_issuer_identity"),
|
x509BrowserLogin(createLoginWithSpecifiedSourceTypeToCustomAttributeConfig(SHA256_THUMBPRINT, "x509_cert_sha256thumbprint"),
|
||||||
userId2, "keycloak", "contact@keycloak.org");
|
userId2, "keycloak", "71237a14c118a90cc8406f14d039ed3431c9065f68e535293ee919d4c33b5e15");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -197,6 +222,22 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest {
|
||||||
userId2, "keycloak", "4105");
|
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
|
@Test
|
||||||
public void loginDuplicateUsersNotAllowed() {
|
public void loginDuplicateUsersNotAllowed() {
|
||||||
|
|
Loading…
Reference in a new issue