X.509 authentication fixes for FIPS

Closes #14967
This commit is contained in:
mposolda 2022-11-21 12:10:08 +01:00 committed by Marek Posolda
parent 5c2a5fac31
commit 3e9c729f9e
17 changed files with 221 additions and 139 deletions

View file

@ -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

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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
``` ```

View file

@ -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;

View file

@ -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:

View file

@ -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();

View file

@ -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
} }

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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
@ -788,7 +781,14 @@ 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),

View file

@ -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) {

View file

@ -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();

View file

@ -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);

View file

@ -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);