Filtered providers: add wolfssl.filtered.useOriginalNames property

pull/246/head
Chris Conlon 2026-07-23 16:50:00 -06:00
parent a02707230d
commit 406f6b7ec0
8 changed files with 334 additions and 24 deletions

View File

@ -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

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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.
*

View File

@ -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);
}
}
}

View File

@ -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() {

View File

@ -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");
}
}
/**