diff --git a/README.md b/README.md index 269c1a49..aadec009 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,8 @@ This library currently supports the following: - ES384 ### RSA -`jsonwebtoken` can only read DER encoded keys currently. If you have openssl installed, + +Use the `decode_rsa` function to use a `(n, e)` modulus, exponent pair. Otherwise if you have openssl installed, you can run the following commands to obtain the DER keys from PKCS#1 (ie with `BEGIN RSA PUBLIC KEY`) .pem. If you have a PKCS#8 pem file (ie starting with `BEGIN PUBLIC KEY`), you will need to first convert it to PKCS#1: `openssl rsa -pubin -in -RSAPublicKey_out -out `. diff --git a/src/crypto.rs b/src/crypto.rs index 96f2774c..d34cf799 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -123,6 +123,40 @@ fn verify_ring( Ok(res.is_ok()) } +/// Compares the signature given with a re-computed signature using the public key for RSA. +/// +/// `signature` is the signature part of a jwt (text after the second '.') +/// +/// `signing_input` is base64(header) + "." + base64(claims) +pub fn verify_rsa( + signature: &str, + signing_input: &str, + (n, e): (&[u8], &[u8]), + algorithm: Algorithm, +) -> Result { + let rsa_parameters = match algorithm { + Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256, + Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384, + Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512, + _ => return Err(new_error(ErrorKind::InvalidAlgorithmName)), + }; + + let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?; + let message = untrusted::Input::from(signing_input.as_bytes()); + let modulus = untrusted::Input::from(n); + let exponent = untrusted::Input::from(e); + let expected_signature = untrusted::Input::from(signature_bytes.as_slice()); + + let res = signature::primitive::verify_rsa( + rsa_parameters, + (modulus, exponent), + message, + expected_signature, + ); + + Ok(res.is_ok()) +} + /// Compares the signature given with a re-computed signature for HMAC or using the public key /// for RSA. /// diff --git a/src/lib.rs b/src/lib.rs index 592e5658..4755627d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ mod header; mod serialization; mod validation; -pub use crypto::{sign, verify, Algorithm}; +pub use crypto::{sign, verify, verify_rsa, Algorithm}; pub use header::Header; pub use serialization::TokenData; pub use validation::Validation; @@ -117,6 +117,49 @@ pub fn decode( Ok(TokenData { header, claims: decoded_claims }) } + +/// Decode a token into a struct containing 2 fields: `claims` and `header`. +/// +/// If the token or its signature is invalid or the claims fail validation, it will return an error. +/// +/// ```rust,ignore +/// #[macro_use] +/// extern crate serde_derive; +/// use jsonwebtoken::{decode, Validation, Algorithm}; +/// +/// #[derive(Debug, Serialize, Deserialize)] +/// struct Claims { +/// sub: String, +/// company: String +/// } +/// +/// let token = "a.jwt.token".to_string(); +/// // Claims is a struct that implements Deserialize +/// let token_data = decode::(&token, (n, e), &Validation::new(Algorithm::HS256)); +/// ``` +pub fn decode_rsa( + token: &str, + (n, e): (&[u8], &[u8]), + validation: &Validation, +) -> Result> { + let (signature, signing_input) = expect_two!(token.rsplitn(2, '.')); + let (claims, header) = expect_two!(signing_input.rsplitn(2, '.')); + let header: Header = from_jwt_part(header)?; + + if !verify_rsa(signature, signing_input, (n, e), header.alg)? { + return Err(new_error(ErrorKind::InvalidSignature)); + } + + if !validation.algorithms.contains(&header.alg) { + return Err(new_error(ErrorKind::InvalidAlgorithm)); + } + + let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?; + validate(&claims_map, validation)?; + + Ok(TokenData { header, claims: decoded_claims }) +} + /// Decode a token without any signature validation into a struct containing 2 fields: `claims` and `header`. /// /// NOTE: Do not use this unless you know what you are doing! If the token's signature is invalid, it will *not* return an error. diff --git a/tests/rsa.rs b/tests/rsa.rs index def1f9ff..4dfafcef 100644 --- a/tests/rsa.rs +++ b/tests/rsa.rs @@ -4,7 +4,7 @@ extern crate serde_derive; extern crate chrono; use chrono::Utc; -use jsonwebtoken::{decode, encode, sign, verify, Algorithm, Header, Validation}; +use jsonwebtoken::{decode, encode, sign, verify, verify_rsa, Algorithm, Header, Validation}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] struct Claims { @@ -42,3 +42,35 @@ fn round_trip_claim() { assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); } + +#[test] +fn round_trip_verification_rsa() { + let modulus: Vec = vec![ + 0xc9, 0x11, 0x3a, 0xac, 0x7b, 0x8d, 0x47, 0x44, 0x1b, 0x1c, 0xed, 0xc7, 0xdc, 0xab, 0x76, + 0xa4, 0xe2, 0x86, 0x56, 0x14, 0x2a, 0x19, 0x95, 0xc8, 0x9c, 0xe7, 0x6e, 0x40, 0xdc, 0x57, + 0xce, 0xe2, 0xa5, 0xbd, 0x04, 0xcb, 0x51, 0x3b, 0xf8, 0x97, 0x8b, 0x20, 0x82, 0x1e, 0x7f, + 0x09, 0x86, 0x22, 0xfd, 0xcb, 0xc8, 0xf9, 0x25, 0xd5, 0x4f, 0xd9, 0x0f, 0x59, 0x22, 0x97, + 0xc4, 0x95, 0xc1, 0x5d, 0xdf, 0xf8, 0x2e, 0x4b, 0xdc, 0x3e, 0xe5, 0x1a, 0x90, 0x1a, 0x00, + 0x91, 0xf8, 0x7e, 0x7a, 0x21, 0x55, 0x32, 0x1d, 0x95, 0xad, 0x4c, 0x96, 0xca, 0x3d, 0xcc, + 0x16, 0x5d, 0x07, 0x4d, 0x51, 0x7d, 0x2b, 0x04, 0x57, 0x2c, 0x07, 0x30, 0x91, 0x11, 0x22, + 0x4b, 0x79, 0xe9, 0x4e, 0x11, 0xd1, 0xc8, 0x8c, 0x6e, 0xcb, 0x46, 0x4c, 0x79, 0x97, 0xf1, + 0x54, 0xbe, 0x5a, 0xac, 0xc8, 0x70, 0xd5, 0x24, 0x44, 0x2c, 0x1f, 0x07, 0xa0, 0x67, 0xc6, + 0xfc, 0x0b, 0x47, 0xf3, 0xd0, 0x48, 0x13, 0xd8, 0xc3, 0x04, 0x76, 0x7d, 0x74, 0xb7, 0xa5, + 0x2b, 0xd6, 0xb5, 0xf3, 0x8c, 0xc0, 0x7f, 0xc2, 0xf0, 0xa0, 0xf2, 0xf1, 0xbc, 0x96, 0xf7, + 0x22, 0x5e, 0x67, 0x9d, 0xca, 0x8f, 0x71, 0x27, 0xca, 0x0c, 0x3a, 0x1d, 0x30, 0x50, 0x48, + 0x31, 0xce, 0x25, 0x43, 0x30, 0xca, 0x2f, 0x98, 0x2f, 0x9a, 0x25, 0xcb, 0x5c, 0x1d, 0x40, + 0x18, 0xb9, 0xbc, 0x28, 0x18, 0xdf, 0x13, 0xcb, 0x37, 0x2f, 0x9c, 0x6a, 0x8b, 0xec, 0x94, + 0xa1, 0xdf, 0xa3, 0xf0, 0xcb, 0x6f, 0x22, 0x3f, 0x35, 0xd9, 0xd9, 0x12, 0xe1, 0x03, 0x22, + 0x45, 0x53, 0x7f, 0x6f, 0x2d, 0xa1, 0xdd, 0x96, 0x3c, 0x2d, 0x85, 0x46, 0xae, 0xa6, 0x57, + 0x65, 0x37, 0x20, 0x9f, 0x6b, 0xa3, 0x9f, 0xcb, 0x8a, 0x8d, 0x72, 0xd9, 0x54, 0x3e, 0x53, + 0x75, + ]; + + let exponent: Vec = vec![0x01, 0x00, 0x01]; + + let encrypted = + sign("hello world", include_bytes!("private_rsa_key.der"), Algorithm::RS256).unwrap(); + let is_valid = + verify_rsa(&encrypted, "hello world", (&modulus, &exponent), Algorithm::RS256).unwrap(); + assert!(is_valid); +}