parent
5c2a5fac31
commit
3e9c729f9e
17 changed files with 221 additions and 139 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -299,7 +299,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
server: ['bcfips-nonapproved-pkcs12']
|
server: ['bcfips-nonapproved-pkcs12']
|
||||||
tests: ['group1']
|
tests: ['group1', 'group2']
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
@ -339,6 +339,7 @@ jobs:
|
||||||
PARAMS["bcfips-nonapproved-pkcs12"]="-Pauth-server-quarkus,auth-server-fips140-2"
|
PARAMS["bcfips-nonapproved-pkcs12"]="-Pauth-server-quarkus,auth-server-fips140-2"
|
||||||
# Tests in the package "forms" and some keystore related tests
|
# Tests in the package "forms" and some keystore related tests
|
||||||
TESTGROUP["group1"]="-Dtest=org.keycloak.testsuite.forms.**,ClientAuthSignedJWTTest,CredentialsTest,JavaKeystoreKeyProviderTest,ServerInfoTest,UserFederationLdapConnectionTest,LDAPUserLoginTest"
|
TESTGROUP["group1"]="-Dtest=org.keycloak.testsuite.forms.**,ClientAuthSignedJWTTest,CredentialsTest,JavaKeystoreKeyProviderTest,ServerInfoTest,UserFederationLdapConnectionTest,LDAPUserLoginTest"
|
||||||
|
TESTGROUP["group2"]="-Dtest=org.keycloak.testsuite.x509.**,MutualTLSClientTest,FAPI1Test,FAPICIBATest" # Tests for X.509 authentication with users and clients
|
||||||
|
|
||||||
./mvnw clean install -nsu -B ${PARAMS["${{ matrix.server }}"]} ${TESTGROUP["${{ matrix.tests }}"]} -f testsuite/integration-arquillian/tests/base/pom.xml | misc/log/trimmer.sh
|
./mvnw clean install -nsu -B ${PARAMS["${{ matrix.server }}"]} ${TESTGROUP["${{ matrix.tests }}"]} -f testsuite/integration-arquillian/tests/base/pom.xml | misc/log/trimmer.sh
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.x509;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.crypto.CryptoIntegration;
|
||||||
|
import org.keycloak.common.crypto.UserIdentityExtractor;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
import org.keycloak.common.util.StreamUtil;
|
||||||
|
import org.keycloak.rule.CryptoInitRule;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/** This is not tested in keycloak-core. The subclasses should be created in the crypto modules to make sure it is tested with corresponding modules (bouncycastle VS bouncycastle-fips) */
|
||||||
|
public abstract class CertificateIdentityExtractorTest {
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractsCertInPemFormat() throws Exception {
|
||||||
|
X509Certificate x509Certificate = getCertificate();
|
||||||
|
|
||||||
|
String certificatePem = PemUtils.encodeCertificate(x509Certificate);
|
||||||
|
|
||||||
|
//X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel();
|
||||||
|
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getCertificatePemIdentityExtractor();
|
||||||
|
|
||||||
|
String userIdentity = (String) extractor.extractUserIdentity(new X509Certificate[]{x509Certificate});
|
||||||
|
|
||||||
|
assertEquals(certificatePem, userIdentity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractsCertInSubjectDNFormat() throws Exception {
|
||||||
|
X509Certificate x509Certificate = getCertificate();
|
||||||
|
|
||||||
|
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getX500NameExtractor("CN", certs -> {
|
||||||
|
return certs[0].getSubjectX500Principal();
|
||||||
|
});
|
||||||
|
String userIdentity = (String) extractor.extractUserIdentity(new X509Certificate[]{x509Certificate});
|
||||||
|
assertEquals("Test User", userIdentity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testX509SubjectAltName_otherName() throws Exception {
|
||||||
|
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().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 = CryptoIntegration.getProvider().getIdentityExtractorProvider().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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.crypto.def.test;
|
||||||
|
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.authentication.x509.CertificateIdentityExtractorTest;
|
||||||
|
import org.keycloak.common.util.Environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class DefaultCertificateIdentityExtractorTest extends CertificateIdentityExtractorTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// Run this test just if java is not in FIPS mode
|
||||||
|
Assume.assumeFalse("Java is in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.crypto.fips.test;
|
||||||
|
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.authentication.x509.CertificateIdentityExtractorTest;
|
||||||
|
import org.keycloak.common.util.Environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class FIPS1402CertificateIdentityExtractorTest extends CertificateIdentityExtractorTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
// Run this test just if java is in FIPS mode
|
||||||
|
Assume.assumeTrue("Java is not in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -126,6 +126,7 @@ mvn clean install -f common -DskipTests=true
|
||||||
mvn clean install -f core -DskipTests=true
|
mvn clean install -f core -DskipTests=true
|
||||||
mvn clean install -f server-spi -DskipTests=true
|
mvn clean install -f server-spi -DskipTests=true
|
||||||
mvn clean install -f server-spi-private -DskipTests=true
|
mvn clean install -f server-spi-private -DskipTests=true
|
||||||
|
mvn clean install -f services -DskipTests=true
|
||||||
mvn clean install -f crypto/fips1402
|
mvn clean install -f crypto/fips1402
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -41,15 +40,18 @@ public class X509ClientAuthenticator extends AbstractClientAuthenticator {
|
||||||
// These are not recognized by default in RFC1779 or RFC2253 and hence not read in the java by default
|
// These are not recognized by default in RFC1779 or RFC2253 and hence not read in the java by default
|
||||||
private static final Map<String, String> CUSTOM_OIDS = new HashMap<>();
|
private static final Map<String, String> CUSTOM_OIDS = new HashMap<>();
|
||||||
private static final Map<String, String> CUSTOM_OIDS_REVERSED = new HashMap<>();
|
private static final Map<String, String> CUSTOM_OIDS_REVERSED = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
CUSTOM_OIDS.put("2.5.4.5", "serialNumber".toUpperCase());
|
CUSTOM_OIDS.put("2.5.4.5", "serialNumber".toUpperCase());
|
||||||
CUSTOM_OIDS.put("2.5.4.15", "businessCategory".toUpperCase());
|
CUSTOM_OIDS.put("2.5.4.15", "businessCategory".toUpperCase());
|
||||||
CUSTOM_OIDS.put("1.3.6.1.4.1.311.60.2.1.3", "jurisdictionCountryName".toUpperCase());
|
CUSTOM_OIDS.put("1.3.6.1.4.1.311.60.2.1.3", "jurisdictionCountryName".toUpperCase());
|
||||||
|
CUSTOM_OIDS.put("1.2.840.113549.1.9.1", "emailAddress".toUpperCase());
|
||||||
|
|
||||||
// Reverse map
|
// Reverse map
|
||||||
for (Map.Entry<String, String> entry : CUSTOM_OIDS.entrySet()) {
|
for (Map.Entry<String, String> entry : CUSTOM_OIDS.entrySet()) {
|
||||||
CUSTOM_OIDS_REVERSED.put(entry.getValue(), entry.getKey());
|
CUSTOM_OIDS_REVERSED.put(entry.getValue(), entry.getKey());
|
||||||
}
|
}
|
||||||
|
CUSTOM_OIDS_REVERSED.put("E", "1.2.840.113549.1.9.1"); // Another synonym for "EMAILADDRESS"
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ServicesLogger logger = ServicesLogger.LOGGER;
|
protected static ServicesLogger logger = ServicesLogger.LOGGER;
|
||||||
|
|
|
@ -149,7 +149,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
private static Function<X509Certificate[], String> getIssuerDNFunc(X509AuthenticatorConfigModel config) {
|
private static Function<X509Certificate[], String> getIssuerDNFunc(X509AuthenticatorConfigModel config) {
|
||||||
return config.isCanonicalDnEnabled() ?
|
return config.isCanonicalDnEnabled() ?
|
||||||
certs -> certs[0].getIssuerX500Principal().getName(X500Principal.CANONICAL) :
|
certs -> certs[0].getIssuerX500Principal().getName(X500Principal.CANONICAL) :
|
||||||
certs -> certs[0].getIssuerDN().getName();
|
certs -> certs[0].getIssuerDN().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
static UserIdentityExtractor fromConfig(X509AuthenticatorConfigModel config) {
|
static UserIdentityExtractor fromConfig(X509AuthenticatorConfigModel config) {
|
||||||
|
@ -168,7 +168,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
case SUBJECTDN:
|
case SUBJECTDN:
|
||||||
func = config.isCanonicalDnEnabled() ?
|
func = config.isCanonicalDnEnabled() ?
|
||||||
certs -> certs[0].getSubjectX500Principal().getName(X500Principal.CANONICAL) :
|
certs -> certs[0].getSubjectX500Principal().getName(X500Principal.CANONICAL) :
|
||||||
certs -> certs[0].getSubjectDN().getName();
|
certs -> certs[0].getSubjectDN().toString();
|
||||||
extractor = userIdExtractor.getPatternIdentityExtractor(pattern, func);
|
extractor = userIdExtractor.getPatternIdentityExtractor(pattern, func);
|
||||||
break;
|
break;
|
||||||
case ISSUERDN:
|
case ISSUERDN:
|
||||||
|
|
|
@ -67,12 +67,11 @@ import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.keycloak.common.crypto.CryptoIntegration;
|
import org.keycloak.common.crypto.CryptoIntegration;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.connections.httpclient.HttpClientProvider;
|
import org.keycloak.connections.httpclient.HttpClientProvider;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
|
||||||
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
|
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.truststore.TruststoreProvider;
|
import org.keycloak.truststore.TruststoreProvider;
|
||||||
import org.keycloak.utils.CRLUtils;
|
import org.keycloak.utils.CRLUtils;
|
||||||
|
@ -982,13 +981,11 @@ public class CertificateValidator {
|
||||||
public GotOCSPFailOpen oCSPResponseCertificate(String responderCert) {
|
public GotOCSPFailOpen oCSPResponseCertificate(String responderCert) {
|
||||||
if (responderCert != null && !responderCert.isEmpty()) {
|
if (responderCert != null && !responderCert.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
_responderCert = XMLSignatureUtil.getX509CertificateFromKeyInfoString(responderCert);
|
_responderCert = PemUtils.decodeCertificate(responderCert);
|
||||||
_responderCert.checkValidity();
|
_responderCert.checkValidity();
|
||||||
} catch(CertificateException e) {
|
} catch(CertificateException e) {
|
||||||
logger.warnf("Ignoring invalid certificate: %s", _responderCert);
|
logger.warnf("Ignoring invalid certificate: %s", _responderCert);
|
||||||
_responderCert = null;
|
_responderCert = null;
|
||||||
} catch (ProcessingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new GotOCSPFailOpen();
|
return new GotOCSPFailOpen();
|
||||||
|
|
|
@ -165,13 +165,11 @@ public class X509ClientCertificateAuthenticator extends AbstractX509ClientCertif
|
||||||
|
|
||||||
// Check whether to display the identity confirmation
|
// Check whether to display the identity confirmation
|
||||||
if (!config.getConfirmationPageDisallowed()) {
|
if (!config.getConfirmationPageDisallowed()) {
|
||||||
// FIXME calling forceChallenge was the only way to display
|
// Calling forceChallenge was the only way to display
|
||||||
// a form to let users either choose the user identity from certificate
|
// a form to let users either choose the user identity from certificate
|
||||||
// or to ignore it and proceed to a normal login screen. Attempting
|
// or to ignore it and proceed to a normal login screen. Attempting
|
||||||
// to call the method "challenge" results in a wrong/unexpected behavior.
|
// to call the method "challenge" results in a wrong/unexpected behavior.
|
||||||
// The question is whether calling "forceChallenge" here is ok from
|
context.forceChallenge(createSuccessResponse(context, certs[0].getSubjectDN().toString()));
|
||||||
// the design viewpoint?
|
|
||||||
context.forceChallenge(createSuccessResponse(context, certs[0].getSubjectDN().getName()));
|
|
||||||
// Do not set the flow status yet, we want to display a form to let users
|
// Do not set the flow status yet, we want to display a form to let users
|
||||||
// choose whether to accept the identity from certificate or to specify username/password explicitly
|
// choose whether to accept the identity from certificate or to specify username/password explicitly
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package org.keycloak.authentication.authenticators.x509;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
|
|
||||||
import org.junit.ClassRule;
|
|
||||||
import org.keycloak.rule.CryptoInitRule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.common.crypto.CryptoIntegration;
|
|
||||||
import org.keycloak.common.crypto.UserIdentityExtractor;
|
|
||||||
import org.keycloak.common.util.PemUtils;
|
|
||||||
import org.keycloak.common.util.StreamUtil;
|
|
||||||
|
|
||||||
public class CertificatePemIdentityExtractorTest {
|
|
||||||
|
|
||||||
@ClassRule
|
|
||||||
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExtractsCertInPemFormat() throws Exception {
|
|
||||||
InputStream is = getClass().getResourceAsStream("/certs/UPN-cert.pem");
|
|
||||||
X509Certificate x509Certificate = PemUtils.decodeCertificate(StreamUtil.readString(is, Charset.defaultCharset()));
|
|
||||||
String certificatePem = PemUtils.encodeCertificate(x509Certificate);
|
|
||||||
|
|
||||||
//X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel();
|
|
||||||
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().getCertificatePemIdentityExtractor();
|
|
||||||
|
|
||||||
String userIdentity = (String) extractor.extractUserIdentity(new X509Certificate[]{x509Certificate});
|
|
||||||
|
|
||||||
assertEquals(certificatePem, userIdentity);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.ClassRule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.common.crypto.CryptoIntegration;
|
|
||||||
import org.keycloak.common.crypto.UserIdentityExtractor;
|
|
||||||
import org.keycloak.common.util.PemUtils;
|
|
||||||
import org.keycloak.common.util.StreamUtil;
|
|
||||||
import org.keycloak.rule.CryptoInitRule;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class SubjectAltNameIdentityExtractorTest {
|
|
||||||
|
|
||||||
@ClassRule
|
|
||||||
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testX509SubjectAltName_otherName() throws Exception {
|
|
||||||
UserIdentityExtractor extractor = CryptoIntegration.getProvider().getIdentityExtractorProvider().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 = CryptoIntegration.getProvider().getIdentityExtractorProvider().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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -81,16 +81,9 @@ Or slightly longer version (that allows you to specify debugging port as well as
|
||||||
|
|
||||||
and you will be able to attach remote debugger to the test. Unfortunately server and adapter are running in different JVMs, so this won't help to debug those.
|
and you will be able to attach remote debugger to the test. Unfortunately server and adapter are running in different JVMs, so this won't help to debug those.
|
||||||
|
|
||||||
### JBoss auth server debugging
|
### Auth server debugging
|
||||||
|
|
||||||
When tests are run on JBoss based container (WildFly/EAP) there is possibility to attach a debugger, by default on localhost:5005.
|
See below in the "Quarkus" section.
|
||||||
|
|
||||||
The server won't wait to attach the debugger. There are some properties what can change the default behaviour.
|
|
||||||
|
|
||||||
-Dauth.server.debug.port=$PORT
|
|
||||||
-Dauth.server.debug.suspend=y
|
|
||||||
|
|
||||||
More info: http://javahowto.blogspot.cz/2010/09/java-agentlibjdwp-for-attaching.html
|
|
||||||
|
|
||||||
### JBoss app server debugging
|
### JBoss app server debugging
|
||||||
|
|
||||||
|
@ -789,6 +782,13 @@ Run tests using the `auth-server-quarkus` profile:
|
||||||
Right now, the server runs in a separate process. To debug the server set `auth.server.debug` system property to `true`.
|
Right now, the server runs in a separate process. To debug the server set `auth.server.debug` system property to `true`.
|
||||||
|
|
||||||
To configure the debugger port, set the `auth.server.debug.port` system property with any valid port number. Default is `5005`.
|
To configure the debugger port, set the `auth.server.debug.port` system property with any valid port number. Default is `5005`.
|
||||||
|
Note you can also set port for example to `*:5005` or `my-host:5005` to set the bind host.
|
||||||
|
|
||||||
|
By default, quarkus server is started in the testsuite and you need to attach remote debugger to it during running. You can
|
||||||
|
use `auth.server.debug.suspend=y` to "suspend" server startup when running testsuite, which means that server startup is blocked
|
||||||
|
until debugger is attached.
|
||||||
|
|
||||||
|
More info: http://javahowto.blogspot.cz/2010/09/java-agentlibjdwp-for-attaching.html
|
||||||
|
|
||||||
## Cookies testing
|
## Cookies testing
|
||||||
In order to reproduce some specific cookies behaviour in browsers (like SameSite policies or 3rd party cookie blocking),
|
In order to reproduce some specific cookies behaviour in browsers (like SameSite policies or 3rd party cookie blocking),
|
||||||
|
|
|
@ -27,7 +27,9 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
@ -174,7 +176,7 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
||||||
}
|
}
|
||||||
|
|
||||||
private Process startContainer() throws IOException {
|
private Process startContainer() throws IOException {
|
||||||
ProcessBuilder pb = new ProcessBuilder(getProcessCommands());
|
ProcessBuilder pb = getProcessBuilder();
|
||||||
File wrkDir = configuration.getProvidersPath().resolve("bin").toFile();
|
File wrkDir = configuration.getProvidersPath().resolve("bin").toFile();
|
||||||
ProcessBuilder builder = pb.directory(wrkDir).redirectErrorStream(true);
|
ProcessBuilder builder = pb.directory(wrkDir).redirectErrorStream(true);
|
||||||
|
|
||||||
|
@ -199,8 +201,10 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
||||||
return builder.start();
|
return builder.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] getProcessCommands() {
|
private ProcessBuilder getProcessBuilder() {
|
||||||
List<String> commands = new ArrayList<>();
|
List<String> commands = new ArrayList<>();
|
||||||
|
Map<String, String> env = new HashMap<>();
|
||||||
|
|
||||||
commands.add(getCommand());
|
commands.add(getCommand());
|
||||||
commands.add("-v");
|
commands.add("-v");
|
||||||
commands.add("start");
|
commands.add("start");
|
||||||
|
@ -209,10 +213,13 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
||||||
|
|
||||||
if (Boolean.parseBoolean(System.getProperty("auth.server.debug", "false"))) {
|
if (Boolean.parseBoolean(System.getProperty("auth.server.debug", "false"))) {
|
||||||
commands.add("--debug");
|
commands.add("--debug");
|
||||||
if (configuration.getDebugPort() > 0) {
|
|
||||||
commands.add(Integer.toString(configuration.getDebugPort()));
|
String debugPort = configuration.getDebugPort() > 0 ? Integer.toString(configuration.getDebugPort()) : System.getProperty("auth.server.debug.port", "5005");
|
||||||
} else {
|
env.put("DEBUG_PORT", debugPort);
|
||||||
commands.add(System.getProperty("auth.server.debug.port", "5005"));
|
|
||||||
|
String debugSuspend = System.getProperty("auth.server.debug.suspend");
|
||||||
|
if (debugSuspend != null) {
|
||||||
|
env.put("DEBUG_SUSPEND", debugSuspend);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +263,12 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
||||||
|
|
||||||
log.debugf("Quarkus parameters: %s", commands);
|
log.debugf("Quarkus parameters: %s", commands);
|
||||||
|
|
||||||
return commands.toArray(new String[0]);
|
String[] processCommands = commands.toArray(new String[0]);
|
||||||
|
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(processCommands);
|
||||||
|
pb.environment().putAll(env);
|
||||||
|
|
||||||
|
return pb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStorageOptions(StoreProvider storeProvider, List<String> commands) {
|
private void addStorageOptions(StoreProvider storeProvider, List<String> commands) {
|
||||||
|
|
|
@ -616,6 +616,7 @@ public class FAPI1Test extends AbstractClientPoliciesTest {
|
||||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||||
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
||||||
clientConfig.setTlsClientAuthSubjectDn("EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US");
|
clientConfig.setTlsClientAuthSubjectDn("EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US");
|
||||||
|
clientConfig.setAllowRegexPatternComparison(false);
|
||||||
});
|
});
|
||||||
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
||||||
ClientRepresentation client = clientResource.toRepresentation();
|
ClientRepresentation client = clientResource.toRepresentation();
|
||||||
|
|
|
@ -370,6 +370,7 @@ public class FAPICIBATest extends AbstractClientPoliciesTest {
|
||||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||||
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
||||||
clientConfig.setTlsClientAuthSubjectDn("EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US");
|
clientConfig.setTlsClientAuthSubjectDn("EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US");
|
||||||
|
clientConfig.setAllowRegexPatternComparison(false);
|
||||||
setClientAuthMethodNeutralSettings(clientRep);
|
setClientAuthMethodNeutralSettings(clientRep);
|
||||||
});
|
});
|
||||||
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
||||||
|
@ -413,6 +414,7 @@ public class FAPICIBATest extends AbstractClientPoliciesTest {
|
||||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||||
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
||||||
clientConfig.setTlsClientAuthSubjectDn("EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US");
|
clientConfig.setTlsClientAuthSubjectDn("EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US");
|
||||||
|
clientConfig.setAllowRegexPatternComparison(false);
|
||||||
setClientAuthMethodNeutralSettings(clientRep);
|
setClientAuthMethodNeutralSettings(clientRep);
|
||||||
});
|
});
|
||||||
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
||||||
|
@ -442,6 +444,7 @@ public class FAPICIBATest extends AbstractClientPoliciesTest {
|
||||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||||
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
||||||
clientConfig.setTlsClientAuthSubjectDn("EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US");
|
clientConfig.setTlsClientAuthSubjectDn("EMAILADDRESS=contact@keycloak.org, CN=Keycloak Intermediate CA, OU=Keycloak, O=Red Hat, ST=MA, C=US");
|
||||||
|
clientConfig.setAllowRegexPatternComparison(false);
|
||||||
setClientAuthMethodNeutralSettings(clientRep);
|
setClientAuthMethodNeutralSettings(clientRep);
|
||||||
});
|
});
|
||||||
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
||||||
|
|
|
@ -67,12 +67,16 @@ public class MutualTLSClientTest extends AbstractTestRealmKeycloakTest {
|
||||||
exactSubjectDNConfiguration.setServiceAccountsEnabled(Boolean.TRUE);
|
exactSubjectDNConfiguration.setServiceAccountsEnabled(Boolean.TRUE);
|
||||||
exactSubjectDNConfiguration.setRedirectUris(Arrays.asList("https://localhost:8543/auth/realms/master/app/auth"));
|
exactSubjectDNConfiguration.setRedirectUris(Arrays.asList("https://localhost:8543/auth/realms/master/app/auth"));
|
||||||
exactSubjectDNConfiguration.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
|
exactSubjectDNConfiguration.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
|
||||||
exactSubjectDNConfiguration.setAttributes(Collections.singletonMap(X509ClientAuthenticator.ATTR_SUBJECT_DN, EXACT_CERTIFICATE_SUBJECT_DN));
|
Map<String, String> attrs = new HashMap<>();
|
||||||
|
attrs.put(X509ClientAuthenticator.ATTR_SUBJECT_DN, EXACT_CERTIFICATE_SUBJECT_DN);
|
||||||
|
attrs.put(X509ClientAuthenticator.ATTR_ALLOW_REGEX_PATTERN_COMPARISON, "false");
|
||||||
|
exactSubjectDNConfiguration.setAttributes(attrs);
|
||||||
|
|
||||||
ClientRepresentation obbSubjectDNConfiguration = KeycloakModelUtils.createClient(testRealm, OBB_SUBJECT_DN_CLIENT_ID);
|
ClientRepresentation obbSubjectDNConfiguration = KeycloakModelUtils.createClient(testRealm, OBB_SUBJECT_DN_CLIENT_ID);
|
||||||
obbSubjectDNConfiguration.setServiceAccountsEnabled(Boolean.TRUE);
|
obbSubjectDNConfiguration.setServiceAccountsEnabled(Boolean.TRUE);
|
||||||
obbSubjectDNConfiguration.setRedirectUris(Arrays.asList("https://localhost:8543/auth/realms/master/app/auth"));
|
obbSubjectDNConfiguration.setRedirectUris(Arrays.asList("https://localhost:8543/auth/realms/master/app/auth"));
|
||||||
obbSubjectDNConfiguration.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
|
obbSubjectDNConfiguration.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
|
||||||
|
obbSubjectDNConfiguration.setAttributes(Collections.singletonMap(X509ClientAuthenticator.ATTR_ALLOW_REGEX_PATTERN_COMPARISON, "false"));
|
||||||
// ATTR_SUBJECT_DN will be set in the individual tests based on the requested Subject DN Format
|
// ATTR_SUBJECT_DN will be set in the individual tests based on the requested Subject DN Format
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +197,6 @@ public class MutualTLSClientTest extends AbstractTestRealmKeycloakTest {
|
||||||
ClientResource client = ApiUtil.findClientByClientId(testRealm(), OBB_SUBJECT_DN_CLIENT_ID);
|
ClientResource client = ApiUtil.findClientByClientId(testRealm(), OBB_SUBJECT_DN_CLIENT_ID);
|
||||||
ClientRepresentation clientRep = client.toRepresentation();
|
ClientRepresentation clientRep = client.toRepresentation();
|
||||||
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||||
config.setAllowRegexPatternComparison(false);
|
|
||||||
config.setTlsClientAuthSubjectDn(expectedSubjectDN);
|
config.setTlsClientAuthSubjectDn(expectedSubjectDN);
|
||||||
client.update(clientRep);
|
client.update(clientRep);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue