RSA-PSS support for both JCA algorithm naming standards
Dear jose4j developer,
Would you please consider enhancing your algorithm name selection mechanism, because its current implementation prevents us from using our crypto-device for RSA-PSS signatures in Java 1.8 and above, regardless of setting its provider in a provider context.
Please excuse me for labeling this issue a blocking bug, but for us that is what it is. Feel free to reassign this to your liking.
Problem description
Our crypto-device has support for several RSA-PSS algorithms, including the range that jose4j supports. However, the Signature implementation of its JCA-compliant security provider does not recognize the "RSASSA-PSS"
algorithm name; it only recognizes RSA-PSS algorithms by their "<digest>with<encryption>and<mgf>"
names.
Note that the vendor of our crypto-device (probably) made that choice, because they do not implement the PSSParameterSpec
interface, which is needed when using the "RSASSA-PSS"
name.
Also note that their choice is entirely legal, because even though the Java Security Standard Algorithm Names document lists "RSASSA-PSS"
as the name of an RSA-PSS algorithm since Java 1.8, that same document for all Java versions (even up to Java 15) also allows the "<digest>with<encryption>and<mgf>"
naming standard (possibly to support provider vendors, such as ours).
The problem that we have is caused by the method with which jose4j chooses which name to use to identify an RSA-PSS algorithm. In human-readable terms it is implemented using the following rule:
If ANY of the installed providers supports the "RSASSA-PSS"
algorithm name, then ALL Signature.getInstance()
calls use that name, regardless of any user-selected provider context.
Because all Java 1.8+ implementations that I know of (Oracle, OpenJDK, and IBM) have built-in support for the "RSASSA-PSS"
algorithm name, jose4j configures itself to use that name, which results in disabling all providers that happen to recognize the "<digest>with<encryption>and<mgf>"
names only. This makes setting such a provider in a org.jose4j.jca.ProviderContext
instance, which we obviously want to do, useless.
Suggested problem resolution
I am not a professional Java programmer, but I think that this issue may be solved (without the need to change any interfaces) by changing the implementation of only two methods:
- the
org.jose4j.jws.RsaUsingShaAlgorithm.choosePssAlgorithmName(String legacyName)
method - the
org.jose4j.jws.BaseSignatureAlgorithm.getSignature(ProviderContext providerContext)
method
Change to the choosePssAlgorithmName
method
In the org.jose4j.jws.RsaUsingShaAlgorithm
class,
change the choosePssAlgorithmName(String legacyName)
method as follows
(the comments explain what I have done):
static String choosePssAlgorithmName(String legacyName)
{
for (String sigAlg : Security.getAlgorithms("Signature"))
{
if (RSASSA_PSS.equalsIgnoreCase(sigAlg))
{
//return sigAlg; // <-- deleted line
return sigAlg + "|" + legacyName; // <-- inserted line
}
}
return legacyName;
}
Change to the getSignature
method
In the org.jose4j.jws.BaseSignatureAlgorithm
class,
change the getSignature(ProviderContext providerContext)
method as follows
(the comments explain what I have done, but note that I did NOT change the algorithmParameterSpec
logic):
private Signature getSignature(ProviderContext providerContext) throws JoseException
{
String sigProvider = providerContext.getSuppliedKeyProviderContext().getSignatureProvider();
String javaAlg = getJavaAlgorithm();
try
{
/* ************************************************************************
* To support both legacy and current algorithm names, REPLACED this line:
* Signature signature = sigProvider == null ? Signature.getInstance(javaAlg) : Signature.getInstance(javaAlg, sigProvider);
* with the following new code block:
**************************************************************************/
Signature signature;
int separator = javaAlg.indexOf('|');
if (separator != -1)
{
String secondaryName = javaAlg.substring(separator + 1);
javaAlg = javaAlg.substring(0, separator); // primary algorithm name
try
{
signature = sigProvider == null ? Signature.getInstance(javaAlg) : Signature.getInstance(javaAlg, sigProvider);
}
catch (NoSuchAlgorithmException e)
{
javaAlg = secondaryName;
signature = sigProvider == null ? Signature.getInstance(javaAlg) : Signature.getInstance(javaAlg, sigProvider);
}
}
else
{
signature = sigProvider == null ? Signature.getInstance(javaAlg) : Signature.getInstance(javaAlg, sigProvider);
}
/* ************************************************************************
* end of new code block
**************************************************************************/
if (algorithmParameterSpec != null)
{
try
{
signature.setParameter(algorithmParameterSpec);
}
catch (UnsupportedOperationException e)
{
if (log.isDebugEnabled())
{
log.debug("Unable to set algorithm parameter spec on Signature (java algorithm name: " + javaAlg +
") so ignoring the UnsupportedOperationException and relying on the default parameters.", e);
}
}
}
return signature;
}
catch (NoSuchAlgorithmException e)
{
/* *****************************************
* Replaced this catcher:
* throw new JoseException("Unable to get an implementation of algorithm name: " + javaAlg, e);
* with the following 4 lines that:
* - report the original getJavaAlgorithm() value and
* - report a provider-specific message if necessary
*/
if (sigProvider != null)
{
throw new JoseException("Unable to get an implementation of algorithm name: " + getJavaAlgorithm() + " from provider " + sigProvider, e);
}
throw new JoseException("Unable to get an implementation of algorithm name: " + getJavaAlgorithm(), e);
}
catch (InvalidAlgorithmParameterException e)
{
throw new JoseException("Invalid algorithm parameter ("+algorithmParameterSpec+") for: " + javaAlg, e);
}
catch (NoSuchProviderException e)
{
/* *******************************************
* Replaced this catcher:
* throw new JoseException("Unable to get an implementation of " + javaAlg + " for provider " + sigProvider, e);
* with the following one, because it will be thrown only if a sigProvider was set.
* Note that it will report on the actual algorithm name (legacy/modern) that was tried.
*/
throw new JoseException("Requested " + javaAlg + " provider " + sigProvider + " is not installed", e);
}
}
Comments (6)
-
-
repo owner 93c0abb introduces a
SignatureAlgorithmOverride
onProviderContext
that allows the caller to specify the alg name to use. Using it to sign in a case like yours might look something like this:
ProviderContext providerContext = new ProviderContext(); ProviderContext.Context suppliedKeyProviderContext = providerContext.getSuppliedKeyProviderContext(); ProviderContext.SignatureAlgorithmOverride sao = new ProviderContext.SignatureAlgorithmOverride("SHA256withRSAandMGF1", null); suppliedKeyProviderContext.setSignatureAlgorithmOverride(sao); providerContext.getSuppliedKeyProviderContext().setSignatureProvider("SomeHSM"); JsonWebSignature jws = new JsonWebSignature(); jws.setProviderContext(providerContext); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_PSS_USING_SHA256); jws.setPayload("stuff"); jws.setKey(...); String compactSerialization = jws.getCompactSerialization();
-
repo owner - changed status to resolved
-
repo owner - changed status to closed
released in v0.7.3
-
Note that Issue
#177“system property to use legacy RSA PSS algorithm names” is related to this and offers an alternative.
-
Account Deactivated reporter Thank you for offering a solution to use the legacy-names, and thus our HSMs, with the RSA-PSS algorithm without having to resort to hacking into Java’s provider architecture
- Log in to comment
I’m sympathetic to the need (though rather annoyed by naming variations that give rise to it) and will consider some kind of enhancement in support of it. But, while your suggested resolution looks like it will work, I’m not comfortable introducing non-trivial changes into common core code in support of an edge case. So I need to think about a different means of support. And honestly I’m unsure when I’ll be able to find time to do the work.
In the meantime, as a workaround, you could potentially:
1) Build and use your own patched version of the library with the changes you’ve suggested or similar.
2) Implement your own simple PSS classes that extend
RsaUsingShaAlgorithm
and use the ‘legacy’ alg names (see below for example). Then, In initialization somewhere, get the JWSAlgorithmFactory
fromAlgorithmFactoryFactory
and register your PSS classesjwsAlgorithmFactory.registerAlgorithm(new SpecialRsaPssSha256());
, which will override the default ones.