RFD: Services lockdown for security providers

Martin Balao mbalao at redhat.com
Wed May 24 21:03:15 UTC 2023


Hi,

Thanks Anthony for your feedback.

We've been exploring the syntax and semantics for this new property 
further, with the goal of making it more consistent and simple while 
retaining expressiveness power. We understand the importance of clarity 
to minimize the risk of security providers, service types or algorithms 
being unexpectedly enabled.

In this new iteration of the proposal, we explore a filter that has 
similarities to the serialization filter (jdk.serialFilter). We think 
that it could be beneficial to leverage on a specification to which the 
user is familiar already.


General structure
====================

jdk.security.providers.filter=pattern-1; pattern-2; ...; pattern-n

The property jdk.security.providers.filter is an overrideable Security 
property. Thus, a System property with the same name exists and, when 
specified, overrides any value in its Security counterpart. When not 
specified (value is null), filtering capabilities are completely 
disabled: all installed security providers, service types and algorithms 
are allowed. If any of these properties are set during run time, the 
filter could be initialized already and the new value may not take effect.

When filtering capabilities are enabled, each service is checked against 
the filter before registration. Notice that this affects both the 
initial list of security providers as well as those dynamically 
installed during run time. Once a service is registered, instances of it 
can be obtained and used without any other checks that could affect 
performance.

The registration of a service involves a combination of a security 
provider, service type and algorithm. Each combination is evaluated 
against the filter patterns, from left to right. When a pattern matches 
—or, in other words, the rule concerns the service to be registered—, a 
decision is made: the service will be allowed or denied. When a decision 
is made, remaining patterns are not checked for the service under 
consideration. When all patterns are checked and a decision is not made, 
the default behavior is to deny the service registration.

Contrary to the serialization filter, white spaces between patterns do 
not have any significance.


Pattern matching
=====================================================

pattern := ! security-provider.service-type.algorithm

pattern := security-provider.service-type.algorithm

A canonical pattern consists of 3 hierarchical levels separated by ".". 
 From left to right in lexicographic order, these levels denote a 
security provider, a service type and an algorithm. If a pattern starts 
with "!", the decision made upon matching is to deny the service 
registration. Otherwise, the service registration is allowed. White 
spaces between "!" and the rest of the pattern do not have any significance.

For a match to be successful, the security provider name, the service 
type and the algorithm have to match the pattern exactly (case 
insensitive). If the service type of a security provider interprets the 
algorithm as a transformation composed of different parts, the full 
transformation has to be specified in the pattern: the filter takes a 
conservative approach and does not make any assumptions of what an 
algorithm name means. For example, "AES" as the algorithm of a canonical 
filter pattern will not match an "AES/ECB/PKCS5Padding" transformation.

If an algorithm alias is specified in the filter pattern, a service 
registering the alias will be matched.

For convenience, it's possible to specify patterns in non-canonical forms:

1) At any level, the security provider, the service type or the 
algorithm name can contain wildcards ("*") to represent zero or more 
repetitions of any character;

2) The .algorithm part can be omitted to imply all algorithms under the 
security provider and service type;

3) The .service-type.algorithm part can be omitted to imply all service 
types and algorithms under the security provider; and,

4) The non-canonical form #1 can be combined with either #2 or #3.


Security provider, service type and algorithm names escaping
=================================================================

If the security provider, service type or algorithm name contains any of 
the characters "\", ".", ";" or "*", they have to be escaped by 
prepending the character "\". If the character "\" is found not escaping 
a character, it's silently discarded.

White spaces are discarded at the beginning and end of names.

It's worth mentioning that the described escaping rules apply to the 
jdk.security.providers.filter property value as read in 
java.lang.System::getProperty or java.security.Security::getProperty. 
Additional escaping might be needed depending on how the property is 
passed. For example, Security properties require "\" characters to be 
escaped. Thus, to match a provider name whose name is "\.", a filter 
would require the "jdk.security.providers.filter=\\\\\\." entry in the 
java.security file. See more about this in java.util.Properties::load [1].


