Add FIPS suite to the new tests (#43431)

* Add FIPS test suite to the new tests

Signed-off-by: Lukas Hanusovsky <lhanusov@redhat.com>

* Tweaks to FIPS suite in new test

Signed-off-by: stianst <stianst@gmail.com>

---------

Signed-off-by: Lukas Hanusovsky <lhanusov@redhat.com>
Signed-off-by: stianst <stianst@gmail.com>
Co-authored-by: stianst <stianst@gmail.com>
This commit is contained in:
Lukas Hanusovsky
2025-11-06 14:08:19 +01:00
committed by GitHub
parent b8a8be33aa
commit 768cea1b82
23 changed files with 633 additions and 328 deletions

View File

@@ -8,8 +8,10 @@ if [ $? -ne 0 ]; then
exit 1
fi
STRICT_OPTIONS=""
TESTSUITE_NAME="FipsNonStrictTestSuite"
if [ "$1" = "strict" ]; then
STRICT_OPTIONS="-Dauth.server.fips.mode=strict -Dauth.server.supported.keystore.types=BCFKS -Dauth.server.keystore.type=bcfks -Dauth.server.supported.rsa.key.sizes=2048,3072,4096"
TESTSUITE_NAME="FipsStrictTestSuite"
fi
echo "STRICT_OPTIONS: $STRICT_OPTIONS"
TESTS=`testsuite/integration-arquillian/tests/base/testsuites/suite.sh fips`
@@ -37,3 +39,6 @@ fi
# Profile app-server-wildfly needs to be explicitly set for FIPS tests
./mvnw test -Dsurefire.rerunFailingTestsCount=$SUREFIRE_RERUN_FAILING_COUNT -nsu -B -Pauth-server-quarkus,auth-server-fips140-2,app-server-wildfly -Dcom.redhat.fips=false $STRICT_OPTIONS -Dtest=$TESTS -pl testsuite/integration-arquillian/tests/base 2>&1 | misc/log/trimmer.sh
# New Base Tests
./mvnw package -nsu -B -Dcom.redhat.fips=false -Dtest=$TESTSUITE_NAME -pl tests/base

View File

@@ -55,6 +55,7 @@ import io.quarkus.bootstrap.workspace.WorkspaceModuleId;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.maven.dependency.DependencyBuilder;
import io.quarkus.runtime.configuration.QuarkusConfigFactory;
import org.keycloak.quarkus.runtime.configuration.IgnoredArtifacts;
public class Keycloak {
@@ -123,7 +124,7 @@ public class Keycloak {
addOptionIfNotSet(args, HttpOptions.HTTP_PORT);
addOptionIfNotSet(args, HttpOptions.HTTPS_PORT);
boolean isFipsEnabled = ofNullable(getOptionValue(args, SecurityOptions.FIPS_MODE)).map(FipsMode::valueOf).orElse(FipsMode.DISABLED).isFipsEnabled();
boolean isFipsEnabled = ofNullable(getOptionValue(args, SecurityOptions.FIPS_MODE)).map(FipsMode::valueOfOption).orElse(FipsMode.DISABLED).isFipsEnabled();
if (isFipsEnabled) {
String logLevel = getOptionValue(args, LoggingOptions.LOG_LEVEL);
@@ -241,11 +242,9 @@ public class Keycloak {
.addExclusion("org.jboss.logmanager", "log4j-jboss-logmanager");
if (fipsEnabled) {
serverDependency.addExclusion("org.bouncycastle", "bcprov-jdk18on");
serverDependency.addExclusion("org.bouncycastle", "bcpkix-jdk18on");
serverDependency.addExclusion("org.keycloak", "keycloak-crypto-default");
IgnoredArtifacts.FIPS_ENABLED.stream().map(s -> s.split(":")).forEach(d -> serverDependency.addExclusion(d[0], d[1]));
} else {
serverDependency.addExclusion("org.keycloak", "keycloak-crypto-fips1402");
IgnoredArtifacts.FIPS_DISABLED.stream().map(s -> s.split(":")).forEach(d -> serverDependency.addExclusion(d[0], d[1]));
}
WorkspaceModule.Mutable builder = WorkspaceModule.builder()
@@ -256,12 +255,6 @@ public class Keycloak {
Dependency.pomImport("org.keycloak", "keycloak-quarkus-parent", keycloakVersion))
.addDependency(serverDependency.build());
if (fipsEnabled) {
builder.addDependency(Dependency.of("org.bouncycastle", "bc-fips"));
builder.addDependency(Dependency.of("org.bouncycastle", "bctls-fips"));
builder.addDependency(Dependency.of("org.bouncycastle", "bcpkix-fips"));
}
for (Dependency dependency : dependencies) {
builder.addDependency(dependency);
}

View File

@@ -3,6 +3,7 @@ package org.keycloak.testframework;
import org.keycloak.testframework.admin.AdminClientFactorySupplier;
import org.keycloak.testframework.admin.AdminClientSupplier;
import org.keycloak.testframework.http.SimpleHttpSupplier;
import org.keycloak.testframework.https.ManagedCertificates;
import org.keycloak.testframework.infinispan.InfinispanExternalServerSupplier;
import org.keycloak.testframework.database.DevFileDatabaseSupplier;
import org.keycloak.testframework.database.DevMemDatabaseSupplier;
@@ -57,7 +58,8 @@ public class CoreTestFrameworkExtension implements TestFrameworkExtension {
public Map<Class<?>, String> valueTypeAliases() {
return Map.of(
KeycloakServer.class, "server",
TestDatabase.class, "database"
TestDatabase.class, "database",
ManagedCertificates.class, "certificates"
);
}

View File

@@ -0,0 +1,6 @@
package org.keycloak.testframework.https;
public interface CertificatesConfig {
CertificatesConfigBuilder configure(CertificatesConfigBuilder config);
}

View File

@@ -0,0 +1,20 @@
package org.keycloak.testframework.https;
import org.keycloak.common.util.KeystoreUtil;
public class CertificatesConfigBuilder {
private KeystoreUtil.KeystoreFormat keystoreFormat = KeystoreUtil.KeystoreFormat.JKS;
public CertificatesConfigBuilder() {
}
public CertificatesConfigBuilder keystoreFormat(KeystoreUtil.KeystoreFormat keystoreFormat) {
this.keystoreFormat = keystoreFormat;
return this;
}
public KeystoreUtil.KeystoreFormat getKeystoreFormat() {
return this.keystoreFormat;
}
}

View File

@@ -1,16 +1,27 @@
package org.keycloak.testframework.https;
import org.keycloak.testframework.config.Config;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
import org.keycloak.testframework.injection.SupplierHelpers;
import org.keycloak.testframework.injection.SupplierOrder;
public class CertificatesSupplier implements Supplier<ManagedCertificates, InjectCertificates> {
@Override
public ManagedCertificates getValue(InstanceContext<ManagedCertificates, InjectCertificates> instanceContext) {
return new ManagedCertificates();
CertificatesConfig certConfig = SupplierHelpers.getInstance(instanceContext.getAnnotation().config());
CertificatesConfigBuilder certBuilder = new CertificatesConfigBuilder();
certBuilder = certConfig.configure(certBuilder);
String supplierConfig = Config.getSupplierConfig(ManagedCertificates.class);
if (supplierConfig != null) {
CertificatesConfig certConfigOverride = SupplierHelpers.getInstance(supplierConfig);
certConfigOverride.configure(certBuilder);
}
return new ManagedCertificates(certBuilder);
}
@Override

View File

@@ -0,0 +1,9 @@
package org.keycloak.testframework.https;
public class DefaultCertificatesConfig implements CertificatesConfig {
@Override
public CertificatesConfigBuilder configure(CertificatesConfigBuilder config) {
return config;
}
}

View File

@@ -8,4 +8,6 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectCertificates {
Class<? extends CertificatesConfig> config() default DefaultCertificatesConfig.class;
}

View File

@@ -30,31 +30,35 @@ public class ManagedCertificates {
private KeyStore serverKeyStore;
private KeyStore clientsTrustStore;
private final Path serverKeystorePath;
private final Path clientsTruststorePath;
private final char[] password;
private final static Path KEYSTORES_DIR = Path.of(System.getProperty("java.io.tmpdir"));
private final static Path SERVER_KEYSTORE_FILE_PATH = KEYSTORES_DIR.resolve("kc-testing-server-keystore.jks");
private final static Path CLIENTS_TRUSTSTORE_FILE_PATH = KEYSTORES_DIR.resolve("kc-testing-clients-truststore.jks");
private final static char[] PASSWORD = "password".toCharArray();
private final static String PRV_KEY_ENTRY = "prvKey";
public final static String CERT_ENTRY = "cert";
public ManagedCertificates() throws ManagedCertificatesException {
public ManagedCertificates(CertificatesConfigBuilder configBuilder) throws ManagedCertificatesException {
if (!CryptoIntegration.isInitialised()) {
CryptoIntegration.setProvider(new DefaultCryptoProvider());
}
cryptoProvider = CryptoIntegration.getProvider();
initServerCerts();
serverKeystorePath = KEYSTORES_DIR.resolve("kc-testing-server-keystore" + "." + configBuilder.getKeystoreFormat().getPrimaryExtension());
clientsTruststorePath = KEYSTORES_DIR.resolve("kc-testing-clients-truststore" + "." + configBuilder.getKeystoreFormat().getPrimaryExtension());
password = configBuilder.getKeystoreFormat() == KeystoreUtil.KeystoreFormat.JKS ? "password".toCharArray() : "passwordpassword".toCharArray();
initServerCerts(configBuilder.getKeystoreFormat());
}
public String getKeycloakServerKeyStorePath() {
return SERVER_KEYSTORE_FILE_PATH.toString();
return serverKeystorePath.toString();
}
public String getKeycloakServerKeyStorePassword() {
return String.valueOf(PASSWORD);
return String.valueOf(password);
}
public KeyStore getClientTrustStore() {
@@ -79,26 +83,26 @@ public class ManagedCertificates {
}
}
private void initServerCerts() throws ManagedCertificatesException {
private void initServerCerts(KeystoreUtil.KeystoreFormat keystoreFormat) throws ManagedCertificatesException {
try {
serverKeyStore = cryptoProvider.getKeyStore(KeystoreUtil.KeystoreFormat.JKS);
clientsTrustStore = cryptoProvider.getKeyStore(KeystoreUtil.KeystoreFormat.JKS);
serverKeyStore = cryptoProvider.getKeyStore(keystoreFormat);
clientsTrustStore = cryptoProvider.getKeyStore(keystoreFormat);
if (Files.exists(SERVER_KEYSTORE_FILE_PATH) && Files.exists(CLIENTS_TRUSTSTORE_FILE_PATH)) {
if (Files.exists(serverKeystorePath) && Files.exists(clientsTruststorePath)) {
LOGGER.debugv("Existing Server KeyStore files found in {0}", KEYSTORES_DIR);
loadKeyStore(serverKeyStore, SERVER_KEYSTORE_FILE_PATH, PASSWORD);
loadKeyStore(clientsTrustStore, CLIENTS_TRUSTSTORE_FILE_PATH, PASSWORD);
loadKeyStore(serverKeyStore, serverKeystorePath, password);
loadKeyStore(clientsTrustStore, clientsTruststorePath, password);
} else {
LOGGER.debugv("Generating Server KeyStore files in {0}", KEYSTORES_DIR);
generateKeystore(serverKeyStore, clientsTrustStore, "localhost");
generateKeystoreAndTruststore(serverKeyStore, clientsTrustStore, "localhost");
// store the generated keystore and truststore in a temp folder
try (FileOutputStream fos = new FileOutputStream(SERVER_KEYSTORE_FILE_PATH.toFile())) {
serverKeyStore.store(fos, PASSWORD);
try (FileOutputStream fos = new FileOutputStream(serverKeystorePath.toFile())) {
serverKeyStore.store(fos, password);
}
try (FileOutputStream fos = new FileOutputStream(CLIENTS_TRUSTSTORE_FILE_PATH.toFile())) {
clientsTrustStore.store(fos, PASSWORD);
try (FileOutputStream fos = new FileOutputStream(clientsTruststorePath.toFile())) {
clientsTrustStore.store(fos, password);
}
}
} catch (Exception e) {
@@ -112,7 +116,7 @@ public class ManagedCertificates {
}
}
private void generateKeystore(KeyStore keyStore, KeyStore trustStore, String subject) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateException, IOException, KeyStoreException, Exception {
private void generateKeystoreAndTruststore(KeyStore keyStore, KeyStore trustStore, String subject) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateException, IOException, KeyStoreException, Exception {
keyStore.load(null);
trustStore.load(null);
@@ -121,7 +125,7 @@ public class ManagedCertificates {
keyStore.setCertificateEntry(CERT_ENTRY, cert);
trustStore.setCertificateEntry(CERT_ENTRY, cert);
keyStore.setKeyEntry(PRV_KEY_ENTRY, keyPair.getPrivate(), PASSWORD, new X509Certificate[]{cert});
keyStore.setKeyEntry(PRV_KEY_ENTRY, keyPair.getPrivate(), password, new X509Certificate[]{cert});
}
private KeyPair generateKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException {
@@ -129,10 +133,10 @@ public class ManagedCertificates {
}
private X509Certificate generateX509CertificateCertificate(KeyPair keyPair, String subject) throws Exception {
// generate a v1 certificate
// generate a v1 root certificate authority certificate
X509Certificate caCert = cryptoProvider.getCertificateUtils().generateV1SelfSignedCertificate(keyPair, subject);
// generate a v3 certificate
// generate a v3 certificate chain
return cryptoProvider.getCertificateUtils().generateV3Certificate(keyPair, keyPair.getPrivate(), caCert, subject);
}
}

View File

@@ -9,6 +9,9 @@ public class SuiteSupport {
private static SuiteConfig suiteConfig = new SuiteConfig();
public static SuiteConfig startSuite() {
if (suiteConfig == null) {
suiteConfig = new SuiteConfig();
}
return suiteConfig;
}
@@ -22,7 +25,12 @@ public class SuiteSupport {
public static class SuiteConfig {
public SuiteConfig registerServerConfig(Class<? extends KeycloakServerConfig> serverConfig) {
SuiteConfigSource.set("kc.test.server.config", serverConfig.getName());
registerSupplierConfig("server", serverConfig);
return this;
}
public SuiteConfig registerSupplierConfig(String supplierValueType, Class<?> supplierConfig) {
SuiteConfigSource.set("kc.test." + supplierValueType + ".config", supplierConfig.getName());
return this;
}

View File

@@ -115,8 +115,7 @@ public class KeycloakServerConfigBuilder {
public boolean tlsEnabled() {
return tlsEnabled ;
}
public KeycloakServerConfigBuilder cacheConfigFile(String resourcePath) {
try {
Path p = Paths.get(Objects.requireNonNull(getClass().getResource(resourcePath)).toURI());

26
tests/FIPS_104-2.md Normal file
View File

@@ -0,0 +1,26 @@
## FIPS 140-2 testing
### Integration tests
On a FIPS enabled platform with FIPS enabled OpenJDK 21, you can run this to test against a Keycloak server with FIPS 140-2 integration enabled.
```
./mvnw clean package -nsu -B -f tests/base/pom.xml \
-Dcom.redhat.fips=false \
-Dtest=FipsStrictTestSuite,FipsNonStrictTestSuite
```
FIPS test suite configuration (strict and non-strict):
[FipsStrictTestSuite](./base/src/test/java/org/keycloak/tests/suites/FipsStrictTestSuite.java),
[FipsNonStrictTestSuite](./base/src/test/java/org/keycloak/tests/suites/FipsNonStrictTestSuite.java)
NOTE 1: The property `com.redhat.fips` is required for disabling FIPS in JVM, on a FIPS enabled environment (operating system or container), where the test suite is executed.
NOTE 3: Example of the server startup log, running in an environment (both -> JVM and operating system/container), where FIPS is enabled:
```
2022-10-11 19:34:29,521 DEBUG [org.keycloak.common.crypto.CryptoIntegration] (main) Using the crypto provider: org.keycloak.crypto.fips.FIPS1402Provider
2022-10-11 19:34:31,072 TRACE [org.keycloak.common.crypto.CryptoIntegration] (main) Java security providers: [
KC(BCFIPS version 1.000203, FIPS-JVM: enabled) version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider,
BCFIPS version 1.000203 - class org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider,
BCJSSE version 1.001202 - class org.bouncycastle.jsse.provider.BouncyCastleJsseProvider,
]
```

View File

@@ -15,9 +15,11 @@
* limitations under the License.
*/
package org.keycloak.testsuite.admin;
package org.keycloak.tests.admin;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.common.Version;
import org.keycloak.crypto.Algorithm;
import org.keycloak.keys.Attributes;
@@ -25,24 +27,26 @@ import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
import org.keycloak.keys.KeyProvider;
import org.keycloak.representations.idm.ComponentTypeRepresentation;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.info.ProviderRepresentation;
import org.keycloak.representations.info.ServerInfoRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.util.KeyUtils;
import org.keycloak.testsuite.util.KeystoreUtils;
import org.keycloak.testframework.annotations.InjectAdminClient;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.tests.utils.Assert;
import org.keycloak.tests.utils.FipsUtils;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ServerInfoTest extends AbstractKeycloakTest {
@KeycloakIntegrationTest
public class ServerInfoTest {
@InjectAdminClient
Keycloak adminClient;
@Test
public void testServerInfo() {
@@ -56,18 +60,21 @@ public class ServerInfoTest extends AbstractKeycloakTest {
assertNotNull(info.getThemes());
assertNotNull(info.getThemes().get("account"));
Assert.assertNames(info.getThemes().get("account"), "base", "keycloak.v3", "custom-account-provider");
Assert.assertNames(info.getThemes().get("account"), "base", "keycloak.v3");
Assert.assertNames(info.getThemes().get("admin"), "base", "keycloak.v2");
Assert.assertNames(info.getThemes().get("email"), "base", "keycloak");
Assert.assertNames(info.getThemes().get("login"), "address", "base", "environment-agnostic", "keycloak", "keycloak.v2", "organization", "themeconfig");
Assert.assertNames(info.getThemes().get("login"), "base", "keycloak", "keycloak.v2");
Assert.assertNames(info.getThemes().get("welcome"), "keycloak");
assertNotNull(info.getEnums());
assertNotNull(info.getMemoryInfo());
assertNotNull(info.getSystemInfo());
FipsUtils fipsUtils = FipsUtils.create(info);
assertNotNull(info.getCryptoInfo());
Assert.assertNames(info.getCryptoInfo().getSupportedKeystoreTypes(), KeystoreUtils.getSupportedKeystoreTypes());
Assert.assertNames(info.getCryptoInfo().getSupportedKeystoreTypes(), fipsUtils.getExpectedSupportedKeyStoreTypes());
Assert.assertNames(info.getCryptoInfo().getClientSignatureSymmetricAlgorithms(), Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
Assert.assertNames(info.getCryptoInfo().getClientSignatureAsymmetricAlgorithms(),
Algorithm.ES256, Algorithm.ES384, Algorithm.ES512,
@@ -83,7 +90,7 @@ public class ServerInfoTest extends AbstractKeycloakTest {
.stream()
.filter(configProp -> Attributes.KEY_SIZE_KEY.equals(configProp.getName()))
.findFirst().orElseThrow(() -> new RuntimeException("Not found provider with ID 'rsa-generated'"));
Assert.assertNames(keySizeRep.getOptions(), KeyUtils.getExpectedSupportedRsaKeySizes());
Assert.assertNames(keySizeRep.getOptions(), fipsUtils.getExpectedSupportedRsaKeySizes());
assertEquals(Version.VERSION, info.getSystemInfo().getVersion());
assertNotNull(info.getSystemInfo().getServerTime());
@@ -91,10 +98,6 @@ public class ServerInfoTest extends AbstractKeycloakTest {
Map<String, ProviderRepresentation> jpaProviders = info.getProviders().get("connectionsJpa").getProviders();
ProviderRepresentation jpaProvider = jpaProviders.values().iterator().next();
log.infof("JPA Connections provider info: %s", jpaProvider.getOperationalInfo());
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
Assertions.assertNotNull(jpaProvider.getOperationalInfo());
}
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* 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.tests.admin.client;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientAttributeCertificateResource;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.def.DefaultCryptoProvider;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.KeyStoreConfig;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.testframework.annotations.InjectAdminClient;
import org.keycloak.testframework.annotations.InjectAdminEvents;
import org.keycloak.testframework.annotations.InjectClient;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.events.AdminEvents;
import org.keycloak.testframework.realm.ManagedClient;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.tests.utils.admin.GenerateKeystoreForTestUtil;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
@KeycloakIntegrationTest
public class CredentialsTest {
@InjectRealm
ManagedRealm managedRealm;
@InjectAdminClient
Keycloak adminClient;
@InjectClient(attachTo = "account")
ManagedClient managedClient;
@InjectAdminEvents
AdminEvents adminEvents;
@BeforeAll
public static void init() {
if(!CryptoIntegration.isInitialised()) {
CryptoIntegration.setProvider(new DefaultCryptoProvider());
}
}
@Test
public void testGetAndRegenerateSecret() {
CredentialRepresentation oldCredential = managedClient.admin().getSecret();
CredentialRepresentation newCredential = managedClient.admin().generateNewSecret();
CredentialRepresentation secretRep = new CredentialRepresentation();
secretRep.setType(CredentialRepresentation.SECRET);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.clientGenerateSecretPath(managedClient.getId()), secretRep, ResourceType.CLIENT);
assertNotNull(oldCredential);
assertNotNull(newCredential);
assertNotEquals(newCredential.getValue(), oldCredential.getValue());
assertEquals(newCredential.getValue(), managedClient.admin().getSecret().getValue());
}
@Test
public void testGetAndRegenerateRegistrationAccessToken() {
ClientRepresentation rep = managedClient.admin().toRepresentation();
String oldToken = rep.getRegistrationAccessToken();
String newToken = managedClient.admin().regenerateRegistrationAccessToken().getRegistrationAccessToken();
assertNull(oldToken); // registration access token not saved in ClientRep
assertNotNull(newToken); // it's only available via regenerateRegistrationAccessToken()
assertNull(managedClient.admin().toRepresentation().getRegistrationAccessToken());
// Test event
ClientRepresentation testedRep = new ClientRepresentation();
testedRep.setClientId(rep.getClientId());
testedRep.setRegistrationAccessToken(newToken);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.clientRegenerateRegistrationAccessTokenPath(managedClient.getId()), testedRep, ResourceType.CLIENT);
}
@Test
public void testGetCertificateResource() {
ClientAttributeCertificateResource certRsc = managedClient.admin().getCertficateResource("jwt.credential");
CertificateRepresentation cert = certRsc.generate();
CertificateRepresentation certFromGet = certRsc.getKeyInfo();
assertEquals(cert.getCertificate(), certFromGet.getCertificate());
assertEquals(cert.getPrivateKey(), certFromGet.getPrivateKey());
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.clientCertificateGenerateSecretPath(managedClient.getId(), "jwt.credential"), cert, ResourceType.CLIENT);
}
@Test
public void testUploadKeyAndCertificate() throws Exception {
String certificate2 = "MIICnTCCAYUCBgFPPQDGxTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE4NTAwNVoXDTI1MDgxNzE4NTE0NVowEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMw3PaBffWxgS2PYSDDBp6As+cNvv9kt2C4f/RDAGmvSIHPFev9kuQiKs3Oaws3ZsV4JG3qHEuYgnh9W4vfe3DwNwtD1bjL5FYBhPBFTw0lAQECYxaBHnkjHwUKp957FqdSPPICm3LjmTcEdlH+9dpp9xHCMbbiNiWDzWI1xSxC8Fs2d0hwz1sd+Q4QeTBPIBWcPM+ICZtNG5MN+ORfayu4X+Me5d0tXG2fQO//rAevk1i5IFjKZuOjTwyKB5SJIY4b8QTeg0g/50IU7Ht00Pxw6CK02dHS+FvXHasZlD3ckomqCDjStTBWdhJo5dST0CbOqalkkpLlCCbGA1yEQRsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUIMeJ+EAo8eNpCG/nXImacjrKakbFnZYBGD/gqeTGaZynkX+jgBSructTHR83zSH+yELEhsAy+3BfK4EEihp+PEcRnK2fASVkHste8AQ7rlzC+HGGirlwrVhWCdizNUCGK80DE537IZ7nmZw6LFG9P5/Q2MvCsOCYjRUvMkukq6TdXBXR9tETwZ+0gpSfsOxjj0ZF7ftTRUSzx4rFfcbM9fRNdVizdOuKGc8HJPA5lLOxV6CyaYIvi3y5RlQI1OHeS34lE4w9CNPRFa/vdxXvN7ClyzA0HMFNWxBN7pC/Ht/FbhSvaAagJBHg+vCrcY5C26Oli7lAglf/zZrwUPs0w==";
ClientAttributeCertificateResource certRsc = managedClient.admin().getCertficateResource("jwt.credential");
KeystoreUtil.KeystoreFormat preferredKeystoreType = KeystoreUtil.KeystoreFormat.valueOf(adminClient.serverInfo().getInfo().getCryptoInfo().getSupportedKeystoreTypes().get(0));
// Generate keystore file and upload privateKey and certificate from it as JKS store (or eventually PKCS12 or BCFKS store according to which one is preferred type)
GenerateKeystoreForTestUtil.KeystoreInfo generatedKeystore = GenerateKeystoreForTestUtil.generateKeystore(preferredKeystoreType, "clientkey", "storepass", "keypass");
MultipartFormDataOutput keyCertForm = new MultipartFormDataOutput();
keyCertForm.addFormData("keystoreFormat", preferredKeystoreType.toString(), MediaType.TEXT_PLAIN_TYPE);
keyCertForm.addFormData("keyAlias", "clientkey", MediaType.TEXT_PLAIN_TYPE);
keyCertForm.addFormData("keyPassword", "keypass", MediaType.TEXT_PLAIN_TYPE);
keyCertForm.addFormData("storePassword", "storepass", MediaType.TEXT_PLAIN_TYPE);
FileInputStream fs = new FileInputStream(generatedKeystore.getKeystoreFile());
byte [] content = fs.readAllBytes();
fs.close();
keyCertForm.addFormData("file", content, MediaType.APPLICATION_OCTET_STREAM_TYPE);
CertificateRepresentation cert = certRsc.uploadJks(keyCertForm);
// Returned cert is not the new state but rather what was extracted from inputs
assertNotNull(cert, "cert not null");
assertEquals(generatedKeystore.getCertificateInfo().getCertificate(), cert.getCertificate(), "cert properly extracted");
assertEquals(generatedKeystore.getCertificateInfo().getPrivateKey(), cert.getPrivateKey(), "privateKey properly extracted");
// Get the certificate - to make sure cert was properly updated
cert = certRsc.getKeyInfo();
assertEquals(generatedKeystore.getCertificateInfo().getCertificate(), cert.getCertificate(), "cert properly set");
assertEquals(generatedKeystore.getCertificateInfo().getPrivateKey(), cert.getPrivateKey(), "privateKey properly set");
// Upload a different certificate via /upload-certificate, privateKey should be nullified
MultipartFormDataOutput form = new MultipartFormDataOutput();
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE);
form.addFormData("file", certificate2.getBytes(StandardCharsets.US_ASCII), MediaType.APPLICATION_OCTET_STREAM_TYPE);
cert = certRsc.uploadJksCertificate(form);
assertNotNull(cert, "cert not null");
assertEquals(certificate2, cert.getCertificate(), "cert properly extracted");
assertNull(cert.getPrivateKey(), "privateKey not included");
// Get the certificate - to make sure cert was properly updated, and privateKey is null
cert = certRsc.getKeyInfo();
assertEquals(certificate2, cert.getCertificate(), "cert properly set");
assertNull(cert.getPrivateKey(), "privateKey nullified");
// Re-upload the private key
certRsc.uploadJks(keyCertForm);
// Upload certificate as PEM via /upload - nullifies the private key
form = new MultipartFormDataOutput();
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE);
form.addFormData("file", certificate2.getBytes(StandardCharsets.US_ASCII), MediaType.APPLICATION_OCTET_STREAM_TYPE);
cert = certRsc.uploadJks(form);
assertNotNull(cert, "cert not null");
assertEquals(certificate2, cert.getCertificate(), "cert properly extracted");
assertNull(cert.getPrivateKey(), "privateKey not included");
// Get the certificate again - to make sure cert is set, and privateKey is null
cert = certRsc.getKeyInfo();
assertEquals(certificate2, cert.getCertificate(), "cert properly set");
assertNull(cert.getPrivateKey(), "privateKey nullified");
// Upload certificate with header - should be stored without header
form = new MultipartFormDataOutput();
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE);
String certificate2WithHeaders = PemUtils.BEGIN_CERT + "\n" + certificate2 + "\n" + PemUtils.END_CERT;
form.addFormData("file", certificate2WithHeaders.getBytes(StandardCharsets.US_ASCII), MediaType.APPLICATION_OCTET_STREAM_TYPE);
cert = certRsc.uploadJks(form);
assertNotNull(cert, "cert not null");
assertEquals(certificate2, cert.getCertificate(),"cert properly extracted");
assertNull(cert.getPrivateKey(), "privateKey not included");
// Get the certificate again - to make sure cert is set, and privateKey is null
cert = certRsc.getKeyInfo();
assertEquals(certificate2, cert.getCertificate(), "cert properly set");
assertNull(cert.getPrivateKey(), "privateKey nullified");
}
@Test
public void testDownloadKeystore() throws Exception {
ClientAttributeCertificateResource certRsc = managedClient.admin().getCertficateResource("jwt.credential");
// generate a key pair first
CertificateRepresentation certrep = certRsc.generate();
KeystoreUtil.KeystoreFormat preferredKeystoreType = KeystoreUtil.KeystoreFormat.valueOf(adminClient.serverInfo().getInfo().getCryptoInfo().getSupportedKeystoreTypes().get(0));
// download the key and certificate
KeyStoreConfig config = new KeyStoreConfig();
config.setFormat(preferredKeystoreType.toString());
config.setKeyAlias("alias");
config.setKeyPassword("keyPass");
config.setStorePassword("storePass");
byte[] result = certRsc.getKeystore(config);
KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(preferredKeystoreType);
keyStore.load(new ByteArrayInputStream(result), "storePass".toCharArray());
Key key = keyStore.getKey("alias", "keyPass".toCharArray());
Certificate cert = keyStore.getCertificate("alias");
assertInstanceOf(X509Certificate.class, cert, "Certificat is X509");
String keyPem = KeycloakModelUtils.getPemFromKey(key);
String certPem = KeycloakModelUtils.getPemFromCertificate((X509Certificate) cert);
assertEquals(certrep.getPrivateKey(), keyPem, "key match");
assertEquals(certrep.getCertificate(), certPem, "cert match");
}
@Test
public void testGenerateAndDownloadKeystore() throws Exception {
ClientAttributeCertificateResource certRsc = managedClient.admin().getCertficateResource("jwt.credential");
// generate a key pair first
CertificateRepresentation firstcert = certRsc.generate();
KeystoreUtil.KeystoreFormat preferredKeystoreType = KeystoreUtil.KeystoreFormat.valueOf(adminClient.serverInfo().getInfo().getCryptoInfo().getSupportedKeystoreTypes().get(0));
KeyStoreConfig config = new KeyStoreConfig();
config.setFormat(preferredKeystoreType.toString());
config.setKeyAlias("alias");
config.setKeyPassword("keyPass");
config.setStorePassword("storePass");
config.setKeySize(4096);
config.setValidity(3);
byte[] result = certRsc.generateAndGetKeystore(config);
KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(preferredKeystoreType);
keyStore.load(new ByteArrayInputStream(result), "storePass".toCharArray());
Key key = keyStore.getKey("alias", "keyPass".toCharArray());
Certificate cert = keyStore.getCertificate("alias");
assertInstanceOf(X509Certificate.class, cert, "Certificat is X509");
String keyPem = KeycloakModelUtils.getPemFromKey(key);
String certPem = KeycloakModelUtils.getPemFromCertificate((X509Certificate) cert);
assertNotEquals(firstcert.getPrivateKey(), keyPem, "new key generated");
assertNotEquals(firstcert.getCertificate(), certPem, "new cert generated");
}
}

View File

@@ -19,7 +19,6 @@ package org.keycloak.tests.admin.realm;
import jakarta.ws.rs.NotFoundException;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.util.Strings;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.events.admin.OperationType;
@@ -126,9 +125,9 @@ public class RealmDefaultConfigTest extends AbstractRealmTest {
role = managedRealm.admin().roles().get("test").toRepresentation();
assertNotNull(role);
managedRealm.admin().roles().get(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + Strings.toLowerCase(managedRealm.getName())).addComposites(Collections.singletonList(role));
managedRealm.admin().roles().get(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + managedRealm.getName().toLowerCase()).addComposites(Collections.singletonList(role));
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.roleResourceCompositesPath(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + Strings.toLowerCase(managedRealm.getName())), Collections.singletonList(role), ResourceType.REALM_ROLE);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.roleResourceCompositesPath(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + managedRealm.getName().toLowerCase()), Collections.singletonList(role), ResourceType.REALM_ROLE);
managedRealm.admin().roles().deleteRole("test");
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.roleResourcePath("test"), ResourceType.REALM_ROLE);

View File

@@ -0,0 +1,53 @@
package org.keycloak.tests.suites;
import org.junit.platform.suite.api.AfterSuite;
import org.junit.platform.suite.api.BeforeSuite;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
import org.keycloak.common.Profile;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.testframework.https.CertificatesConfig;
import org.keycloak.testframework.https.CertificatesConfigBuilder;
import org.keycloak.testframework.injection.SuiteSupport;
import org.keycloak.testframework.server.KeycloakServerConfig;
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
import org.keycloak.tests.admin.ServerInfoTest;
import org.keycloak.tests.admin.client.CredentialsTest;
@Suite
@SelectClasses({CredentialsTest.class, ServerInfoTest.class})
public class FipsNonStrictTestSuite {
@BeforeSuite
public static void beforeSuite() {
SuiteSupport.startSuite()
.registerServerConfig(FipsNonStrictServerConfig.class)
.registerSupplierConfig("certificates", FipsNonStrictCertificatesConfig.class);;
}
@AfterSuite
public static void afterSuite() {
SuiteSupport.stopSuite();
}
public static class FipsNonStrictServerConfig implements KeycloakServerConfig {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
return config.features(Profile.Feature.FIPS).tlsEnabled(true)
.option("fips-mode", "non-strict")
.dependency("org.bouncycastle", "bc-fips")
.dependency("org.bouncycastle", "bctls-fips")
.dependency("org.bouncycastle", "bcpkix-fips")
.dependency("org.bouncycastle", "bcutil-fips");
}
}
public static class FipsNonStrictCertificatesConfig implements CertificatesConfig {
@Override
public CertificatesConfigBuilder configure(CertificatesConfigBuilder config) {
return config.keystoreFormat(KeystoreUtil.KeystoreFormat.PKCS12);
}
}
}

View File

@@ -0,0 +1,56 @@
package org.keycloak.tests.suites;
import org.junit.platform.suite.api.AfterSuite;
import org.junit.platform.suite.api.BeforeSuite;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
import org.keycloak.common.Profile;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.testframework.https.CertificatesConfig;
import org.keycloak.testframework.https.CertificatesConfigBuilder;
import org.keycloak.testframework.injection.SuiteSupport;
import org.keycloak.testframework.server.KeycloakServerConfig;
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
import org.keycloak.tests.admin.ServerInfoTest;
import org.keycloak.tests.admin.client.CredentialsTest;
@Suite
@SelectClasses({CredentialsTest.class, ServerInfoTest.class})
public class FipsStrictTestSuite {
@BeforeSuite
public static void beforeSuite() {
SuiteSupport.startSuite()
.registerServerConfig(FipsStrictServerConfig.class)
.registerSupplierConfig("certificates", FipsStrictCertificatesConfig.class);
}
@AfterSuite
public static void afterSuite() {
SuiteSupport.stopSuite();
}
public static class FipsStrictServerConfig implements KeycloakServerConfig {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
return config.features(Profile.Feature.FIPS).tlsEnabled(true)
.option("fips-mode", "strict")
.option("spi-password-hashing-pbkdf2-max-padding-length", "14")
.option("spi-password-hashing-pbkdf2-sha256-max-padding-length", "14")
.option("spi-password-hashing-pbkdf2-sha512-max-padding-length", "14")
.dependency("org.bouncycastle", "bc-fips")
.dependency("org.bouncycastle", "bctls-fips")
.dependency("org.bouncycastle", "bcpkix-fips")
.dependency("org.bouncycastle", "bcutil-fips");
}
}
public static class FipsStrictCertificatesConfig implements CertificatesConfig {
@Override
public CertificatesConfigBuilder configure(CertificatesConfigBuilder config) {
return config.keystoreFormat(KeystoreUtil.KeystoreFormat.BCFKS);
}
}
}

View File

@@ -2,10 +2,9 @@ package org.keycloak.tests.suites;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
import org.keycloak.tests.admin.AdminHeadersTest;
import org.keycloak.tests.admin.client.CredentialsTest;
@Suite
// TODO: Select relevant test classes or packages once they have been migrated
@SelectClasses(AdminHeadersTest.class)
@SelectClasses({CredentialsTest.class})
public class JDKTestSuite {
}

View File

@@ -0,0 +1,32 @@
package org.keycloak.tests.utils;
import org.keycloak.representations.info.ServerInfoRepresentation;
public class FipsUtils {
private final String cryptoProvider;
private FipsUtils(ServerInfoRepresentation info) {
this.cryptoProvider = info.getCryptoInfo().getCryptoProvider();
}
public static FipsUtils create(ServerInfoRepresentation info) {
return new FipsUtils(info);
}
public String[] getExpectedSupportedKeyStoreTypes() {
return switch (cryptoProvider) {
case "FIPS1402Provider" -> new String[] { "PKCS12", "BCFKS" };
case "Fips1402StrictCryptoProvider" -> new String[] { "BCFKS" };
default -> new String[] { "JKS", "PKCS12", "BCFKS" };
};
}
public String[] getExpectedSupportedRsaKeySizes() {
return switch (cryptoProvider) {
case "Fips1402StrictCryptoProvider" -> new String[]{"2048", "3072", "4096"};
default -> new String[]{"1024", "2048", "3072", "4096"};
};
}
}

View File

@@ -0,0 +1,67 @@
package org.keycloak.tests.utils.admin;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.representations.idm.CertificateRepresentation;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
public class GenerateKeystoreForTestUtil {
private final static Path KEYSTORES_DIR = Path.of(System.getProperty("java.io.tmpdir"));
public static KeystoreInfo generateKeystore(org.keycloak.common.util.KeystoreUtil.KeystoreFormat keystoreType, String subject, String keystorePassword, String keyPassword) throws Exception {
return generateKeystore(keystoreType, subject, keystorePassword, keyPassword, KeyUtils.generateRsaKeyPair(2048));
}
public static KeystoreInfo generateKeystore(org.keycloak.common.util.KeystoreUtil.KeystoreFormat keystoreType, String subject, String keystorePassword, String keyPassword, KeyPair keyPair) throws Exception {
X509Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject);
return generateKeystore(keystoreType, subject, keystorePassword, keyPassword, keyPair.getPrivate(), certificate);
}
public static KeystoreInfo generateKeystore(org.keycloak.common.util.KeystoreUtil.KeystoreFormat keystoreType,
String subject, String keystorePassword, String keyPassword, PrivateKey privKey, Certificate certificate) throws Exception {
String fileName = "keystore." + keystoreType.getPrimaryExtension();
KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(keystoreType);
keyStore.load(null, null);
Certificate[] chain = {certificate};
keyStore.setKeyEntry(subject, privKey, keyPassword.trim().toCharArray(), chain);
File file = KEYSTORES_DIR.resolve(fileName).toFile();
keyStore.store(new FileOutputStream(file), keystorePassword.trim().toCharArray());
CertificateRepresentation certRep = new CertificateRepresentation();
certRep.setPrivateKey(PemUtils.encodeKey(privKey));
certRep.setPublicKey(PemUtils.encodeKey(certificate.getPublicKey()));
certRep.setCertificate(PemUtils.encodeCertificate(certificate));
return new KeystoreInfo(certRep, file);
}
public static class KeystoreInfo {
private final CertificateRepresentation certificateInfo;
private final File keystoreFile;
private KeystoreInfo(CertificateRepresentation certificateInfo, File keystoreFile) {
this.certificateInfo = certificateInfo;
this.keystoreFile = keystoreFile;
}
public CertificateRepresentation getCertificateInfo() {
return certificateInfo;
}
public File getKeystoreFile() {
return keystoreFile;
}
}
}

View File

@@ -1,259 +0,0 @@
/*
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* 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.testsuite.admin.client;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
import org.junit.Before;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.keycloak.admin.client.resource.ClientAttributeCertificateResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.PemUtils;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.KeyStoreConfig;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.testsuite.AbstractClientTest;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.KeystoreUtils;
import jakarta.ws.rs.core.MediaType;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.nio.charset.Charset;
import java.security.Key;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class CredentialsTest extends AbstractClientTest {
private ClientResource accountClient;
private String accountClientDbId;
@Before
public void init() {
accountClient = findClientResourceById("account");
accountClientDbId = accountClient.toRepresentation().getId();
}
@Test
public void testGetAndRegenerateSecret() {
CredentialRepresentation oldCredential = accountClient.getSecret();
CredentialRepresentation newCredential = accountClient.generateNewSecret();
CredentialRepresentation secretRep = new CredentialRepresentation();
secretRep.setType(CredentialRepresentation.SECRET);
assertAdminEvents.assertEvent(getRealmId(), OperationType.ACTION, AdminEventPaths.clientGenerateSecretPath(accountClientDbId), secretRep, ResourceType.CLIENT);
assertNotNull(oldCredential);
assertNotNull(newCredential);
assertNotEquals(newCredential.getValue(), oldCredential.getValue());
assertEquals(newCredential.getValue(), accountClient.getSecret().getValue());
}
@Test
public void testGetAndRegenerateRegistrationAccessToken() {
ClientRepresentation rep = accountClient.toRepresentation();
String oldToken = rep.getRegistrationAccessToken();
String newToken = accountClient.regenerateRegistrationAccessToken().getRegistrationAccessToken();
assertNull(oldToken); // registration access token not saved in ClientRep
assertNotNull(newToken); // it's only available via regenerateRegistrationAccessToken()
assertNull(accountClient.toRepresentation().getRegistrationAccessToken());
// Test event
ClientRepresentation testedRep = new ClientRepresentation();
testedRep.setClientId(rep.getClientId());
testedRep.setRegistrationAccessToken(newToken);
assertAdminEvents.assertEvent(getRealmId(), OperationType.ACTION, AdminEventPaths.clientRegenerateRegistrationAccessTokenPath(accountClientDbId), testedRep, ResourceType.CLIENT);
}
@Test
public void testGetCertificateResource() {
ClientAttributeCertificateResource certRsc = accountClient.getCertficateResource("jwt.credential");
CertificateRepresentation cert = certRsc.generate();
CertificateRepresentation certFromGet = certRsc.getKeyInfo();
assertEquals(cert.getCertificate(), certFromGet.getCertificate());
assertEquals(cert.getPrivateKey(), certFromGet.getPrivateKey());
assertAdminEvents.assertEvent(getRealmId(), OperationType.ACTION, AdminEventPaths.clientCertificateGenerateSecretPath(accountClientDbId, "jwt.credential"), cert, ResourceType.CLIENT);
}
@Test
public void testUploadKeyAndCertificate() throws Exception {
TemporaryFolder folder = new TemporaryFolder();
folder.create();
try {
String certificate2 = "MIICnTCCAYUCBgFPPQDGxTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE4NTAwNVoXDTI1MDgxNzE4NTE0NVowEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMw3PaBffWxgS2PYSDDBp6As+cNvv9kt2C4f/RDAGmvSIHPFev9kuQiKs3Oaws3ZsV4JG3qHEuYgnh9W4vfe3DwNwtD1bjL5FYBhPBFTw0lAQECYxaBHnkjHwUKp957FqdSPPICm3LjmTcEdlH+9dpp9xHCMbbiNiWDzWI1xSxC8Fs2d0hwz1sd+Q4QeTBPIBWcPM+ICZtNG5MN+ORfayu4X+Me5d0tXG2fQO//rAevk1i5IFjKZuOjTwyKB5SJIY4b8QTeg0g/50IU7Ht00Pxw6CK02dHS+FvXHasZlD3ckomqCDjStTBWdhJo5dST0CbOqalkkpLlCCbGA1yEQRsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUIMeJ+EAo8eNpCG/nXImacjrKakbFnZYBGD/gqeTGaZynkX+jgBSructTHR83zSH+yELEhsAy+3BfK4EEihp+PEcRnK2fASVkHste8AQ7rlzC+HGGirlwrVhWCdizNUCGK80DE537IZ7nmZw6LFG9P5/Q2MvCsOCYjRUvMkukq6TdXBXR9tETwZ+0gpSfsOxjj0ZF7ftTRUSzx4rFfcbM9fRNdVizdOuKGc8HJPA5lLOxV6CyaYIvi3y5RlQI1OHeS34lE4w9CNPRFa/vdxXvN7ClyzA0HMFNWxBN7pC/Ht/FbhSvaAagJBHg+vCrcY5C26Oli7lAglf/zZrwUPs0w==";
ClientAttributeCertificateResource certRsc = accountClient.getCertficateResource("jwt.credential");
KeystoreUtil.KeystoreFormat preferredKeystoreType = KeystoreUtils.getPreferredKeystoreType();
// Generate keystore file and upload privateKey and certificate from it as JKS store (or eventually PKCS12 or BCFKS store according to which one is preferred type)
KeystoreUtils.KeystoreInfo generatedKeystore = KeystoreUtils.generateKeystore(folder, preferredKeystoreType, "clientkey", "storepass", "keypass");
MultipartFormDataOutput keyCertForm = new MultipartFormDataOutput();
keyCertForm.addFormData("keystoreFormat", preferredKeystoreType.toString(), MediaType.TEXT_PLAIN_TYPE);
keyCertForm.addFormData("keyAlias", "clientkey", MediaType.TEXT_PLAIN_TYPE);
keyCertForm.addFormData("keyPassword", "keypass", MediaType.TEXT_PLAIN_TYPE);
keyCertForm.addFormData("storePassword", "storepass", MediaType.TEXT_PLAIN_TYPE);
FileInputStream fs = new FileInputStream(generatedKeystore.getKeystoreFile());
byte [] content = fs.readAllBytes();
fs.close();
keyCertForm.addFormData("file", content, MediaType.APPLICATION_OCTET_STREAM_TYPE);
CertificateRepresentation cert = certRsc.uploadJks(keyCertForm);
// Returned cert is not the new state but rather what was extracted from inputs
assertNotNull("cert not null", cert);
assertEquals("cert properly extracted", generatedKeystore.getCertificateInfo().getCertificate(), cert.getCertificate());
assertEquals("privateKey properly extracted", generatedKeystore.getCertificateInfo().getPrivateKey(), cert.getPrivateKey());
// Get the certificate - to make sure cert was properly updated
cert = certRsc.getKeyInfo();
assertEquals("cert properly set", generatedKeystore.getCertificateInfo().getCertificate(), cert.getCertificate());
assertEquals("privateKey properly set", generatedKeystore.getCertificateInfo().getPrivateKey(), cert.getPrivateKey());
// Upload a different certificate via /upload-certificate, privateKey should be nullified
MultipartFormDataOutput form = new MultipartFormDataOutput();
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE);
form.addFormData("file", certificate2.getBytes(Charset.forName("ASCII")), MediaType.APPLICATION_OCTET_STREAM_TYPE);
cert = certRsc.uploadJksCertificate(form);
assertNotNull("cert not null", cert);
assertEquals("cert properly extracted", certificate2, cert.getCertificate());
assertNull("privateKey not included", cert.getPrivateKey());
// Get the certificate - to make sure cert was properly updated, and privateKey is null
cert = certRsc.getKeyInfo();
assertEquals("cert properly set", certificate2, cert.getCertificate());
assertNull("privateKey nullified", cert.getPrivateKey());
// Re-upload the private key
certRsc.uploadJks(keyCertForm);
// Upload certificate as PEM via /upload - nullifies the private key
form = new MultipartFormDataOutput();
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE);
form.addFormData("file", certificate2.getBytes(Charset.forName("ASCII")), MediaType.APPLICATION_OCTET_STREAM_TYPE);
cert = certRsc.uploadJks(form);
assertNotNull("cert not null", cert);
assertEquals("cert properly extracted", certificate2, cert.getCertificate());
assertNull("privateKey not included", cert.getPrivateKey());
// Get the certificate again - to make sure cert is set, and privateKey is null
cert = certRsc.getKeyInfo();
assertEquals("cert properly set", certificate2, cert.getCertificate());
assertNull("privateKey nullified", cert.getPrivateKey());
// Upload certificate with header - should be stored without header
form = new MultipartFormDataOutput();
form.addFormData("keystoreFormat", "Certificate PEM", MediaType.TEXT_PLAIN_TYPE);
String certificate2WithHeaders = PemUtils.BEGIN_CERT + "\n" + certificate2 + "\n" + PemUtils.END_CERT;
form.addFormData("file", certificate2WithHeaders.getBytes(Charset.forName("ASCII")), MediaType.APPLICATION_OCTET_STREAM_TYPE);
cert = certRsc.uploadJks(form);
assertNotNull("cert not null", cert);
assertEquals("cert properly extracted", certificate2, cert.getCertificate());
assertNull("privateKey not included", cert.getPrivateKey());
// Get the certificate again - to make sure cert is set, and privateKey is null
cert = certRsc.getKeyInfo();
assertEquals("cert properly set", certificate2, cert.getCertificate());
assertNull("privateKey nullified", cert.getPrivateKey());
} finally {
folder.delete();
}
}
@Test
public void testDownloadKeystore() throws Exception {
ClientAttributeCertificateResource certRsc = accountClient.getCertficateResource("jwt.credential");
// generate a key pair first
CertificateRepresentation certrep = certRsc.generate();
KeystoreUtil.KeystoreFormat preferredKeystoreType = KeystoreUtils.getPreferredKeystoreType();
// download the key and certificate
KeyStoreConfig config = new KeyStoreConfig();
config.setFormat(preferredKeystoreType.toString());
config.setKeyAlias("alias");
config.setKeyPassword("keyPass");
config.setStorePassword("storePass");
byte[] result = certRsc.getKeystore(config);
KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(preferredKeystoreType);
keyStore.load(new ByteArrayInputStream(result), "storePass".toCharArray());
Key key = keyStore.getKey("alias", "keyPass".toCharArray());
Certificate cert = keyStore.getCertificate("alias");
assertTrue("Certificat is X509", cert instanceof X509Certificate);
String keyPem = KeycloakModelUtils.getPemFromKey(key);
String certPem = KeycloakModelUtils.getPemFromCertificate((X509Certificate) cert);
assertEquals("key match", certrep.getPrivateKey(), keyPem);
assertEquals("cert match", certrep.getCertificate(), certPem);
}
@Test
public void testGenerateAndDownloadKeystore() throws Exception {
ClientAttributeCertificateResource certRsc = accountClient.getCertficateResource("jwt.credential");
// generate a key pair first
CertificateRepresentation firstcert = certRsc.generate();
KeystoreUtil.KeystoreFormat preferredKeystoreType = KeystoreUtils.getPreferredKeystoreType();
KeyStoreConfig config = new KeyStoreConfig();
config.setFormat(preferredKeystoreType.toString());
config.setKeyAlias("alias");
config.setKeyPassword("keyPass");
config.setStorePassword("storePass");
config.setKeySize(4096);
config.setValidity(3);
byte[] result = certRsc.generateAndGetKeystore(config);
KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(preferredKeystoreType);
keyStore.load(new ByteArrayInputStream(result), "storePass".toCharArray());
Key key = keyStore.getKey("alias", "keyPass".toCharArray());
Certificate cert = keyStore.getCertificate("alias");
assertTrue("Certificat is X509", cert instanceof X509Certificate);
String keyPem = KeycloakModelUtils.getPemFromKey(key);
String certPem = KeycloakModelUtils.getPemFromCertificate((X509Certificate) cert);
assertNotEquals("new key generated", firstcert.getPrivateKey(), keyPem);
assertNotEquals("new cert generated", firstcert.getCertificate(), certPem);
}
}

View File

@@ -3,9 +3,7 @@ LoginTotpTest
PasswordHashingTest
ClientAuthSignedJWTTest
ClientAuthEdDSASignedJWTTest
CredentialsTest
JavaKeystoreKeyProviderTest
ServerInfoTest
UserFederationLdapConnectionTest
LDAPUserLoginTest
org.keycloak.testsuite.x509.**

View File

@@ -1,6 +1,5 @@
AccountRestServiceTest
AuthorizationCodeTest
CredentialsTest
DeployedScriptAuthenticatorTest
ExportImportTest
GeneratedRsaKeyProviderTest