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 e58c2a4292..930cea310a 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 @@ -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; 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 03e8a33434..ea77149e81 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 @@ -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, 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 881fdb7671..e44453b248 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 @@ -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> 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 ; iMarek Posolda + */ +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); + } + + +} diff --git a/services/src/test/resources/certs/UPN-cert.pem b/services/src/test/resources/certs/UPN-cert.pem new file mode 100644 index 0000000000..23f9279429 --- /dev/null +++ b/services/src/test/resources/certs/UPN-cert.pem @@ -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----- \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-san-email@localhost.cert.pem b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-san-email@localhost.cert.pem deleted file mode 100644 index 4a00a127c4..0000000000 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-san-email@localhost.cert.pem +++ /dev/null @@ -1,38 +0,0 @@ ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-san@localhost.cert.pem b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-san@localhost.cert.pem new file mode 100644 index 0000000000..cf12f0c9cd --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-san@localhost.cert.pem @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIHPDCCBSSgAwIBAgICEAcwDQYJKoZIhvcNAQELBQAwgYcxCzAJBgNVBAYTAlVT +MQswCQYDVQQIDAJNQTEQMA4GA1UECgwHUmVkIEhhdDERMA8GA1UECwwIS2V5Y2xv +YWsxITAfBgNVBAMMGEtleWNsb2FrIEludGVybWVkaWF0ZSBDQTEjMCEGCSqGSIb3 +DQEJARYUY29udGFjdEBrZXljbG9hay5vcmcwHhcNMTkwMzA4MTgyMjU5WhcNNDYw +NzI0MTgyMjU5WjCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMQ8wDQYDVQQH +DAZCb3N0b24xEDAOBgNVBAoMB1JlZCBIYXQxETAPBgNVBAsMCEtleWNsb2FrMRIw +EAYDVQQDDAl0ZXN0LXVzZXIxIjAgBgkqhkiG9w0BCQEWE3Rlc3QtdXNlckBsb2Nh +bGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDpitg+FXUbxjlI +wD1l6Jef4ZDMAjSl4DtGa4E5ga8yJ/BfDv0AmL5DYEQEyASDdvpzSvj3o/erRx84 +TwtOzuyjAy53I0hI45mdsZr4dhYz6/saKE/sdJs792vTIVQmI1hzO8fi1rgADJ3u +MT8deADFWWvj+2E5s2m2zFhzPYPSLcY8pf46ZLfS5lrGYdl77fejYD+AhtVXoJpd +JzZ0egCMCpSpdseTTLl64QrNsp9D60lcMx7HSGo6mkwxnncIVqS8wsv/5Nyi0/cn +UWoYW1CliuPAzy3/nCbm1RnBP4XYgEKgNQv91Jv5F0dT3CIxt2C3l2r4Zk/+x+d5 +UXtZnR5lJ9W+1a+qGF+7pZ/MGagTL3Hjitt8JCmPe9I9jeOlIwAXMPX51HJCmII6 +b/CNNvT4JyIAY1962cjJkQfCocPjHFSMdA7Bce6CXHOWVdekTOLR8ddOxdPODgZA +5KidJONqcNYKbKL5Z/j1ShnrQRhWwALDcDDGcZiU/69UVVpOLqXvx381s9T78HE4 +2kQ/DM4QtesTq+x0fLg0QxVONPl+ZpBCZM70+fooe2uuE7EDWblPw8d4+Z3GKbSz +JdBb85TZXw5Gd1wlEH5K/aP58XavQ0wRqcupzGguQTH/Dys41wupYqFAUExSRqx7 +HOfT0yNBkjl5JbP4DuPeEpmyJApmqwIDAQABo4IBrTCCAakwCQYDVR0TBAIwADAR +BglghkgBhvhCAQEEBAMCBaAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2VuZXJh +dGVkIENsaWVudCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU+mP2lV1sZIgt0Drjepyg +o2YEXW0wDgYDVR0PAQH/BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF +BQcDBDAqBgNVHR8EIzAhMB+gHaAbhhlodHRwOi8vbG9jYWxob3N0Ojg4ODgvY3Js +MDYGCCsGAQUFBwEBBCowKDAmBggrBgEFBQcwAYYaaHR0cDovL2xvY2FsaG9zdDo4 +ODg4L29zY3AwgaEGA1UdEQSBmTCBloEbdGVzdC11c2VyLWFsdG1haWxAbG9jYWxo +b3N0hwTAqAcBghR3d3cuZXhhbXBsZS10ZXN0LmNvbYYbaHR0cDovL3d3dy5leGFt +cGxlLXRlc3QuY29toBUGAyoDBKAODAxteV90ZXN0X3VzZXKgJwYKKwYBBAGCNxQC +A6AZDBd0ZXN0X3Vwbl9uYW1lQGxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAgEA +USFwAvT4dyxCP3Uqf+ztEWJx82L0rXuy9H+9nr1LC6AiHqyDzgtzwqF/clLmOJU6 +JTFhNxf3fZUdHsLxNXpnpaZbYCkuo+Yh0FY3J3Qnhzht+csroqN/PWKmBV+dN8kq +SWw1327LHsX3C6ItnMUigUmMyYx+2WtNxCweacFczlwCpAx2cy+/eP4jX9tMWg8h +/AZs7XJL4zwqum7bSIsp2EkbeIqH60bqcMy6tFAb1+OwahHW8dSub4TQCpHPR5y7 +0CJNQXUOSUTuQ51KndYqmoAL6xaQ0l1NCECZ2DGI6ja3HjjCXbxswv50i/0+xmUn +261IzBuWHQ56ub/fuTjLlC/O4QhSQZm0pd1zEtVlUg8+uApohyJgUSR2QO6iDWC8 +zE5JjxVVg6h7ynEBtMQYkt0WXdfGQPMkUgHWaRl125GZHajsxxTfhbAHqVGITZ6z +eYYn8F3GM3Dp4ph+V0zRgaF37JoBT0x7xnDZXBXyzCa6w7/3/ijg6RMFwbVU//c8 +htlcilkLcXOTS3C9+OThSLK8yBlQy0GYQqiWYWuMEPXY7QksaCM6A7P0M4+d8bvS +nbVsveIXho1bpiVOjobJJP+Lk88CUGvgaV4P+ksWdRKjzc2TGJo8k9kYTECVGaCp +z/X6dohZxgFWxLcZQ8q5HYqIcyH/qbBy4z14yerbOqs= +-----END CERTIFICATE----- diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-san@localhost.p12 b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-san@localhost.p12 new file mode 100644 index 0000000000..4aa422bc2e Binary files /dev/null and b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/certs/clients/test-user-san@localhost.p12 differ diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/intermediate/openssl-san.cnf b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/intermediate/openssl-san.cnf index 4bf6ffc46e..2b7c9acd9b 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/intermediate/openssl-san.cnf +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/intermediate/openssl-san.cnf @@ -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`). diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/intermediate/serial b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/intermediate/serial index dd11724042..fb35a14c02 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/intermediate/serial +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/intermediate/serial @@ -1 +1 @@ -1001 +1007 diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml index a8317fb34a..4270d8e9ff 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml @@ -195,7 +195,7 @@ ${common.resources}/pki/root/ca - certs/clients/test-user-san-email@localhost.cert.pem + certs/clients/test-user-san@localhost.cert.pem certs/clients/test-user@localhost.key.pem diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml index a62dd1f640..ad2b506391 100644 --- a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml @@ -88,6 +88,12 @@ ${common.resources}/keystore + + ${common.resources}/pki/root/ca + + certs/clients/* + + diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 32d34529c6..e73fcee258 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -273,7 +273,7 @@ ${containers.home}/auth-server-undertow - *.jks,*.crt,*.truststore + *.jks,*.crt,*.truststore,*.crl,*.key,certs/clients/* 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 e5190ee005..ebc2c5997c 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 @@ -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) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginSubjectAltNameEmailTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginSubjectAltNameTest.java similarity index 66% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginSubjectAltNameEmailTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginSubjectAltNameTest.java index a91a492ff2..86110beb25 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginSubjectAltNameEmailTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginSubjectAltNameTest.java @@ -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"); } } \ No newline at end of file diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakServer.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakServer.java index 8871bfbeac..c78e542272 100755 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakServer.java +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakServer.java @@ -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(); } } } diff --git a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json index 5ed3ac19af..13f9b5764c 100755 --- a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json +++ b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json @@ -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 + } } }