Filtered providers: add wolfssl.filtered.*.additionalServices properties
parent
406f6b7ec0
commit
dd3220f710
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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 : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue