KEYCLOAK-6056 Map user by Subject Alternative Name (otherName) when authenticating user with X509
This commit is contained in:
parent
cf35a4648b
commit
a48698caa3
18 changed files with 374 additions and 92 deletions
|
@ -59,6 +59,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
|||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
|
||||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
|
||||
public static final String MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL = "Subject's Alternative Name E-mail";
|
||||
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";
|
||||
|
@ -152,6 +153,9 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
|||
case SUBJECTALTNAME_EMAIL:
|
||||
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(1);
|
||||
break;
|
||||
case SUBJECTALTNAME_OTHERNAME:
|
||||
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(0);
|
||||
break;
|
||||
case ISSUERDN_CN:
|
||||
extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, issuer);
|
||||
break;
|
||||
|
|
|
@ -44,6 +44,7 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client
|
|||
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;
|
||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN;
|
||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_CN;
|
||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL;
|
||||
|
@ -72,6 +73,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
|
|||
MAPPING_SOURCE_CERT_SUBJECTDN,
|
||||
MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL,
|
||||
MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL,
|
||||
MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME,
|
||||
MAPPING_SOURCE_CERT_SUBJECTDN_CN,
|
||||
MAPPING_SOURCE_CERT_ISSUERDN,
|
||||
MAPPING_SOURCE_CERT_ISSUERDN_EMAIL,
|
||||
|
|
|
@ -19,12 +19,18 @@
|
|||
package org.keycloak.authentication.authenticators.x509;
|
||||
|
||||
import freemarker.template.utility.NullArgumentException;
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.ASN1TaggedObject;
|
||||
import org.bouncycastle.asn1.DERUTF8String;
|
||||
import org.bouncycastle.asn1.x500.RDN;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
|
@ -101,6 +107,9 @@ public abstract class UserIdentityExtractor {
|
|||
*/
|
||||
static class SubjectAltNameExtractor extends UserIdentityExtractor {
|
||||
|
||||
// User Principal Name. Used typically by Microsoft in certificates for Smart Card Login
|
||||
private static final String UPN_OID = "1.3.6.1.4.1.311.20.2.3";
|
||||
|
||||
private final int generalName;
|
||||
|
||||
/**
|
||||
|
@ -127,19 +136,79 @@ public abstract class UserIdentityExtractor {
|
|||
|
||||
Iterator<List<?>> iterator = subjectAlternativeNames.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
boolean foundUpn = false;
|
||||
String tempOtherName = null;
|
||||
String tempOid = null;
|
||||
|
||||
while (iterator.hasNext() && !foundUpn) {
|
||||
List<?> next = iterator.next();
|
||||
|
||||
if (Integer.class.cast(next.get(0)) == generalName) {
|
||||
return next.get(1);
|
||||
|
||||
// We will try to find UPN_OID among the subjectAltNames of type 'otherName' . Just if not found, we will fallback to the other type
|
||||
for (int i = 1 ; i<next.size() ; i++) {
|
||||
Object obj = next.get(i);
|
||||
|
||||
// We have Subject Alternative Name of other type than 'otherName' . Just return it directly
|
||||
if (generalName != 0) {
|
||||
logger.tracef("Extracted identity '%s' from Subject Alternative Name of type '%d'", obj, generalName);
|
||||
return obj;
|
||||
}
|
||||
|
||||
byte[] otherNameBytes = (byte[]) obj;
|
||||
|
||||
try {
|
||||
ASN1InputStream asn1Stream = new ASN1InputStream(new ByteArrayInputStream(otherNameBytes));
|
||||
ASN1Encodable asn1otherName = asn1Stream.readObject();
|
||||
asn1otherName = unwrap(asn1otherName);
|
||||
|
||||
ASN1Sequence asn1Sequence = ASN1Sequence.getInstance(asn1otherName);
|
||||
|
||||
if (asn1Sequence != null) {
|
||||
ASN1Encodable encodedOid = asn1Sequence.getObjectAt(0);
|
||||
ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(unwrap(encodedOid));
|
||||
tempOid = oid.getId();
|
||||
|
||||
ASN1Encodable principalNameEncoded = asn1Sequence.getObjectAt(1);
|
||||
DERUTF8String principalName = DERUTF8String.getInstance(unwrap(principalNameEncoded));
|
||||
|
||||
tempOtherName = principalName.getString();
|
||||
|
||||
// We found UPN among the 'otherName' principal. We don't need to look other
|
||||
if (UPN_OID.equals(tempOid)) {
|
||||
foundUpn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to parse subjectAltName", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
logger.tracef("Parsed otherName from subjectAltName. OID: '%s', Principal: '%s'", tempOid, tempOtherName);
|
||||
|
||||
return tempOtherName;
|
||||
|
||||
} catch (CertificateParsingException cause) {
|
||||
logger.errorf(cause, "Failed to obtain identity from subjectAltName extension");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private ASN1Encodable unwrap(ASN1Encodable encodable) {
|
||||
while (encodable instanceof ASN1TaggedObject) {
|
||||
ASN1TaggedObject taggedObj = (ASN1TaggedObject) encodable;
|
||||
encodable = taggedObj.getObject();
|
||||
}
|
||||
|
||||
return encodable;
|
||||
}
|
||||
}
|
||||
|
||||
static class PatternMatcher extends UserIdentityExtractor {
|
||||
|
|
|
@ -61,6 +61,7 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel {
|
|||
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);
|
||||
|
||||
private String name;
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.authentication.authenticators.x509;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.cert.X509Certificate;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SubjectAltNameIdentityExtractorTest {
|
||||
|
||||
@Test
|
||||
public void testX509SubjectAltName_otherName() throws Exception {
|
||||
UserIdentityExtractor extractor = UserIdentityExtractor.getSubjectAltNameExtractor(0);
|
||||
|
||||
X509Certificate cert = getCertificate();
|
||||
|
||||
Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert});
|
||||
Assert.assertEquals("test-user@some-company-domain", upn);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testX509SubjectAltName_email() throws Exception {
|
||||
UserIdentityExtractor extractor = UserIdentityExtractor.getSubjectAltNameExtractor(1);
|
||||
|
||||
X509Certificate cert = getCertificate();
|
||||
|
||||
Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert});
|
||||
Assert.assertEquals("test@somecompany.com", upn);
|
||||
}
|
||||
|
||||
|
||||
private X509Certificate getCertificate() throws Exception {
|
||||
InputStream is = getClass().getResourceAsStream("/certs/UPN-cert.pem");
|
||||
|
||||
String s = StreamUtil.readString(is, Charset.defaultCharset());
|
||||
|
||||
return PemUtils.decodeCertificate(s);
|
||||
}
|
||||
|
||||
|
||||
}
|
29
services/src/test/resources/certs/UPN-cert.pem
Normal file
29
services/src/test/resources/certs/UPN-cert.pem
Normal file
|
@ -0,0 +1,29 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE6DCCA9CgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpzELMAkGA1UEBhMCVVMx
|
||||
HTAbBgNVBAgMFERpc3RyaWN0IG9mIENvbHVtYmlhMRMwEQYDVQQHDApXYXNoaW5n
|
||||
dG9uMRUwEwYDVQQKDAxTb21lIENvbXBhbnkxGDAWBgNVBAsMD1NvbWUgRGVwYXJ0
|
||||
bWVudDEQMA4GA1UEAwwHVGVzdCBDQTEhMB8GCSqGSIb3DQEJARYSY2FAc29tZWNv
|
||||
bXBhbnkuY29tMB4XDTE0MDYxMjE3MDkxMloXDTI0MDYxMjE3MDkxMlowgZYxCzAJ
|
||||
BgNVBAYTAlVTMR0wGwYDVQQIDBREaXN0cmljdCBvZiBDb2x1bWJpYTEVMBMGA1UE
|
||||
CgwMU29tZSBDb21wYW55MRgwFgYDVQQLDA9Tb21lIERlcGFydG1lbnQxEjAQBgNV
|
||||
BAMMCVRlc3QgVXNlcjEjMCEGCSqGSIb3DQEJARYUdGVzdEBzb21lY29tcGFueS5j
|
||||
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD42s0Q8YXv/rQrk4rF
|
||||
4aHmuz8Tq9jWk3bU/tJoBgZTG2xCYfolT2z4j2Qa6kjXucEJuqOKihxMMZ1We0G4
|
||||
I6tm5QJxqkEoUYmUZHu/QZSrH1gwgS0yjvfq+Kk+yvKqplXDUyxbLRuMBRgFRCy0
|
||||
TUvdJPE4IQZQCcHir0Vqs667vj0UjSpI+y0BDZPY5CRePRSKcjM4ixoR9B8xj5kg
|
||||
RcMxg4EszC2oK7z0IuuYKi0ZOdot1wVKP4OD/9evE2wjUYVeYCAV9y7tMlVsN0N5
|
||||
dRplCSIa/CA5gTMod3C92t83VoPqfb0f71cNQAsx1V3dNtOKnTOoG5jX70RR4Rqk
|
||||
8ItNAgMBAAGjggEsMIIBKDAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAL
|
||||
BgNVHQ8EBAMCBeAwKwYJYIZIAYb4QgENBB4WHFNtYXJ0IENhcmQgTG9naW4gQ2Vy
|
||||
dGlmaWNhdGUwHQYDVR0OBBYEFN1P5EBNqZ+MGrJQziiVMhkKAXr9MB8GA1UdIwQY
|
||||
MBaAFCHrFg422S+AWOHXfPIdqxbRhXegME4GA1UdEQRHMEWBFHRlc3RAc29tZWNv
|
||||
bXBhbnkuY29toC0GCisGAQQBgjcUAgOgHwwddGVzdC11c2VyQHNvbWUtY29tcGFu
|
||||
eS1kb21haW4wHQYDVR0SBBYwFIESY2FAc29tZWNvbXBhbnkuY29tMB8GA1UdJQQY
|
||||
MBYGCCsGAQUFBwMCBgorBgEEAYI3FAICMA0GCSqGSIb3DQEBCwUAA4IBAQCPI5Zk
|
||||
DqGHkKfFhRjlzLLajUEveggs74x3roi6S0zlpXpbPA3iZ2N8COf/QZL3twKunbP9
|
||||
XpmW/pcSji3+pil9aHPRn69S4cSuKdN5ZP9oQhkZxdk2UFS8Ts0WA+SUDJ3qTEtA
|
||||
Q0HswBFBzWGOi0zkCtvAaBa8WSnDPtUN5RzmUtkKoMxBzu/MEMWNYXZyk/G2NHMJ
|
||||
jh0N+ICpRNpnXGIZBwFymIRGH/PjtVkArVXy0hILWP/qfYzYMFgUBl0InyQzT0Hw
|
||||
gzGdoeK7fVYrPWLK3ryRqqSR1XvfKFHwlnIg4XTBN4Cj7m5TmpntgVhO2JpNDjLr
|
||||
K/NmtORHe6OhF17I
|
||||
-----END CERTIFICATE-----
|
|
@ -1,38 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,41 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
Binary file not shown.
|
@ -84,7 +84,8 @@ stateOrProvinceName_default = MA
|
|||
localityName_default = Boston
|
||||
0.organizationName_default = Red Hat
|
||||
organizationalUnitName_default = Keycloak
|
||||
emailAddress_default = contact@keycloak.org
|
||||
commonName_default = test-user
|
||||
emailAddress_default = test-user@localhost
|
||||
|
||||
[ v3_ca ]
|
||||
# Extensions for a typical CA (`man x509v3_config`).
|
||||
|
@ -106,13 +107,21 @@ basicConstraints = CA:FALSE
|
|||
nsCertType = client, email
|
||||
nsComment = "OpenSSL Generated Client Certificate"
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid,issuer
|
||||
#authorityKeyIdentifier = keyid,issuer
|
||||
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth, emailProtection
|
||||
crlDistributionPoints = URI:http://localhost:8888/crl
|
||||
authorityInfoAccess = OCSP;URI:http://localhost:8888/oscp
|
||||
subjectAltName=email:copy
|
||||
subjectAltName=email:move
|
||||
subjectAltName=@user_subject_alt_names
|
||||
|
||||
[ user_subject_alt_names ]
|
||||
email = test-user-altmail@localhost
|
||||
IP = 192.168.7.1
|
||||
DNS = www.example-test.com
|
||||
URI = http://www.example-test.com
|
||||
otherName.1 = 1.2.3.4;UTF8:my_test_user
|
||||
otherName.2 = 1.3.6.1.4.1.311.20.2.3;UTF8:test_upn_name@localhost
|
||||
|
||||
|
||||
[ server_cert ]
|
||||
# Extensions for server certificates (`man x509v3_config`).
|
||||
|
|
|
@ -1 +1 @@
|
|||
1001
|
||||
1007
|
||||
|
|
|
@ -195,7 +195,7 @@
|
|||
<resource>
|
||||
<directory>${common.resources}/pki/root/ca</directory>
|
||||
<includes>
|
||||
<include>certs/clients/test-user-san-email@localhost.cert.pem</include>
|
||||
<include>certs/clients/test-user-san@localhost.cert.pem</include>
|
||||
<include>certs/clients/test-user@localhost.key.pem</include>
|
||||
</includes>
|
||||
</resource>
|
||||
|
|
|
@ -88,6 +88,12 @@
|
|||
<resource>
|
||||
<directory>${common.resources}/keystore</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>${common.resources}/pki/root/ca</directory>
|
||||
<includes>
|
||||
<include>certs/clients/*</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@
|
|||
<outputDirectory>${containers.home}/auth-server-undertow</outputDirectory>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<includes>*.jks,*.crt,*.truststore</includes>
|
||||
<includes>*.jks,*.crt,*.truststore,*.crl,*.key,certs/clients/*</includes>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
|
|
@ -49,7 +49,6 @@ import org.keycloak.testsuite.util.AdminEventPaths;
|
|||
import org.keycloak.testsuite.util.AssertAdminEvents;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.DroneUtils;
|
||||
import org.keycloak.testsuite.util.PhantomJSBrowser;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
@ -68,6 +67,7 @@ import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorC
|
|||
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_CN;
|
||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL;
|
||||
|
||||
|
@ -117,24 +117,39 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe
|
|||
Assume.assumeTrue(AUTH_SERVER_SSL_REQUIRED);
|
||||
}
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void onBeforeTestClass() {
|
||||
if (isAuthServerJBoss()) {
|
||||
String authServerHome = System.getProperty("auth.server.home");
|
||||
configurePhantomJS("/ca.crt", "/client.crt", "/client.key", "secret");
|
||||
}
|
||||
|
||||
if (authServerHome != null && System.getProperty("auth.server.ssl.required") != null) {
|
||||
|
||||
/**
|
||||
* Setup phantom JS to be used for mutual TLS testing. All file paths are relative to "authServerHome"
|
||||
*
|
||||
* @param certificatesPath
|
||||
* @param clientCertificateFile
|
||||
* @param clientKeyFile
|
||||
* @param clientKeyPassword
|
||||
*/
|
||||
protected static void configurePhantomJS(String certificatesPath, String clientCertificateFile, String clientKeyFile, String clientKeyPassword) {
|
||||
String authServerHome = System.getProperty("auth.server.home");
|
||||
|
||||
if (authServerHome != null && System.getProperty("auth.server.ssl.required") != null) {
|
||||
if (isAuthServerJBoss()) {
|
||||
authServerHome = authServerHome + "/standalone/configuration";
|
||||
StringBuilder cliArgs = new StringBuilder();
|
||||
|
||||
cliArgs.append("--ignore-ssl-errors=true ");
|
||||
cliArgs.append("--web-security=false ");
|
||||
cliArgs.append("--ssl-certificates-path=").append(authServerHome).append("/ca.crt ");
|
||||
cliArgs.append("--ssl-client-certificate-file=").append(authServerHome).append("/client.crt ");
|
||||
cliArgs.append("--ssl-client-key-file=").append(authServerHome).append("/client.key ");
|
||||
cliArgs.append("--ssl-client-key-passphrase=secret ");
|
||||
|
||||
System.setProperty("keycloak.phantomjs.cli.args", cliArgs.toString());
|
||||
}
|
||||
|
||||
StringBuilder cliArgs = new StringBuilder();
|
||||
|
||||
cliArgs.append("--ignore-ssl-errors=true ");
|
||||
cliArgs.append("--web-security=false ");
|
||||
cliArgs.append("--ssl-certificates-path=").append(authServerHome).append(certificatesPath).append(" ");
|
||||
cliArgs.append("--ssl-client-certificate-file=").append(authServerHome).append(clientCertificateFile).append(" ");
|
||||
cliArgs.append("--ssl-client-key-file=").append(authServerHome).append(clientKeyFile).append(" ");
|
||||
cliArgs.append("--ssl-client-key-passphrase=" + clientKeyPassword).append(" ");
|
||||
|
||||
System.setProperty("keycloak.phantomjs.cli.args", cliArgs.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,6 +198,8 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe
|
|||
userId = user.getId();
|
||||
|
||||
user.singleAttribute("x509_certificate_identity","-");
|
||||
user.singleAttribute("alternative_email", "test-user-altmail@localhost");
|
||||
user.singleAttribute("upn", "test_upn_name@localhost");
|
||||
updateUser(user);
|
||||
}
|
||||
|
||||
|
@ -343,11 +360,20 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe
|
|||
.setUserIdentityMapperType(USERNAME_EMAIL);
|
||||
}
|
||||
|
||||
protected static X509AuthenticatorConfigModel createLoginSubjectAltNameEmail2UsernameOrEmailConfig() {
|
||||
protected static X509AuthenticatorConfigModel createLoginSubjectAltNameEmail2UserAttributeConfig() {
|
||||
return new X509AuthenticatorConfigModel()
|
||||
.setConfirmationPageAllowed(true)
|
||||
.setMappingSourceType(SUBJECTALTNAME_EMAIL)
|
||||
.setUserIdentityMapperType(USERNAME_EMAIL);
|
||||
.setUserIdentityMapperType(USER_ATTRIBUTE)
|
||||
.setCustomAttributeName("alternative_email");
|
||||
}
|
||||
|
||||
protected static X509AuthenticatorConfigModel createLoginSubjectAltNameOtherName2UserAttributeConfig() {
|
||||
return new X509AuthenticatorConfigModel()
|
||||
.setConfirmationPageAllowed(true)
|
||||
.setMappingSourceType(SUBJECTALTNAME_OTHERNAME)
|
||||
.setUserIdentityMapperType(USER_ATTRIBUTE)
|
||||
.setCustomAttributeName("upn");
|
||||
}
|
||||
|
||||
protected static X509AuthenticatorConfigModel createLoginSubjectEmailWithKeyUsage(String keyUsage) {
|
||||
|
|
|
@ -39,7 +39,7 @@ import org.openqa.selenium.WebDriver;
|
|||
* @date 8/12/2016
|
||||
*/
|
||||
|
||||
public class X509BrowserLoginSubjectAltNameEmailTest extends AbstractX509AuthenticationTest {
|
||||
public class X509BrowserLoginSubjectAltNameTest extends AbstractX509AuthenticationTest {
|
||||
|
||||
@Page
|
||||
@PhantomJSBrowser
|
||||
|
@ -64,23 +64,8 @@ public class X509BrowserLoginSubjectAltNameEmailTest extends AbstractX509Authent
|
|||
|
||||
@BeforeClass
|
||||
public static void onBeforeTestClass() {
|
||||
if (Boolean.parseBoolean(System.getProperty("auth.server.jboss"))) {
|
||||
String authServerHome = System.getProperty("auth.server.home");
|
||||
|
||||
if (authServerHome != null && System.getProperty("auth.server.ssl.required") != null) {
|
||||
authServerHome = authServerHome + "/standalone/configuration";
|
||||
StringBuilder cliArgs = new StringBuilder();
|
||||
|
||||
cliArgs.append("--ignore-ssl-errors=true ");
|
||||
cliArgs.append("--web-security=false ");
|
||||
cliArgs.append("--ssl-certificates-path=" + authServerHome + "/ca.crt ");
|
||||
cliArgs.append("--ssl-client-certificate-file=" + authServerHome + "/certs/clients/test-user-san-email@localhost.cert.pem ");
|
||||
cliArgs.append("--ssl-client-key-file=" + authServerHome + "/certs/clients/test-user@localhost.key.pem ");
|
||||
cliArgs.append("--ssl-client-key-passphrase=password");
|
||||
|
||||
System.setProperty("keycloak.phantomjs.cli.args", cliArgs.toString());
|
||||
}
|
||||
}
|
||||
configurePhantomJS("/ca.crt", "/certs/clients/test-user-san@localhost.cert.pem",
|
||||
"/certs/clients/test-user@localhost.key.pem", "password");
|
||||
}
|
||||
|
||||
private void login(X509AuthenticatorConfigModel config, String userId, String username, String attemptedUsername) {
|
||||
|
@ -91,7 +76,7 @@ public class X509BrowserLoginSubjectAltNameEmailTest extends AbstractX509Authent
|
|||
|
||||
loginConfirmationPage.open();
|
||||
|
||||
Assert.assertTrue(loginConfirmationPage.getSubjectDistinguishedNameText().equals("CN=test-user, OU=Keycloak, O=Red Hat, L=Boston, ST=MA, C=US"));
|
||||
Assert.assertEquals("EMAILADDRESS=test-user@localhost, CN=test-user, OU=Keycloak, O=Red Hat, L=Boston, ST=MA, C=US", loginConfirmationPage.getSubjectDistinguishedNameText());
|
||||
Assert.assertEquals(username, loginConfirmationPage.getUsernameText());
|
||||
|
||||
loginConfirmationPage.confirm();
|
||||
|
@ -107,7 +92,12 @@ public class X509BrowserLoginSubjectAltNameEmailTest extends AbstractX509Authent
|
|||
}
|
||||
|
||||
@Test
|
||||
public void loginAsUserFromCertSubjectEmail() {
|
||||
login(createLoginSubjectAltNameEmail2UsernameOrEmailConfig(), userId, "test-user@localhost", "test-user@localhost");
|
||||
public void loginAsUserFromCertSANEmail() {
|
||||
login(createLoginSubjectAltNameEmail2UserAttributeConfig(), userId, "test-user@localhost", "test-user-altmail@localhost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginAsUserFromCertSANUpn() {
|
||||
login(createLoginSubjectAltNameOtherName2UserAttributeConfig(), userId, "test-user@localhost", "test_upn_name@localhost");
|
||||
}
|
||||
}
|
|
@ -39,9 +39,14 @@ import org.keycloak.services.managers.RealmManager;
|
|||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
import org.keycloak.testsuite.util.cli.TestsuiteCLI;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.xnio.Options;
|
||||
import org.xnio.SslClientAuthMode;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.servlet.DispatcherType;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -380,7 +385,9 @@ public class KeycloakServer {
|
|||
.setIoThreads(config.getWorkerThreads() / 8);
|
||||
|
||||
if (config.getPortHttps() != -1) {
|
||||
builder = builder.addHttpsListener(config.getPortHttps(), config.getHost(), createSSLContext());
|
||||
builder = builder
|
||||
.addHttpsListener(config.getPortHttps(), config.getHost(), createSSLContext())
|
||||
.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.REQUESTED);
|
||||
}
|
||||
|
||||
server = new UndertowJaxrsServer();
|
||||
|
@ -476,12 +483,29 @@ public class KeycloakServer {
|
|||
}
|
||||
|
||||
private SSLContext createSSLContext() throws Exception {
|
||||
KeyManager[] keyManagers = getKeyManagers();
|
||||
|
||||
if (keyManagers == null) {
|
||||
return SSLContext.getDefault();
|
||||
}
|
||||
|
||||
TrustManager[] trustManagers = getTrustManagers();
|
||||
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(keyManagers, trustManagers, null);
|
||||
return sslContext;
|
||||
}
|
||||
|
||||
|
||||
private KeyManager[] getKeyManagers() throws Exception {
|
||||
String keyStorePath = System.getProperty("keycloak.tls.keystore.path");
|
||||
|
||||
if (keyStorePath == null) {
|
||||
return SSLContext.getDefault();
|
||||
return null;
|
||||
}
|
||||
|
||||
log.infof("Loading keystore from file: %s", keyStorePath);
|
||||
|
||||
InputStream stream = Files.newInputStream(Paths.get(keyStorePath));
|
||||
|
||||
if (stream == null) {
|
||||
|
@ -490,20 +514,41 @@ public class KeycloakServer {
|
|||
|
||||
try (InputStream is = stream) {
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
|
||||
char[] keyStorePassword = System.getProperty("keycloak.tls.keystore.password", "password").toCharArray();
|
||||
|
||||
keyStore.load(is, keyStorePassword);
|
||||
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
|
||||
keyManagerFactory.init(keyStore, keyStorePassword);
|
||||
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
return keyManagerFactory.getKeyManagers();
|
||||
}
|
||||
}
|
||||
|
||||
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
|
||||
|
||||
return sslContext;
|
||||
private TrustManager[] getTrustManagers() throws Exception {
|
||||
String trustStorePath = System.getProperty("keycloak.tls.truststore.path");
|
||||
|
||||
if (trustStorePath == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
log.infof("Loading truststore from file: %s", trustStorePath);
|
||||
|
||||
InputStream stream = Files.newInputStream(Paths.get(trustStorePath));
|
||||
|
||||
if (stream == null) {
|
||||
throw new RuntimeException("Could not load truststore");
|
||||
}
|
||||
|
||||
try (InputStream is = stream) {
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
char[] keyStorePassword = System.getProperty("keycloak.tls.truststore.password", "password").toCharArray();
|
||||
keyStore.load(is, keyStorePassword);
|
||||
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init(keyStore);
|
||||
|
||||
return trustManagerFactory.getTrustManagers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,5 +118,39 @@
|
|||
"enabled": true
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"login-protocol": {
|
||||
"saml": {
|
||||
"knownProtocols": [
|
||||
"http=${auth.server.http.port}",
|
||||
"https=${auth.server.https.port}"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"x509cert-lookup": {
|
||||
"provider": "${keycloak.x509cert.lookup.provider:default}",
|
||||
"default": {
|
||||
"enabled": true
|
||||
},
|
||||
"haproxy": {
|
||||
"enabled": true,
|
||||
"sslClientCert": "x-ssl-client-cert",
|
||||
"sslCertChainPrefix": "x-ssl-client-cert-chain",
|
||||
"certificateChainLength": 1
|
||||
},
|
||||
"apache": {
|
||||
"enabled": true,
|
||||
"sslClientCert": "x-ssl-client-cert",
|
||||
"sslCertChainPrefix": "x-ssl-client-cert-chain",
|
||||
"certificateChainLength": 1
|
||||
},
|
||||
"nginx": {
|
||||
"enabled": true,
|
||||
"sslClientCert": "x-ssl-client-cert",
|
||||
"sslCertChainPrefix": "x-ssl-client-cert-chain",
|
||||
"certificateChainLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue