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 @@
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,26 @@ 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 "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; + } + } + /** * 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 @@