From 6d4b57e7fdf19e65d07ea16dc9858aa066650e80 Mon Sep 17 00:00:00 2001 From: Anshuman Date: Fri, 25 Sep 2026 11:57:19 -0400 Subject: [PATCH 1/2] US1216212: Default to `UseJvmDefault` when FIPS is detected --- README.md | 27 ++++++---- caf-ssl-core/pom.xml | 2 +- .../ssl/core/SslProviderConfigurator.java | 45 +++++++++++++--- .../ssl/core/SslProviderConfiguratorTest.java | 54 +++++++++++++------ caf-ssl-dropwizard/pom.xml | 2 +- caf-ssl-spring/pom.xml | 2 +- pom.xml | 2 +- release-notes-2.0.2.md | 8 --- release-notes-2.1.0.md | 10 ++++ 9 files changed, 106 insertions(+), 46 deletions(-) delete mode 100644 release-notes-2.0.2.md create mode 100644 release-notes-2.1.0.md diff --git a/README.md b/README.md index 4ba21b8..be0ec2d 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,7 @@ Finally, add the following environment variables: - `SSL_VALIDATE_CERTS `: Optional. This property configures whether to validate certs. Defaults to **false**. - `SSL_DISABLE_SNI_HOST_CHECK`: Optional. This property configures whether to disable the SNI host check. Defaults to **false**. - `HTTPS_PORT`: Optional. This property configures the port for the HTTPS server. Defaults to **8443**. -- `SSL_JCE_PROVIDER_POLICY`: Optional. Controls TLS JCE provider selection strategy. Defaults to **UseBouncyCastleIfNeededForPqc**. - Supported values: - - **UseBouncyCastle**: Always register Bouncy Castle providers and configure BCJSSE for HTTPS. - - **UseBouncyCastleIfNeededForPqc**: Register Bouncy Castle only when PQC support is not already available from the runtime. - - **UseJvmDefault**: Do not register Bouncy Castle providers; use JVM defaults. +- `SSL_JCE_PROVIDER_POLICY`: Optional. Controls TLS JCE provider selection strategy. See [SSL_JCE_PROVIDER_POLICY](#ssl_jce_provider_policy) below. ## caf-ssl-spring @@ -73,12 +69,7 @@ To use, import the dependency into your project: Provider selection is controlled by the same environment variable as `caf-ssl-dropwizard`: -- `SSL_JCE_PROVIDER_POLICY`: Optional. Controls TLS JCE provider selection strategy. Defaults to **UseBouncyCastleIfNeededForPqc**. - Supported values: - - **UseBouncyCastle**: Always register Bouncy Castle providers. - - **UseBouncyCastleIfNeededForPqc**: Register Bouncy Castle only when PQC support is not already available from the runtime. - - **UseJvmDefault**: Do not register Bouncy Castle providers; use JVM defaults. - +- `SSL_JCE_PROVIDER_POLICY`: Optional. Controls TLS JCE provider selection strategy. See [SSL_JCE_PROVIDER_POLICY](#ssl_jce_provider_policy) below. TLS ciphers: caf-ssl-spring sets approved suites by default. @@ -91,3 +82,17 @@ $env:CAF_SSL_CIPHER_SUITES="TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384" This module holds the shared provider-selection and BouncyCastle registration logic used by both `caf-ssl-dropwizard` and `caf-ssl-spring`. Services do not depend on it directly. + +## SSL_JCE_PROVIDER_POLICY + +Both `caf-ssl-dropwizard` and `caf-ssl-spring` are controlled by the same `SSL_JCE_PROVIDER_POLICY` environment +variable, which selects the TLS JCE provider strategy. Optional; defaults to **UseBouncyCastleIfNeededForPqc**. +Supported values: + +- **UseBouncyCastle**: Always register Bouncy Castle providers (and, for `caf-ssl-dropwizard`, configure BCJSSE for HTTPS). +- **UseBouncyCastleIfNeededForPqc**: Register Bouncy Castle only when PQC support is not already available from the runtime. +- **UseJvmDefault**: Do not register Bouncy Castle providers; use JVM defaults. + +FIPS and PQC are mutually exclusive: if the JVM is detected to be running in FIPS mode, the default and +**UseBouncyCastleIfNeededForPqc** policies automatically behave as **UseJvmDefault** instead of registering +Bouncy Castle. No extra configuration is needed for this; an explicit **UseBouncyCastle** value still wins. diff --git a/caf-ssl-core/pom.xml b/caf-ssl-core/pom.xml index e8205b7..3562814 100644 --- a/caf-ssl-core/pom.xml +++ b/caf-ssl-core/pom.xml @@ -22,7 +22,7 @@ com.github.cafapi.ssl caf-ssl - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT 4.0.0 diff --git a/caf-ssl-core/src/main/java/com/github/cafapi/ssl/core/SslProviderConfigurator.java b/caf-ssl-core/src/main/java/com/github/cafapi/ssl/core/SslProviderConfigurator.java index 4d0670a..a2609e3 100644 --- a/caf-ssl-core/src/main/java/com/github/cafapi/ssl/core/SslProviderConfigurator.java +++ b/caf-ssl-core/src/main/java/com/github/cafapi/ssl/core/SslProviderConfigurator.java @@ -15,11 +15,14 @@ */ package com.github.cafapi.ssl.core; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.Provider; import java.security.Security; import java.util.Arrays; import java.util.List; +import java.util.Locale; import javax.net.ssl.SSLContext; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; @@ -33,11 +36,16 @@ *

The behaviour is driven by the {@value #SSL_JCE_PROVIDER_POLICY_ENV} environment variable:

* * + *

FIPS and PQC are mutually exclusive: BouncyCastle's PQC support is not FIPS-certified, so the default + * and {@code UseBouncyCastleIfNeededForPqc} policies fall back to JVM defaults when the host is detected to be + * running in FIPS mode (see {@link #isFipsEnabled()}). No extra configuration is required for this; an explicit + * {@code UseBouncyCastle} policy still overrides it.

+ * *

This class carries no Dropwizard or Spring dependency so both bundles can share it.

*/ public final class SslProviderConfigurator @@ -76,6 +84,9 @@ public final class SslProviderConfigurator static final String POLICY_USE_BOUNCY_CASTLE_IF_NEEDED_FOR_PQC = "UseBouncyCastleIfNeededForPqc"; static final String POLICY_USE_JVM_DEFAULT = "UseJvmDefault"; + /** Linux kernel flag consulted to detect FIPS mode. */ + static final Path LINUX_FIPS_FLAG_FILE = Path.of("/proc/sys/crypto/fips_enabled"); + private static final Logger LOGGER = LoggerFactory.getLogger(SslProviderConfigurator.class); private SslProviderConfigurator() @@ -93,7 +104,8 @@ private SslProviderConfigurator() */ public static boolean configure(final boolean insertJsseProviderAtHighestPriority) { - final boolean useBouncyCastle = shouldUseBouncyCastle(System.getenv(SSL_JCE_PROVIDER_POLICY_ENV)); + final boolean useBouncyCastle = shouldUseBouncyCastle( + System.getenv(SSL_JCE_PROVIDER_POLICY_ENV), isRuntimePqcSupported(), isFipsEnabled()); if (useBouncyCastle) { registerBouncyCastleProviders(insertJsseProviderAtHighestPriority); LOGGER.info("caf-ssl: registered BouncyCastle providers for PQC TLS ({})", PQC_NAMED_GROUP); @@ -115,14 +127,14 @@ public static String resolveApprovedCipherSuites() } /** - * Resolves the policy, probing the runtime for PQC support when the policy defers to it. + * Resolves the policy, probing the runtime for PQC support and FIPS mode when the policy defers to them. * * @param policy the raw {@value #SSL_JCE_PROVIDER_POLICY_ENV} value (may be {@code null}) * @return {@code true} if BouncyCastle should be registered */ public static boolean shouldUseBouncyCastle(final String policy) { - return shouldUseBouncyCastle(policy, isRuntimePqcSupported()); + return shouldUseBouncyCastle(policy, isRuntimePqcSupported(), isFipsEnabled()); } /** @@ -130,15 +142,17 @@ public static boolean shouldUseBouncyCastle(final String policy) * * @param policy the raw {@value #SSL_JCE_PROVIDER_POLICY_ENV} value (may be {@code null}) * @param runtimePqcSupported whether the runtime can already negotiate the PQC hybrid group + * @param fipsEnabled whether the host is running in FIPS mode * @return {@code true} if BouncyCastle should be registered */ - public static boolean shouldUseBouncyCastle(final String policy, final boolean runtimePqcSupported) + public static boolean shouldUseBouncyCastle(final String policy, final boolean runtimePqcSupported, + final boolean fipsEnabled) { final String normalizedPolicy = policy == null ? null : policy.trim(); if (normalizedPolicy == null || normalizedPolicy.isEmpty() || POLICY_USE_BOUNCY_CASTLE_IF_NEEDED_FOR_PQC.equalsIgnoreCase(normalizedPolicy)) { - return !runtimePqcSupported; + return !fipsEnabled && !runtimePqcSupported; } if (POLICY_USE_BOUNCY_CASTLE.equalsIgnoreCase(normalizedPolicy)) { @@ -173,6 +187,25 @@ public static boolean isRuntimePqcSupported() } } + /** + * Detects whether the JVM is running in FIPS mode + */ + public static boolean isFipsEnabled() + { + final String osName = System.getProperty("os.name", ""); + if (!osName.toLowerCase(Locale.ROOT).contains("linux")) { + return false; + } + + try { + return Files.exists(LINUX_FIPS_FLAG_FILE) + && "1".equals(Files.readString(LINUX_FIPS_FLAG_FILE).trim()); + } catch (final Exception e) { + LOGGER.warn("caf-ssl: unable to read {}; assuming FIPS is not enabled", LINUX_FIPS_FLAG_FILE, e); + return false; + } + } + /** * Registers the BouncyCastle JCE and JSSE providers. Idempotent: existing providers are reused and not * duplicated. When {@code insertJsseProviderAtHighestPriority} is {@code true}, an already-registered diff --git a/caf-ssl-core/src/test/java/com/github/cafapi/ssl/core/SslProviderConfiguratorTest.java b/caf-ssl-core/src/test/java/com/github/cafapi/ssl/core/SslProviderConfiguratorTest.java index 263ec2a..2f25656 100644 --- a/caf-ssl-core/src/test/java/com/github/cafapi/ssl/core/SslProviderConfiguratorTest.java +++ b/caf-ssl-core/src/test/java/com/github/cafapi/ssl/core/SslProviderConfiguratorTest.java @@ -34,7 +34,6 @@ import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; import org.junit.jupiter.api.Test; @@ -43,48 +42,69 @@ final class SslProviderConfiguratorTest @Test void useBouncyCastlePolicyAlwaysRegisters() { - assertTrue(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastle", true)); - assertTrue(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastle", false)); + assertTrue(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastle", true, false)); + assertTrue(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastle", false, false)); } @Test void useJvmDefaultPolicyNeverRegisters() { - assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("UseJvmDefault", true)); - assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("UseJvmDefault", false)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("UseJvmDefault", true, false)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("UseJvmDefault", false, false)); } @Test void ifNeededPolicyFollowsRuntimeSupport() { - assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastleIfNeededForPqc", true)); - assertTrue(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastleIfNeededForPqc", false)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastleIfNeededForPqc", true, false)); + assertTrue(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastleIfNeededForPqc", false, false)); } @Test void nullEmptyAndBlankDefaultToIfNeeded() { - assertFalse(SslProviderConfigurator.shouldUseBouncyCastle(null, true)); - assertTrue(SslProviderConfigurator.shouldUseBouncyCastle(null, false)); - assertTrue(SslProviderConfigurator.shouldUseBouncyCastle("", false)); - assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("", true)); - assertTrue(SslProviderConfigurator.shouldUseBouncyCastle(" ", false)); - assertFalse(SslProviderConfigurator.shouldUseBouncyCastle(" ", true)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle(null, true, false)); + assertTrue(SslProviderConfigurator.shouldUseBouncyCastle(null, false, false)); + assertTrue(SslProviderConfigurator.shouldUseBouncyCastle("", false, false)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("", true, false)); + assertTrue(SslProviderConfigurator.shouldUseBouncyCastle(" ", false, false)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle(" ", true, false)); } @Test void policyMatchingIsCaseInsensitiveAndTrimmed() { - assertTrue(SslProviderConfigurator.shouldUseBouncyCastle(" usebouncycastle ", false)); - assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("USEJVMDEFAULT", true)); - assertFalse(SslProviderConfigurator.shouldUseBouncyCastle(" usebouncycastleifneededforpqc ", true)); + assertTrue(SslProviderConfigurator.shouldUseBouncyCastle(" usebouncycastle ", false, false)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("USEJVMDEFAULT", true, false)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle(" usebouncycastleifneededforpqc ", true, false)); } @Test void unknownPolicyThrows() { assertThrows(IllegalArgumentException.class, - () -> SslProviderConfigurator.shouldUseBouncyCastle("Nonsense", false)); + () -> SslProviderConfigurator.shouldUseBouncyCastle("Nonsense", false, false)); + } + + @Test + void fipsEnabledOverridesDefaultAndIfNeededPolicies() + { + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle(null, false, true)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastleIfNeededForPqc", false, true)); + } + + @Test + void fipsEnabledDoesNotOverrideExplicitPolicies() + { + assertTrue(SslProviderConfigurator.shouldUseBouncyCastle("UseBouncyCastle", false, true)); + assertFalse(SslProviderConfigurator.shouldUseBouncyCastle("UseJvmDefault", true, true)); + } + + @Test + void isFipsEnabledNeverThrows() + { + // Fail-safe contract: probing for FIPS mode must never abort startup. + SslProviderConfigurator.isFipsEnabled(); } @Test diff --git a/caf-ssl-dropwizard/pom.xml b/caf-ssl-dropwizard/pom.xml index 2ce1918..2f62983 100644 --- a/caf-ssl-dropwizard/pom.xml +++ b/caf-ssl-dropwizard/pom.xml @@ -22,7 +22,7 @@ com.github.cafapi.ssl caf-ssl - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT 4.0.0 diff --git a/caf-ssl-spring/pom.xml b/caf-ssl-spring/pom.xml index 3e27399..7bbc31e 100644 --- a/caf-ssl-spring/pom.xml +++ b/caf-ssl-spring/pom.xml @@ -22,7 +22,7 @@ com.github.cafapi.ssl caf-ssl - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index ed0bd58..11b2926 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ com.github.cafapi.ssl caf-ssl - 2.0.2-SNAPSHOT + 2.1.0-SNAPSHOT pom CAF SSL Modules diff --git a/release-notes-2.0.2.md b/release-notes-2.0.2.md deleted file mode 100644 index b365463..0000000 --- a/release-notes-2.0.2.md +++ /dev/null @@ -1,8 +0,0 @@ -!not-ready-for-release! - -#### Version Number -${version-number} - -#### New Features - -#### Known Issues diff --git a/release-notes-2.1.0.md b/release-notes-2.1.0.md new file mode 100644 index 0000000..99567a6 --- /dev/null +++ b/release-notes-2.1.0.md @@ -0,0 +1,10 @@ +!not-ready-for-release! + +#### Version Number +${version-number} + +#### New Features +- US1216212: Default to `UseJvmDefault` when FIPS is detected, since FIPS and PQC are mutually exclusive. + +#### Known Issues +- None From 114c0d51da7ba72dd047b24b09a218218fab4d53 Mon Sep 17 00:00:00 2001 From: Anshuman <48919248+anshuman0799@users.noreply.github.com> Date: Fri, 25 Sep 2026 12:06:19 -0400 Subject: [PATCH 2/2] Improve FIPS flag reading logic in SslProviderConfigurator Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../github/cafapi/ssl/core/SslProviderConfigurator.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/caf-ssl-core/src/main/java/com/github/cafapi/ssl/core/SslProviderConfigurator.java b/caf-ssl-core/src/main/java/com/github/cafapi/ssl/core/SslProviderConfigurator.java index a2609e3..0af8db2 100644 --- a/caf-ssl-core/src/main/java/com/github/cafapi/ssl/core/SslProviderConfigurator.java +++ b/caf-ssl-core/src/main/java/com/github/cafapi/ssl/core/SslProviderConfigurator.java @@ -198,11 +198,12 @@ public static boolean isFipsEnabled() } try { - return Files.exists(LINUX_FIPS_FLAG_FILE) - && "1".equals(Files.readString(LINUX_FIPS_FLAG_FILE).trim()); - } catch (final Exception e) { - LOGGER.warn("caf-ssl: unable to read {}; assuming FIPS is not enabled", LINUX_FIPS_FLAG_FILE, e); + return "1".equals(Files.readString(LINUX_FIPS_FLAG_FILE).trim()); + } catch (final java.nio.file.NoSuchFileException e) { return false; + } catch (final Exception e) { + LOGGER.warn("caf-ssl: unable to read {}; assuming FIPS is enabled", LINUX_FIPS_FLAG_FILE, e); + return true; } }