From 406f6b7ec0435caeb5e0dc908d514da5a5206e0c Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 23 Jul 2026 16:50:00 -0600 Subject: [PATCH] Filtered providers: add wolfssl.filtered.useOriginalNames property --- examples/filtered-providers/README.md | 60 ++++++ .../security/providers/FilteredSun.java | 16 +- .../security/providers/FilteredSunEC.java | 15 +- .../providers/FilteredSunRsaSign.java | 14 +- .../providers/ProviderServiceCopier.java | 33 ++++ .../test/FilteredProviderFunctionalTest.java | 171 +++++++++++++++++- .../test/FilteredProviderNegativeTest.java | 20 +- .../test/FilteredProviderSmokeTest.java | 29 ++- 8 files changed, 334 insertions(+), 24 deletions(-) diff --git a/examples/filtered-providers/README.md b/examples/filtered-providers/README.md index 590d69bc..9affbec8 100644 --- a/examples/filtered-providers/README.md +++ b/examples/filtered-providers/README.md @@ -114,6 +114,66 @@ Each `Filtered*.java` has a single `serviceSupported()` method, which is the only place that controls which services pass through. Edit it, rebuild, and redeploy if you need to do something different. +## Using the original Sun provider names + +Some legacy code, OpenJDK code, or other libraries may hardcode specific +provider names, such as: + +```java +CertificateFactory.getInstance("X.509", "SUN"); +``` + +With the filtered providers registered under their default names, those calls +will throw `NoSuchProviderException` because the `SUN` providers may have been +purposefully unregistered for FIPS compliance. + +To keep this type of code working, the `wolfssl.filtered.useOriginalNames` +Security property can be set to `true` and the filtered providers will register +under the original provider names that they are filtering instead of their +`Filtered*` names. + +``` +wolfssl.filtered.useOriginalNames=true +``` + +This is a Security property (not System property), and is read at provider +construction time, so it can also be set programmatically with +`Security.setProperty()` as long as that happens before the providers are first +instantiated. On images that set `security.overridePropertiesFile=false` the +value is fixed by the image's `java.security` file and cannot be changed from +the command line. + +Pros (why enable it): + +- Legacy application code and third-party jars that pin the original + provider names work unmodified, e.g. + `CertificateFactory.getInstance("X.509", "SUN")`. +- OpenJDK code paths that internally look up Sun providers by name keep + resolving, without patching or shading. +- No application-side try/catch fallback shims are needed for the + allow-listed services. + +Cons (why the default leaves the `Filtered*` names): + +- A hardened provider masquerading as the stock one can hide the hardening + from casual inspection: monitoring that records only `Provider.getName()` + sees `SUN` on both stock and hardened JREs. Use `Provider.getInfo()` or the + provider class name to distinguish them. +- With the default `Filtered*` names, a pinned lookup fails loudly with + `NoSuchProviderException` at the exact call site that needs fixing. Enabling + the name override trades that diagnosability for compatibility. +- The original providers must not also be registered. If both the real `SUN` + and a filtered provider named `SUN` end up registered, lookups resolve to + whichever is first in the provider order. +- Always register by class name (`security.provider.N = + com.wolfssl.security.providers.FilteredSun`). Class-name registration is + unaffected by the name change. Registration by provider *name* + (`security.provider.N = SUN`) must not be used with this feature: the JDK + resolves the built-in provider names internally before consulting classpath + providers, so a `SUN`/`SunEC`/`SunRsaSign` entry always loads the stock Sun + provider, silently bypassing the filtered one and reinstating the + non-validated crypto regardless of this property. + ## Tests The filtered-providers tests run automatically on JDK 9+ alongside the main diff --git a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSun.java b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSun.java index 8275a06d..66ba49f9 100644 --- a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSun.java +++ b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSun.java @@ -24,9 +24,9 @@ import java.security.Provider; import java.util.Set; /** - * FilteredSun is a custom security provider that filters out - * cryptographic services from the original SUN provider, retaining - * only the supporting non-cryptographic services. + * FilteredSun is a custom security provider that filters out cryptographic + * services from the original SUN provider, retaining only the supporting + * non-cryptographic services. * * It retains only the services: * - CertStore.Collection @@ -35,6 +35,14 @@ import java.util.Set; * - Configuration.JavaLoginConfig * - Policy.JavaPolicy * + * Set the wolfssl.filtered.useOriginalNames Security property to "true" + * (in java.security, or via Security.setProperty() before this provider is + * first instantiated) to register this provider under the original "SUN" name + * instead of "FilteredSun". This keeps applications and JDK code with + * hardcoded provider names working (e.g. + * CertificateFactory.getInstance("X.509", "SUN")). Only the allow-listed + * services above are exposed regardless of the registered name. + * * Set the system property wolfssl.filtered.debug=true to enable verbose * load/copy logging to stderr. Requires Java 9+ and the JVM module flags * documented in docs/add-opens.md. @@ -46,7 +54,7 @@ public class FilteredSun extends Provider { public FilteredSun() { - super("FilteredSun", + super(ProviderServiceCopier.resolveName("FilteredSun", "SUN"), System.getProperty("java.specification.version"), "Filtered SUN for non-crypto ops"); diff --git a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunEC.java b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunEC.java index 693de45e..176b506b 100644 --- a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunEC.java +++ b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunEC.java @@ -24,14 +24,21 @@ import java.security.Provider; import java.util.Set; /** - * FilteredSunEC is a custom security provider that filters out - * cryptographic services from the original SunEC provider, retaining - * only the supporting non-cryptographic services. + * FilteredSunEC is a custom security provider that filters out cryptographic + * services from the original SunEC provider, retaining only the supporting + * non-cryptographic services. * * It retains only: * * - AlgorithmParameters.EC * + * Set the wolfssl.filtered.useOriginalNames Security property to "true" (in + * java.security, or via Security.setProperty() before this provider is first + * instantiated) to register this provider under the original "SunEC" name + * instead of "FilteredSunEC". This keeps applications and JDK code with + * hardcoded provider names working. Only the allow-listed services above are + * exposed regardless of the registered name. + * * Set the system property wolfssl.filtered.debug=true to enable verbose * load/copy logging to stderr. Requires Java 9+ and the JVM module flags * documented in docs/add-opens.md. @@ -43,7 +50,7 @@ public class FilteredSunEC extends Provider { public FilteredSunEC() { - super("FilteredSunEC", + super(ProviderServiceCopier.resolveName("FilteredSunEC", "SunEC"), System.getProperty("java.specification.version"), "Filtered SunEC for non-crypto ops"); diff --git a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunRsaSign.java b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunRsaSign.java index dc3ee295..ae1c6d62 100644 --- a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunRsaSign.java +++ b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunRsaSign.java @@ -25,12 +25,19 @@ import java.util.Set; /** * FilteredSunRsaSign is a custom security provider that filters out - * cryptographic services from the original SunRsaSign provider, - * retaining only the supporting non-cryptographic services. + * cryptographic services from the original SunRsaSign provider, retaining + * only the supporting non-cryptographic services. * * It retains only: * - KeyFactory.RSASSA-PSS * + * Set the wolfssl.filtered.useOriginalNames Security property to "true" (in + * java.security, or via Security.setProperty() before this provider is first + * instantiated) to register this provider under the original "SunRsaSign" + * name instead of "FilteredSunRsaSign". This keeps applications and JDK code + * with hardcoded provider names working. Only the allow-listed services above + * are exposed regardless of the registered name. + * * Set the system property wolfssl.filtered.debug=true to enable verbose * load/copy logging to stderr. Requires Java 9+ and the JVM module flags * documented in docs/add-opens.md. @@ -42,7 +49,8 @@ public class FilteredSunRsaSign extends Provider { public FilteredSunRsaSign() { - super("FilteredSunRsaSign", + super(ProviderServiceCopier.resolveName( + "FilteredSunRsaSign", "SunRsaSign"), System.getProperty("java.specification.version"), "Filtered SunRsaSign for non-crypto ops"); diff --git a/examples/filtered-providers/src/com/wolfssl/security/providers/ProviderServiceCopier.java b/examples/filtered-providers/src/com/wolfssl/security/providers/ProviderServiceCopier.java index ec64ed6e..fd76fc4c 100644 --- a/examples/filtered-providers/src/com/wolfssl/security/providers/ProviderServiceCopier.java +++ b/examples/filtered-providers/src/com/wolfssl/security/providers/ProviderServiceCopier.java @@ -23,6 +23,7 @@ package com.wolfssl.security.providers; import java.lang.reflect.Field; import java.security.NoSuchAlgorithmException; import java.security.Provider; +import java.security.Security; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -49,6 +50,38 @@ final class ProviderServiceCopier { private ProviderServiceCopier() { } + /** + * Resolve the name a filtered provider should register under. + * + * By default the filtered name is returned. When the + * wolfssl.filtered.useOriginalNames Security property is set to "true" + * (in java.security, or via Security.setProperty() before the providers + * are first instantiated), the original Sun provider name is returned + * instead, so that applications and JDK code with hardcoded provider + * names keep working. + * + * NOTE: with this property enabled, the providers must be registered by + * class name in java.security. A provider-name entry + * (ex: security.provider.N=SUN) resolves to the stock Sun provider through + * the JDK built-in name resolution, silently bypassing the filtered + * provider. + * + * @param filteredName default name, e.g. "FilteredSun" + * @param originalName original Sun provider name, e.g. "SUN" + * + * @return the name to pass to the Provider super constructor + */ + static String resolveName(String filteredName, String originalName) { + + String prop = Security.getProperty("wolfssl.filtered.useOriginalNames"); + + if (prop != null && "true".equalsIgnoreCase(prop.trim())) { + return originalName; + } + + return filteredName; + } + /** * Build a copy of originalService owned by target. * diff --git a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderFunctionalTest.java b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderFunctionalTest.java index 2a294748..458c056b 100644 --- a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderFunctionalTest.java +++ b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderFunctionalTest.java @@ -59,6 +59,10 @@ import com.wolfssl.wolfcrypt.test.TimedTestWatcher; * Also asserts "no crypto leaked". Iterating each provider's getServices() * must not surface any service whose type is in the blocked crypto set. * + * Also covers the wolfssl.filtered.useOriginalNames Security property, which + * makes the providers register under the original Sun provider names for + * compatibility with code that hardcodes those names. + * * Requires Java 9+. See examples/filtered-providers/docs/add-opens.md for the * required (JDK-version-dependent) JVM module flags. */ @@ -73,6 +77,9 @@ public class FilteredProviderFunctionalTest { private static String caEccCertDer; + /** Security property controlling filtered provider registration names. */ + private static final String NAME_PROP = "wolfssl.filtered.useOriginalNames"; + @Rule(order = Integer.MIN_VALUE) public TestRule testWatcher = TimedTestWatcher.create(); @@ -84,9 +91,20 @@ public class FilteredProviderFunctionalTest { System.out.println("FilteredSun* provider functional test"); - Security.addProvider(new FilteredSun()); - Security.addProvider(new FilteredSunEC()); - Security.addProvider(new FilteredSunRsaSign()); + /* Pin the name override property to "false" while constructing and + * registering the providers, so registration names stay FilteredSun* + * even if the test JVM's java.security sets + * wolfssl.filtered.useOriginalNames=true (e.g. on a hardened + * image). Restore the prior value afterward. */ + String prev = Security.getProperty(NAME_PROP); + Security.setProperty(NAME_PROP, "false"); + try { + Security.addProvider(new FilteredSun()); + Security.addProvider(new FilteredSunEC()); + Security.addProvider(new FilteredSunRsaSign()); + } finally { + Security.setProperty(NAME_PROP, (prev != null) ? prev : "false"); + } /* Relative path from repo root; forked tests have cwd = basedir. */ String certPre = ""; @@ -171,5 +189,152 @@ public class FilteredProviderFunctionalTest { BLOCKED_TYPES.contains(type)); } } + + /** + * Restore the Security property to its prior value, or to "false" + * (equivalent to unset for name resolution) if it was never set. + */ + private static void restoreSecurityProperty(String prev) { + Security.setProperty(NAME_PROP, (prev != null) ? prev : "false"); + } + + /** + * Return the 1-based registration position of the named provider, + * or -1 if not registered. + */ + private static int providerPosition(String name) { + + Provider[] providers = Security.getProviders(); + + for (int i = 0; i < providers.length; i++) { + if (providers[i].getName().equals(name)) { + return i + 1; + } + } + + return -1; + } + + @Test + public void testDefaultNamesUnchanged() { + String prev = Security.getProperty(NAME_PROP); + + try { + Security.setProperty(NAME_PROP, "false"); + + assertEquals("FilteredSun", + new FilteredSun().getName()); + assertEquals("FilteredSunEC", + new FilteredSunEC().getName()); + assertEquals("FilteredSunRsaSign", + new FilteredSunRsaSign().getName()); + + } finally { + restoreSecurityProperty(prev); + } + } + + @Test + public void testSecurityPropertyEnablesOriginalNames() { + String prev = Security.getProperty(NAME_PROP); + + try { + Security.setProperty(NAME_PROP, "true"); + + assertEquals("SUN", new FilteredSun().getName()); + assertEquals("SunEC", new FilteredSunEC().getName()); + assertEquals("SunRsaSign", new FilteredSunRsaSign().getName()); + + } finally { + restoreSecurityProperty(prev); + } + } + + @Test + public void testSystemPropertyIsIgnored() { + String prev = Security.getProperty(NAME_PROP); + + try { + /* Only the Security property controls the name. A system property + * of the same name must have no effect. */ + Security.setProperty(NAME_PROP, "false"); + System.setProperty(NAME_PROP, "true"); + + assertEquals("FilteredSun", new FilteredSun().getName()); + + } finally { + System.clearProperty(NAME_PROP); + restoreSecurityProperty(prev); + } + } + + @Test + public void testInfoStringUnchangedWithOverride() { + String prev = Security.getProperty(NAME_PROP); + + try { + Security.setProperty(NAME_PROP, "true"); + + /* getInfo() must keep identifying the provider as filtered even + * when registered under the original name, so audits and telemetry + * can distinguish it from the stock SUN. */ + Provider p = new FilteredSun(); + assertEquals("SUN", p.getName()); + assertEquals("Filtered SUN for non-crypto ops", p.getInfo()); + + } finally { + restoreSecurityProperty(prev); + } + } + + @Test + public void testHardcodedSunLookupResolvesWithOverride() + throws Exception { + + String prev = Security.getProperty(NAME_PROP); + Provider realSun = Security.getProvider("SUN"); + int realSunPos = providerPosition("SUN"); + Provider filtered = null; + boolean filteredAdded = false; + + try { + Security.setProperty(NAME_PROP, "true"); + + filtered = new FilteredSun(); + assertEquals("SUN", filtered.getName()); + + /* Simulate the hardened JRE: the real SUN is not registered, the + * filtered provider takes its place. On a stock test JDK the real + * SUN is registered, so swap it out for the test duration. */ + if (realSun != null) { + Security.removeProvider("SUN"); + } + assertTrue("could not register filtered provider as SUN", + Security.addProvider(filtered) != -1); + filteredAdded = true; + + /* Hardcoded provider name lookup must now resolve to the + * filtered provider instance */ + CertificateFactory cf = + CertificateFactory.getInstance("X.509", "SUN"); + assertNotNull("CertificateFactory X.509 not resolved from " + + "provider registered as SUN", cf); + assertSame("lookup did not resolve to the filtered provider", + filtered, cf.getProvider()); + + } finally { + if (filteredAdded) { + Security.removeProvider(filtered.getName()); + } + if (realSun != null && Security.getProvider("SUN") == null) { + if (realSunPos > 0) { + Security.insertProviderAt(realSun, realSunPos); + } else { + Security.addProvider(realSun); + } + } + restoreSecurityProperty(prev); + } + } } diff --git a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderNegativeTest.java b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderNegativeTest.java index 67508f34..df78afb3 100644 --- a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderNegativeTest.java +++ b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderNegativeTest.java @@ -54,6 +54,9 @@ import com.wolfssl.wolfcrypt.test.TimedTestWatcher; */ public class FilteredProviderNegativeTest { + /** Security property controlling filtered provider registration names. */ + private static final String NAME_PROP = "wolfssl.filtered.useOriginalNames"; + @Rule(order = Integer.MIN_VALUE) public TestRule testWatcher = TimedTestWatcher.create(); @@ -65,9 +68,20 @@ public class FilteredProviderNegativeTest { System.out.println("FilteredSun* provider negative test"); - Security.addProvider(new FilteredSun()); - Security.addProvider(new FilteredSunEC()); - Security.addProvider(new FilteredSunRsaSign()); + /* Pin the name override property to "false" while constructing and + * registering the providers, so registration names stay FilteredSun* + * even if the test JVM's java.security sets + * wolfssl.filtered.useOriginalNames=true (e.g. on a hardened image). + * Restore the prior value afterward. */ + String prev = Security.getProperty(NAME_PROP); + Security.setProperty(NAME_PROP, "false"); + try { + Security.addProvider(new FilteredSun()); + Security.addProvider(new FilteredSunEC()); + Security.addProvider(new FilteredSunRsaSign()); + } finally { + Security.setProperty(NAME_PROP, (prev != null) ? prev : "false"); + } } private static int javaMajorVersion() { diff --git a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderSmokeTest.java b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderSmokeTest.java index 5b71ae71..db47e1e1 100644 --- a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderSmokeTest.java +++ b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderSmokeTest.java @@ -63,6 +63,10 @@ public class FilteredProviderSmokeTest { private static Provider sunEc; private static Provider sunRsa; + /** Security property controlling filtered provider registration names. */ + private static final String NAME_PROP = + "wolfssl.filtered.useOriginalNames"; + @Rule(order = Integer.MIN_VALUE) public TestRule testWatcher = TimedTestWatcher.create(); @@ -74,14 +78,25 @@ public class FilteredProviderSmokeTest { System.out.println("FilteredSun* provider smoke test"); - /* Construct all three providers; must not throw. */ - sun = new FilteredSun(); - sunEc = new FilteredSunEC(); - sunRsa = new FilteredSunRsaSign(); + /* Pin the name override property to "false" while constructing and + * registering the providers, so registration names stay FilteredSun* + * even if the test JVM's java.security sets + * wolfssl.filtered.useOriginalNames=true (e.g. on a hardened image). + * Restore the prior value afterward. */ + String prev = Security.getProperty(NAME_PROP); + Security.setProperty(NAME_PROP, "false"); + try { + /* Construct all three providers; must not throw. */ + sun = new FilteredSun(); + sunEc = new FilteredSunEC(); + sunRsa = new FilteredSunRsaSign(); - Security.addProvider(sun); - Security.addProvider(sunEc); - Security.addProvider(sunRsa); + Security.addProvider(sun); + Security.addProvider(sunEc); + Security.addProvider(sunRsa); + } finally { + Security.setProperty(NAME_PROP, (prev != null) ? prev : "false"); + } } /**