From 00e4c3567a485c7f543da849f147d323a4a33712 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Thu, 15 Sep 2022 22:56:25 -0300 Subject: [PATCH] Make it possible to switch between BC and BC-FIPS libraries Closes #12424 --- .../common/crypto/CryptoIntegration.java | 11 ++- .../fips/Fips1402StrictCryptoProvider.java | 17 +++++ .../fips/KeycloakFipsSecurityProvider.java | 5 +- .../keycloak/config/ClassLoaderOptions.java | 9 +++ .../org/keycloak/config/OptionCategory.java | 1 + .../org/keycloak/config/SecurityOptions.java | 31 ++++++++ .../quarkus/deployment/KeycloakProcessor.java | 12 +++ quarkus/runtime/pom.xml | 12 ++- .../quarkus/runtime/KeycloakRecorder.java | 21 +++++- .../mappers/ClassLoaderPropertyMappers.java | 44 +++++++++++ .../mappers/PropertyMappers.java | 2 + .../mappers/SecurityPropertyMappers.java | 43 +++++++++++ .../java/org/keycloak/it/utils/Maven.java | 69 +++++++++++++++++ .../it/utils/RawKeycloakDistribution.java | 8 ++ .../keycloak/it/cli/dist/FipsDistTest.java | 74 +++++++++++++++++++ ...Test.testStartDevHelpAll.unix.approved.txt | 6 ++ ...t.testStartDevHelpAll.windows.approved.txt | 29 +++++--- ...andTest.testStartHelpAll.unix.approved.txt | 6 ++ ...Test.testStartHelpAll.windows.approved.txt | 29 +++++--- 19 files changed, 399 insertions(+), 30 deletions(-) create mode 100644 crypto/fips1402/src/main/java/org/keycloak/crypto/fips/Fips1402StrictCryptoProvider.java create mode 100644 quarkus/config-api/src/main/java/org/keycloak/config/ClassLoaderOptions.java create mode 100644 quarkus/config-api/src/main/java/org/keycloak/config/SecurityOptions.java create mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java create mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/SecurityPropertyMappers.java create mode 100644 quarkus/tests/integration/src/main/java/org/keycloak/it/utils/Maven.java create mode 100644 quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FipsDistTest.java 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 f27da43768..dee1f6bf28 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 0000000000..30f04a2bd8 --- /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 306da92983..2d95bd4fb1 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 0000000000..5dc467a1df --- /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 ccaa98a6cb..389e310cbe 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 0000000000..63cc4fe1cb --- /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 eb0bc70147..cec7939343 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 f8b21b6a74..e65b09e112 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 08d2262e56..adcaef1cd4 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 0000000000..8026565ce6 --- /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 c840a6a40b..6741d50414 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 0000000000..6c7db320a7 --- /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 0000000000..8d34e71a09 --- /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 e56851b148..006cf8cf41 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 0000000000..3930c71a87 --- /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 1faaa3ff51..6401054e80 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 8ebb93bfd7..eb56e899ef 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 eec38c0312..e4af4f7337 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 96d3bbafd7..94220d6d44 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: