RSA-PSS support for both JCA algorithm naming standards

Issue #175 closed
Breur, Peter created an issue

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:

  1. the org.jose4j.jws.RsaUsingShaAlgorithm.choosePssAlgorithmName(String legacyName) method
  2. 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)

  1. Brian Campbell

    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 JWS AlgorithmFactory from AlgorithmFactoryFactory and register your PSS classes jwsAlgorithmFactory.registerAlgorithm(new SpecialRsaPssSha256());, which will override the default ones.

        public class SpecialRsaPssSha256 extends RsaUsingShaAlgorithm {
            public SpecialRsaPssSha256() {
                super(AlgorithmIdentifiers.RSA_PSS_USING_SHA256, "SHA256withRSAandMGF1");
            }
        }
    

  2. Brian Campbell repo owner

    93c0abb introduces a SignatureAlgorithmOverride on ProviderContext 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();
    

  3. Brian Campbell

    Note that Issue #177 “system property to use legacy RSA PSS algorithm names” is related to this and offers an alternative.

  4. Breur, Peter 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 🙂

  5. Log in to comment