Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.

Expand All @@ -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.
2 changes: 1 addition & 1 deletion caf-ssl-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>com.github.cafapi.ssl</groupId>
<artifactId>caf-ssl</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,11 +36,16 @@
* <p>The behaviour is driven by the {@value #SSL_JCE_PROVIDER_POLICY_ENV} environment variable:</p>
* <ul>
* <li>{@code UseBouncyCastleIfNeededForPqc} (default) - register BouncyCastle only when the JVM's own
* TLS stack cannot negotiate the PQC hybrid group.</li>
* TLS stack cannot negotiate the PQC hybrid group, and only when the JVM is not running in FIPS mode.</li>
* <li>{@code UseBouncyCastle} - always register BouncyCastle.</li>
* <li>{@code UseJvmDefault} - never register BouncyCastle; rely on the JVM's default providers.</li>
* </ul>
*
* <p>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.</p>
*
* <p>This class carries no Dropwizard or Spring dependency so both bundles can share it.</p>
*/
public final class SslProviderConfigurator
Expand Down Expand Up @@ -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()
Expand All @@ -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);
Expand All @@ -115,30 +127,32 @@ 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());
}

/**
* Pure policy resolution, independent of the runtime, so it can be unit tested in isolation.
*
* @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)
Comment on lines +148 to +149

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4c92729.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 9b5b385.

{
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)) {
Expand Down Expand Up @@ -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;
}
Comment thread
Copilot marked this conversation as resolved.
}

/**
* Registers the BouncyCastle JCE and JSSE providers. Idempotent: existing providers are reused and not
* duplicated. When {@code insertJsseProviderAtHighestPriority} is {@code true}, an already-registered
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();
Comment on lines +103 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 1c33e22.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in afbc0df.

}

@Test
Expand Down
2 changes: 1 addition & 1 deletion caf-ssl-dropwizard/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>com.github.cafapi.ssl</groupId>
<artifactId>caf-ssl</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion caf-ssl-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>com.github.cafapi.ssl</groupId>
<artifactId>caf-ssl</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

<groupId>com.github.cafapi.ssl</groupId>
<artifactId>caf-ssl</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<name>CAF SSL Modules</name>
Expand Down
8 changes: 0 additions & 8 deletions release-notes-2.0.2.md

This file was deleted.

10 changes: 10 additions & 0 deletions release-notes-2.1.0.md
Original file line number Diff line number Diff line change
@@ -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