Examples (correct)
====================

--

Enable all security providers, service types and algorithms:

jdk.security.providers.filter=

or

jdk.security.providers.filter=*

or

jdk.security.providers.filter=*.*

or

jdk.security.providers.filter=*.*.*

--

Enable everything except for the MD5 algorithm in MessageDigest services 
when implemented by the SUN security provider:

jdk.security.providers.filter=!SUN.MessageDigest.MD5; *

--

Enable everything except for the MD5 algorithm in MessageDigest 
services, irrespective of the security provider:

jdk.security.providers.filter=!*.MessageDigest.MD5; *

--

Enable everything except for algorithms using MD5, irrespective of the 
security provider and the service type:

jdk.security.providers.filter=!*.*.*MD5*; *

Notice that in this case there are wildcards at the beginning and end of 
the algorithm name. The reason is to match MD5 uses in algorithms such 
as HmacMD5, MD5withRSA, PBEWithMD5AndDES, etc.

--

Enable everything except for the RC4 algorithm in Cipher services when 
implemented by the SunJCE security provider:

jdk.security.providers.filter=!SunJCE.Cipher.ARCFOUR; *

or

jdk.security.providers.filter=!SunJCE.Cipher.RC4; *

or

jdk.security.providers.filter=!SunJCE.Cipher.1\.2\.840\.113549\.3\.4; *

--

Enable the SUN security provider only, with all its service types and 
algorithms. Other security providers must be disabled.

jdk.security.providers.filter=SUN

--

Enable the SUN security provider only, with all its service types and 
algorithms except for MessageDigest. Other security providers must be 
disabled.

jdk.security.providers.filter=!SUN.MessageDigest; SUN

--


Examples (mistakes)
====================

--

Enable everything except for the MD5 algorithm, irrespective of the 
security provider and the service type:

jdk.security.providers.filter=*; !*.*.MD5

This is wrong because the pattern "*" is matched first and a decision 
allowing MD5 will be made immediately after. The pattern "!*.*.MD5" will 
never be checked.

--

Enable all SUN service types except for MessageDigest. Disable other 
security providers.

jdk.security.providers.filter=!SUN.MessageDigest

While non-SUN security providers are effectively disabled, this is wrong 
because SUN services other than MessageDigest will not match any pattern 
and, by default, the decision is to deny registration.

--

Enable the SunPKCS11 security provider only.

jdk.security.providers.filter=SunPKCS11

This is wrong because the SunPKCS11 provider has to be identified by its 
name instead of its class. A possible name would have the form of 
SunPKCS11-NAME. In a filter, this can be matched either by 
"SunPKCS11-NAME" or "SunPKCS11-*".

--


Look forward to your thoughts.

Thanks.-


--
[1] - 
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Properties.html#load%28java.io.Reader%29

(†) - Thanks to Francisco Ferrari (@fferrari) for his contributions to 
this proposal.




