Filtered providers: add wolfssl.filtered.*.additionalServices properties

pull/246/head
Chris Conlon 2026-07-31 12:18:13 -06:00
parent 406f6b7ec0
commit dd3220f710
8 changed files with 925 additions and 91 deletions

View File

@ -111,8 +111,12 @@ your system:
## Customizing the filter
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.
only place that controls which services pass through. Edit, rebuild, and
redeploy if you need to do something different than the default behavior.
To grant exceptions for individual services without recompiling, see
[Allowing additional services](#allowing-additional-services-through-the-filter)
below.
## Using the original Sun provider names
@ -136,30 +140,28 @@ under the original provider names that they are filtering instead of their
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.
This is a Security property (not System property), read at provider
construction time. It can also be set with `Security.setProperty()` before
the providers are first instantiated. `security.overridePropertiesFile=false`
blocks an alternate properties file (`-Djava.security.properties`), though
early `Security.setProperty()` calls still apply.
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")`.
- Legacy application code and third-party jars that pin the original provider
names work unmodified (ex: `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.
resolving, without patching.
- No application-side try/catch fallback shims are needed for the allow-listed
services.
Cons (why the default leaves the `Filtered*` names):
Cons (why the default uses the `Filtered*` provider 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
from 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 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`
@ -174,6 +176,109 @@ Cons (why the default leaves the `Filtered*` names):
provider, silently bypassing the filtered one and reinstating the
non-validated crypto regardless of this property.
## Allowing additional services through the filter
Some JDK and application code depends on non-FIPS-validated algorithms, and
sometimes FIPS-specific exceptions have been made in those cases. One example
offender is `java.util.UUID.nameUUIDFromBytes()`, which calls
`MessageDigest.getInstance("MD5")` and throws `InternalError` if no registered
provider offers MD5, so on a hardened image any code generating name-based
(version 3) UUIDs fails at startup.
To grant exceptions for individual services without recompiling, set the
per-provider `additionalServices` Security properties to a comma-separated
list of `Type.Algorithm` entries, ex:
```
wolfssl.filtered.sun.additionalServices=MessageDigest.MD5
wolfssl.filtered.sunec.additionalServices=
wolfssl.filtered.sunrsasign.additionalServices=
```
With the example above, `FilteredSun` also copies `MessageDigest.MD5` from
the original `SUN` provider, and `UUID.nameUUIDFromBytes()` works again. The
grant is exact: only the listed algorithm passes through, not the whole
service type.
Entry semantics:
- Entries are split on the **first** `.` into a service type and an algorithm,
because algorithm names can themselves contain dots
(ex: `CertStore.com.sun.security.IndexedCollection`).
- Matching is case-insensitive against canonical names. Aliases cannot specify
a grant. A granted service keeps its original aliases and stays reachable
under them (ex: the MD5 OID `1.2.840.113549.2.5`), the same as the
compiled-in allow-list services.
- Malformed entries (no dot, empty type, or empty algorithm) are ignored.
- Like `wolfssl.filtered.useOriginalNames`, these are Security properties
(not System properties), read at provider construction time.
`security.overridePropertiesFile=false` blocks an alternate properties
file (`-Djava.security.properties`), but application code running before
the providers are first instantiated can still change them with
`Security.setProperty()`.
- These properties are independent of `wolfssl.filtered.useOriginalNames`.
Pros (why grant exceptions this way):
- Code that requires a non-validated algorithm for a non-security purpose
(ex: version 3 UUIDs) works without application changes or re-enabling
the algorithm in the wolfCrypt build.
- The granted algorithm is served by the JDK's pure-Java Sun implementation,
keeping it outside the validated wolfCrypt module boundary.
- The exception list is an auditable line in `java.security`, reviewable
in image diffs and enumerable at runtime via `Provider.getServices()`.
Cons (why the default grants nothing):
- A granted service is reachable by **any** code in the JVM, not only the
caller it was granted for. Grants must be deliberate and meaningful due to
potential use of non-FIPS validated cryptography.
- Each entry widens the audited service surface. Compliance sign-off should
cover every entry and the reason it is needed.
- Wildcards are deliberately not supported, so the granted surface stays
explicitly enumerable.
## Common configuration mistakes
The properties above fail closed: a value that does not parse leaves the
default behavior in place. The providers warn to stderr at construction time
for mistakes they can detect. A correct configuration prints nothing.
- `#` starts a comment only at the beginning of a line in `java.security`,
so `wolfssl.filtered.useOriginalNames=true # on` sets the value to
`true # on`, which is unrecognized (treated as `false`). Same for
`additionalServices` entries.
- Do not quote values: `="MessageDigest.MD5"` includes the quote characters
and matches nothing.
- Use canonical `Type.Algorithm` names in grants. Aliases and OID forms
(ex: `MessageDigest.1.2.840.113549.2.5`) never match an entry.
- Grants are per provider: `KeyFactory.EC` belongs in
`wolfssl.filtered.sunec.additionalServices`, not `...sun...`. An entry
matching no service of the wrapped provider is ignored.
- `wolfssl.filtered.useOriginalNames` and the `additionalServices` properties
are Security properties. Setting them with `-D` (ex: via
`JAVA_TOOL_OPTIONS`) creates an ignored system property. The exception is
`wolfssl.filtered.debug`, a system property that must be set with `-D`.
- Property keys are case-sensitive and fixed lowercase:
`wolfssl.filtered.sunec.additionalServices`, never `...SunEC...`.
- Long values need a trailing `\` to continue on the next line. Without it the
continuation becomes a different, silently ignored property.
- Keep `security.provider.N` numbering consecutive from 1. The JDK stops
reading at the first gap and silently ignores later entries.
- Register the providers by *their* class names only. Both
`security.provider.N=SUN` and `=sun.security.provider.Sun` load the
stock Sun provider through a hardcoded JDK fast path, and the name form
`security.provider.N=FilteredSun` stops resolving once
`wolfssl.filtered.useOriginalNames=true` is set (the JDK matches the
entry against the resolved name, then `SUN`).
- On images with `security.overridePropertiesFile=false`,
`-Djava.security.properties` has no effect. Edit the image `java.security`
file instead.
- If a provider is missing at runtime, check stderr for constructor failures
(ex: missing `--add-opens` flags) and rerun with
`-Djava.security.debug=provider`. The JDK swallows provider construction
errors silently.
## Tests
The filtered-providers tests run automatically on JDK 9+ alongside the main

View File

@ -43,6 +43,12 @@ import java.util.Set;
* CertificateFactory.getInstance("X.509", "SUN")). Only the allow-listed
* services above are exposed regardless of the registered name.
*
* Set the wolfssl.filtered.sun.additionalServices Security property to a
* comma-separated list of Type.Algorithm entries to allow additional SUN
* services through the filter without recompiling (ex: MessageDigest.MD5
* keeps java.util.UUID.nameUUIDFromBytes() working). See
* ProviderServiceCopier.serviceAllowedByProperty() for entry syntax.
*
* 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.
@ -59,6 +65,8 @@ public class FilteredSun extends Provider {
"Filtered SUN for non-crypto ops");
try {
ProviderServiceCopier.warnIgnoredSystemProperties("sun",
"FilteredSun");
if (DEBUG) {
System.err.println("Loading original SUN...");
}
@ -70,9 +78,15 @@ public class FilteredSun extends Provider {
original.getServices().size());
}
String addProp =
ProviderServiceCopier.additionalServicesProperty("sun");
Set<String> grants =
ProviderServiceCopier.additionalServiceKeys(addProp);
Set<Provider.Service> services = original.getServices();
for (Provider.Service s : services) {
if (serviceSupported(s)) {
if (serviceSupported(s) ||
ProviderServiceCopier.serviceAllowedByProperty(grants, s)) {
if (DEBUG) {
System.err.println("Copying " + s.getType() + "." +
s.getAlgorithm() + " with class: " +
@ -84,6 +98,9 @@ public class FilteredSun extends Provider {
}
}
ProviderServiceCopier.warnIgnoredEntries("sun", "FilteredSun",
addProp, original);
if (DEBUG) {
System.err.println("FilteredSun initialized successfully " +
"with " + getServices().size() + " services.");
@ -100,11 +117,11 @@ public class FilteredSun extends Provider {
}
/**
* Checks if the given service is supported by this provider.
* This is the filtering logic that determines which services
* are retained in the FilteredSun provider.
* Compiled-in allow-list controlling which services are retained.
* Services can also pass the filter through the
* wolfssl.filtered.sun.additionalServices Security property.
*
* Edit this method to change the filtering logic.
* Edit this method to change the compiled-in filtering logic.
*
* @param service the service to check
*

View File

@ -39,6 +39,11 @@ import java.util.Set;
* hardcoded provider names working. Only the allow-listed services above are
* exposed regardless of the registered name.
*
* Set the wolfssl.filtered.sunec.additionalServices Security property to
* a comma-separated list of Type.Algorithm entries to allow additional
* SunEC services through the filter. See
* ProviderServiceCopier.serviceAllowedByProperty() for entry syntax.
*
* 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.
@ -55,6 +60,8 @@ public class FilteredSunEC extends Provider {
"Filtered SunEC for non-crypto ops");
try {
ProviderServiceCopier.warnIgnoredSystemProperties("sunec",
"FilteredSunEC");
if (DEBUG) {
System.err.println("Loading original SunEC...");
}
@ -71,9 +78,15 @@ public class FilteredSunEC extends Provider {
"available: " + original.getServices().size());
}
String addProp =
ProviderServiceCopier.additionalServicesProperty("sunec");
Set<String> grants =
ProviderServiceCopier.additionalServiceKeys(addProp);
Set<Provider.Service> services = original.getServices();
for (Provider.Service s : services) {
if (serviceSupported(s)) {
if (serviceSupported(s) ||
ProviderServiceCopier.serviceAllowedByProperty(grants, s)) {
if (DEBUG) {
System.err.println("Copying " + s.getType() + "." +
s.getAlgorithm() + " with class: " +
@ -84,6 +97,10 @@ public class FilteredSunEC extends Provider {
ProviderServiceCopier.buildService(this, s, true));
}
}
ProviderServiceCopier.warnIgnoredEntries("sunec", "FilteredSunEC",
addProp, original);
if (DEBUG) {
System.err.println("FilteredSunEC initialized successfully " +
"with " + getServices().size() + " services.");
@ -100,11 +117,11 @@ public class FilteredSunEC extends Provider {
}
/**
* Checks if the given service is supported by this provider.
* This is the filtering logic that determines which services
* are retained in the FilteredSunEC provider.
* Compiled-in allow-list controlling which services are retained.
* Services can also pass the filter through the
* wolfssl.filtered.sunec.additionalServices Security property.
*
* Edit this method to change the filtering logic.
* Edit this method to change the compiled-in filtering logic.
*
* @param service the service to check
*

View File

@ -38,6 +38,11 @@ import java.util.Set;
* with hardcoded provider names working. Only the allow-listed services above
* are exposed regardless of the registered name.
*
* Set the wolfssl.filtered.sunrsasign.additionalServices Security property
* to a comma-separated list of Type.Algorithm entries to allow additional
* SunRsaSign services through the filter. See
* ProviderServiceCopier.serviceAllowedByProperty() for entry syntax.
*
* 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.
@ -55,6 +60,8 @@ public class FilteredSunRsaSign extends Provider {
"Filtered SunRsaSign for non-crypto ops");
try {
ProviderServiceCopier.warnIgnoredSystemProperties("sunrsasign",
"FilteredSunRsaSign");
if (DEBUG) {
System.err.println("Loading original SunRsaSign...");
}
@ -67,9 +74,15 @@ public class FilteredSunRsaSign extends Provider {
"Services available: " + original.getServices().size());
}
String addProp = ProviderServiceCopier
.additionalServicesProperty("sunrsasign");
Set<String> grants =
ProviderServiceCopier.additionalServiceKeys(addProp);
Set<Provider.Service> services = original.getServices();
for (Provider.Service s : services) {
if (serviceSupported(s)) {
if (serviceSupported(s) ||
ProviderServiceCopier.serviceAllowedByProperty(grants, s)) {
if (DEBUG) {
System.err.println("Copying " + s.getType() + "." +
s.getAlgorithm() + " with class: " +
@ -81,10 +94,12 @@ public class FilteredSunRsaSign extends Provider {
}
}
ProviderServiceCopier.warnIgnoredEntries("sunrsasign",
"FilteredSunRsaSign", addProp, original);
if (DEBUG) {
System.err.println("FilteredSunRsaSign initialized " +
"successfully with " + getServices().size() +
" services.");
"successfully with " + getServices().size() + " services.");
}
} catch (Exception e) {
@ -98,11 +113,11 @@ public class FilteredSunRsaSign extends Provider {
}
/**
* Checks if the given service is supported by this provider.
* This is the filtering logic that determines which services
* are retained in the FilteredSunRsaSign provider.
* Compiled-in allow-list controlling which services are retained.
* Services can also pass the filter through the
* wolfssl.filtered.sunrsasign.additionalServices Security property.
*
* Edit this method to change the filtering logic.
* Edit this method to change the compiled-in filtering logic.
*
* @param service the service to check
*

View File

@ -26,9 +26,12 @@ import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Shared helper for the FilteredSun, FilteredSunEC, and FilteredSunRsaSign
@ -50,6 +53,15 @@ final class ProviderServiceCopier {
private ProviderServiceCopier() {
}
/** Security property enabling original Sun provider names. */
private static final String USE_ORIGINAL_NAMES_PROP =
"wolfssl.filtered.useOriginalNames";
/** Name of the per-provider additionalServices Security property. */
private static String additionalServicesPropName(String providerKey) {
return "wolfssl.filtered." + providerKey + ".additionalServices";
}
/**
* Resolve the name a filtered provider should register under.
*
@ -73,15 +85,208 @@ final class ProviderServiceCopier {
*/
static String resolveName(String filteredName, String originalName) {
String prop = Security.getProperty("wolfssl.filtered.useOriginalNames");
String prop = Security.getProperty(USE_ORIGINAL_NAMES_PROP);
if (prop != null && "true".equalsIgnoreCase(prop.trim())) {
return originalName;
if (prop != null) {
String val = prop.trim();
if ("true".equalsIgnoreCase(val)) {
return originalName;
}
/* Warn on values other than "true"/"false"/"", such as a
* java.security inline comment mistake like: "true # comment" */
if (!val.isEmpty() && !"false".equalsIgnoreCase(val)) {
System.err.println(filteredName + ": unrecognized value '" +
val + "' for Security property " +
USE_ORIGINAL_NAMES_PROP + ", treating as false");
}
}
return filteredName;
}
/**
* Split a trimmed Type.Algorithm entry on its first '.' only, since
* algorithm names may themselves contain dots
* (ex: CertStore.com.sun.security.IndexedCollection).
*
* @param entry trimmed Type.Algorithm entry
*
* @return array of type and algorithm, or null if malformed (no dot,
* empty type, or empty algorithm)
*/
private static String[] parseEntry(String entry) {
int dot;
String type, algo;
dot = entry.indexOf('.');
if (dot <= 0 || dot == entry.length() - 1) {
return null;
}
type = entry.substring(0, dot).trim();
algo = entry.substring(dot + 1).trim();
if (type.isEmpty() || algo.isEmpty()) {
return null;
}
return new String[] { type, algo };
}
/** Lowercase "type.algorithm" key used for grant matching. */
private static String serviceKey(String type, String algo) {
return (type + "." + algo).toLowerCase(Locale.ROOT);
}
/**
* Read the per-provider additionalServices Security property. Read once
* per construction and passed to both additionalServiceKeys() and
* warnIgnoredEntries(), so the grant and warning passes see the same
* value.
*
* @param providerKey lowercase key: "sun", "sunec", or "sunrsasign"
*
* @return property value, or null if unset
*/
static String additionalServicesProperty(String providerKey) {
return Security.getProperty(additionalServicesPropName(providerKey));
}
/**
* Parse an additionalServices property value into grant keys for
* serviceAllowedByProperty(). The value is a comma-separated list of
* Type.Algorithm entries, for example:
*
* wolfssl.filtered.sun.additionalServices=MessageDigest.MD5
*
* Entries match case-insensitively against canonical service names.
* Aliases cannot specify a grant, though a granted copy keeps its
* aliases. Malformed entries are ignored here and reported by
* warnIgnoredEntries(). Security property only, a System property of
* the same name would have no effect.
*
* @param prop property value from additionalServicesProperty()
*
* @return grant keys, empty if the property is unset or empty
*/
static Set<String> additionalServiceKeys(String prop) {
Set<String> keys = new HashSet<>();
if (prop == null || prop.trim().isEmpty()) {
return keys;
}
for (String entry : prop.split(",")) {
String[] parts = parseEntry(entry.trim());
if (parts != null) {
keys.add(serviceKey(parts[0], parts[1]));
}
}
return keys;
}
/**
* Check if grantKeys (from additionalServiceKeys()) grants a service.
*
* @param grantKeys parsed grant keys
* @param service candidate service from the original Sun provider
*
* @return true if granted
*/
static boolean serviceAllowedByProperty(Set<String> grantKeys,
Provider.Service service) {
return grantKeys.contains(
serviceKey(service.getType(), service.getAlgorithm()));
}
/**
* Warn to System.err for each additionalServices entry that will
* never grant a service: malformed entries and entries matching no
* original provider service. Called after each constructor's copy
* loop so misconfigured grants show at startup. Empty entries are
* skipped.
*
* @param providerKey lowercase key: "sun", "sunec", or "sunrsasign"
* @param providerName fixed filtered name to prefix warnings with
* (not getName(), which may be the original Sun name)
* @param prop property value from additionalServicesProperty(), the
* same value the grant pass parsed
* @param original the original Sun provider
*/
static void warnIgnoredEntries(String providerKey, String providerName,
String prop, Provider original) {
String propName = additionalServicesPropName(providerKey);
if (prop == null || prop.trim().isEmpty()) {
return;
}
/* Canonical keys only, matching the grant pass. Must not use
* original.getService(), it resolves aliases and would report an
* alias/OID entry as matched when the grant pass never matches
* it. */
Set<String> originalKeys = new HashSet<>();
for (Provider.Service s : original.getServices()) {
originalKeys.add(serviceKey(s.getType(), s.getAlgorithm()));
}
for (String rawEntry : prop.split(",")) {
String entry = rawEntry.trim();
if (entry.isEmpty()) {
continue;
}
String[] parts = parseEntry(entry);
if (parts == null) {
System.err.println(providerName +
": ignored malformed entry '" + entry + "' in " +
propName + " (expected Type.Algorithm)");
continue;
}
if (!originalKeys.contains(serviceKey(parts[0], parts[1]))) {
System.err.println(providerName + ": entry '" + entry +
"' in " + propName + " matches no " +
original.getName() + " service, entry ignored (use " +
"canonical Type.Algorithm names, aliases and OIDs do " +
"not match)");
}
}
}
/**
* Warn to System.err when a system property matching one of the Security
* properties this provider reads is set. System properties are ignored.
* This catches -Dwolfssl.filtered...=... configs (ex: via
* JAVA_TOOL_OPTIONS, which can only set system properties).
*
* @param providerKey lowercase key: "sun", "sunec", or "sunrsasign"
* @param providerName filtered provider name to prefix warnings with
*/
static void warnIgnoredSystemProperties(String providerKey,
String providerName) {
String[] propNames = {
USE_ORIGINAL_NAMES_PROP,
additionalServicesPropName(providerKey) };
for (String propName : propNames) {
if (System.getProperty(propName) != null) {
System.err.println(providerName + ": system property " +
propName + " is ignored, set it as a Security " +
"property in java.security or with " +
"Security.setProperty() before provider construction");
}
}
}
/**
* Build a copy of originalService owned by target.
*

View File

@ -28,14 +28,22 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.PrintStream;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.security.Provider;
import java.security.Security;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@ -59,9 +67,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.
* Also covers the wolfssl.filtered.useOriginalNames Security property
* (register under the original Sun names) and the
* wolfssl.filtered.[provider].additionalServices properties (grant individual
* services, ex: MessageDigest.MD5 for java.util.UUID.nameUUIDFromBytes()).
*
* Requires Java 9+. See examples/filtered-providers/docs/add-opens.md for the
* required (JDK-version-dependent) JVM module flags.
@ -78,7 +87,27 @@ public class FilteredProviderFunctionalTest {
private static String caEccCertDer;
/** Security property controlling filtered provider registration names. */
private static final String NAME_PROP = "wolfssl.filtered.useOriginalNames";
private static final String NAME_PROP =
"wolfssl.filtered.useOriginalNames";
/** Security property granting additional FilteredSun services. */
private static final String ADD_PROP =
"wolfssl.filtered.sun.additionalServices";
/** Security property granting additional FilteredSunEC services. */
private static final String EC_ADD_PROP =
"wolfssl.filtered.sunec.additionalServices";
/** Security property granting additional FilteredSunRsaSign services. */
private static final String RSA_ADD_PROP =
"wolfssl.filtered.sunrsasign.additionalServices";
/** Properties pinned to benign values while registering providers. */
private static final String[] PIN_PROPS = {
NAME_PROP, ADD_PROP, EC_ADD_PROP, RSA_ADD_PROP };
/** Benign pin values for PIN_PROPS. */
private static final String[] PIN_VALS = { "false", "", "", "" };
@Rule(order = Integer.MIN_VALUE)
public TestRule testWatcher = TimedTestWatcher.create();
@ -91,19 +120,24 @@ public class FilteredProviderFunctionalTest {
System.out.println("FilteredSun* provider functional test");
/* 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");
/* Pin properties to benign values during registration so names stay
* FilteredSun* and no extra services are granted, even if the test
* JVM's java.security sets them. Restore afterward. */
String[] prev = new String[PIN_PROPS.length];
for (int i = 0; i < PIN_PROPS.length; i++) {
prev[i] = Security.getProperty(PIN_PROPS[i]);
Security.setProperty(PIN_PROPS[i], PIN_VALS[i]);
}
try {
Security.addProvider(new FilteredSun());
Security.addProvider(new FilteredSunEC());
Security.addProvider(new FilteredSunRsaSign());
} finally {
Security.setProperty(NAME_PROP, (prev != null) ? prev : "false");
for (int i = 0; i < PIN_PROPS.length; i++) {
Security.setProperty(PIN_PROPS[i],
(prev[i] != null) ? prev[i] : PIN_VALS[i]);
}
}
/* Relative path from repo root; forked tests have cwd = basedir. */
@ -190,18 +224,12 @@ public class FilteredProviderFunctionalTest {
}
}
/**
* Restore the Security property to its prior value, or to "false"
* (equivalent to unset for name resolution) if it was never set.
*/
/** Restore NAME_PROP to prev, or "false" (equivalent to unset). */
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.
*/
/** 1-based registration position of the named provider, or -1. */
private static int providerPosition(String name) {
Provider[] providers = Security.getProviders();
@ -255,12 +283,19 @@ public class FilteredProviderFunctionalTest {
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. */
/* A system property of the same name must have no effect and must
* trigger the ignored-property warning */
Security.setProperty(NAME_PROP, "false");
System.setProperty(NAME_PROP, "true");
assertEquals("FilteredSun", new FilteredSun().getName());
final Provider[] holder = new Provider[1];
String err = captureStderr(() -> {
holder[0] = new FilteredSun();
});
assertEquals("FilteredSun", holder[0].getName());
assertTrue("no ignored-system-property warning printed",
err.contains("system property") && err.contains(NAME_PROP));
} finally {
System.clearProperty(NAME_PROP);
@ -275,9 +310,8 @@ public class FilteredProviderFunctionalTest {
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. */
/* getInfo() must keep identifying the provider as filtered so
* audits can distinguish it from the stock SUN */
Provider p = new FilteredSun();
assertEquals("SUN", p.getName());
assertEquals("Filtered SUN for non-crypto ops", p.getInfo());
@ -303,9 +337,8 @@ public class FilteredProviderFunctionalTest {
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. */
/* Simulate the hardened JRE: swap the real SUN out and register
* the filtered provider in its place */
if (realSun != null) {
Security.removeProvider("SUN");
}
@ -313,8 +346,7 @@ public class FilteredProviderFunctionalTest {
Security.addProvider(filtered) != -1);
filteredAdded = true;
/* Hardcoded provider name lookup must now resolve to the
* filtered provider instance */
/* Hardcoded name lookup must resolve to the filtered instance */
CertificateFactory cf =
CertificateFactory.getInstance("X.509", "SUN");
assertNotNull("CertificateFactory X.509 not resolved from " +
@ -336,5 +368,427 @@ public class FilteredProviderFunctionalTest {
restoreSecurityProperty(prev);
}
}
/** Restore ADD_PROP to prev, or "" (equivalent to unset). */
private static void restoreAdditionalServices(String prev) {
Security.setProperty(ADD_PROP, (prev != null) ? prev : "");
}
/** Return lowercase hex encoding of the given bytes. */
private static String toHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/** Return the set of "Type.Algorithm" keys exposed by a provider. */
private static Set<String> serviceKeys(Provider p) {
Set<String> keys = new HashSet<>();
for (Provider.Service svc : p.getServices()) {
keys.add(svc.getType() + "." + svc.getAlgorithm());
}
return keys;
}
/** Run r with System.err captured, return output, restore stream. */
private static String captureStderr(Runnable r) {
PrintStream prevErr = System.err;
ByteArrayOutputStream buf = new ByteArrayOutputStream();
PrintStream capture = new PrintStream(buf, true);
try {
System.setErr(capture);
r.run();
} finally {
System.setErr(prevErr);
capture.close();
}
return buf.toString();
}
@Test
public void testAdditionalServicesDefaultBlocked() {
String prev = Security.getProperty(ADD_PROP);
try {
Security.setProperty(ADD_PROP, "");
Provider p = new FilteredSun();
assertNull("MessageDigest.MD5 exposed without property grant",
p.getService("MessageDigest", "MD5"));
} finally {
restoreAdditionalServices(prev);
}
}
@Test
public void testAdditionalServicesGrantsMd5() throws Exception {
String prev = Security.getProperty(ADD_PROP);
try {
Security.setProperty(ADD_PROP, "MessageDigest.MD5");
Provider p = new FilteredSun();
assertNotNull("MessageDigest.MD5 not granted by property",
p.getService("MessageDigest", "MD5"));
/* Only the listed algorithm is granted, not the whole type */
assertNull("MessageDigest.SHA-256 leaked by MD5 grant",
p.getService("MessageDigest", "SHA-256"));
/* Granted service must be usable end-to-end. RFC 1321 test
* vector: MD5("abc") */
MessageDigest md = MessageDigest.getInstance("MD5", p);
byte[] digest = md.digest("abc".getBytes("UTF-8"));
assertEquals("granted MD5 service computed wrong digest",
"900150983cd24fb0d6963f7d28e17f72", toHex(digest));
} finally {
restoreAdditionalServices(prev);
}
}
@Test
public void testAdditionalServicesCaseInsensitive() {
String prev = Security.getProperty(ADD_PROP);
try {
Security.setProperty(ADD_PROP, "messagedigest.md5");
Provider p = new FilteredSun();
assertNotNull("case-insensitive entry not matched",
p.getService("MessageDigest", "MD5"));
} finally {
restoreAdditionalServices(prev);
}
}
@Test
public void testAdditionalServicesMalformedIgnored() {
String prev = Security.getProperty(ADD_PROP);
try {
/* Baseline service set with no grants */
Security.setProperty(ADD_PROP, "");
Set<String> baseline = serviceKeys(new FilteredSun());
/* Malformed entries must not throw and must leave the service
* set identical to the no-grant baseline */
Security.setProperty(ADD_PROP,
"MessageDigest, .MD5, MessageDigest., , ,,");
assertEquals("malformed entries changed the service set",
baseline, serviceKeys(new FilteredSun()));
} finally {
restoreAdditionalServices(prev);
}
}
@Test
public void testUuidNameFromBytesUsesGrantedMd5() throws Exception {
String prevAdd = Security.getProperty(ADD_PROP);
String prevName = Security.getProperty(NAME_PROP);
/* Swap out every provider offering MessageDigest.MD5 (SUN on a
* stock JDK, possibly more elsewhere) plus the default FilteredSun
* from setup, keyed by 1-based registration position */
TreeMap<Integer, Provider> removed = new TreeMap<>();
Provider[] md5Provs = Security.getProviders("MessageDigest.MD5");
if (md5Provs != null) {
for (Provider p : md5Provs) {
int pos = providerPosition(p.getName());
assertTrue("registered provider has no position", pos > 0);
removed.put(pos, p);
}
}
Provider prevFiltered = Security.getProvider("FilteredSun");
if (prevFiltered != null) {
int pos = providerPosition("FilteredSun");
assertTrue("FilteredSun has no position", pos > 0);
removed.put(pos, prevFiltered);
}
Provider granting = null;
boolean grantingAdded = false;
try {
Security.setProperty(NAME_PROP, "false");
Security.setProperty(ADD_PROP, "MessageDigest.MD5");
granting = new FilteredSun();
/* Simulate the hardened JRE: no registered provider offers MD5
* until the granting filtered provider is registered */
for (Provider p : removed.values()) {
Security.removeProvider(p.getName());
}
assertTrue("could not register granting filtered provider",
Security.addProvider(granting) != -1);
grantingAdded = true;
/* UUID.nameUUIDFromBytes()'s no-provider MD5 lookup must
* resolve to the granting filtered provider */
MessageDigest md = MessageDigest.getInstance("MD5");
assertSame("MD5 did not resolve to the filtered provider",
granting, md.getProvider());
/* Must produce the deterministic version 3 UUID for "test" */
UUID uuid = UUID.nameUUIDFromBytes("test".getBytes("UTF-8"));
assertEquals("UUID is not version 3 (MD5)", 3, uuid.version());
assertEquals("098f6bcd-4621-3373-8ade-4e832627b4f6",
uuid.toString());
} finally {
if (grantingAdded) {
Security.removeProvider(granting.getName());
}
/* Reinsert removed providers at original positions, ascending */
for (Map.Entry<Integer, Provider> e : removed.entrySet()) {
if (Security.getProvider(e.getValue().getName()) == null) {
Security.insertProviderAt(e.getValue(), e.getKey());
}
}
restoreAdditionalServices(prevAdd);
restoreSecurityProperty(prevName);
}
}
@Test
public void testAdditionalServicesGrantsSunEc() throws Exception {
String prev = Security.getProperty(EC_ADD_PROP);
try {
Security.setProperty(EC_ADD_PROP, "KeyFactory.EC");
Provider p = new FilteredSunEC();
assertNotNull("KeyFactory.EC not granted by property",
p.getService("KeyFactory", "EC"));
/* Instantiation exercises the delegating newInstance() path */
assertNotNull("granted KeyFactory.EC failed to instantiate",
KeyFactory.getInstance("EC", p));
} finally {
Security.setProperty(EC_ADD_PROP, (prev != null) ? prev : "");
}
}
@Test
public void testAdditionalServicesGrantsSunRsaSign() throws Exception {
String prev = Security.getProperty(RSA_ADD_PROP);
try {
Security.setProperty(RSA_ADD_PROP, "Signature.SHA256withRSA");
Provider p = new FilteredSunRsaSign();
assertNotNull("Signature.SHA256withRSA not granted by property",
p.getService("Signature", "SHA256withRSA"));
/* Instantiation exercises the delegating newInstance() path */
assertNotNull("granted Signature failed to instantiate",
Signature.getInstance("SHA256withRSA", p));
} finally {
Security.setProperty(RSA_ADD_PROP, (prev != null) ? prev : "");
}
}
@Test
public void testAdditionalServicesWithOriginalNames() {
String prevName = Security.getProperty(NAME_PROP);
String prevAdd = Security.getProperty(ADD_PROP);
try {
/* With both properties set, the provider registers under the
* original name and carries the granted service */
Security.setProperty(NAME_PROP, "true");
Security.setProperty(ADD_PROP, "MessageDigest.MD5");
Provider p = new FilteredSun();
assertEquals("SUN", p.getName());
assertNotNull("MessageDigest.MD5 not granted with name override",
p.getService("MessageDigest", "MD5"));
} finally {
restoreAdditionalServices(prevAdd);
restoreSecurityProperty(prevName);
}
}
@Test
public void testAdditionalServicesUnmatchedEntryWarns() {
String prev = Security.getProperty(ADD_PROP);
try {
/* Baseline service set with no grants */
Security.setProperty(ADD_PROP, "");
Set<String> baseline = serviceKeys(new FilteredSun());
/* A wrong-provider entry (KeyFactory.EC lives in SunEC) and
* an OID alias entry must both warn and grant nothing */
Security.setProperty(ADD_PROP,
"KeyFactory.EC, MessageDigest.1.2.840.113549.2.5");
final Provider[] holder = new Provider[1];
String err = captureStderr(() -> {
holder[0] = new FilteredSun();
});
assertTrue("no warning for unmatched entry KeyFactory.EC",
err.contains("'KeyFactory.EC'"));
assertTrue("no warning for OID alias entry",
err.contains("'MessageDigest.1.2.840.113549.2.5'"));
assertTrue("warning does not name the property",
err.contains(ADD_PROP));
assertEquals("unmatched entries changed the service set",
baseline, serviceKeys(holder[0]));
} finally {
restoreAdditionalServices(prev);
}
}
@Test
public void testAdditionalServicesMalformedEntryWarns() {
String prev = Security.getProperty(ADD_PROP);
String prevSys = System.getProperty(ADD_PROP);
/* Clear any environment-set system property so its ignored-property
* warning cannot pollute the captured stderr */
System.clearProperty(ADD_PROP);
try {
/* A malformed entry must warn without blocking a valid grant
* in the same list */
Security.setProperty(ADD_PROP,
"MessageDigest, MessageDigest.MD5");
final Provider[] holder = new Provider[1];
String err = captureStderr(() -> {
holder[0] = new FilteredSun();
});
assertTrue("no malformed-entry warning printed",
err.contains("malformed") &&
err.contains("'MessageDigest'"));
assertNotNull("valid entry not granted alongside malformed one",
holder[0].getService("MessageDigest", "MD5"));
/* Empty entries from stray commas must stay silent */
Security.setProperty(ADD_PROP, " , ,,");
String err2 = captureStderr(() -> {
new FilteredSun();
});
assertFalse("empty entries should not warn",
err2.contains(ADD_PROP));
} finally {
if (prevSys != null) {
System.setProperty(ADD_PROP, prevSys);
}
restoreAdditionalServices(prev);
}
}
@Test
public void testUnrecognizedUseOriginalNamesValueWarns() {
String prev = Security.getProperty(NAME_PROP);
String prevSys = System.getProperty(NAME_PROP);
/* Clear any environment-set system property so its ignored-property
* warning cannot pollute the captured stderr */
System.clearProperty(NAME_PROP);
try {
/* Unrecognized value must warn and fall back to the filtered
* name (ex: inline-comment mistake "true # comment") */
Security.setProperty(NAME_PROP, "yes");
final Provider[] holder = new Provider[1];
String err = captureStderr(() -> {
holder[0] = new FilteredSun();
});
assertTrue("no unrecognized-value warning printed",
err.contains("'yes'") && err.contains(NAME_PROP));
assertEquals("unrecognized value did not fall back to " +
"filtered name", "FilteredSun", holder[0].getName());
/* "false" must not warn */
Security.setProperty(NAME_PROP, "false");
String err2 = captureStderr(() -> {
new FilteredSun();
});
assertFalse("value 'false' must not warn",
err2.contains(NAME_PROP));
} finally {
if (prevSys != null) {
System.setProperty(NAME_PROP, prevSys);
}
restoreSecurityProperty(prev);
}
}
@Test
public void testAdditionalServicesSystemPropertyIsIgnored() {
String prev = Security.getProperty(ADD_PROP);
try {
/* A system property must not grant services and must trigger
* the ignored-property warning */
Security.setProperty(ADD_PROP, "");
System.setProperty(ADD_PROP, "MessageDigest.MD5");
final Provider[] holder = new Provider[1];
String err = captureStderr(() -> {
holder[0] = new FilteredSun();
});
assertNull("system property must not grant services",
holder[0].getService("MessageDigest", "MD5"));
assertTrue("no ignored-system-property warning printed",
err.contains("system property") && err.contains(ADD_PROP));
} finally {
System.clearProperty(ADD_PROP);
restoreAdditionalServices(prev);
}
}
@Test
public void testAdditionalServicesUnmatchedEntryWarnsEcRsa() {
String prevEc = Security.getProperty(EC_ADD_PROP);
String prevRsa = Security.getProperty(RSA_ADD_PROP);
try {
/* MessageDigest.MD5 exists in neither SunEC nor SunRsaSign;
* each warning must name its own property */
Security.setProperty(EC_ADD_PROP, "MessageDigest.MD5");
Security.setProperty(RSA_ADD_PROP, "MessageDigest.MD5");
String err = captureStderr(() -> {
new FilteredSunEC();
new FilteredSunRsaSign();
});
assertTrue("warning does not name the sunec property",
err.contains(EC_ADD_PROP));
assertTrue("warning does not name the sunrsasign property",
err.contains(RSA_ADD_PROP));
} finally {
Security.setProperty(EC_ADD_PROP,
(prevEc != null) ? prevEc : "");
Security.setProperty(RSA_ADD_PROP,
(prevRsa != null) ? prevRsa : "");
}
}
}

View File

@ -54,8 +54,15 @@ 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";
/** Properties pinned to benign values while registering providers. */
private static final String[] PIN_PROPS = {
"wolfssl.filtered.useOriginalNames",
"wolfssl.filtered.sun.additionalServices",
"wolfssl.filtered.sunec.additionalServices",
"wolfssl.filtered.sunrsasign.additionalServices" };
/** Benign pin values for PIN_PROPS. */
private static final String[] PIN_VALS = { "false", "", "", "" };
@Rule(order = Integer.MIN_VALUE)
public TestRule testWatcher = TimedTestWatcher.create();
@ -68,19 +75,23 @@ public class FilteredProviderNegativeTest {
System.out.println("FilteredSun* provider negative test");
/* 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");
/* Pin properties to benign values during registration so names stay
* FilteredSun* and no extra services are granted, even if the test
* JVM's java.security sets them. Restore afterward. */
String[] prev = new String[PIN_PROPS.length];
for (int i = 0; i < PIN_PROPS.length; i++) {
prev[i] = Security.getProperty(PIN_PROPS[i]);
Security.setProperty(PIN_PROPS[i], PIN_VALS[i]);
}
try {
Security.addProvider(new FilteredSun());
Security.addProvider(new FilteredSunEC());
Security.addProvider(new FilteredSunRsaSign());
} finally {
Security.setProperty(NAME_PROP, (prev != null) ? prev : "false");
for (int i = 0; i < PIN_PROPS.length; i++) {
Security.setProperty(PIN_PROPS[i],
(prev[i] != null) ? prev[i] : PIN_VALS[i]);
}
}
}

View File

@ -63,9 +63,15 @@ 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";
/** Properties pinned to benign values while registering providers. */
private static final String[] PIN_PROPS = {
"wolfssl.filtered.useOriginalNames",
"wolfssl.filtered.sun.additionalServices",
"wolfssl.filtered.sunec.additionalServices",
"wolfssl.filtered.sunrsasign.additionalServices" };
/** Benign pin values for PIN_PROPS. */
private static final String[] PIN_VALS = { "false", "", "", "" };
@Rule(order = Integer.MIN_VALUE)
public TestRule testWatcher = TimedTestWatcher.create();
@ -78,13 +84,14 @@ public class FilteredProviderSmokeTest {
System.out.println("FilteredSun* provider smoke test");
/* 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");
/* Pin properties to benign values during registration so names
* stay FilteredSun* and no extra services are granted, even if
* the test JVM's java.security sets them. Restore afterward. */
String[] prev = new String[PIN_PROPS.length];
for (int i = 0; i < PIN_PROPS.length; i++) {
prev[i] = Security.getProperty(PIN_PROPS[i]);
Security.setProperty(PIN_PROPS[i], PIN_VALS[i]);
}
try {
/* Construct all three providers; must not throw. */
sun = new FilteredSun();
@ -95,7 +102,10 @@ public class FilteredProviderSmokeTest {
Security.addProvider(sunEc);
Security.addProvider(sunRsa);
} finally {
Security.setProperty(NAME_PROP, (prev != null) ? prev : "false");
for (int i = 0; i < PIN_PROPS.length; i++) {
Security.setProperty(PIN_PROPS[i],
(prev[i] != null) ? prev[i] : PIN_VALS[i]);
}
}
}