jsonwebtoken: DecodingKey::from_rsa_components fails when modulus encoded with standard base64 character set

Hello!

jsonwebtoken version: 7.2.0 rustc version: rustc 1.47.0 (18bf6b4f0 2020-10-07)

I have built a token validator for tokens served from AWS Cognito using their JWKS. The validation steps are

  • fetch the JWKs
  • find the appropriate JWK data for the token (decode_header, extract kid, look up corresponding key in JWKs)
  • pass the modulus and exponent to DecodingKey::from_rsa_components
  • validate as usual

Out of the box, validation fails with a rather obscure Error(Base64(InvalidByte(52, 43))). After digging into it for a while, I discovered that AWS Cognito JWKs are using the standard base64 character set, which trips up the b64_decode wrapper.

Here is a test case that demonstrates the same issue using the PKCS8 keypair in the test suite:

#[test]
fn rsa_modulus_exponent_standard_charset() {
    let privkey_pem = include_bytes!("private_rsa_key_pkcs8.pem");

    // modulus encoded as base64 from public_rsa_key_pkcs8.pem
    // $ openssl rsa -pubin -in public_rsa_key_pkcs8.pem -text -noout | egrep '^    ' | xxd -r -p | base64 | tr -d '\n'
    let n = "AMkROqx7jUdEGxztx9yrdqTihlYUKhmVyJznbkDcV87ipb0Ey1E7+JeLIIIefwmGIv3LyPkl1U/ZD1kil8SVwV3f+C5L3D7lGpAaAJH4fnohVTIdla1Mlso9zBZdB01RfSsEVywHMJERIkt56U4R0ciMbstGTHmX8VS+WqzIcNUkRCwfB6BnxvwLR/PQSBPYwwR2fXS3pSvWtfOMwH/C8KDy8byW9yJeZ53Kj3Enygw6HTBQSDHOJUMwyi+YL5oly1wdQBi5vCgY3xPLNy+caovslKHfo/DLbyI/NdnZEuEDIkVTf28tod2WPC2FRq6mV2U3IJ9ro5/Lio1y2VQ+U3U=";

    // $ openssl rsa -pubin -in public_rsa_key_pkcs8.pem -text -noout | awk '/Exponent/ { printf "0x%06x",$2 }' | xxd -r -p | base64
    let e = "AQAB";

    // transcoding the modulus into the URL safe charset allows the test to pass
    // let n = "AMkROqx7jUdEGxztx9yrdqTihlYUKhmVyJznbkDcV87ipb0Ey1E7-JeLIIIefwmGIv3LyPkl1U_ZD1kil8SVwV3f-C5L3D7lGpAaAJH4fnohVTIdla1Mlso9zBZdB01RfSsEVywHMJERIkt56U4R0ciMbstGTHmX8VS-WqzIcNUkRCwfB6BnxvwLR_PQSBPYwwR2fXS3pSvWtfOMwH_C8KDy8byW9yJeZ53Kj3Enygw6HTBQSDHOJUMwyi-YL5oly1wdQBi5vCgY3xPLNy-caovslKHfo_DLbyI_NdnZEuEDIkVTf28tod2WPC2FRq6mV2U3IJ9ro5_Lio1y2VQ-U3U=";

    let my_claims = Claims {
        sub: "b@b.com".to_string(),
        company: "ACME".to_string(),
        exp: Utc::now().timestamp() + 10000,
    };

    for &alg in RSA_ALGORITHMS {
        let token =
            encode(&Header::new(alg), &my_claims, &EncodingKey::from_rsa_pem(privkey_pem).unwrap())
                .unwrap();

        let token_data = decode::<Claims>(
            &token,
            &DecodingKey::from_rsa_components(n, e),
            &Validation::new(alg),
        )
        .unwrap();

        assert_eq!(my_claims, token_data.claims);
        assert!(token_data.header.kid.is_none());
    }
}

Test output:

$ cargo test
   Compiling jsonwebtoken v7.2.0 (/home/drocco/source/sandbox/rust/jsonwebtoken)
    Finished test [unoptimized + debuginfo] target(s) in 1.19s
     Running target/debug/deps/jsonwebtoken-a8164d7fdb367e8b
...
failures:

---- rsa::rsa_modulus_exponent_standard_charset stdout ----
thread 'rsa::rsa_modulus_exponent_standard_charset' panicked at 'called `Result::unwrap()` on an `Err` value: Error(Base64(InvalidByte(52, 43)))', tests/rsa/mod.rs:173:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    rsa::rsa_modulus_exponent_standard_charset

test result: FAILED. 10 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--test lib'

Environment:

$ rustup show
Default host: x86_64-unknown-linux-gnu
rustup home:  /home/drocco/.rustup

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu

active toolchain
----------------

stable-x86_64-unknown-linux-gnu (default)
rustc 1.47.0 (18bf6b4f0 2020-10-07)

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 18 (8 by maintainers)

Most upvoted comments

I ran into a similar issue while writing some unit tests for code using this library. It doesn’t seem to be an issue when using the full key data - I was able to create an EncodingKey using non-URL-safe base64 data, but when I try and make a DecodingKey using a modulus and exponent with non-URL-safe base64 data I got the same errors as above. Manually transcoding my modulus to URL-safe made it pass. (@drocco007, your post saved me a ton of time digging 😊)

Is there a reason those two types seem to behave differently for the base64 passed to them?