On 2/24/23 14:49, Anthony Scarpino wrote:
> Hi Martin,
> 
> Interesting proposal.  I think Alternative 1 is a better direction to 
> explore from a code structure standpoint.  If I remember correctly, 
> Preferred Provider is accessed when getting a service or instance of the 
> algorithm.  That happens on a per-operation basis.  What you describe is 
> something that would reshape contents of the ProviderList where 
> algorithms or services would not be in the list at all.  That is were I 
> think #2 gets too complex in trying to handle both in the same property. 
>   #2 may end up putting all checks in a per-operation check, hindering 
> performance every time as the list grows.
> 
> I agree this is mostly used in the FIPS situation or where someone wants 
> to disable an algorithm completely, say MD5.  In those cases it's best 
> to just prevent the algorithm from ever being available.
> 
> On the smaller details side that you list.  I think the name ".enabled" 
> doesn't fit, particularly as the first thing in the example disables all 
> Ciphers :).  I don't have any suggestions at this time.
> 
> As far as the syntax.  I think it maybe a bit difficult to parse in code 
> and mental to disable all Ciphers, then enable just for SunJCE and SUN. 
> The SUN '*" confused me until I realized you were enabling Ciphers. 
> Seems too easy to get wrong.  I know you weren't making a formal spec, 
> but we have to start somewhere.
> 
> thanks
> 
> Tony
> 
> 
> On 2/17/23 10:52 AM, Martin Balao wrote:
>> Hi,
>>
>> We would like to discuss a limitation in the current configuration 
>> capabilities for security providers and possible solutions that we are 
>> exploring (†).
>>
>> As you know, current configuration capabilities in java.security allow 
>> users to install security providers, decide their priority in a list 
>> (security.provider.<n> properties) and even circumvent this priority 
>> for specific algorithms (jdk.security.provider.preferred property). 
>> However, there is no granularity in terms of what service types and 
>> algorithms are enabled once a security provider is installed: it's an 
>> all or nothing scheme. It is worth noting that security providers can 
>> bring with them a diverse range of service types. As an example, the 
>> SUN security provider comes with the following service types: 
>> SecureRandom, Signature, KeyPairGenerator, 
>> AlgorithmParameterGenerator, AlgorithmParameters, KeyFactory, 
>> MessageDigest, CertificateFactory, KeyStore, CertStore, Policy, 
>> Configuration, CertPathBuilder and CertPathValidator [1].
>>
>> In some cases, the user may need to enforce that all cryptographic 
>> primitives come from a specific security provider. This could happen, 
>> for example, when operating in a FIPS-compliant environment or under 
>> strict security policies. To better illustrate, let's say that the 
>> user requires that all cryptographic operations are performed in a 
>> Hardware Security Module (HSM). On the OpenJDK side, this means that 
>> the implementation for Cipher, Signature, Mac and other cryptographic 
>> services must be the one in the SunPKCS11 security provider. Let's 
>> also suppose that other non-cryptographic services such as those for 
>> certificates validation and TLS are required, and their implementation 
>> is in the SUN and SunJSSE security providers respectively. Setting 
>> SunPKCS11 at the highest priority of the list is not a strong 
>> guarantee to ensure that all cryptographic operations come from it: 
>> it's possible that an algorithm for Signature is not implemented in 
>> SunPKCS11 or in its underlying token but in the SUN security provider. 
>> Disabling the SUN security provider wouldn't be an option in this case 
>> because we need its certificates validation service.
>>
>> This problem goes beyond OpenJDK default security providers. Even if 
>> we come up with a new layout for service types, algorithms and 
>> providers —putting backward compatibility issues aside—, there is 
>> always the possibility that a 3rd party security provider does not 
>> follow any services grouping convention. It might also be the case 
>> that we need to disable a specific algorithm only —i.e. for 
>> cryptographic policy reasons— and TLS or JAR signing properties fall 
>> short.
>>
>> In our view, it would be beneficial to add more configuration 
>> flexibility and control to the existing API in which any security 
>> provider can be plugged in, in the form of deciding which service 
>> types and algorithms are enabled for each installed provider.
>>
>> There are 2 alternatives that we are exploring to tackle this problem.
>>
>> Alternative #1
>> ===========================
>>
>> Introduce a new security property to decide which service types and 
>> algorithms are enabled for each security provider. The default value 
>> for this property would be empty, which keeps this feature disabled 
>> and all services from installed security providers available.
>>
>> As for the new property's syntax and semantics, we've been considering 
>> an allow-list along the lines of:
>>
>> jdk.security.provider.enabled = security-provider-1 { service-type-1 : 
>> alg-1, ... ; ... } , ...
>>
>> Note: we need a formal syntax specification, this is for illustration 
>> only.
>>
>> As part of the syntax we are considering the use of wildcards (*) to 
>> match multiple security providers, service types and algorithms, and 
>> minus signs (-) to remove service types. When a service type is 
>> removed, the action applies to all algorithms and any attempt to 
>> specify them explicitly would be an error. The minus sign cannot be 
>> used at the algorithm level. We are also thinking that in case of a 
>> partial or total contradiction between conditions, the right-most 
>> value applies on top of the others. If a security provider, service 
>> type or algorithm does not exist, we can simply write a debug warning 
>> and ignore it. As for the name of the algorithms, we can also include 
>> Ciphers transformations.
>>
>> Example:
>>
>> jdk.security.provider.enabled = * { -Cipher }, SunJCE { Cipher : 
>> AES/GCM/NoPadding, DES ; Signature }, SUN { * ; -Signature }
>>
>> This would be interpreted as:
>>
>>   * Irrespective of the provider (*), Cipher services should be 
>> removed (-). This rule would be superfluous in this case because the 
>> property itself is an allow-list and there is nothing to the left that 
>> enables Cipher service types for any provider.
>>   * From the SunJCE security provider, Cipher services with 
>> AES/GCM/NoPadding and DES transformations are allowed, and Signature 
>> services with any algorithm are allowed. Notice that there is a 
>> shortcut here: the algorithm list that follows the service name, "': 
>> alg-1, ..." is optional. When omitted all the service's algorithms are 
>> enabled.
>>   * From the SUN security provider, every service type is allowed 
>> except Signature (recall that a minus sign can only apply to a 
>> service, removing all associated algorithms).
>>
>> It's not the goal of this proposal to invalidate property values that 
>> lead to inconsistent internal states, such as "the Cipher service of 
>> SunJCE depends on AlgorithmParameters from SUN". This is because the 
>> combinations for a check are virtually infinite: there can be 3rd 
>> party security providers with their own semantics and dependencies. In 
>> the same way, we cannot determine at start time any application 
>> dependencies. It's up to the user to analyze all types of dependencies 
>> before setting a value.
>>
>>
>> Alternative #2
>> ===========================
>>
>> Introduce a boolean security property to turn the value of the 
>> existing jdk.security.provider.preferred property into the only 
>> combinations of algorithm, service and provider that are allowed:
>>
>> jdk.security.provider.preferredOnly = true
>>
>> The default value for the new property would be "false", keeping the 
>> current "preferred" behavior in which all algorithms and services from 
>> installed security providers are available.
>>
>> Contrary to Alternative #1, the user has to explicitly list the 
>> algorithms and cannot rely on wildcards to express wide categories 
>> such as "all Cipher algorithms from SunJCE" or "all algorithms from 
>> SunJCE". The use of minus signs to remove service types or algorithms 
>> wouldn't be available either.
>>
>> In order to mitigate the burden on users we can consider extending 
>> jdk.security.provider.preferred syntax as long as we keep 
>> backward-compatibility and stay within the boundaries of a "preferred" 
>> semantics. For example, we can accept a value of 
>> "jdk.security.provider.preferred=SunJCE" to mean that any service and 
>> any algorithm from SunJCE is either preferred or allowed, depending on 
>> the value of jdk.security.provider.preferredOnly. This case would be a 
>> service type and algorithm wildcard. We can also define an 
>> algorithms-only wildcard, such as Cipher.*:SunJCE.
>>
>> Alternative #2 has the advantage of reusing most or all of the 
>> existing syntax. However, it's worth noticing that it implies an 
>> overloaded semantic that can turn confusing or inconvenient in some 
>> cases. As an example, a user that relies on the prioritized security 
>> providers list for most of the algorithms and has only a few preferred 
>> exceptions, would need to express preferences by extension upon 
>> turning on this feature. Alternative #1 keeps preferences and 
>> availability as two separate concepts, in a more clear way.
>>
>>
>> Thanks,
>> Martin.-
>>
>> -- 
>> [1] - 
>> https://docs.oracle.com/en/java/javase/17/security/oracle-providers.html#GUID-3A80CC46-91E1-4E47-AC51-CB7B782CEA7D
>> (†) - Thanks to @fferrari for his contributions to this proposal.
>>
> 



More information about the security-dev mailing list