Android: JWS Validation always returning FALSE , with SIGNED state

Issue #391 new
medlamine Semassel created an issue

Hello,

Maybe related to #298

I want to validate signature of JWS structure with PS256 algorithm. So i use RSASSAVerifier. But it returns always false

I am using : org.bouncycastle:bcprov-jdk15on:1.56

and com.nimbusds:nimbus-jose-jwt:8.19

Also i need to mention that the problem is present in Android API 23

I must mention also that the same code is working with Android API 24

see my code below :

 fun jwsValidateSignatureAndReturnBody(jws: String?): String {

        // Parser
        val jwsObject: JWSObject

        try {
            jwsObject = JWSObject.parse(jws)
        } catch (e: ParseException) {
            throw RuntimeException("JWS parsing failed")
        }

        // Verifiy
        try {
            var verifier: JWSVerifier? = null

            Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
            Security.addProvider(BouncyCastleProviderSingleton.getInstance())

            val alg = jwsObject.header.algorithm

            if (alg == JWSAlgorithm.PS256 || alg == JWSAlgorithm.RS256) {

                Timber.i("lamine key  --> check" + Security.getProviders().get(0).values)
                Timber.i("lamine key  --> check" + Security.getProviders().get(1).values)
                Timber.i("lamine key  --> check" + Security.getProviders().get(2).values)
                Timber.i("lamine key  --> check" + Security.getProviders().get(3).values)
                Timber.i("lamine key  --> check" + Security.getProviders().get(4).values)
                Timber.i("lamine key  --> check" + Security.getProviders().get(5).values)


                Timber.i("lamine key  --> check -> " + JCASupport.isSupported(JWSAlgorithm.PS256))

                val x509CertChain = jwsObject.header.x509CertChain
                verifier =
                    RSASSAVerifier(X509CertUtils.parse(x509CertChain[0].decode()).publicKey as RSAPublicKey)

            } else if (alg == JWSAlgorithm.ES256) {
                verifier = ECDSAVerifier(ECKey.parse(jwsObject.header.jwk.toJSONString()))
            } else {
                // unsupported algorithm
                throw RuntimeException()
            }
            Timber.i("lamine key  --> check" + jwsObject.verify(verifier))
            Timber.i("lamine key  --> check" + JCASupport.isSupported(JWSAlgorithm.PS256))
            if (!jwsObject.verify(verifier)) {
                throw RuntimeException("JWS validation return false")
            }
        } catch (e: Exception) {
            throw RuntimeException(e)
        }

        return jwsObject.payload.toString()
    }

