diff --git a/examples/filtered-providers/README.md b/examples/filtered-providers/README.md index 590d69bc..fcfa1496 100644 --- a/examples/filtered-providers/README.md +++ b/examples/filtered-providers/README.md @@ -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 diff --git a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSun.java b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSun.java index 8275a06d..c3f9b51d 100644 --- a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSun.java +++ b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSun.java @@ -24,9 +24,9 @@ import java.util.Set; /** - * FilteredSun is a custom security provider that filters out - * cryptographic services from the original SUN provider, retaining - * only the supporting non-cryptographic services. + * FilteredSun is a custom security provider that filters out cryptographic + * services from the original SUN provider, retaining only the supporting + * non-cryptographic services. * * It retains only the services: * - CertStore.Collection @@ -35,6 +35,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. @@ -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..."); } @@ -62,9 +78,15 @@ public FilteredSun() { original.getServices().size()); } + String addProp = + ProviderServiceCopier.additionalServicesProperty("sun"); + Set grants = + ProviderServiceCopier.additionalServiceKeys(addProp); + Set 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: " + @@ -76,6 +98,9 @@ public FilteredSun() { } } + ProviderServiceCopier.warnIgnoredEntries("sun", "FilteredSun", + addProp, original); + if (DEBUG) { System.err.println("FilteredSun initialized successfully " + "with " + getServices().size() + " services."); @@ -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 * diff --git a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunEC.java b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunEC.java index 693de45e..8ec8bb3e 100644 --- a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunEC.java +++ b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunEC.java @@ -24,14 +24,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. @@ -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..."); } @@ -64,9 +78,15 @@ public FilteredSunEC() { "available: " + original.getServices().size()); } + String addProp = + ProviderServiceCopier.additionalServicesProperty("sunec"); + Set grants = + ProviderServiceCopier.additionalServiceKeys(addProp); + Set 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: " + @@ -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."); @@ -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 * diff --git a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunRsaSign.java b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunRsaSign.java index dc3ee295..daeba87a 100644 --- a/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunRsaSign.java +++ b/examples/filtered-providers/src/com/wolfssl/security/providers/FilteredSunRsaSign.java @@ -25,12 +25,24 @@ /** * FilteredSunRsaSign is a custom security provider that filters out - * cryptographic services from the original SunRsaSign provider, - * retaining only the supporting non-cryptographic services. + * cryptographic services from the original SunRsaSign provider, retaining + * only the supporting non-cryptographic services. * * It retains only: * - KeyFactory.RSASSA-PSS * + * Set the wolfssl.filtered.useOriginalNames Security property to "true" (in + * java.security, or via Security.setProperty() before this provider is first + * instantiated) to register this provider under the original "SunRsaSign" + * name instead of "FilteredSunRsaSign". This keeps applications and JDK code + * with hardcoded provider names working. Only the allow-listed services above + * are exposed regardless of the registered name. + * + * Set the 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. @@ -42,11 +54,14 @@ public class FilteredSunRsaSign extends Provider { public FilteredSunRsaSign() { - super("FilteredSunRsaSign", + super(ProviderServiceCopier.resolveName( + "FilteredSunRsaSign", "SunRsaSign"), System.getProperty("java.specification.version"), "Filtered SunRsaSign for non-crypto ops"); try { + ProviderServiceCopier.warnIgnoredSystemProperties("sunrsasign", + "FilteredSunRsaSign"); if (DEBUG) { System.err.println("Loading original SunRsaSign..."); } @@ -59,9 +74,15 @@ public FilteredSunRsaSign() { "Services available: " + original.getServices().size()); } + String addProp = ProviderServiceCopier + .additionalServicesProperty("sunrsasign"); + Set grants = + ProviderServiceCopier.additionalServiceKeys(addProp); + Set 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: " + @@ -73,10 +94,12 @@ public FilteredSunRsaSign() { } } + 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) { @@ -90,11 +113,11 @@ public FilteredSunRsaSign() { } /** - * 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 * diff --git a/examples/filtered-providers/src/com/wolfssl/security/providers/ProviderServiceCopier.java b/examples/filtered-providers/src/com/wolfssl/security/providers/ProviderServiceCopier.java index ec64ed6e..a205d7cf 100644 --- a/examples/filtered-providers/src/com/wolfssl/security/providers/ProviderServiceCopier.java +++ b/examples/filtered-providers/src/com/wolfssl/security/providers/ProviderServiceCopier.java @@ -23,11 +23,15 @@ import java.lang.reflect.Field; import java.security.NoSuchAlgorithmException; import java.security.Provider; +import java.security.Security; import java.util.ArrayList; import java.util.HashMap; +import java.util.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 @@ -49,6 +53,240 @@ 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. + * + * By default the filtered name is returned. When the + * wolfssl.filtered.useOriginalNames Security property is set to "true" + * (in java.security, or via Security.setProperty() before the providers + * are first instantiated), the original Sun provider name is returned + * instead, so that applications and JDK code with hardcoded provider + * names keep working. + * + * NOTE: with this property enabled, the providers must be registered by + * class name in java.security. A provider-name entry + * (ex: security.provider.N=SUN) resolves to the stock Sun provider through + * the JDK built-in name resolution, silently bypassing the filtered + * provider. + * + * @param filteredName default name, e.g. "FilteredSun" + * @param originalName original Sun provider name, e.g. "SUN" + * + * @return the name to pass to the Provider super constructor + */ + static String resolveName(String filteredName, String originalName) { + + String prop = Security.getProperty(USE_ORIGINAL_NAMES_PROP); + + 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 additionalServiceKeys(String prop) { + + Set 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 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 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. * diff --git a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderFunctionalTest.java b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderFunctionalTest.java index 2a294748..c7ff36f6 100644 --- a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderFunctionalTest.java +++ b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderFunctionalTest.java @@ -28,14 +28,22 @@ 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,6 +67,11 @@ * 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 + * (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. */ @@ -73,6 +86,29 @@ public class FilteredProviderFunctionalTest { private static String caEccCertDer; + /** Security property controlling filtered provider registration names. */ + 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(); @@ -84,9 +120,25 @@ public static void checkJavaVersionAndInstall() { System.out.println("FilteredSun* provider functional test"); - Security.addProvider(new FilteredSun()); - Security.addProvider(new FilteredSunEC()); - Security.addProvider(new FilteredSunRsaSign()); + /* 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 { + 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. */ String certPre = ""; @@ -171,5 +223,572 @@ private void assertNoBlockedServices(String providerName) { BLOCKED_TYPES.contains(type)); } } + + /** Restore NAME_PROP to prev, or "false" (equivalent to unset). */ + private static void restoreSecurityProperty(String prev) { + Security.setProperty(NAME_PROP, (prev != null) ? prev : "false"); + } + + /** 1-based registration position of the named provider, or -1. */ + private static int providerPosition(String name) { + + Provider[] providers = Security.getProviders(); + + for (int i = 0; i < providers.length; i++) { + if (providers[i].getName().equals(name)) { + return i + 1; + } + } + + return -1; + } + + @Test + public void testDefaultNamesUnchanged() { + String prev = Security.getProperty(NAME_PROP); + + try { + Security.setProperty(NAME_PROP, "false"); + + assertEquals("FilteredSun", + new FilteredSun().getName()); + assertEquals("FilteredSunEC", + new FilteredSunEC().getName()); + assertEquals("FilteredSunRsaSign", + new FilteredSunRsaSign().getName()); + + } finally { + restoreSecurityProperty(prev); + } + } + + @Test + public void testSecurityPropertyEnablesOriginalNames() { + String prev = Security.getProperty(NAME_PROP); + + try { + Security.setProperty(NAME_PROP, "true"); + + assertEquals("SUN", new FilteredSun().getName()); + assertEquals("SunEC", new FilteredSunEC().getName()); + assertEquals("SunRsaSign", new FilteredSunRsaSign().getName()); + + } finally { + restoreSecurityProperty(prev); + } + } + + @Test + public void testSystemPropertyIsIgnored() { + String prev = Security.getProperty(NAME_PROP); + + try { + /* 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"); + + 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); + restoreSecurityProperty(prev); + } + } + + @Test + public void testInfoStringUnchangedWithOverride() { + String prev = Security.getProperty(NAME_PROP); + + try { + Security.setProperty(NAME_PROP, "true"); + + /* getInfo() must keep identifying the provider as filtered 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()); + + } finally { + restoreSecurityProperty(prev); + } + } + + @Test + public void testHardcodedSunLookupResolvesWithOverride() + throws Exception { + + String prev = Security.getProperty(NAME_PROP); + Provider realSun = Security.getProvider("SUN"); + int realSunPos = providerPosition("SUN"); + Provider filtered = null; + boolean filteredAdded = false; + + try { + Security.setProperty(NAME_PROP, "true"); + + filtered = new FilteredSun(); + assertEquals("SUN", filtered.getName()); + + /* Simulate the hardened JRE: swap the real SUN out and register + * the filtered provider in its place */ + if (realSun != null) { + Security.removeProvider("SUN"); + } + assertTrue("could not register filtered provider as SUN", + Security.addProvider(filtered) != -1); + filteredAdded = true; + + /* Hardcoded name lookup must resolve to the filtered instance */ + CertificateFactory cf = + CertificateFactory.getInstance("X.509", "SUN"); + assertNotNull("CertificateFactory X.509 not resolved from " + + "provider registered as SUN", cf); + assertSame("lookup did not resolve to the filtered provider", + filtered, cf.getProvider()); + + } finally { + if (filteredAdded) { + Security.removeProvider(filtered.getName()); + } + if (realSun != null && Security.getProvider("SUN") == null) { + if (realSunPos > 0) { + Security.insertProviderAt(realSun, realSunPos); + } else { + Security.addProvider(realSun); + } + } + restoreSecurityProperty(prev); + } + } + + /** 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 serviceKeys(Provider p) { + Set 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 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 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 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 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 : ""); + } + } } diff --git a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderNegativeTest.java b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderNegativeTest.java index 67508f34..69d4833b 100644 --- a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderNegativeTest.java +++ b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderNegativeTest.java @@ -54,6 +54,16 @@ */ public class FilteredProviderNegativeTest { + /** 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(); @@ -65,9 +75,24 @@ public static void checkJavaVersionAndInstall() { System.out.println("FilteredSun* provider negative test"); - Security.addProvider(new FilteredSun()); - Security.addProvider(new FilteredSunEC()); - Security.addProvider(new FilteredSunRsaSign()); + /* 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 { + for (int i = 0; i < PIN_PROPS.length; i++) { + Security.setProperty(PIN_PROPS[i], + (prev[i] != null) ? prev[i] : PIN_VALS[i]); + } + } } private static int javaMajorVersion() { diff --git a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderSmokeTest.java b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderSmokeTest.java index 5b71ae71..bc07eade 100644 --- a/src/test/java/com/wolfssl/security/providers/test/FilteredProviderSmokeTest.java +++ b/src/test/java/com/wolfssl/security/providers/test/FilteredProviderSmokeTest.java @@ -63,6 +63,16 @@ public class FilteredProviderSmokeTest { private static Provider sunEc; private static Provider sunRsa; + /** 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(); @@ -74,14 +84,29 @@ public static void checkJavaVersionAndInstall() { System.out.println("FilteredSun* provider smoke test"); - /* Construct all three providers; must not throw. */ - sun = new FilteredSun(); - sunEc = new FilteredSunEC(); - sunRsa = new FilteredSunRsaSign(); - - Security.addProvider(sun); - Security.addProvider(sunEc); - Security.addProvider(sunRsa); + /* 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(); + sunEc = new FilteredSunEC(); + sunRsa = new FilteredSunRsaSign(); + + Security.addProvider(sun); + Security.addProvider(sunEc); + Security.addProvider(sunRsa); + } finally { + for (int i = 0; i < PIN_PROPS.length; i++) { + Security.setProperty(PIN_PROPS[i], + (prev[i] != null) ? prev[i] : PIN_VALS[i]); + } + } } /**