jwks-rsa-java icon indicating copy to clipboard operation
jwks-rsa-java copied to clipboard

EC key invalid decoding

Open pstojek opened this issue 2 years ago • 2 comments

Signum supposed to be used after base64 decoding: https://github.com/auth0/jwks-rsa-java/blob/master/src/main/java/com/auth0/jwk/Jwk.java#L196-L197

Sample: ECPoint ecPoint = new ECPoint(new BigInteger(1, Base64.getUrlDecoder().decode(stringValue("x"))), new BigInteger(1, Base64.getUrlDecoder().decode(stringValue("y"))));

Similar approach is already used in lines 183-184 for RSA Algorithm.

Issue causes invalid pubKey decoding resulting in signature validation failure.

pstojek avatar Jul 26 '22 14:07 pstojek

Hi @pstojek, can you provide a sample so that we can reproduce this?

poovamraj avatar Jul 31 '22 13:07 poovamraj

I am attaching sample with wiremock test simulating jwks server written in Kotlin - Failure rate is 3/4 (when signum of x/y matters). Solution provided in first comment fixes the issue. If we use that key later e.g. jwt token validation then we will receive invalid signature, because of improper EC pub key decoding.

import com.auth0.jwk.JwkProviderBuilder
import com.github.tomakehurst.wiremock.client.WireMock.get
import com.github.tomakehurst.wiremock.client.WireMock.ok
import com.github.tomakehurst.wiremock.client.WireMock.stubFor
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo
import com.github.tomakehurst.wiremock.junit5.WireMockTest
import com.nimbusds.jose.jwk.Curve
import com.nimbusds.jose.jwk.ECKey
import com.nimbusds.jose.jwk.KeyUse
import com.nimbusds.jose.jwk.gen.ECKeyGenerator
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.net.URL
import java.security.interfaces.ECPublicKey
import java.time.Duration
import java.util.UUID


@WireMockTest
internal class JwksTest {
    @Test
    fun `should pull same public key as on jwks server`(wmRuntimeInfo: WireMockRuntimeInfo) {
        // PART 1: Stab JWKS server response with com.nimbusds.jose.jwk library:
        val keyId = UUID.randomUUID().toString()

        val serverJwk: ECKey = ECKeyGenerator(Curve.P_384)
            .keyUse(KeyUse.SIGNATURE)
            .keyID(keyId)
            .generate()

        stubFor(
            get("/.well-known/jwks.json").willReturn(
                ok("{\"keys\":[${serverJwk.toPublicJWK().toJSONString()}]}")
            )
        )

        val serverPublicKey = serverJwk.toPublicJWK().toECPublicKey()

        // PART 2: client side using com.auth0.jwk.JwkProviderBuilder
        val jwkProvider = JwkProviderBuilder(
            URL(wmRuntimeInfo.httpBaseUrl + "/.well-known/jwks.json")
        )
            .cached(5, Duration.ofHours(6))
            .timeouts(1000, 1000)
            .rateLimited(false)
            .build()

        val pulledECPublicKey = jwkProvider.get(keyId).publicKey as ECPublicKey

        // Success rate 1/2 for following condition:
        assertEquals(serverPublicKey.w.affineX, pulledECPublicKey.w.affineX)
        // Success rate 1/2 for following condition:
        assertEquals(serverPublicKey.w.affineY, pulledECPublicKey.w.affineY)
    }
}

pstojek avatar Aug 01 '22 08:08 pstojek

I'm not certain if I had exactly the same issue, but my EC JWK -> public token was failing with something like a "coordinates out of range" error.

Anyways I switched to https://github.com/fusionauth/fusionauth-jwt to grab the public key. Their logic is really quite similar to the logic in this library so not exactly sure what the difference is.

salami avatar Sep 01 '22 20:09 salami

Thanks for raising this and providing a test case! @pstojek and @salami if you're able to try out #153 and verify it resolves the issue that would be great! 👍

jimmyjames avatar Sep 07 '22 23:09 jimmyjames

Yes, it fixes the issue. Thanks

pstojek avatar Sep 08 '22 18:09 pstojek