Skip to content
Merged
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
169 changes: 167 additions & 2 deletions examples/filtered-providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,173 @@ 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

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), 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 (ex: `CertificateFactory.getInstance("X.509", "SUN")`).
- OpenJDK code paths that internally look up Sun providers by name keep
resolving, without patching.
- No application-side try/catch fallback shims are needed for the allow-listed
services.

Cons (why the default uses the `Filtered*` provider names):

- A hardened provider masquerading as the stock one can hide the hardening
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`
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.

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
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
Expand All @@ -35,6 +35,20 @@
* - 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 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.
Expand All @@ -46,11 +60,13 @@ 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");

try {
ProviderServiceCopier.warnIgnoredSystemProperties("sun",
"FilteredSun");
if (DEBUG) {
System.err.println("Loading original SUN...");
}
Expand All @@ -62,9 +78,15 @@ public FilteredSun() {
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: " +
Expand All @@ -76,6 +98,9 @@ public FilteredSun() {
}
}

ProviderServiceCopier.warnIgnoredEntries("sun", "FilteredSun",
addProp, original);

if (DEBUG) {
System.err.println("FilteredSun initialized successfully " +
"with " + getServices().size() + " services.");
Expand All @@ -92,11 +117,11 @@ public FilteredSun() {
}

/**
* 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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,26 @@
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 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.
Expand All @@ -43,11 +55,13 @@ 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");

try {
ProviderServiceCopier.warnIgnoredSystemProperties("sunec",
"FilteredSunEC");
if (DEBUG) {
System.err.println("Loading original SunEC...");
}
Expand All @@ -64,9 +78,15 @@ public FilteredSunEC() {
"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: " +
Expand All @@ -77,6 +97,10 @@ public FilteredSunEC() {
ProviderServiceCopier.buildService(this, s, true));
}
}

ProviderServiceCopier.warnIgnoredEntries("sunec", "FilteredSunEC",
addProp, original);

if (DEBUG) {
System.err.println("FilteredSunEC initialized successfully " +
"with " + getServices().size() + " services.");
Expand All @@ -93,11 +117,11 @@ public FilteredSunEC() {
}

/**
* 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
*
Expand Down
Loading
Loading