diff --git a/server-spi-private/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java b/common/src/main/java/org/keycloak/common/enums/HostnameVerificationPolicy.java similarity index 96% rename from server-spi-private/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java rename to common/src/main/java/org/keycloak/common/enums/HostnameVerificationPolicy.java index 4c4f069d7c..45f99c896e 100755 --- a/server-spi-private/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java +++ b/common/src/main/java/org/keycloak/common/enums/HostnameVerificationPolicy.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.keycloak.truststore; +package org.keycloak.common.enums; public enum HostnameVerificationPolicy { diff --git a/docs/documentation/release_notes/index.adoc b/docs/documentation/release_notes/index.adoc index 6a0e60d892..5195032a73 100644 --- a/docs/documentation/release_notes/index.adoc +++ b/docs/documentation/release_notes/index.adoc @@ -13,6 +13,9 @@ include::topics/templates/document-attributes.adoc[] :release_header_latest_link: {releasenotes_link_latest} include::topics/templates/release-header.adoc[] +== {project_name_full} 24.0.0 +include::topics/24_0_0.adoc[leveloffset=2] + == {project_name_full} 23.0.0 include::topics/23_0_0.adoc[leveloffset=2] diff --git a/docs/documentation/release_notes/topics/24_0_0.adoc b/docs/documentation/release_notes/topics/24_0_0.adoc index 32ca9f19a8..533b6a58be 100644 --- a/docs/documentation/release_notes/topics/24_0_0.adoc +++ b/docs/documentation/release_notes/topics/24_0_0.adoc @@ -2,8 +2,12 @@ The Keycloak JS adapter now uses the https://webpack.js.org/guides/package-exports/[`exports` field] in `package.json`. This improves support for more modern bundlers like Webpack 5 and Vite, but comes with some unavoidable breaking changes. Consult the link:{upgradingguide_link}[{upgradingguide_name}] for more details. += Truststore Improvements + +Keycloak introduces an improved truststores configuration options. The Keycloak truststore is now used across the server: for outgoing connections, mTLS, database drivers and more. It's no longer needed to configure separate truststores for individual areas. To configure the truststore, you can put your truststores files or certificates in the default `conf/truststores`, or use the new `truststore-paths` config option. For details refer to the relevant https://www.keycloak.org/server/keycloak-truststore[guide]. + == Automatic certificate management for SAML identity providers The SAML identity providers can now be configured to automatically download the signing certificates from the IDP entity metadata descriptor endpoint. In order to use the new feature the option `Metadata descriptor URL` should be configured in the provider (URL where the IDP metadata information with the certificates is published) and `Use metadata descriptor URL` needs to be `ON`. The certificates are automatically downloaded and cached in the `public-key-storage` SPI from that URL. The certificates can also be reloaded or imported from the admin console, using the action combo in the provider page. -See the https://www.keycloak.org/docs/latest/server_admin/index.html#saml-v2-0-identity-providers[documentation] for more details about the new options. \ No newline at end of file +See the https://www.keycloak.org/docs/latest/server_admin/index.html#saml-v2-0-identity-providers[documentation] for more details about the new options. diff --git a/docs/documentation/server_admin/topics/user-federation/ldap.adoc b/docs/documentation/server_admin/topics/user-federation/ldap.adoc index 4df2909017..921fe12107 100644 --- a/docs/documentation/server_admin/topics/user-federation/ldap.adoc +++ b/docs/documentation/server_admin/topics/user-federation/ldap.adoc @@ -74,11 +74,9 @@ Hover the mouse pointer over the tooltips in the Admin Console to see more detai ==== Connecting to LDAP over SSL -When you configure a secure connection URL to your LDAP store (for example,`ldaps://myhost.com:636`), {project_name} uses SSL to communicate with the LDAP server. Configure a truststore on the {project_name} server side so that {project_name} can trust the SSL connection to LDAP. +When you configure a secure connection URL to your LDAP store (for example,`ldaps://myhost.com:636`), {project_name} uses SSL to communicate with the LDAP server. Configure a truststore on the {project_name} server side so that {project_name} can trust the SSL connection to LDAP - see https://www.keycloak.org/server/keycloak-truststore[Configuring a Truststore] {section}. -Configure the global truststore for {project_name} with the Truststore SPI. For more information about configuring the global truststore, see the https://www.keycloak.org/server/keycloak-truststore[Configuring a Truststore] {section}. If you do not configure the Truststore SPI, the truststore falls back to the default mechanism provided by Java, which can be the file supplied by the `javax.net.ssl.trustStore` system property or the cacerts file from the JDK if the system property is unset. - -The `Use Truststore SPI` configuration property, in the LDAP federation provider configuration, controls the truststore SPI. By default, {project_name} sets the property to `Always`, which is adequate for most deployments. {project_name} uses the Truststore SPI if the connection URL to LDAP starts with `ldaps` only. +The `Use Truststore SPI` configuration property is deprecated. It should normally be left as `Always`. ==== Synchronizing LDAP users to {project_name} diff --git a/docs/documentation/upgrading/topics/keycloak/changes-24_0_0.adoc b/docs/documentation/upgrading/topics/keycloak/changes-24_0_0.adoc index 054d6dfd05..fcd28313cc 100644 --- a/docs/documentation/upgrading/topics/keycloak/changes-24_0_0.adoc +++ b/docs/documentation/upgrading/topics/keycloak/changes-24_0_0.adoc @@ -12,3 +12,9 @@ import AuthZ from 'keycloak-js/dist/keycloak-authz.js'; import Keycloak from 'keycloak-js'; import AuthZ from 'keycloak-js/authz'; ---- + += Truststore Changes + +The `spi-truststore-file-*` options and the truststore related options `https-trust-store-*` are deprecated, please use the new default location for truststore material, `conf/truststores`, or specify your desired paths via the `truststore-paths` option. For details refer to the relevant https://www.keycloak.org/server/keycloak-truststore[guide]. + +The `tls-hostname-verifier` property should be used instead of the `spi-truststore-file-hostname-verification-policy` property. diff --git a/docs/documentation/upgrading/topics/keycloak/changes.adoc b/docs/documentation/upgrading/topics/keycloak/changes.adoc index 2ceb0b05c9..688aa3dda9 100644 --- a/docs/documentation/upgrading/topics/keycloak/changes.adoc +++ b/docs/documentation/upgrading/topics/keycloak/changes.adoc @@ -1,5 +1,9 @@ == Migration Changes +=== Migrating to 24.0.0 + +include::changes-24_0_0.adoc[leveloffset=3] + === Migrating to 23.0.2 include::changes-23_0_2.adoc[leveloffset=3] diff --git a/docs/guides/server/keycloak-truststore.adoc b/docs/guides/server/keycloak-truststore.adoc index ef4f15a92a..3ef0f546bc 100644 --- a/docs/guides/server/keycloak-truststore.adoc +++ b/docs/guides/server/keycloak-truststore.adoc @@ -2,52 +2,36 @@ <#import "/templates/kc.adoc" as kc> <@tmpl.guide -title="Configuring trusted certificates for outgoing requests" -summary="How to configure the {project_name} Truststore to communicate with external services through TLS." -includedOptions=""> +title="Configuring trusted certificates" +summary="How to configure the {project_name} Truststore to communicate through TLS." +includedOptions="truststore-paths tls-hostname-verifier"> -When {project_name} communicates with external services through TLS, it has to validate the remote server’s certificate in order to ensure it is connecting to a trusted server. This is necessary in order to prevent man-in-the-middle attacks. The certificates of these remote server’s or the CA that signed these certificates must be put in a truststore. This truststore is managed by the Keycloak server. +When {project_name} communicates with external services or has an incoming connection through TLS, it has to validate the remote certificate in order to ensure it is connecting to a trusted server. This is necessary in order to prevent man-in-the-middle attacks. -The truststore is used when connecting securely to identity brokers, LDAP identity providers, when sending emails, and for backchannel communication with client applications. It is also useful -when you want to change the policy on how host names are verified and trusted by the server. +The certificates of these clients or servers, or the CA that signed these certificates, must be put in a truststore. This truststore is then configured for use by Keycloak. -By default, a truststore provider is not configured, and any TLS/HTTPS connections fall back to standard Java Truststore configuration. If there is no trust established, then these outgoing requests will fail. +== Configuring the System Truststore -== Configuring the {project_name} Truststore +The existing Java default truststore certs will always be trusted. If you need additional certificates, which will be the case if you have self-signed or internal certificate authorities that are not recognized by the JRE, they can be included in the `conf/truststores` directory or subdirectories. The certs may be in PEM files, or PKCS12 files with extension `.p12` or `.pfx`. If in PKCS12, the certs must be unencrypted - meaning no password is expected. -You can add your truststore configuration by entering this command: +If you need an alternative path, use the `--truststore-paths` option to specify additional files or directories where PEM or PKCS12 files are located. Paths are relative to where you launched {project_name}, so absolute paths are recommended instead. If a directory is specified, it will be recursively scanned for truststore files. -<@kc.start parameters="--spi-truststore-file-file=myTrustStore.jks --spi-truststore-file-password=password --spi-truststore-file-hostname-verification-policy=ANY"/> +After all applicable certs are included, the truststore will be used as the system default truststore via the `javax.net.ssl` properties, and as the default for internal usage within {project_name}. -The following are possible configuration options for this setting: +For example: -file:: -The path to a Java keystore file. -TLS requests need a way to verify the host of the server to which they are talking. -This is what the truststore does. -The keystore contains one or more trusted host certificates or certificate authorities. -This truststore file should only contain public certificates of your secured hosts. -This is _REQUIRED_ if any of these properties are defined. +<@kc.start parameters="--truststore-paths=/opt/truststore/myTrustStore.pfx,/opt/other-truststore/myOtherTrustStore.pem" /> -password:: -Password of the keystore. -This option is _REQUIRED_ if any of these properties are defined. +It is still possible to directly set your own `javax.net.ssl` truststore System properties, but it's recommended to use the `--truststore-paths` instead. -hostname-verification-policy:: -For HTTPS requests, this option verifies the hostname of the server's certificate. Default: `WILDCARD` +== Hostname Verification Policy + +You may refine how hostnames are verified by TLS connections with the `tls-hostname-verifier` property. + +* `WILDCARD` (the default) allows wildcards in subdomain names, such as *.foo.com. * `ANY` means that the hostname is not verified. -* `WILDCARD` allows wildcards in subdomain names, such as *.foo.com. * When using `STRICT`, the Common Name (CN) must match the hostname exactly. + Please note that this setting does not apply to LDAP secure connections, which require strict hostname checking. -type:: -The type of truststore, such as `jks`, `pkcs12` or `bcfks`. If not provided, the type would be detected based on the truststore -file extension or platform default type. - -=== Example of a truststore configuration -The following is an example configuration for a truststore that allows you to create trustful connections to all `mycompany.org` domains and its subdomains: - -<@kc.start parameters="--spi-truststore-file-file=path/to/truststore.jks --spi-truststore-file-password=change_me --spi-truststore-file-hostname-verification-policy=WILDCARD"/> - diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java index 80750dc61c..4641000d54 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java @@ -89,17 +89,17 @@ public class HttpOptions { public static final Option HTTPS_TRUST_STORE_FILE = new OptionBuilder<>("https-trust-store-file", File.class) .category(OptionCategory.HTTP) - .description("The trust store which holds the certificate information of the certificates to trust.") + .description("DEPRECATED: The trust store which holds the certificate information of the certificates to trust.") .build(); public static final Option HTTPS_TRUST_STORE_PASSWORD = new OptionBuilder<>("https-trust-store-password", String.class) .category(OptionCategory.HTTP) - .description("The password of the trust store file.") + .description("DEPRECATED: The password of the trust store file.") .build(); public static final Option HTTPS_TRUST_STORE_TYPE = new OptionBuilder<>("https-trust-store-type", String.class) .category(OptionCategory.HTTP) - .description("The type of the trust store file. " + + .description("DEPRECATED: The type of the trust store file. " + "If not given, the type is automatically detected based on the file name. " + "If '" + SecurityOptions.FIPS_MODE.getKey() + "' is set to '" + FipsMode.STRICT + "' and no value is set, it defaults to 'BCFKS'.") .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 08182df513..8d1ae30e1e 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 @@ -7,13 +7,14 @@ public enum OptionCategory { TRANSACTION("Transaction",30, ConfigSupportLevel.SUPPORTED), FEATURE("Feature", 40, ConfigSupportLevel.SUPPORTED), HOSTNAME("Hostname", 50, ConfigSupportLevel.SUPPORTED), - HTTP("HTTP/TLS", 60, ConfigSupportLevel.SUPPORTED), + HTTP("HTTP(S)", 60, ConfigSupportLevel.SUPPORTED), HEALTH("Health", 70, ConfigSupportLevel.SUPPORTED), CONFIG("Config", 75, ConfigSupportLevel.SUPPORTED), METRICS("Metrics", 80, ConfigSupportLevel.SUPPORTED), PROXY("Proxy", 90, ConfigSupportLevel.SUPPORTED), VAULT("Vault", 100, ConfigSupportLevel.SUPPORTED), LOGGING("Logging", 110, ConfigSupportLevel.SUPPORTED), + TRUSTSTORE("Truststore", 115, ConfigSupportLevel.SUPPORTED), SECURITY("Security", 120, ConfigSupportLevel.SUPPORTED), EXPORT("Export", 130, ConfigSupportLevel.SUPPORTED), IMPORT("Import", 140, ConfigSupportLevel.SUPPORTED), 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 index f4a63e39e3..6434162403 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/SecurityOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/SecurityOptions.java @@ -1,11 +1,11 @@ package org.keycloak.config; -import java.util.Arrays; -import java.util.List; - import org.keycloak.common.Profile; import org.keycloak.common.crypto.FipsMode; +import java.util.Arrays; +import java.util.List; + public class SecurityOptions { public static final Option FIPS_MODE = new OptionBuilder<>("fips-mode", FipsMode.class) diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/TruststoreOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/TruststoreOptions.java new file mode 100644 index 0000000000..3d0240d8e0 --- /dev/null +++ b/quarkus/config-api/src/main/java/org/keycloak/config/TruststoreOptions.java @@ -0,0 +1,18 @@ +package org.keycloak.config; + +import org.keycloak.common.enums.HostnameVerificationPolicy; + +public class TruststoreOptions { + + public static final Option TRUSTSTORE_PATHS = new OptionBuilder<>("truststore-paths", String.class) + .category(OptionCategory.TRUSTSTORE) + .description("List of pkcs12 (p12 or pfx file extensions), PEM files, or directories containing those files that will be used as a system truststore.") + .build(); + + public static final Option HOSTNAME_VERIFICATION_POLICY = new OptionBuilder<>("tls-hostname-verifier", HostnameVerificationPolicy.class) + .category(OptionCategory.TRUSTSTORE) + .description("The TLS hostname verification policy for out-going HTTPS and SMTP requests.") + .defaultValue(HostnameVerificationPolicy.WILDCARD) + .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 c7daea5474..3bb27a0c64 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 @@ -230,6 +230,13 @@ class KeycloakProcessor { recorder.configureProfile(profile.getName(), profile.getFeatures()); } + @Record(ExecutionTime.STATIC_INIT) + @BuildStep + @Consume(ConfigBuildItem.class) + void configureTruststore(KeycloakRecorder recorder) { + recorder.configureTruststore(); + } + /** * Check whether JDBC driver is present for the specified DB * diff --git a/quarkus/dist/assembly.xml b/quarkus/dist/assembly.xml index e2e0f4d817..b3565e6475 100755 --- a/quarkus/dist/assembly.xml +++ b/quarkus/dist/assembly.xml @@ -80,6 +80,14 @@ *.* + + + src/main/content/conf + conf/truststores + + **/* + + src/main/content/data data diff --git a/quarkus/dist/src/main/content/conf/README.md b/quarkus/dist/src/main/content/conf/README.md index 9d25889308..d5517b8af2 100644 --- a/quarkus/dist/src/main/content/conf/README.md +++ b/quarkus/dist/src/main/content/conf/README.md @@ -1,4 +1,7 @@ Configure the server ==================== -Files in this directory are used to configure the server. Please consult the [configuration guides](https://www.keycloak.org/guides#server) for more information. \ No newline at end of file +Files in this directory are used to configure the server. Please consult the [configuration guides](https://www.keycloak.org/guides#server) for more information. + +Use the truststores subdirectory to provide additional trusted certificates. Please consult the [truststore guide](https://www.keycloak.org/server/keycloak-truststore) for more information. + 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 269b9f677e..e28dce4e72 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 @@ -17,11 +17,13 @@ package org.keycloak.quarkus.runtime; +import java.io.File; import java.lang.annotation.Annotation; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.BiConsumer; -import java.util.function.Predicate; +import java.util.stream.Stream; import io.agroal.api.AgroalDataSource; import io.quarkus.agroal.DataSource; @@ -32,13 +34,13 @@ import liquibase.Scope; import org.hibernate.cfg.AvailableSettings; import org.infinispan.manager.DefaultCacheManager; -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.common.crypto.FipsMode; +import org.keycloak.config.TruststoreOptions; import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory; @@ -48,6 +50,7 @@ import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; import org.keycloak.quarkus.runtime.storage.legacy.infinispan.CacheManagerFactory; import org.keycloak.theme.ClasspathThemeProviderFactory; +import org.keycloak.truststore.TruststoreBuilder; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; @@ -68,6 +71,23 @@ public class KeycloakRecorder { Profile.init(profileName, features); } + public void configureTruststore() { + String[] truststores = Configuration.getOptionalKcValue(TruststoreOptions.TRUSTSTORE_PATHS.getKey()) + .map(s -> s.split(",")).orElse(new String[0]); + + String dataDir = Environment.getDataDir(); + + File truststoresDir = Optional.ofNullable(Environment.getHomePath()).map(path -> path.resolve("conf").resolve("truststores").toFile()).orElse(null); + + if (truststoresDir != null && truststoresDir.exists() && Optional.ofNullable(truststoresDir.list()).map(a -> a.length).orElse(0) > 0) { + truststores = Stream.concat(Stream.of(truststoresDir.getAbsolutePath()), Stream.of(truststores)).toArray(String[]::new); + } else if (truststores.length == 0) { + return; // nothing to configure, we'll just use the system default + } + + TruststoreBuilder.setSystemTruststore(truststores, true, dataDir); + } + public void configureLiquibase(Map> services) { ServiceLocator locator = Scope.getCurrentScope().getServiceLocator(); if (locator instanceof FastServiceLocator) 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 2cbcbd7abb..9ceadbad6d 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 @@ -40,6 +40,7 @@ public final class PropertyMappers { MAPPERS.addAll(SecurityPropertyMappers.getMappers()); MAPPERS.addAll(ExportPropertyMappers.getMappers()); MAPPERS.addAll(ImportPropertyMappers.getMappers()); + MAPPERS.addAll(TruststorePropertyMappers.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 index cefc543c1b..88dfce60e8 100644 --- 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 @@ -21,7 +21,7 @@ final class SecurityPropertyMappers { return new PropertyMapper[] { fromOption(SecurityOptions.FIPS_MODE).transformer(SecurityPropertyMappers::resolveFipsMode) .paramLabel("mode") - .build() + .build(), }; } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/TruststorePropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/TruststorePropertyMappers.java new file mode 100644 index 0000000000..626ed80dd9 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/TruststorePropertyMappers.java @@ -0,0 +1,21 @@ +package org.keycloak.quarkus.runtime.configuration.mappers; + +import org.keycloak.config.TruststoreOptions; + +import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; + +public class TruststorePropertyMappers { + + public static PropertyMapper[] getMappers() { + return new PropertyMapper[] { + fromOption(TruststoreOptions.TRUSTSTORE_PATHS) + .paramLabel(TruststoreOptions.TRUSTSTORE_PATHS.getKey()) + .build(), + fromOption(TruststoreOptions.HOSTNAME_VERIFICATION_POLICY) + .paramLabel(TruststoreOptions.HOSTNAME_VERIFICATION_POLICY.getKey()) + .to("kc.spi-truststore-file-hostname-verification-policy") + .build(), + }; + } + +} 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 index 3d89da2f4c..d9917900cd 100644 --- 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 @@ -105,6 +105,20 @@ public class FipsDistTest { }); } + @Test + void testUnencryptedPkcs12TrustStoreInStrictMode(KeycloakDistribution dist) { + runOnFipsEnabledDistribution(dist, () -> { + String truststoreName = "keycloak-truststore.p12"; + dist.copyOrReplaceFileFromClasspath("/" + truststoreName, Path.of("conf", truststoreName)); + + RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); + Path truststorePath = rawDist.getDistPath().resolve("conf").resolve(truststoreName).toAbsolutePath(); + + CLIResult cliResult = dist.run("--verbose", "start", "--fips-mode=strict", "--truststore-paths=" + truststorePath); + cliResult.assertStarted(); + }); + } + @Test void testUnsupportedHttpsPkcs12KeyStoreInStrictMode(KeycloakDistribution dist) { runOnFipsEnabledDistribution(dist, () -> { diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/TruststoreDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/TruststoreDistTest.java new file mode 100644 index 0000000000..06e2ae8ac2 --- /dev/null +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/TruststoreDistTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021 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 io.restassured.RestAssured; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +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 org.keycloak.truststore.TruststoreBuilder; + +import java.nio.file.Path; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static io.restassured.RestAssured.given; + +@DistributionTest(keepAlive = true) +@RawDistOnly(reason = "Containers are immutable") +public class TruststoreDistTest { + + @BeforeAll + static void before() { + RestAssured.reset(); + } + + @Test + void testMutualAuthWithTruststorePaths(KeycloakDistribution dist) { + String[] truststoreNames = new String[] { "keycloak-truststore.p12", "self-signed.pem" }; + Stream.of(truststoreNames).forEach(truststoreName -> { + dist.copyOrReplaceFileFromClasspath("/" + truststoreName, Path.of("conf", truststoreName)); + }); + + RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); + String paths = Stream.of(truststoreNames).map(truststoreName -> rawDist.getDistPath().resolve("conf") + .resolve(truststoreName).toAbsolutePath().toString()).collect(Collectors.joining(",")); + dist.copyOrReplaceFileFromClasspath("/self-signed.p12", Path.of("conf", "self-signed.p12")); + Path keyStore = rawDist.getDistPath().resolve("conf").resolve("self-signed.p12").toAbsolutePath(); + + rawDist.run("--verbose", "start", "--http-enabled=true", "--hostname=mykeycloak.org", + "--truststore-paths=" + paths, "--https-client-auth=required", "--https-key-store-file=" + keyStore); + + given().trustStore(TruststoreDistTest.class.getResource("/self-signed-truststore.p12").getPath(), TruststoreBuilder.DUMMY_PASSWORD) + .keyStore(TruststoreDistTest.class.getResource("/self-signed.p12").getPath(), "password") + .get("https://mykeycloak.org:8443").then().body(Matchers.containsString("https://mykeycloak.org")); + } + + @Test + void testMutualAuthWithDefaultTruststoresDir(KeycloakDistribution dist) { + String[] truststoreNames = new String[] { "keycloak-truststore.p12", "self-signed.pem" }; + Stream.of(truststoreNames).forEach(truststoreName -> { + dist.copyOrReplaceFileFromClasspath("/" + truststoreName, Path.of("conf", "truststores", truststoreName)); + }); + + RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); + dist.copyOrReplaceFileFromClasspath("/self-signed.p12", Path.of("conf", "self-signed.p12")); + Path keyStore = rawDist.getDistPath().resolve("conf").resolve("self-signed.p12").toAbsolutePath(); + + rawDist.run("--verbose", "start", "--http-enabled=true", "--hostname=mykeycloak.org", + "--https-client-auth=required", "--https-key-store-file=" + keyStore); + + given().trustStore(TruststoreDistTest.class.getResource("/self-signed-truststore.p12").getPath(), TruststoreBuilder.DUMMY_PASSWORD) + .keyStore(TruststoreDistTest.class.getResource("/self-signed.p12").getPath(), "password") + .get("https://mykeycloak.org:8443").then().body(Matchers.containsString("https://mykeycloak.org")); + } + +} diff --git a/quarkus/tests/integration/src/test/resources/keycloak-truststore.p12 b/quarkus/tests/integration/src/test/resources/keycloak-truststore.p12 new file mode 100644 index 0000000000..351011d890 Binary files /dev/null and b/quarkus/tests/integration/src/test/resources/keycloak-truststore.p12 differ diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt index 110edeeb7a..7cc2bdbacd 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt @@ -60,7 +60,7 @@ Feature: multi-site, par, preview, recovery-codes, scripts, step-up-authentication, token-exchange, transient-users, update-email, web-authn. -HTTP/TLS: +HTTP(S): --http-relative-path Set the path relative to '/' for serving resources. The path must start with a diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt index 7e8015f1cb..70d55adbdf 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt @@ -60,7 +60,7 @@ Feature: preview, recovery-codes, scripts, step-up-authentication, token-exchange, transient-users, update-email, web-authn. -HTTP/TLS: +HTTP(S): --http-relative-path Set the path relative to '/' for serving resources. The path must start with a diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt index 4122d72a85..53a0b75bd6 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt @@ -130,6 +130,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Export: --dir Set the path to a directory where files will be created with the exported data. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt index 4122d72a85..b5a41d8b0f 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt @@ -130,6 +130,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Export: --dir Set the path to a directory where files will be created with the exported data. @@ -142,4 +151,4 @@ Export: --users-per-file Set the number of users per file. It is used only if 'users' is set to 'different_files'. Increasing this number leads to exponentially increasing - export times. Default: 50. \ No newline at end of file + export times. Default: 50. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt index 50e0fd6a1a..58cd0eb971 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt @@ -130,6 +130,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Import: --dir Set the path to a directory where files will be read from. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt index 50e0fd6a1a..58cd0eb971 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt @@ -130,6 +130,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Import: --dir Set the path to a directory where files will be read from. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt index a063dd2d41..8635796b03 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt @@ -116,7 +116,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -150,14 +150,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Health: @@ -245,6 +245,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Security: --fips-mode Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt index dc94454de1..5546b6274b 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt @@ -111,7 +111,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -145,14 +145,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Health: @@ -228,6 +228,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Security: --fips-mode Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt index a063dd2d41..8635796b03 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt @@ -116,7 +116,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -150,14 +150,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Health: @@ -245,6 +245,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Security: --fips-mode Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt index dc94454de1..5546b6274b 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt @@ -111,7 +111,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -145,14 +145,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Health: @@ -228,6 +228,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Security: --fips-mode Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt index 93cca11673..5e47d17251 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt @@ -117,7 +117,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -151,14 +151,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Health: @@ -246,6 +246,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Security: --fips-mode Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt index a48071c61d..6994c9d1fb 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt @@ -112,7 +112,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -146,14 +146,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Health: @@ -229,6 +229,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Security: --fips-mode Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt index 93cca11673..5e47d17251 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt @@ -117,7 +117,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -151,14 +151,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Health: @@ -246,6 +246,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Security: --fips-mode Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt index a48071c61d..6994c9d1fb 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt @@ -112,7 +112,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -146,14 +146,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Health: @@ -229,6 +229,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + Security: --fips-mode Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt index b384a48b12..9d504aaf28 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt @@ -76,7 +76,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -107,14 +107,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Config: @@ -188,6 +188,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + 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/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.windows.approved.txt index d4ea386323..9fca392fe1 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.windows.approved.txt @@ -69,7 +69,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -100,14 +100,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Proxy: @@ -169,6 +169,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + 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/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt index b384a48b12..9d504aaf28 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt @@ -76,7 +76,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -107,14 +107,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Config: @@ -188,6 +188,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + 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/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.windows.approved.txt index b75834a7a9..b7539fee0c 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.windows.approved.txt @@ -69,7 +69,7 @@ Hostname: URL this option should be enabled. Default: false. --hostname-url Set the base URL for frontend URLs, including scheme, host, port and path. -HTTP/TLS: +HTTP(S): --http-enabled Enables the HTTP listener. Default: false. @@ -100,14 +100,14 @@ HTTP/TLS: --https-protocols The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file - The trust store which holds the certificate information of the certificates to - trust. + DEPRECATED: The trust store which holds the certificate information of the + certificates to trust. --https-trust-store-password - The password of the trust store file. + DEPRECATED: The password of the trust store file. --https-trust-store-type - The type of the trust store file. If not given, the type is automatically - detected based on the file name. If 'fips-mode' is set to 'strict' and no - value is set, it defaults to 'BCFKS'. + DEPRECATED: The type of the trust store file. If not given, the type is + automatically detected based on the file name. If 'fips-mode' is set to + 'strict' and no value is set, it defaults to 'BCFKS'. Proxy: @@ -169,6 +169,15 @@ Logging: categories and their levels. For the root category, you don't need to specify a category. Default: info. +Truststore: + +--tls-hostname-verifier + The TLS hostname verification policy for out-going HTTPS and SMTP requests. + Possible values are: ANY, WILDCARD, STRICT. Default: WILDCARD. +--truststore-paths + List of pkcs12 (p12 or pfx file extensions), PEM files, or directories + containing those files that will be used as a system truststore. + 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/self-signed-truststore.p12 b/quarkus/tests/integration/src/test/resources/self-signed-truststore.p12 new file mode 100644 index 0000000000..e452802dce Binary files /dev/null and b/quarkus/tests/integration/src/test/resources/self-signed-truststore.p12 differ diff --git a/quarkus/tests/integration/src/test/resources/self-signed.p12 b/quarkus/tests/integration/src/test/resources/self-signed.p12 new file mode 100644 index 0000000000..9776106dd5 Binary files /dev/null and b/quarkus/tests/integration/src/test/resources/self-signed.p12 differ diff --git a/quarkus/tests/integration/src/test/resources/self-signed.pem b/quarkus/tests/integration/src/test/resources/self-signed.pem new file mode 100644 index 0000000000..c5927586f2 --- /dev/null +++ b/quarkus/tests/integration/src/test/resources/self-signed.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIEM0VH1DANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJL +QzERMA8GA1UECBMIa2V5Y2xvYWsxETAPBgNVBAcTCGtleWNsb2FrMREwDwYDVQQK +EwhrZXljbG9hazERMA8GA1UECxMIa2V5Y2xvYWsxEjAQBgNVBAMTCUtleSBDbG9h +azAgFw0yMzExMDkxMjMxNDZaGA8yMDUxMDMyNjEyMzE0NlowbTELMAkGA1UEBhMC +S0MxETAPBgNVBAgTCGtleWNsb2FrMREwDwYDVQQHEwhrZXljbG9hazERMA8GA1UE +ChMIa2V5Y2xvYWsxETAPBgNVBAsTCGtleWNsb2FrMRIwEAYDVQQDEwlLZXkgQ2xv +YWswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv1M8ZiKSONnryBcTy +u6u0d/cJ9s0kZAvO+Q62I5W6e6r0zFYlUjheRSTF7PdypAxhJPMRVGZoE0WCKUMn +4D1E43ZC8bl8IgzpC8m2hbtJsVcbZs0H4D9jHjHvBiPgdkmTc7F/pU8jk8oD3L+Y +olzOFLI/wpwC7kx9O1bzXVpTKOg12JqrKRHIfdvU4mbMlRTRX+AX6Iw5QMiy8vfA +77uCkkxPInx18Z4MU6vqjvAWXos5gQE1fVUyHsmWlBp8Eis7jD7d52Th1uK3nJLo +4wnKzNXDXq34o25xR6j1BpU+v8iqkcDdpJncQHuo/iHK2WZh8xgQMxGoc33nslxB +JvuPAgMBAAGjITAfMB0GA1UdDgQWBBTp2UwAeIxtlKqNFrW4jUVnJ8CgAjANBgkq +hkiG9w0BAQsFAAOCAQEANfkQnompu7woA7fCJb5O+WCNTGoAdFO36tjyRczbRO2G +W7GqPFupObQhAQTtZ55pBfhZTz30lrM9C6vAMDIhfqjYfdATeflALCZH4lbLRcxf +bMq/FVRz1p+hEUGwNeguxCiZ7ZqfqchwiW8oeEeG4MYdYOfdR1pglF6Ak0pkWwqE +KqfuTjllGJRkLJco6ApDA6viBnGUX9T4Xb+xsfzTiOj93k9Z1aWzLB4MCupS+Z1h +VC39DBEUe04OEczu8ZT4A20hiOp+wA2YuToxVVncpnMdZhe1hbzKrTTflM22mcs1 +6Fqjo2BBJQ/sfhWMB7KYpnI78n/bkVP2S4hE3JChXA== +-----END CERTIFICATE----- diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java index 9e72a1c07c..f26c660931 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java @@ -258,11 +258,12 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { URL contextRoot = new URL(scheme + "://localhost:" + port + ("/" + relativePath + "/realms/master/").replace("//", "/")); HttpURLConnection connection = null; long startTime = System.currentTimeMillis(); + Exception ex = null; while (true) { if (System.currentTimeMillis() - startTime > getStartTimeout()) { throw new IllegalStateException( - "Timeout [" + getStartTimeout() + "] while waiting for Quarkus server"); + "Timeout [" + getStartTimeout() + "] while waiting for Quarkus server", ex); } if (!keycloak.isAlive()) { @@ -287,6 +288,7 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { break; } } catch (Exception ignore) { + ex = ignore; } finally { if (connection != null) { connection.disconnect(); @@ -314,12 +316,15 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { private SSLSocketFactory createInsecureSslSocketFactory() throws IOException { TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() { + @Override public void checkClientTrusted(final X509Certificate[] chain, final String authType) { } + @Override public void checkServerTrusted(final X509Certificate[] chain, final String authType) { } + @Override public X509Certificate[] getAcceptedIssuers() { return null; } diff --git a/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java index 9f05da694a..95d557852a 100755 --- a/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java @@ -17,6 +17,7 @@ package org.keycloak.truststore; +import org.keycloak.common.enums.HostnameVerificationPolicy; import org.keycloak.provider.Provider; import java.security.cert.X509Certificate; diff --git a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java index d1395faa00..b08caa5033 100755 --- a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java +++ b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java @@ -198,11 +198,11 @@ public class DefaultHttpClientFactory implements HttpClientFactory { TruststoreProvider truststoreProvider = session.getProvider(TruststoreProvider.class); boolean disableTruststoreProvider = truststoreProvider == null || truststoreProvider.getTruststore() == null; - + if (disableTruststoreProvider) { logger.warn("TruststoreProvider is disabled"); } else { - builder.hostnameVerification(HttpClientBuilder.HostnameVerificationPolicy.valueOf(truststoreProvider.getPolicy().name())); + builder.hostnameVerification(truststoreProvider.getPolicy()); try { builder.trustStore(truststoreProvider.getTruststore()); } catch (Exception e) { @@ -214,7 +214,7 @@ public class DefaultHttpClientFactory implements HttpClientFactory { logger.warn("TrustManager is disabled"); builder.disableTrustManager(); } - + if (clientKeystore != null) { clientKeystore = EnvUtil.replace(clientKeystore); try { diff --git a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java index 40cc6c3150..778f87fbed 100755 --- a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java +++ b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java @@ -27,6 +27,7 @@ import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.impl.NoConnectionReuseStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.keycloak.common.enums.HostnameVerificationPolicy; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -53,35 +54,23 @@ import java.util.concurrent.TimeUnit; * @version $Revision: 1 $ */ public class HttpClientBuilder { - public enum HostnameVerificationPolicy { - /** - * Hostname verification is not done on the server's certificate - */ - ANY, - /** - * Allows wildcards in subdomain names i.e. *.foo.com - */ - WILDCARD, - /** - * CN must match hostname connecting to - */ - STRICT - } - /** * @author Bill Burke * @version $Revision: 1 $ */ private static class PassthroughTrustManager implements X509TrustManager { + @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } + @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } + @Override public X509Certificate[] getAcceptedIssuers() { return null; } diff --git a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java index 1b34f7f0ca..a86671bd25 100644 --- a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java +++ b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java @@ -19,10 +19,10 @@ package org.keycloak.email; import jakarta.mail.internet.MimeUtility; import org.jboss.logging.Logger; +import org.keycloak.common.enums.HostnameVerificationPolicy; import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import org.keycloak.services.ServicesLogger; -import org.keycloak.truststore.HostnameVerificationPolicy; import org.keycloak.truststore.JSSETruststoreConfigurator; import org.keycloak.vault.VaultStringSecret; diff --git a/services/src/main/java/org/keycloak/truststore/FileTruststoreProvider.java b/services/src/main/java/org/keycloak/truststore/FileTruststoreProvider.java index f65de26ba4..751a059daa 100755 --- a/services/src/main/java/org/keycloak/truststore/FileTruststoreProvider.java +++ b/services/src/main/java/org/keycloak/truststore/FileTruststoreProvider.java @@ -17,6 +17,8 @@ package org.keycloak.truststore; +import org.keycloak.common.enums.HostnameVerificationPolicy; + import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.Map; diff --git a/services/src/main/java/org/keycloak/truststore/FileTruststoreProviderFactory.java b/services/src/main/java/org/keycloak/truststore/FileTruststoreProviderFactory.java index e565aede25..551caa07fc 100755 --- a/services/src/main/java/org/keycloak/truststore/FileTruststoreProviderFactory.java +++ b/services/src/main/java/org/keycloak/truststore/FileTruststoreProviderFactory.java @@ -19,6 +19,7 @@ package org.keycloak.truststore; import org.jboss.logging.Logger; import org.keycloak.Config; +import org.keycloak.common.enums.HostnameVerificationPolicy; import org.keycloak.common.util.KeystoreUtil; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -27,7 +28,6 @@ import org.keycloak.provider.ProviderConfigurationBuilder; import java.io.File; import java.io.FileInputStream; -import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.KeyStore; @@ -53,6 +53,8 @@ import javax.security.auth.x500.X500Principal; */ public class FileTruststoreProviderFactory implements TruststoreProviderFactory { + static final String HOSTNAME_VERIFICATION_POLICY = "hostname-verification-policy"; + private static final Logger log = Logger.getLogger(FileTruststoreProviderFactory.class); private TruststoreProvider provider; @@ -72,29 +74,40 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory String storepath = config.get("file"); String pass = config.get("password"); - String policy = config.get("hostname-verification-policy"); + String policy = config.get(HOSTNAME_VERIFICATION_POLICY); String configuredType = config.get("type"); - // if "truststore" . "file" is not configured then it is disabled - if (storepath == null && pass == null && policy == null) { - return; - } - HostnameVerificationPolicy verificationPolicy = null; KeyStore truststore = null; - + boolean system = false; if (storepath == null) { - throw new RuntimeException("Attribute 'file' missing in 'truststore':'file' configuration"); + storepath = System.getProperty(TruststoreBuilder.SYSTEM_TRUSTSTORE_KEY); + if (storepath == null) { + File defaultTrustStore = TruststoreBuilder.getJRETruststore(); + if (!defaultTrustStore.exists()) { + throw new RuntimeException("Attribute 'file' missing in 'truststore':'file' configuration, and could not find the system truststore"); + } + storepath = defaultTrustStore.getAbsolutePath(); + system = true; + } + // should there be an exception if pass / type are configured for the spi-truststore + pass = System.getProperty(TruststoreBuilder.SYSTEM_TRUSTSTORE_PASSWORD_KEY, system ? "changeit" : null); + configuredType = System.getProperty(TruststoreBuilder.SYSTEM_TRUSTSTORE_TYPE_KEY); } - if (pass == null) { - throw new RuntimeException("Attribute 'password' missing in 'truststore':'file' configuration"); - } - String type = KeystoreUtil.getKeystoreType(configuredType, storepath, KeyStore.getDefaultType()); try { truststore = loadStore(storepath, type, pass == null ? null :pass.toCharArray()); } catch (Exception e) { - throw new RuntimeException("Failed to initialize TruststoreProviderFactory: " + new File(storepath).getAbsolutePath() + ", truststore type: " + type, e); + // in fips mode the default truststore type can be pkcs12, but the cacerts file will still be jks + if (system && !"jks".equalsIgnoreCase(type)) { + try { + truststore = loadStore(storepath, "jks", pass == null ? null :pass.toCharArray()); + } catch (Exception e1) { + } + } + if (truststore == null) { + throw new RuntimeException("Failed to initialize TruststoreProviderFactory: " + new File(storepath).getAbsolutePath() + ", truststore type: " + type, e); + } } if (policy == null) { verificationPolicy = HostnameVerificationPolicy.WILDCARD; @@ -115,15 +128,9 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory private KeyStore loadStore(String path, String type, char[] password) throws Exception { KeyStore ks = KeyStore.getInstance(type); - InputStream is = new FileInputStream(path); - try { + try (InputStream is = new FileInputStream(path)) { ks.load(is, password); return ks; - } finally { - try { - is.close(); - } catch (IOException ignored) { - } } } @@ -146,24 +153,24 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory .property() .name("file") .type("string") - .helpText("The file path of the trust store from where the certificates are going to be read from to validate TLS connections.") + .helpText("DEPRECATED: The file path of the trust store from where the certificates are going to be read from to validate TLS connections.") .add() .property() .name("password") .type("string") - .helpText("The trust store password.") + .helpText("DEPRECATED: The trust store password.") .add() .property() - .name("hostname-verification-policy") + .name(HOSTNAME_VERIFICATION_POLICY) .type("string") - .helpText("The hostname verification policy.") + .helpText("DEPRECATED: The hostname verification policy.") .options(Arrays.stream(HostnameVerificationPolicy.values()).map(HostnameVerificationPolicy::name).map(String::toLowerCase).toArray(String[]::new)) .defaultValue(HostnameVerificationPolicy.WILDCARD.name().toLowerCase()) .add() .property() .name("type") .type("string") - .helpText("Type of the truststore. If not provided, the type would be detected based on the truststore file extension or platform default type.") + .helpText("DEPRECATED: Type of the truststore. If not provided, the type would be detected based on the truststore file extension or platform default type.") .add() .build(); } @@ -184,14 +191,14 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory private void readTruststore(KeyStore truststore) { //Reading truststore aliases & certificates - Enumeration enumeration; + Enumeration enumeration; try { enumeration = truststore.aliases(); log.trace("Checking " + truststore.size() + " entries from the truststore."); while(enumeration.hasMoreElements()) { - String alias = (String)enumeration.nextElement(); + String alias = enumeration.nextElement(); readTruststoreEntry(truststore, alias); } } catch (KeyStoreException e) { diff --git a/services/src/main/java/org/keycloak/truststore/JSSETruststoreConfigurator.java b/services/src/main/java/org/keycloak/truststore/JSSETruststoreConfigurator.java index d36539bcc5..494d1688ba 100755 --- a/services/src/main/java/org/keycloak/truststore/JSSETruststoreConfigurator.java +++ b/services/src/main/java/org/keycloak/truststore/JSSETruststoreConfigurator.java @@ -17,14 +17,10 @@ package org.keycloak.truststore; -import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; -import org.apache.http.conn.ssl.StrictHostnameVerifier; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; @@ -94,29 +90,6 @@ public class JSSETruststoreConfigurator { return tm; } - public HostnameVerifier getHostnameVerifier() { - if (provider == null) { - return null; - } - - HostnameVerificationPolicy policy = provider.getPolicy(); - switch (policy) { - case ANY: - return new HostnameVerifier() { - @Override - public boolean verify(String s, SSLSession sslSession) { - return true; - } - }; - case WILDCARD: - return new BrowserCompatHostnameVerifier(); - case STRICT: - return new StrictHostnameVerifier(); - default: - throw new IllegalStateException("Unknown policy: " + policy.name()); - } - } - public TruststoreProvider getProvider() { return provider; } diff --git a/services/src/main/java/org/keycloak/truststore/TruststoreBuilder.java b/services/src/main/java/org/keycloak/truststore/TruststoreBuilder.java new file mode 100644 index 0000000000..f70131a9d1 --- /dev/null +++ b/services/src/main/java/org/keycloak/truststore/TruststoreBuilder.java @@ -0,0 +1,246 @@ +/* + * Copyright 2023 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.truststore; + +import org.jboss.logging.Logger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Builds a system-wide truststore from the given config options. + */ +public class TruststoreBuilder { + + public static final String SYSTEM_TRUSTSTORE_KEY = "javax.net.ssl.trustStore"; + public static final String SYSTEM_TRUSTSTORE_PASSWORD_KEY = "javax.net.ssl.trustStorePassword"; + public static final String SYSTEM_TRUSTSTORE_TYPE_KEY = "javax.net.ssl.trustStoreType"; + private static final String CERT_PROTECTION_ALGORITHM_KEY = "keystore.pkcs12.certProtectionAlgorithm"; + public static final String DUMMY_PASSWORD = "keycloakchangeit"; // fips length compliant dummy password + static final String PKCS12 = "PKCS12"; + + private static final Logger LOGGER = Logger.getLogger(TruststoreBuilder.class); + + public static void setSystemTruststore(String[] truststores, boolean trustStoreIncludeDefault, String dataDir) { + KeyStore truststore = createMergedTruststore(truststores, trustStoreIncludeDefault); + + // save with a dummy password just in case some logic that uses the system properties needs to have one + File file = saveTruststore(truststore, dataDir, DUMMY_PASSWORD.toCharArray()); + + // finally update the system properties + System.setProperty(TruststoreBuilder.SYSTEM_TRUSTSTORE_KEY, file.getAbsolutePath()); + System.setProperty(TruststoreBuilder.SYSTEM_TRUSTSTORE_TYPE_KEY, PKCS12); + System.setProperty(TruststoreBuilder.SYSTEM_TRUSTSTORE_PASSWORD_KEY, DUMMY_PASSWORD); + } + + static File saveTruststore(KeyStore truststore, String dataDir, char[] password) { + File file = new File(dataDir, "keycloak-truststore.p12"); + file.getParentFile().mkdirs(); + try (FileOutputStream fos = new FileOutputStream(file)) { + // this should inhibit the use of encryption in storing the certs + // it's of course not concurrency safe, but it should only be run at startup + String oldValue = System.setProperty(CERT_PROTECTION_ALGORITHM_KEY, "NONE"); + truststore.store(fos, password); + if (oldValue != null) { + System.setProperty(CERT_PROTECTION_ALGORITHM_KEY, oldValue); + } else { + System.getProperties().remove(CERT_PROTECTION_ALGORITHM_KEY); + } + } catch (Exception e) { + throw new RuntimeException("Failed to save truststore: " + file.getAbsolutePath(), e); + } + return file; + } + + static KeyStore createMergedTruststore(String[] truststores, boolean trustStoreIncludeDefault) { + KeyStore truststore = createPkcs12KeyStore(); + + if (trustStoreIncludeDefault) { + includeDefaultTruststore(truststore); + } + + mergeFiles(truststores, truststore, true); + return truststore; + } + + private static void mergeFiles(String[] truststores, KeyStore truststore, boolean topLevel) { + for (String file : truststores) { + File f = new File(file); + if (f.isDirectory()) { + mergeFiles(Stream.of(f.listFiles()).map(File::getAbsolutePath).toArray(String[]::new), truststore, false); + } else { + if (file.endsWith(".p12") || file.endsWith(".pfx")) { + mergeTrustStore(truststore, file, loadStore(file, PKCS12, null)); + } else { + mergePemFile(truststore, file, topLevel); + } + } + } + } + + static KeyStore createPkcs12KeyStore() { + try { + KeyStore truststore = KeyStore.getInstance(PKCS12); + truststore.load(null, null); + return truststore; + } catch (Exception e) { + throw new RuntimeException("Failed to initialize truststore: cannot create a PKCS12 keystore", e); + } + } + + + /** + * Include the default truststore, if it can be found. + *

+ * The existing system properties will be preserved so that this logic can be rerun without consuming + * the newly created merged truststore. + */ + static void includeDefaultTruststore(KeyStore truststore) { + String originalTruststoreKey = TruststoreBuilder.SYSTEM_TRUSTSTORE_KEY + ".orig"; + String originalTruststoreTypeKey = TruststoreBuilder.SYSTEM_TRUSTSTORE_TYPE_KEY + ".orig"; + String originalTruststorePasswordKey = TruststoreBuilder.SYSTEM_TRUSTSTORE_PASSWORD_KEY + ".orig"; + + String trustStorePath = System.getProperty(originalTruststoreKey); + String type = PKCS12; + String password = null; + File defaultTrustStore = null; + if (trustStorePath == null) { + trustStorePath = System.getProperty(TruststoreBuilder.SYSTEM_TRUSTSTORE_KEY); + if (trustStorePath == null) { + defaultTrustStore = getJRETruststore(); + } else { + type = System.getProperty(TruststoreBuilder.SYSTEM_TRUSTSTORE_TYPE_KEY, KeyStore.getDefaultType()); + password = System.getProperty(TruststoreBuilder.SYSTEM_TRUSTSTORE_PASSWORD_KEY); + // save the original information + System.setProperty(originalTruststoreKey, trustStorePath); + System.setProperty(originalTruststoreTypeKey, type); + if (password == null) { + System.getProperties().remove(originalTruststorePasswordKey); + } else { + System.setProperty(originalTruststorePasswordKey, password); + } + defaultTrustStore = new File(trustStorePath); + } + } else { + type = System.getProperty(originalTruststoreTypeKey); + password = System.getProperty(originalTruststorePasswordKey); + defaultTrustStore = new File(trustStorePath); + } + + if (defaultTrustStore.exists()) { + String path = defaultTrustStore.getAbsolutePath(); + mergeTrustStore(truststore, path, + loadStore(path, type, Optional.ofNullable(password).map(String::toCharArray).orElse(null))); + } else { + LOGGER.warnf("Default truststore was to be included, but could not be found at: %s", defaultTrustStore); + } + } + + static File getJRETruststore() { + // try jre locations - there doesn't seem to be a good default mechanism for this + String securityDirectory = System.getProperty("java.home") + File.separator + "lib" + File.separator + + "security"; + File jssecacertsFile = new File(securityDirectory, "jssecacerts"); + if (jssecacertsFile.exists() && jssecacertsFile.isFile()) { + return jssecacertsFile; + } + return new File(securityDirectory, "cacerts"); + } + + static KeyStore loadStore(String path, String type, char[] password) { + try (InputStream is = new FileInputStream(path)) { + KeyStore ks = KeyStore.getInstance(type); + ks.load(is, password); + return ks; + } catch (Exception e) { + throw new RuntimeException( + "Failed to initialize truststore: " + new File(path).getAbsolutePath() + ", type: " + type, e); + } + } + + private static void mergePemFile(KeyStore truststore, String file, boolean isPem) { + try (FileInputStream pemInputStream = new FileInputStream(file)) { + CertificateFactory certFactory = CertificateFactory.getInstance("X509"); + boolean loadedAny = false; + while (pemInputStream.available() > 0) { + X509Certificate cert; + try { + cert = (X509Certificate) certFactory.generateCertificate(pemInputStream); + loadedAny = true; + } catch (CertificateException e) { + if (pemInputStream.available() > 0 || !loadedAny) { + // any remaining input means there is an actual problem with the key contents or + // file format + if (isPem) { + throw e; + } + LOGGER.infof(e, + "The file %s may not be in PEM format, it will not be used to create the merged truststore", + new File(file).getAbsolutePath()); + continue; + } + LOGGER.debugf(e, + "The trailing entry for %s generated a certificate exception, assuming instead that the file ends with comments", + new File(file).getAbsolutePath()); + continue; + } + setCertificateEntry(truststore, cert); + } + } catch (Exception e) { + throw new RuntimeException( + "Failed to initialize truststore, could not merge: " + new File(file).getAbsolutePath(), e); + } + } + + private static void setCertificateEntry(KeyStore truststore, Certificate cert) throws KeyStoreException { + String alias = null; + if (cert instanceof X509Certificate) { + X509Certificate x509Cert = (X509Certificate)cert; + // use an alias that should be unique, yet deterministic + alias = x509Cert.getSubjectX500Principal().getName() + "_" + x509Cert.getSerialNumber().toString(16); + } else { + // isn't expected + alias = String.valueOf(Collections.list(truststore.aliases()).size()); + } + truststore.setCertificateEntry(alias, cert); + } + + private static void mergeTrustStore(KeyStore truststore, String file, KeyStore additionalStore) { + try { + for (String alias : Collections.list(additionalStore.aliases())) { + if (additionalStore.isCertificateEntry(alias)) { + setCertificateEntry(truststore, additionalStore.getCertificate(alias)); + } + } + } catch (Exception e) { + throw new RuntimeException( + "Failed to initialize truststore, could not merge: " + new File(file).getAbsolutePath(), e); + } + } +} diff --git a/services/src/test/java/org/keycloak/connections/httpclient/DefaultHttpClientFactoryTest.java b/services/src/test/java/org/keycloak/connections/httpclient/DefaultHttpClientFactoryTest.java index a242a913af..0ad2510ecb 100644 --- a/services/src/test/java/org/keycloak/connections/httpclient/DefaultHttpClientFactoryTest.java +++ b/services/src/test/java/org/keycloak/connections/httpclient/DefaultHttpClientFactoryTest.java @@ -18,7 +18,6 @@ package org.keycloak.connections.httpclient; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; import java.io.IOException; import java.net.InetAddress; @@ -26,7 +25,6 @@ import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.Properties; import javax.net.ssl.SSLPeerUnverifiedException; @@ -39,11 +37,7 @@ import org.junit.Test; import org.keycloak.models.KeycloakSession; import org.keycloak.services.DefaultKeycloakSession; import org.keycloak.services.DefaultKeycloakSessionFactory; -import org.keycloak.services.util.JsonConfigProvider; -import org.keycloak.services.util.JsonConfigProvider.JsonScope; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.keycloak.utils.ScopeUtil; public class DefaultHttpClientFactoryTest { private static final String DISABLE_TRUST_MANAGER_PROPERTY = "disable-trust-manager"; @@ -54,12 +48,12 @@ public class DefaultHttpClientFactoryTest { Map values = new HashMap<>(); values.put(DISABLE_TRUST_MANAGER_PROPERTY, "true"); DefaultHttpClientFactory factory = new DefaultHttpClientFactory(); - factory.init(scope(values)); + factory.init(ScopeUtil.createScope(values)); KeycloakSession session = new DefaultKeycloakSession(new DefaultKeycloakSessionFactory()); HttpClientProvider provider = factory.create(session); Optional testURL = getTestURL(); Assume.assumeTrue( "Could not get test url for domain", testURL.isPresent() ); - try (CloseableHttpClient httpClient = (CloseableHttpClient) provider.getHttpClient(); + try (CloseableHttpClient httpClient = provider.getHttpClient(); CloseableHttpResponse response = httpClient.execute(new HttpGet(testURL.get()))) { assertEquals(HttpStatus.SC_NOT_FOUND,response.getStatusLine().getStatusCode()); } @@ -68,42 +62,16 @@ public class DefaultHttpClientFactoryTest { @Test(expected = SSLPeerUnverifiedException.class) public void createHttpClientProviderWithUnvailableURL() throws IOException { DefaultHttpClientFactory factory = new DefaultHttpClientFactory(); - factory.init(scope(new HashMap<>())); + factory.init(ScopeUtil.createScope(new HashMap<>())); KeycloakSession session = new DefaultKeycloakSession(new DefaultKeycloakSessionFactory()); HttpClientProvider provider = factory.create(session); - try (CloseableHttpClient httpClient = (CloseableHttpClient) provider.getHttpClient()) { + try (CloseableHttpClient httpClient = provider.getHttpClient()) { Optional testURL = getTestURL(); Assume.assumeTrue("Could not get test url for domain", testURL.isPresent()); httpClient.execute(new HttpGet(testURL.get())); } } - private JsonScope scope(Map properties) { - ObjectMapper mapper = new ObjectMapper(); - try { - JsonNode config = mapper.readTree(json(properties)); - return new JsonConfigProvider(config,new Properties()).new JsonScope(config); - } catch (IOException e) { - fail("Could not parse json"); - } - return null; - } - - private String json(Map properties) { - String[] params = properties.entrySet().stream().map(e -> param(e.getKey(), e.getValue())).toArray(String[]::new); - - StringBuilder sb = new StringBuilder(); - sb.append("{"); - sb.append(String.join(",", params)); - sb.append("}"); - - return sb.toString(); - } - - private String param(String key, String value) { - return "\"" + key + "\"" + " : " + "\"" + value + "\""; - } - private Optional getTestURL() { try { // Convert domain name to ip to make request by ip diff --git a/services/src/test/java/org/keycloak/protocol/saml/profile/util/SoapTest.java b/services/src/test/java/org/keycloak/protocol/saml/profile/util/SoapTest.java index 93b6910a95..39e91aca34 100644 --- a/services/src/test/java/org/keycloak/protocol/saml/profile/util/SoapTest.java +++ b/services/src/test/java/org/keycloak/protocol/saml/profile/util/SoapTest.java @@ -16,8 +16,6 @@ */ package org.keycloak.protocol.saml.profile.util; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; @@ -28,8 +26,6 @@ import java.net.InetSocketAddress; import java.net.URI; import java.util.Collections; import java.util.HashMap; -import java.util.Map; -import java.util.Properties; import jakarta.ws.rs.core.HttpHeaders; import jakarta.xml.soap.SOAPException; import jakarta.xml.soap.SOAPMessage; @@ -57,8 +53,7 @@ import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request; import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder; import org.keycloak.services.DefaultKeycloakSession; import org.keycloak.services.DefaultKeycloakSessionFactory; -import org.keycloak.services.util.JsonConfigProvider; -import org.keycloak.services.util.JsonConfigProvider.JsonScope; +import org.keycloak.utils.ScopeUtil; import org.w3c.dom.Document; /** @@ -101,33 +96,6 @@ public class SoapTest { server.stop(0); } - - private String param(String key, String value) { - return "\"" + key + "\"" + " : " + "\"" + value + "\""; - } - - private String json(Map properties) { - String[] params = properties.entrySet().stream().map(e -> param(e.getKey(), e.getValue())).toArray(String[]::new); - - StringBuilder sb = new StringBuilder(); - sb.append("{"); - sb.append(String.join(",", params)); - sb.append("}"); - - return sb.toString(); - } - - private JsonScope createScope(Map properties) { - ObjectMapper mapper = new ObjectMapper(); - try { - JsonNode config = mapper.readTree(json(properties)); - return new JsonConfigProvider(config, new Properties()).new JsonScope(config); - } catch (IOException e) { - Assert.fail("Could not parse json"); - } - return null; - } - private LogoutRequestType createLogoutRequestType() throws ConfigurationException { NameIDType nameId = new NameIDType(); nameId.setFormat(URI.create(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get())); @@ -181,9 +149,9 @@ public class SoapTest { @Override public Config.Scope scope(String... scope) { if (scope.length == 2 && "connectionsHttpClient".equals(scope[0]) && "default".equals(scope[1])) { - return createScope(Collections.singletonMap("proxy-mappings", "localhost;http://localhost:8281")); + return ScopeUtil.createScope(Collections.singletonMap("proxy-mappings", "localhost;http://localhost:8281")); } - return createScope(new HashMap<>()); + return ScopeUtil.createScope(new HashMap<>()); } }); DefaultKeycloakSessionFactory sessionFactory = new DefaultKeycloakSessionFactory(); diff --git a/services/src/test/java/org/keycloak/truststore/FileTruststoreProviderFactoryTest.java b/services/src/test/java/org/keycloak/truststore/FileTruststoreProviderFactoryTest.java new file mode 100644 index 0000000000..91aea6f706 --- /dev/null +++ b/services/src/test/java/org/keycloak/truststore/FileTruststoreProviderFactoryTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 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.truststore; + +import org.junit.Test; +import org.keycloak.common.enums.HostnameVerificationPolicy; +import org.keycloak.utils.ScopeUtil; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class FileTruststoreProviderFactoryTest { + + @Test + public void testFallbackToSystemTruststore() throws IOException { + FileTruststoreProviderFactory factory = new FileTruststoreProviderFactory(); + factory.init(ScopeUtil.createScope(new HashMap<>())); + TruststoreProvider provider = factory.create(null); + assertNotNull(provider.getTruststore()); + assertEquals(HostnameVerificationPolicy.WILDCARD, provider.getPolicy()); + } + + @Test + public void testFallbackToSystemTruststoreWithHostnameVerification() throws IOException { + Map values = new HashMap<>(); + values.put(FileTruststoreProviderFactory.HOSTNAME_VERIFICATION_POLICY, + HostnameVerificationPolicy.ANY.name()); + FileTruststoreProviderFactory factory = new FileTruststoreProviderFactory(); + factory.init(ScopeUtil.createScope(values)); + TruststoreProvider provider = factory.create(null); + assertNotNull(provider.getTruststore()); + assertEquals(HostnameVerificationPolicy.ANY, provider.getPolicy()); + } + +} diff --git a/services/src/test/java/org/keycloak/truststore/TruststoreBuilderTest.java b/services/src/test/java/org/keycloak/truststore/TruststoreBuilderTest.java new file mode 100644 index 0000000000..de47a7c19f --- /dev/null +++ b/services/src/test/java/org/keycloak/truststore/TruststoreBuilderTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 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.truststore; + +import org.junit.Test; + +import java.io.File; +import java.net.URL; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +public class TruststoreBuilderTest { + + @Test + public void testMergedTrustStore() throws Exception { + URL url = TruststoreBuilderTest.class.getResource("/truststores/keycloak.pem"); + + KeyStore storeWithoutDefaults = TruststoreBuilder.createMergedTruststore(new String[] { url.getPath() }, false); + ArrayList storeWithoutDefaultsAliases = Collections.list(storeWithoutDefaults.aliases()); + assertEquals(2, storeWithoutDefaultsAliases.size()); + + KeyStore storeWithDefaults = TruststoreBuilder.createMergedTruststore(new String[] { url.getPath() }, true); + ArrayList storeWithDefaultsAliases = Collections.list(storeWithDefaults.aliases()); + int certs = storeWithDefaultsAliases.size(); + assertTrue(certs > 2); + assertTrue(storeWithDefaultsAliases.containsAll(storeWithoutDefaultsAliases)); + + // saving / loading should provide the certs even without a password + char[] password = null; + File saved = TruststoreBuilder.saveTruststore(storeWithDefaults, "target", password); + + KeyStore savedLoaded = TruststoreBuilder.loadStore(saved.getAbsolutePath(), TruststoreBuilder.PKCS12, password); + assertEquals(certs, Collections.list(savedLoaded.aliases()).size()); + } + + @Test + public void testMergedTrustStoreFromDirectory() throws Exception { + URL url = TruststoreBuilderTest.class.getResource("/truststores/keycloak.pem"); + + KeyStore storeWithoutDefaults = TruststoreBuilder + .createMergedTruststore(new String[] { new File(url.getPath()).getParent() }, false); + ArrayList storeWithoutDefaultsAliases = Collections.list(storeWithoutDefaults.aliases()); + assertEquals(2, storeWithoutDefaultsAliases.size()); + } + + @Test + public void testFailsWithInvalidFile() throws Exception { + URL url = TruststoreBuilderTest.class.getResource("/truststores/invalid"); + + assertThrows(RuntimeException.class, () -> TruststoreBuilder + .createMergedTruststore(new String[] { new File(url.getPath()).getAbsolutePath() }, false)); + } + +} diff --git a/services/src/test/java/org/keycloak/utils/ScopeUtil.java b/services/src/test/java/org/keycloak/utils/ScopeUtil.java new file mode 100644 index 0000000000..0b3f569415 --- /dev/null +++ b/services/src/test/java/org/keycloak/utils/ScopeUtil.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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.utils; + +import org.junit.Assert; +import org.keycloak.services.util.JsonConfigProvider; +import org.keycloak.services.util.JsonConfigProvider.JsonScope; + +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class ScopeUtil { + + public static JsonScope createScope(Map properties) { + ObjectMapper mapper = new ObjectMapper(); + try { + JsonNode config = mapper.readTree(json(properties)); + return new JsonConfigProvider(config, new Properties()).new JsonScope(config); + } catch (IOException e) { + Assert.fail("Could not parse json"); + } + return null; + } + + static String json(Map properties) { + String[] params = properties.entrySet().stream().map(e -> param(e.getKey(), e.getValue())).toArray(String[]::new); + + StringBuilder sb = new StringBuilder(); + sb.append("{"); + sb.append(String.join(",", params)); + sb.append("}"); + + return sb.toString(); + } + + static String param(String key, String value) { + return "\"" + key + "\"" + " : " + "\"" + value + "\""; + } + +} diff --git a/services/src/test/resources/truststores/invalid b/services/src/test/resources/truststores/invalid new file mode 100644 index 0000000000..66ef525e64 --- /dev/null +++ b/services/src/test/resources/truststores/invalid @@ -0,0 +1 @@ +I am not a PEM file \ No newline at end of file diff --git a/services/src/test/resources/truststores/keycloak.pem b/services/src/test/resources/truststores/keycloak.pem new file mode 100644 index 0000000000..4d5c901918 --- /dev/null +++ b/services/src/test/resources/truststores/keycloak.pem @@ -0,0 +1,53 @@ +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIJAOMEN39fZf7uMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCTUExDzANBgNVBAcMBkJvc3RvbjEQMA4GA1UECgwHUmVkIEhhdDERMA8GA1UECwwI +S2V5Y2xvYWsxFDASBgNVBAMMC0tleWNsb2FrIENBMSMwIQYJKoZIhvcNAQkBFhRjb250YWN0QGtl +eWNsb2FrLm9yZzAeFw0xODAyMjAxOTQ3NTFaFw00NTA3MDgxOTQ3NTFaMIGLMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCTUExDzANBgNVBAcMBkJvc3RvbjEQMA4GA1UECgwHUmVkIEhhdDERMA8GA1UE +CwwIS2V5Y2xvYWsxFDASBgNVBAMMC0tleWNsb2FrIENBMSMwIQYJKoZIhvcNAQkBFhRjb250YWN0 +QGtleWNsb2FrLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJlGjg05FzCm3f3Y +dIbMHNYuORfiP2n6YhX7vQyDjF/4gh7EYEYgE7spJ864/DySQenJ55Jn22K/1MQ1rOHcqfTioIgN +3eEAyyuMDx60KU3frMBRYeCgLJVZQHr06x+Sh/+SbbIYq/558+g/6PSZjmPBindHsPzGuBPaLOW4 +Jz47CA73L/su2qnJGeAiUaK/tXmANs1bqJbiNRDr9IJFkdusx1mql2ElfknJT8U+LBPOOID/S7Xd +83SKtpFIQ8Vikb6C0SKnopOJiG2uWg5g7CYlNYxJpAM25zhDqp71bl8zOsIL2tFfUAvvoBnhN31k +DIl8RZJ5ELnh+t5SCfwbgdfMzS7uht8qVTeZ0/BG80Lzl1gfzNR8q45gsKC97mg7Voj68kt2aZr+ +E3Ng1guK69gePMxCpqLyjwlKz187mNUme+zxg2gL2egs4M6uffqsEd0c5QryrRSTcIXi8Bim6PDh +L93dBsenAIg25DOJNA6Vt2LELoe9w0TkL48UwUvU6GYB7/zM/z3EW45ZkRhHWK+HZppqDAb05lgJ +eeKUxxdUSy+ot7ls6cSqACYofVjPoVHPD5Ncx+6NGHPGM5N3FGvMMh64PYpChyVWDTEfrZIS7Yyj +9Iz/2eCxV3cOcO4bU0K6kx/dWRic5B5ymVtRME93+Of/hQuta4uLhlo8ZxRpAgMBAAGjYzBhMB0G +A1UdDgQWBBQiuPS7cwDHKT+TgKX2HFICast6UjAfBgNVHSMEGDAWgBQiuPS7cwDHKT+TgKX2HFIC +ast6UjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEA +VCVXdx79ooKyOaL+S49S4agP7mE4IxuDefDwQ2dm996wpk3nntg0y54Auu1Y2plJirBhTvYZ1Red +LNBMVBypm6BQpNn37u5TI39/FYsoGFPINu1EzLTYl0bFKc0w7UFlFusje9zXLWISm8uTNzxJ1RGL +rcnv9gfiXPKxAmN0cz9WY0vm+0+OV50HvLyUyqGKxyWmt2ek4jV+oEhsMMSO/MVNNXHEo2MAGcA2 +3XPe7FZkiFB1suDIMzzUFCrRBtoZjYSUeyN9Pd0Yg3twl96CLqld4xFjsKMIsz0ACGRI8OpzeHAs +ePH4yS94E6nLwWH9YTi6pgTtoXSaVBLvIYpVHi8UAyIBFNqLMCukoq0OBlOdkO0zescmpEtp8GiU +WMuB7x+kkaSxmsujEfL3mRWshkqaz/ZHPKXaNtPBUtIMjQnTMBF/wQjZxCGAps8dOMZ9pYnZcmVz +0KeXpBJe1j+47MhItgt1wQNoyr4iBaxE3fAF/Arr/IZtIf0erXOjc7P6dEQW+WiKWvEA5Mp+4tV3 +Zj2pwSSX5bKDKx4RAkoW1jLTE1KN5RWvF8phStLty83gTd5wgykFSl65Lu7KIBW9HH3LIK46fb+c +OBOZfSn3mdQXrbuXNUXgbhrsetnBfPNMAkJjaBQLNTxebIvXndiTIEsWqHS7h1x+kBkDOKhwSCc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEsTCCApmgAwIBAgICC7kwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVTMQswCQYDVQQI +DAJNQTEPMA0GA1UEBwwGQm9zdG9uMRAwDgYDVQQKDAdSZWQgSGF0MREwDwYDVQQLDAhLZXljbG9h +azEUMBIGA1UEAwwLS2V5Y2xvYWsgQ0ExIzAhBgkqhkiG9w0BCQEWFGNvbnRhY3RAa2V5Y2xvYWsu +b3JnMB4XDTE5MDMyMTE4MTk1MFoXDTQ2MDgwNjE4MTk1MFowZDELMAkGA1UEBhMCVVMxCzAJBgNV +BAgMAk1BMRAwDgYDVQQKDAdSZWQgSGF0MREwDwYDVQQLDAhLZXljbG9hazEjMCEGA1UEAwwaS2V5 +Y2xvYWsgSW50ZXJtZWRpYXRlIENBIDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD1 +afusL2MqMMxg20HTGyVt5nxc9MvpzU9yWhKyper9hh/iEDkH06SZ+vInUWGTeRfGmoA3W+IGF4Tz +SFO2BjOLz6bowNTqpWONZAK0swMauE23sbxA7XfKxmIt5pDkQWAWb2kzLoKEXkbmdlF+O/qcM8i+ +1U7fpg1NjQI1DHvBVMvul/aGyzP6q/HjNEg+7o3y21T/3gq/Yma9L+awX1wLLxtDlSGjP1Q2C19w +87ijUIwFUo7AzTOn03SIt+Pfc1cdIsmjUG2lDqJTyo/VuqmfWWBMm6p+Ya/Z4Nipk87nxoW60y4E +jRL7vxx9vyWiRUhP/2pEE5y9LQ0uzxxA4kIPAgMBAAGjRTBDMB0GA1UdDgQWBBQYXI8JjLfLFM+o +Cghx8t/R+2iv0zASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAgEAB5SrTyWz95GqgG0zrtIwJArMY4EUuISGWEgX8GpVyP9xTM1he/vOXqFpAQHtygiq +5yhyrrMJuu5/m0Ca5NZM1NaM6unJr7mIPEKKBP/85Fue/gBe/Q0Vx1VnmxZhszqQT4hLIfR8anLX +gK9w8E6lB4HAXgK+VUzzxXLE43oc8/L9swEnvCKsLHKynexNxKRIZD3GPmLCciOz+101Fb6a0Nc8 ++A+5soiQi8gC/K9ygXvxqmHtnrTndZmrtJFOaeXwMDAw7q3j25MkRt4fBPcKX0soBuI6/cTE2veH +GPYXW4QbVswrhJVD/vVuU0VFR1fLnuw9NgDkGz0qoi20x/Ew3HiVi++9EU9YsPgRStcfsCTfth2T +LsPmSFSKeGMR3e+Hr6xr6fcahSl6QmKcI60EHw+kKqQ3bBq7QOxizEuQ0SunRyuEUewnhMT9s7sI +rZ4M2fyiWH3GVm2971J0/ey3NEYSq/Y9fAO6+9xGWlvaps8X5GLs/XYt9HwZNtdb9F3YMNSFi10W +BIKqdpppLT9uuIDSK5S+s5chuychy2TZiCBwKBt1lRdqEZ6J4vMj7M5eIdj8Pmvp+tmL1kewuTfK +aU+2Bpy6l42sYkyMjy7Z0XyMITIBnAf9SmElQPUjq+IIYL6HK/i8mI15AHHIiwm1eaUdbvBKJnQS +0++b0iwvQA4= +-----END CERTIFICATE----- diff --git a/testsuite/integration-arquillian/servers/auth-server/common/keystore/keycloak-truststore.p12 b/testsuite/integration-arquillian/servers/auth-server/common/keystore/keycloak-truststore.p12 new file mode 100644 index 0000000000..3d69527c22 Binary files /dev/null and b/testsuite/integration-arquillian/servers/auth-server/common/keystore/keycloak-truststore.p12 differ diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java index 5db8d7d9d1..aae5beff53 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java @@ -21,6 +21,7 @@ import org.jboss.resteasy.annotations.cache.NoCache; import org.keycloak.http.HttpRequest; import org.keycloak.Config; import org.keycloak.common.Profile; +import org.keycloak.common.enums.HostnameVerificationPolicy; import org.keycloak.common.util.HtmlUtils; import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; @@ -86,7 +87,6 @@ import org.keycloak.testsuite.util.FeatureDeployerUtil; import org.keycloak.timer.TimerProvider; import org.keycloak.truststore.FileTruststoreProvider; import org.keycloak.truststore.FileTruststoreProviderFactory; -import org.keycloak.truststore.HostnameVerificationPolicy; import org.keycloak.truststore.TruststoreProvider; import org.keycloak.util.JsonSerialization; import org.keycloak.utils.MediaType; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java index 96455a69dc..5ae007e93d 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java @@ -19,13 +19,13 @@ package org.keycloak.testsuite.client.resources; import org.jboss.resteasy.annotations.cache.NoCache; import org.keycloak.common.Profile; +import org.keycloak.common.enums.HostnameVerificationPolicy; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.components.TestProvider; import org.keycloak.testsuite.rest.representation.AuthenticatorState; -import org.keycloak.truststore.HostnameVerificationPolicy; import org.keycloak.utils.MediaType; import jakarta.ws.rs.Consumes; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/ssl/TrustStoreEmailTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/ssl/TrustStoreEmailTest.java index a966204bad..d00fe85b48 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/ssl/TrustStoreEmailTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/ssl/TrustStoreEmailTest.java @@ -21,6 +21,7 @@ import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.common.enums.HostnameVerificationPolicy; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; @@ -39,7 +40,6 @@ import org.keycloak.testsuite.util.AccountHelper; import org.keycloak.testsuite.util.MailServerConfiguration; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.SslMailServer; -import org.keycloak.truststore.HostnameVerificationPolicy; import static org.junit.Assert.assertEquals; import static org.keycloak.testsuite.util.MailAssert.assertEmailAndGetUrl;