Comments (15)

  1. Matej Mijoski

    It seems that I am also facing this issue. I’m using the new serialized with detached=true but verification fails every time. Not sure if the problem is somewhere in my code.

            byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
            byte[] encodedPub = Base64.getDecoder().decode(publicKeyPEM);
    
            KeyFactory keyFactory1 = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec1 = new X509EncodedKeySpec(encodedPub);
            RSAPublicKey publicKey = (RSAPublicKey) keyFactory1.generatePublic(keySpec1);
    
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
            RSAPrivateKey privateKey = (RSAPrivateKey)keyFactory.generatePrivate(keySpec);
    
            JWSSigner signer = new RSASSASigner(privateKey);
    
            HashMap<String, Object> criticalParameters = new HashMap<>();
            criticalParameters.put("http://openbanking.org.uk/iat", 1501497671);
            criticalParameters.put("http://openbanking.org.uk/iss", keyORG);
            criticalParameters.put("http://openbanking.org.uk/tan", "openbankingtest.org.uk");
    
            JWSObject jwsObject = new JWSObject(
                    new JWSHeader.Builder(JWSAlgorithm.RS256)
                            .base64URLEncodePayload(false)
                            .keyID(keyID)
                            .criticalParams(criticalParameters.keySet())
                            .customParams(criticalParameters)
                            .build(),
                    payload);
    
            jwsObject.sign(signer);
            JWSVerifier jwsVerifier = new RSASSAVerifier(publicKey, criticalParameters.keySet());
    
    
            String jws = jwsObject.serialize(true);
            JWSObject parsedJWSObject = JWSObject.parse(jws, payload);
    
            if (parsedJWSObject.verify(new RSASSAVerifier(publicKey))) {
                System.out.println("Valid");
            } else {
                System.out.println("Invalid");
            }
    

  2. Yavor Vasilev

    @medlamine Semassel

    Hi,

    This is not related to your question, but I noticed an issue with the code - JWS objects must not be verified with public keys included in the JWS header, otherwise anyone can generate a JWK, and sign a JWS with it, which the receiver is always going to accept. This is akin to accepting self-signed certificates.

    ECDSAVerifier(ECKey.parse(jwsObject.header.jwk.toJSONString()))
    

    The JWK header parameter is intended for communicating ephemeral keys in ECDH.

    Similarly, X.509 certificates in the JWS header must not be used to verify the signure, unless the certificate chain is resolved to a trusted certificate authority (CA).

  3. Yavor Vasilev

    Hi @Matej Mijoski,

    If you replace

            JWSVerifier jwsVerifier = new RSASSAVerifier(publicKey, criticalParameters.keySet());
    
            String jws = jwsObject.serialize(true);
            JWSObject parsedJWSObject = JWSObject.parse(jws, payload);
    
            if (parsedJWSObject.verify(new RSASSAVerifier(publicKey))) {
    

    with

            JWSVerifier jwsVerifier = new RSASSAVerifier(publicKey, criticalParameters.keySet());
    
            String jws = jwsObject.serialize(true);
            JWSObject parsedJWSObject = JWSObject.parse(jws, payload);
    
            if (parsedJWSObject.verify(jwsVerifier)) {
    

    the signature validation will work :)

  4. Yavor Vasilev

    @medlamine Semassel

    I suggest you create a simple sign - verify test to isolate the issue with the PS256 alg.

    https://bitbucket.org/connect2id/nimbus-jose-jwt/src/9e0d46e2790d8b80b8b3131ec618e94f7ce6164a/src/test/java/com/nimbusds/jose/crypto/RSASSATest.java#lines-403

    Normally, if a signing algorithm is not supported by the JCA provider this will result in a exception (post the stack trace here).

    What does JCASupport.isSupported(JWSAlgorithm.PS256)) return in Android 23 and 24?

  5. medlamine Semassel reporter

    Thanks for your comments @Yavor Vasilev , actually i did use

     if (parsedJWSObject.verify(jwsVerifier)) {
    

    but still got false with verify, and the

    JCASupport.isSupported(JWSAlgorithm.PS256)) 
    

    return True.

    as I explained the code is working with Android above API 23

  6. Vladimir Dzhuvinov

    It looks like the underlying crypto provider (Bouncy Castle) doesn’t understand the newer PSS alg names.

    Which version of the Bouncy Castle do you have?

    I’m not really familiar with Android, bear that in mind.

  7. Vladimir Dzhuvinov

    Try this or a newer release and let me know if you have success, it received an updated to the JCA logic for PSS:

    The change was from a PR by a developer who was experiencing issues with his HSM and the mix might also help here.

    version 9.11.3 (2021-08-01)
        * Refactors RSASSA.getSignerAndVerifier to obtain a Signature for PSxxx
          without a PSSParameterSpec when the JCA algorithm name contains the
          necessary PSS parameters. Intended to prevent an
          UnsupportedOperationException with the nCipher JCA provider.
    

    The reason the older lib version worked is that uses a SHA alg identifier that is understood by older Android BouncyCastle versions. That identifier however is not standard and breaks standard compliant JCA providers. https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest

  8. Vladimir Dzhuvinov

    A technical solution to handle such non-std JCA alg names is to provide an API to the lib, to allow setting of alternative names, e.g. “SHA-256” → “SHA256”.

  9. Log in to comment