diff --git a/common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java b/common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java
index f27da437687..dee1f6bf28f 100644
--- a/common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java
+++ b/common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java
@@ -26,13 +26,13 @@ public class CryptoIntegration {
if (cryptoProvider == null) {
cryptoProvider = detectProvider(classLoader);
logger.debugv("BouncyCastle provider: {0}", BouncyIntegration.PROVIDER);
-
- if (logger.isTraceEnabled()) {
- logger.tracef(dumpJavaSecurityProviders());
- }
}
}
}
+
+ if (logger.isTraceEnabled()) {
+ logger.tracef(dumpJavaSecurityProviders());
+ }
}
public static CryptoProvider getProvider() {
@@ -67,4 +67,7 @@ public class CryptoIntegration {
return builder.append("]").toString();
}
+ public static void setProvider(CryptoProvider provider) {
+ cryptoProvider = provider;
+ }
}
diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/Fips1402StrictCryptoProvider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/Fips1402StrictCryptoProvider.java
new file mode 100644
index 00000000000..30f04a2bd83
--- /dev/null
+++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/Fips1402StrictCryptoProvider.java
@@ -0,0 +1,17 @@
+package org.keycloak.crypto.fips;
+
+import org.bouncycastle.crypto.CryptoServicesRegistrar;
+
+/**
+ *
A {@link FIPS1402Provider} that forces BC to run in FIPS approve mode by default.
+ *
+ *
In order to set the default mode the {@code org.bouncycastle.fips.approved_only} must be set. Otherwise,
+ * calling {@link CryptoServicesRegistrar#setApprovedOnlyMode(boolean)} the mode is set on a per thread-basis and does not work
+ * well when handling requests using multiple threads.
+ */
+public class Fips1402StrictCryptoProvider extends FIPS1402Provider {
+
+ static {
+ System.setProperty("org.bouncycastle.fips.approved_only", Boolean.TRUE.toString());
+ }
+}
diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/KeycloakFipsSecurityProvider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/KeycloakFipsSecurityProvider.java
index 306da92983b..2d95bd4fb14 100644
--- a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/KeycloakFipsSecurityProvider.java
+++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/KeycloakFipsSecurityProvider.java
@@ -1,5 +1,7 @@
package org.keycloak.crypto.fips;
+import static org.bouncycastle.crypto.CryptoServicesRegistrar.isInApprovedOnlyMode;
+
import java.security.Provider;
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
@@ -17,11 +19,10 @@ public class KeycloakFipsSecurityProvider extends Provider {
private final BouncyCastleFipsProvider bcFipsProvider;
public KeycloakFipsSecurityProvider(BouncyCastleFipsProvider bcFipsProvider) {
- super("KC", 1, "Keycloak pseudo provider");
+ super("KC(" + bcFipsProvider.toString() + (isInApprovedOnlyMode() ? " Approved Mode" : "") + ")", 1, "Keycloak pseudo provider");
this.bcFipsProvider = bcFipsProvider;
}
-
@Override
public synchronized final Service getService(String type, String algorithm) {
// Using 'SecureRandom.getInstance("SHA1PRNG")' will delegate to BCFIPS DEFAULT provider instead of returning SecureRandom based on potentially unsecure SHA1PRNG
diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/ClassLoaderOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/ClassLoaderOptions.java
new file mode 100644
index 00000000000..5dc467a1df6
--- /dev/null
+++ b/quarkus/config-api/src/main/java/org/keycloak/config/ClassLoaderOptions.java
@@ -0,0 +1,9 @@
+package org.keycloak.config;
+
+public class ClassLoaderOptions {
+
+ public static final Option IGNORE_ARTIFACTS = new OptionBuilder<>("class-loader-ignore-artifacts", String.class)
+ .category(OptionCategory.GENERAL)
+ .hidden()
+ .build();
+}
diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/OptionCategory.java b/quarkus/config-api/src/main/java/org/keycloak/config/OptionCategory.java
index ccaa98a6cbd..389e310cbe3 100644
--- a/quarkus/config-api/src/main/java/org/keycloak/config/OptionCategory.java
+++ b/quarkus/config-api/src/main/java/org/keycloak/config/OptionCategory.java
@@ -14,6 +14,7 @@ public enum OptionCategory {
PROXY("Proxy", 90, ConfigSupportLevel.SUPPORTED),
VAULT("Vault", 100, ConfigSupportLevel.SUPPORTED),
LOGGING("Logging", 110, ConfigSupportLevel.SUPPORTED),
+ SECURITY("Security", 120, ConfigSupportLevel.EXPERIMENTAL),
GENERAL("General", 999, ConfigSupportLevel.SUPPORTED);
private String heading;
diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/SecurityOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/SecurityOptions.java
new file mode 100644
index 00000000000..63cc4fe1cb9
--- /dev/null
+++ b/quarkus/config-api/src/main/java/org/keycloak/config/SecurityOptions.java
@@ -0,0 +1,31 @@
+package org.keycloak.config;
+
+public class SecurityOptions {
+
+ public enum FipsMode {
+ enabled("org.keycloak.crypto.fips.FIPS1402Provider"),
+ strict("org.keycloak.crypto.fips.Fips1402StrictCryptoProvider"),
+ disabled("org.keycloak.crypto.def.DefaultCryptoProvider");
+
+ private String providerClassName;
+
+ FipsMode(String providerClassName) {
+ this.providerClassName = providerClassName;
+ }
+
+ public boolean isFipsEnabled() {
+ return this.equals(enabled) || this.equals(strict);
+ }
+
+ public String getProviderClassName() {
+ return providerClassName;
+ }
+ }
+
+ public static final Option FIPS_MODE = new OptionBuilder<>("fips-mode", FipsMode.class)
+ .category(OptionCategory.SECURITY)
+ .buildTime(true)
+ .description("Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on non-approved mode. For full FIPS compliance, set 'strict' to run on approved mode.")
+ .defaultValue(FipsMode.disabled)
+ .build();
+}
diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java
index eb0bc70147f..cec7939343b 100644
--- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java
+++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java
@@ -89,6 +89,7 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.keycloak.Config;
+import org.keycloak.config.SecurityOptions;
import org.keycloak.config.StorageOptions;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.connections.jpa.JpaConnectionSpi;
@@ -563,6 +564,17 @@ class KeycloakProcessor {
}));
}
+ @Consume(KeycloakSessionFactoryPreInitBuildItem.class)
+ @BuildStep
+ @Record(ExecutionTime.STATIC_INIT)
+ void setCryptoProvider(KeycloakRecorder recorder) {
+ SecurityOptions.FipsMode fipsMode = Configuration.getOptionalValue(
+ MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + SecurityOptions.FIPS_MODE.getKey()).map(
+ SecurityOptions.FipsMode::valueOf).orElse(SecurityOptions.FipsMode.disabled);
+
+ recorder.setCryptoProvider(fipsMode);
+ }
+
@BuildStep(onlyIf = IsDevelopment.class)
void configureDevMode(BuildProducer hotFiles) {
hotFiles.produce(new HotDeploymentWatchedFileBuildItem("META-INF/keycloak.conf"));
diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml
index f8b21b6a74b..e65b09e1124 100644
--- a/quarkus/runtime/pom.xml
+++ b/quarkus/runtime/pom.xml
@@ -148,7 +148,17 @@
org.keycloak
- ${keycloak.crypto.artifactId}
+ keycloak-crypto-default
+
+
+ org.keycloak
+ keycloak-crypto-fips1402
+
+
+ *
+ *
+
+
org.keycloak
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java
index 08d2262e569..adcaef1cd45 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java
@@ -38,6 +38,9 @@ import io.vertx.ext.web.RoutingContext;
import org.keycloak.Config;
import org.keycloak.common.Profile;
+import org.keycloak.common.crypto.CryptoIntegration;
+import org.keycloak.common.crypto.CryptoProvider;
+import org.keycloak.config.SecurityOptions;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
@@ -70,7 +73,7 @@ public class KeycloakRecorder {
Map, Map>>> factories,
Map, String> defaultProviders,
Map preConfiguredProviders,
- List themes, Boolean reaugmented) {
+ List themes, boolean reaugmented) {
Config.init(new MicroProfileConfigProvider());
Profile.setInstance(new QuarkusProfile());
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, themes, reaugmented));
@@ -162,4 +165,20 @@ public class KeycloakRecorder {
}
};
}
+
+ public void setCryptoProvider(SecurityOptions.FipsMode fipsMode) {
+ String cryptoProvider = fipsMode.getProviderClassName();
+
+ try {
+ CryptoIntegration.setProvider(
+ (CryptoProvider) Thread.currentThread().getContextClassLoader().loadClass(cryptoProvider).getDeclaredConstructor().newInstance());
+ } catch (ClassNotFoundException | NoClassDefFoundError cause) {
+ if (fipsMode.isFipsEnabled()) {
+ throw new RuntimeException("Failed to configure FIPS. Make sure you have added the Bouncy Castle FIPS dependencies to the 'providers' directory.");
+ }
+ throw new RuntimeException("Unexpected error when configuring the crypto provider: " + cryptoProvider, cause);
+ } catch (Exception cause) {
+ throw new RuntimeException("Unexpected error when configuring the crypto provider: " + cryptoProvider, cause);
+ }
+ }
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java
new file mode 100644
index 00000000000..8026565ce6b
--- /dev/null
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java
@@ -0,0 +1,44 @@
+package org.keycloak.quarkus.runtime.configuration.mappers;
+
+import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
+
+import java.util.Optional;
+import org.keycloak.config.ClassLoaderOptions;
+import org.keycloak.config.SecurityOptions;
+import org.keycloak.quarkus.runtime.Environment;
+import org.keycloak.quarkus.runtime.configuration.Configuration;
+import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
+
+import io.smallrye.config.ConfigSourceInterceptorContext;
+import io.smallrye.config.ConfigValue;
+
+final class ClassLoaderPropertyMappers {
+
+ private ClassLoaderPropertyMappers(){}
+
+ public static PropertyMapper[] getMappers() {
+ return new PropertyMapper[] {
+ fromOption(ClassLoaderOptions.IGNORE_ARTIFACTS)
+ .to("quarkus.class-loading.removed-artifacts")
+ .transformer(ClassLoaderPropertyMappers::resolveIgnoredArtifacts)
+ .build()
+ };
+ }
+
+ private static Optional resolveIgnoredArtifacts(Optional value, ConfigSourceInterceptorContext context) {
+ if (Environment.isRebuildCheck() || Environment.isRebuild()) {
+ ConfigValue fipsEnabled = Configuration.getConfigValue(
+ MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + SecurityOptions.FIPS_MODE.getKey());
+
+ if (fipsEnabled != null && SecurityOptions.FipsMode.valueOf(fipsEnabled.getValue()).isFipsEnabled()) {
+ return Optional.of(
+ "org.bouncycastle:bcprov-jdk15on,org.bouncycastle:bcpkix-jdk15on,org.keycloak:keycloak-crypto-default");
+ }
+
+ return Optional.of(
+ "org.keycloak:keycloak-crypto-fips1402,org.bouncycastle:bc-fips,org.bouncycastle:bctls-fips,org.bouncycastle:bcpkix-fips");
+ }
+
+ return value;
+ }
+}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java
index c840a6a40b9..6741d504149 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java
@@ -38,6 +38,8 @@ public final class PropertyMappers {
MAPPERS.addAll(LoggingPropertyMappers.getMappers());
MAPPERS.addAll(TransactionPropertyMappers.getTransactionPropertyMappers());
MAPPERS.addAll(StoragePropertyMappers.getMappers());
+ MAPPERS.addAll(ClassLoaderPropertyMappers.getMappers());
+ MAPPERS.addAll(SecurityPropertyMappers.getMappers());
}
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/SecurityPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/SecurityPropertyMappers.java
new file mode 100644
index 00000000000..6c7db320a74
--- /dev/null
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/SecurityPropertyMappers.java
@@ -0,0 +1,43 @@
+package org.keycloak.quarkus.runtime.configuration.mappers;
+
+import static java.util.Optional.of;
+import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
+
+import java.util.Optional;
+import org.keycloak.config.SecurityOptions;
+
+import io.smallrye.config.ConfigSourceInterceptorContext;
+
+final class SecurityPropertyMappers {
+
+ private SecurityPropertyMappers() {
+ }
+
+ public static PropertyMapper[] getMappers() {
+ return new PropertyMapper[] {
+ fromOption(SecurityOptions.FIPS_MODE).transformer(SecurityPropertyMappers::resolveFipsMode)
+ .paramLabel("mode")
+ .build()
+ };
+ }
+
+ private static Optional resolveFipsMode(Optional value, ConfigSourceInterceptorContext context) {
+ if (value.isEmpty()) {
+ return of(SecurityOptions.FipsMode.disabled.toString());
+ }
+
+ return of(SecurityOptions.FipsMode.valueOf(value.get()).toString());
+ }
+
+ private static Optional resolveSecurityProvider(Optional value,
+ ConfigSourceInterceptorContext configSourceInterceptorContext) {
+ SecurityOptions.FipsMode fipsMode = value.map(SecurityOptions.FipsMode::valueOf)
+ .orElse(SecurityOptions.FipsMode.disabled);
+
+ if (fipsMode.isFipsEnabled()) {
+ return of("BCFIPS");
+ }
+
+ return value;
+ }
+}
diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/Maven.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/Maven.java
new file mode 100644
index 00000000000..8d34e71a09d
--- /dev/null
+++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/Maven.java
@@ -0,0 +1,69 @@
+/*
+ * 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.it.utils;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
+import org.eclipse.aether.resolution.ArtifactDescriptorResult;
+import org.eclipse.aether.resolution.ArtifactRequest;
+
+import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext;
+import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
+import io.quarkus.bootstrap.utils.BuildToolHelper;
+
+public final class Maven {
+
+ public static Path resolveArtifact(String groupId, String artifactId) {
+ try {
+ Path projectDir = BuildToolHelper.getProjectDir(Paths.get(Maven.class.getResource(".").toURI()));
+ BootstrapMavenContext ctx = new BootstrapMavenContext(
+ BootstrapMavenContext.config().setPreferPomsFromWorkspace(true).setWorkspaceModuleParentHierarchy(true)
+ .setCurrentProject(projectDir.toString()));
+ LocalProject project = ctx.getCurrentProject();
+ RepositorySystem repositorySystem = ctx.getRepositorySystem();
+ List remoteRepositories = ctx.getRemoteRepositories();
+ ArtifactDescriptorResult descrResult = repositorySystem.readArtifactDescriptor(
+ ctx.getRepositorySystemSession(),
+ new ArtifactDescriptorRequest()
+ .setArtifact(new DefaultArtifact(project.getGroupId(), project.getArtifactId(), "pom", project.getVersion()))
+ .setRepositories(remoteRepositories));
+
+ for (org.eclipse.aether.graph.Dependency dependency : descrResult.getManagedDependencies()) {
+ Artifact artifact = dependency.getArtifact();
+
+ if (artifact.getGroupId().equals(groupId) && artifact.getArtifactId().equals(artifactId)) {
+ return repositorySystem.resolveArtifact(
+ ctx.getRepositorySystemSession(),
+ new ArtifactRequest().setArtifact(artifact)
+ .setRepositories(remoteRepositories))
+ .getArtifact().getFile().toPath();
+ }
+ }
+ } catch (Exception cause) {
+ throw new RuntimeException("Failed to resolve artifact", cause);
+ }
+
+ return null;
+ }
+}
diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java
index e56851b148e..006cf8cf418 100644
--- a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java
+++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java
@@ -467,6 +467,14 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
+ public void copyProvider(String groupId, String artifactId) {
+ try {
+ Files.copy(Maven.resolveArtifact(groupId, artifactId), getDistPath().resolve("providers").resolve(artifactId + ".jar"));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to copy JAR file to 'providers' directory", cause);
+ }
+ }
+
private void updateProperties(Consumer propertiesConsumer, File propertiesFile) {
Properties properties = new Properties();
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FipsDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FipsDistTest.java
new file mode 100644
index 00000000000..3930c71a876
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FipsDistTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.it.cli.dist;
+
+import java.util.function.Consumer;
+import org.junit.jupiter.api.Test;
+import org.keycloak.it.junit5.extension.BeforeStartDistribution;
+import org.keycloak.it.junit5.extension.CLIResult;
+import org.keycloak.it.junit5.extension.DistributionTest;
+import org.keycloak.it.junit5.extension.RawDistOnly;
+import org.keycloak.it.utils.KeycloakDistribution;
+import org.keycloak.it.utils.RawKeycloakDistribution;
+
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+
+@DistributionTest(reInstall = DistributionTest.ReInstall.BEFORE_TEST)
+@RawDistOnly(reason = "Containers are immutable")
+public class FipsDistTest {
+
+ @Test
+ @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--fips-mode=enabled", "--cache=local", "--log-level=org.keycloak.common.crypto.CryptoIntegration:trace" })
+ @BeforeStartDistribution(FipsDistTest.InstallBcFipsDependencies.class)
+ void testFipsNonApprovedMode(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertStarted();
+ cliResult.assertMessage("Java security providers: [ \n"
+ + " KC(BCFIPS version 1.000203) version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider");
+ }
+
+ @Test
+ @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--fips-mode=strict", "--cache=local", "--log-level=org.keycloak.common.crypto.CryptoIntegration:trace" })
+ @BeforeStartDistribution(FipsDistTest.InstallBcFipsDependencies.class)
+ void testFipsApprovedMode(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertStarted();
+ cliResult.assertMessage("org.bouncycastle.crypto.fips.FipsUnapprovedOperationError: password must be at least 112 bits");
+ cliResult.assertMessage("Java security providers: [ \n"
+ + " KC(BCFIPS version 1.000203 Approved Mode) version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider");
+ }
+
+ @Test
+ @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--fips-mode=enabled", "--cache=local", "--log-level=org.keycloak.common.crypto.CryptoIntegration:trace" })
+ void failStartDueToMissingFipsDependencies(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertError("Failed to configure FIPS. Make sure you have added the Bouncy Castle FIPS dependencies to the 'providers' directory.");
+ }
+
+ public static class InstallBcFipsDependencies implements Consumer {
+
+ @Override
+ public void accept(KeycloakDistribution distribution) {
+ RawKeycloakDistribution rawDist = (RawKeycloakDistribution) distribution;
+ rawDist.copyProvider("org.bouncycastle", "bc-fips");
+ rawDist.copyProvider("org.bouncycastle", "bctls-fips");
+ rawDist.copyProvider("org.bouncycastle", "bcpkix-fips");
+ }
+ }
+}
diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.unix.approved.txt
index 1faaa3ff519..6401054e80d 100644
--- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.unix.approved.txt
+++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.unix.approved.txt
@@ -279,6 +279,12 @@ Logging:
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
+Security (Experimental):
+
+--fips-mode Experimental: Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on
+ non-approved mode. For full FIPS compliance, set 'strict' to run on approved
+ mode. Possible values are: enabled, strict, disabled. Default: disabled.
+
Do NOT start the server using this command when deploying to production.
Use 'kc.sh start-dev --help-all' to list all available options, including build
diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.windows.approved.txt
index 8ebb93bfd70..eb56e899efb 100644
--- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.windows.approved.txt
+++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartDevHelpAll.windows.approved.txt
@@ -128,18 +128,18 @@ Transaction:
Feature:
--features Enables a set of one or more features. Possible values are: authorization,
- account2, account-api, admin-fine-grained-authz, admin2, docker,
- impersonation, openshift-integration, scripts, token-exchange, web-authn,
- client-policies, ciba, map-storage, par, declarative-user-profile,
- dynamic-scopes, client-secret-rotation, step-up-authentication,
- recovery-codes, update-email, preview.
+ account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
+ docker, impersonation, openshift-integration, scripts, token-exchange,
+ web-authn, client-policies, ciba, map-storage, par,
+ declarative-user-profile, dynamic-scopes, client-secret-rotation,
+ step-up-authentication, recovery-codes, update-email, preview.
--features-disabled
Disables a set of one or more features. Possible values are: authorization,
- account2, account-api, admin-fine-grained-authz, admin2, docker,
- impersonation, openshift-integration, scripts, token-exchange, web-authn,
- client-policies, ciba, map-storage, par, declarative-user-profile,
- dynamic-scopes, client-secret-rotation, step-up-authentication,
- recovery-codes, update-email, preview.
+ account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
+ docker, impersonation, openshift-integration, scripts, token-exchange,
+ web-authn, client-policies, ciba, map-storage, par,
+ declarative-user-profile, dynamic-scopes, client-secret-rotation,
+ step-up-authentication, recovery-codes, update-email, preview.
Hostname:
@@ -174,7 +174,8 @@ HTTP/TLS:
--http-host The used HTTP Host. Default: 0.0.0.0.
--http-port The used HTTP port. Default: 8080.
--http-relative-path
- Set the path relative to '/' for serving resources. Default: /.
+ Set the path relative to '/' for serving resources. The path must start with a
+ '/'. Default: /.
--https-certificate-file
The file path to a server certificate or certificate chain in PEM format.
--https-certificate-key-file
@@ -278,6 +279,12 @@ Logging:
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
+Security (Experimental):
+
+--fips-mode Experimental: Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on
+ non-approved mode. For full FIPS compliance, set 'strict' to run on approved
+ mode. Possible values are: enabled, strict, disabled. Default: disabled.
+
Do NOT start the server using this command when deploying to production.
Use 'kc.bat start-dev --help-all' to list all available options, including
diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.unix.approved.txt
index eec38c03125..e4af4f73375 100644
--- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.unix.approved.txt
+++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.unix.approved.txt
@@ -285,6 +285,12 @@ Logging:
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
+Security (Experimental):
+
+--fips-mode Experimental: Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on
+ non-approved mode. For full FIPS compliance, set 'strict' to run on approved
+ mode. Possible values are: enabled, strict, disabled. Default: disabled.
+
By default, this command tries to update the server configuration by running a
'build' before starting the server. You can disable this behavior by using the
'--optimized' option:
diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.windows.approved.txt
index 96d3bbafd75..94220d6d44c 100644
--- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.windows.approved.txt
+++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/approvals/cli/help/HelpCommandTest.testStartHelpAll.windows.approved.txt
@@ -134,18 +134,18 @@ Transaction:
Feature:
--features Enables a set of one or more features. Possible values are: authorization,
- account2, account-api, admin-fine-grained-authz, admin2, docker,
- impersonation, openshift-integration, scripts, token-exchange, web-authn,
- client-policies, ciba, map-storage, par, declarative-user-profile,
- dynamic-scopes, client-secret-rotation, step-up-authentication,
- recovery-codes, update-email, preview.
+ account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
+ docker, impersonation, openshift-integration, scripts, token-exchange,
+ web-authn, client-policies, ciba, map-storage, par,
+ declarative-user-profile, dynamic-scopes, client-secret-rotation,
+ step-up-authentication, recovery-codes, update-email, preview.
--features-disabled
Disables a set of one or more features. Possible values are: authorization,
- account2, account-api, admin-fine-grained-authz, admin2, docker,
- impersonation, openshift-integration, scripts, token-exchange, web-authn,
- client-policies, ciba, map-storage, par, declarative-user-profile,
- dynamic-scopes, client-secret-rotation, step-up-authentication,
- recovery-codes, update-email, preview.
+ account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
+ docker, impersonation, openshift-integration, scripts, token-exchange,
+ web-authn, client-policies, ciba, map-storage, par,
+ declarative-user-profile, dynamic-scopes, client-secret-rotation,
+ step-up-authentication, recovery-codes, update-email, preview.
Hostname:
@@ -180,7 +180,8 @@ HTTP/TLS:
--http-host The used HTTP Host. Default: 0.0.0.0.
--http-port The used HTTP port. Default: 8080.
--http-relative-path
- Set the path relative to '/' for serving resources. Default: /.
+ Set the path relative to '/' for serving resources. The path must start with a
+ '/'. Default: /.
--https-certificate-file
The file path to a server certificate or certificate chain in PEM format.
--https-certificate-key-file
@@ -284,6 +285,12 @@ Logging:
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
+Security (Experimental):
+
+--fips-mode Experimental: Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on
+ non-approved mode. For full FIPS compliance, set 'strict' to run on approved
+ mode. Possible values are: enabled, strict, disabled. Default: disabled.
+
By default, this command tries to update the server configuration by running a
'build' before starting the server. You can disable this behavior by using the
'--optimized' option: