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, extractkid, 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)
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
EncodingKeyusing non-URL-safe base64 data, but when I try and make aDecodingKeyusing 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?