From 571898252f9acdf58b5332c45e2afd071e1e4004 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 3 Nov 2019 06:22:51 -0600 Subject: [PATCH 01/24] Add PEM decoding support (#106) * Add PEM support with pem and simple_asn1. Documentation TODO * Make pkcs1 and pkcs8 versions of the RSA key, confirm they pass tests. * Add documentation, simplify * Update readme * Bump pem version * Remove extra print --- Cargo.toml | 2 + README.md | 30 ++- src/lib.rs | 30 +++ src/pem_decoder.rs | 198 ++++++++++++++++++ tests/ecdsa.rs | 15 +- tests/private_ecdsa_key.pem | 5 + ..._rsa_key.pem => private_rsa_key_pkcs1.pem} | 0 tests/private_rsa_key_pkcs8.pem | 28 +++ tests/public_ecdsa_key.pem | 4 + tests/public_rsa_key_pkcs1.pem | 8 + ...c_rsa_key.pem => public_rsa_key_pkcs8.pem} | 0 tests/rsa.rs | 32 ++- 12 files changed, 346 insertions(+), 6 deletions(-) create mode 100644 src/pem_decoder.rs create mode 100644 tests/private_ecdsa_key.pem rename tests/{private_rsa_key.pem => private_rsa_key_pkcs1.pem} (100%) create mode 100644 tests/private_rsa_key_pkcs8.pem create mode 100644 tests/public_ecdsa_key.pem create mode 100644 tests/public_rsa_key_pkcs1.pem rename tests/{public_rsa_key.pem => public_rsa_key_pkcs8.pem} (100%) diff --git a/Cargo.toml b/Cargo.toml index c7a8393..94d5a76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,5 @@ serde = "1.0" ring = { version = "0.16.5", features = ["std"] } base64 = "0.10" chrono = "0.4" +pem = "0.7" +simple_asn1 = "0.4.0" diff --git a/README.md b/README.md index ecdb723..2f5359d 100644 --- a/README.md +++ b/README.md @@ -113,8 +113,10 @@ This library currently supports the following: - ES384 ### RSA -`jsonwebtoken` can only read DER encoded keys currently. 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. +`jsonwebtoken` can read DER and PEM encoded keys. + +#### DER Encoded +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 `. @@ -127,3 +129,27 @@ $ openssl rsa -in private_rsa_key.der -inform DER -RSAPublicKey_out -outform DER If you are getting an error with your public key, make sure you get it by using the command above to ensure it is in the right format. + +#### PEM Encoded +To generate a PKCS#1 RSA key, run `openssl genrsa -out private_rsa_key_pkcs1.pem 2048` +To convert a PKCS#1 RSA key to a PKCS#8 RSA key, run `openssl pkcs8 -topk8 -inform pem -in private_rsa_key_pkcs1.pem -outform pem -nocrypt -out private_rsa_key_pkcs8.pem` + +To use a PEM encoded private / public keys, a pem struct is returned by `decode_pem`. +This carries the lifetime of the data inside. Finally to use the key like any other +use the `.as_key(alg)` function on the pem struct. +``` +let privkey_pem = decode_pem(pem_string_here).unwrap(); +let privkey = privkey_pem.as_key(Algorithm::RS256).unwrap(); +``` + +### ECDSA +`jsonwebtoken` can read PKCS#8 DER encoded private keys and public keys, as well as PEM encoded keys. Like RSA, to read a PEM key, you must use the pem decoder. + +To generate an EC key, you can do the following. + +```bash +// private key +openssl ecparam -genkey -name prime256v1 | openssl ec -out private_key.pem +// public key +openssl ec -in private_key.pem -pubout -out public_key.pem +``` diff --git a/src/lib.rs b/src/lib.rs index 5061bd7..0243844 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ extern crate chrono; extern crate ring; extern crate serde; extern crate serde_json; +extern crate simple_asn1; mod algorithms; mod crypto; @@ -19,6 +20,7 @@ mod header; mod keys; mod serialization; mod validation; +mod pem_decoder; pub use algorithms::Algorithm; pub use crypto::{sign, verify}; @@ -26,6 +28,7 @@ pub use header::Header; pub use keys::Key; pub use serialization::TokenData; pub use validation::Validation; +pub use pem_decoder::PemEncodedKey; use serde::de::DeserializeOwned; use serde::ser::Serialize; @@ -164,3 +167,30 @@ pub fn decode_header(token: &str) -> Result
{ let (_, header) = expect_two!(signing_input.rsplitn(2, '.')); from_jwt_part(header) } + +/// Decode a PEM string to obtain its key +/// +/// This must be a tagged PEM encoded key, tags start with `-----BEGIN ..-----` +/// and end with a `-----END ..-----` +/// +/// ```rust +/// use jsonwebtoken::{decode_pem, sign, verify, Algorithm}; +/// +/// let pem_content = "-----BEGIN PRIVATE KEY----- +/// MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWTFfCGljY6aw3Hrt +/// kHmPRiazukxPLb6ilpRAewjW8nihRANCAATDskChT+Altkm9X7MI69T3IUmrQU0L +/// 950IxEzvw/x5BMEINRMrXLBJhqzO9Bm+d6JbqA21YQmd1Kt4RzLJR1W+ +/// -----END PRIVATE KEY-----"; +/// +/// // First use decode_pem from jsonwebtoken +/// let privkey_pem = decode_pem(pem_content).unwrap(); +/// // If it decodes Ok, then you can start using it with a given algorithm +/// let privkey = privkey_pem.as_key().unwrap(); +/// +/// // When using the as_key function, you do not need to wrap in Key::Der or Key::Pkcs8 +/// // The same code can be used for public keys too. +/// let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); +/// ``` +pub fn decode_pem(content: &str) -> Result { + PemEncodedKey::read(content) +} diff --git a/src/pem_decoder.rs b/src/pem_decoder.rs new file mode 100644 index 0000000..32c9b7c --- /dev/null +++ b/src/pem_decoder.rs @@ -0,0 +1,198 @@ +use crate::keys::Key; +use crate::errors::{Result, ErrorKind}; + +extern crate pem; +extern crate simple_asn1; + +use simple_asn1::{OID, BigUint}; + +/// The return type of a successful PEM encoded key with `decode_pem` +/// +/// This struct gives a way to parse a string to a key for use in jsonwebtoken. +/// A struct is necessary as it provides the lifetime of the key +/// +/// PEM public private keys are encoded PKCS#1 or PKCS#8 +/// You will find that with PKCS#8 RSA keys that the PKCS#1 content +/// is embedded inside. This is what is provided to ring via `Key::Der` +/// For EC keys, they are always PKCS#8 on the outside but like RSA keys +/// EC keys contain a section within that ultimately has the configuration +/// that ring uses. +/// Documentation about these formats is at +/// PKCS#1: https://tools.ietf.org/html/rfc8017 +/// PKCS#8: https://tools.ietf.org/html/rfc5958 +pub struct PemEncodedKey { + content: Vec, + asn1: Vec, + pem_type: PemType, + encoded_with: PemEncodedWith, +} + +impl PemEncodedKey { + /// Read the PEM file for later key use + pub fn read(input: &str) -> Result { + match pem::parse(input) { + Ok(content) => { + let pem_contents = content.contents; + let asn1_content = match simple_asn1::from_der(pem_contents.as_slice()) { + Ok(asn1) => asn1, + Err(_) => return Err(ErrorKind::InvalidKeyFormat)?, + }; + + match content.tag.as_ref() { + // This handles a PKCS#1 RSA Private key + "RSA PRIVATE KEY" => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::RSAPrivateKey, + encoded_with: PemEncodedWith::PKCS1, + }), + "RSA PUBLIC KEY" => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::RSAPublicKey, + encoded_with: PemEncodedWith::PKCS1, + }), + + // No "EC PRIVATE KEY" + // https://security.stackexchange.com/questions/84327/converting-ecc-private-key-to-pkcs1-format + // "there is no such thing as a "PKCS#1 format" for elliptic curve (EC) keys" + + // This handles PKCS#8 private keys + "PRIVATE KEY" => { + match classify_pem(&asn1_content) { + Option::Some(Classification::EC) => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::ECPrivateKey, + encoded_with: PemEncodedWith::PKCS8, + }), + Option::Some(Classification::RSA) => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::RSAPrivateKey, + encoded_with: PemEncodedWith::PKCS8, + }), + _ => return Err(ErrorKind::InvalidKeyFormat)?, + } + } + + // This handles PKCS#8 public keys + "PUBLIC KEY" => { + match classify_pem(&asn1_content) { + Option::Some(Classification::EC) => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::ECPublicKey, + encoded_with: PemEncodedWith::PKCS8, + }), + Option::Some(Classification::RSA) => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::RSAPublicKey, + encoded_with: PemEncodedWith::PKCS8, + }), + _ => return Err(ErrorKind::InvalidKeyFormat)?, + } + } + + // Unknown type + _ => return Err(ErrorKind::InvalidKeyFormat)?, + } + }, + Err(_) => return Err(ErrorKind::InvalidKeyFormat)?, + } + } + + /// This will do the initial parsing of a PEM file. + /// Supported tagged pems include "RSA PRIVATE KEY", "RSA PUBLIC KEY", + /// "PRIVATE KEY", "PUBLIC KEY" + /// PEMs with multiple tagged portions are not supported + pub fn as_key(&self) -> Result> { + match self.encoded_with { + PemEncodedWith::PKCS1 => Ok(Key::Der(self.content.as_slice())), + PemEncodedWith::PKCS8 => { + match self.pem_type { + PemType::RSAPrivateKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), + PemType::RSAPublicKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), + PemType::ECPrivateKey => Ok(Key::Pkcs8(self.content.as_slice())), + PemType::ECPublicKey => Ok(Key::Pkcs8(extract_first_bitstring(&self.asn1)?)), + } + }, + } + } +} + +#[derive(Debug)] +#[derive(PartialEq)] +/// Supported PEM files for EC and RSA Public and Private Keys +enum PemType { + ECPublicKey, + ECPrivateKey, + RSAPublicKey, + RSAPrivateKey, +} + +#[derive(Debug)] +#[derive(PartialEq)] +enum PemEncodedWith { + PKCS1, + PKCS8, +} + +#[derive(Debug)] +#[derive(PartialEq)] +enum Classification { + EC, + RSA, +} + +// This really just finds and returns the first bitstring or octet string +// Which is the x coordinate for EC public keys +// And the DER contents of an RSA key +// Though PKCS#11 keys shouldn't have anything else. +// It will get confusing with certificates. +fn extract_first_bitstring(asn1: &Vec) -> Result<&[u8]> { + for asn1_entry in asn1.iter() { + match asn1_entry { + simple_asn1::ASN1Block::Sequence(_, entries) => { + if let Ok(result) = extract_first_bitstring(entries) { + return Ok(result); + } + } + simple_asn1::ASN1Block::BitString(_, _, value) => { + return Ok(value.as_ref()); + } + simple_asn1::ASN1Block::OctetString(_, value) => { + return Ok(value.as_ref()); + } + _ => () + } + } + return Err(ErrorKind::InvalidEcdsaKey)? +} + +fn classify_pem(asn1: &Vec) -> Option { + // These should be constant but the macro requires + // #![feature(const_vec_new)] + let ec_public_key_oid = simple_asn1::oid!(1,2,840,10045,2,1); + let rsa_public_key_oid = simple_asn1::oid!(1,2,840,113549,1,1,1); + + for asn1_entry in asn1.iter() { + match asn1_entry { + simple_asn1::ASN1Block::Sequence(_, entries) => { + if let Some(classification) = classify_pem(entries) { + return Some(classification); + } + } + simple_asn1::ASN1Block::ObjectIdentifier(_, oid) => { + if oid == ec_public_key_oid { + return Option::Some(Classification::EC); + } else if oid == rsa_public_key_oid { + return Option::Some(Classification::RSA); + } + } + _ => {} + } + } + return Option::default(); +} \ No newline at end of file diff --git a/tests/ecdsa.rs b/tests/ecdsa.rs index 12cf78d..e4ffa34 100644 --- a/tests/ecdsa.rs +++ b/tests/ecdsa.rs @@ -4,7 +4,7 @@ extern crate serde_derive; extern crate chrono; use chrono::Utc; -use jsonwebtoken::{decode, encode, sign, verify, Algorithm, Header, Key, Validation}; +use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Claims { @@ -14,7 +14,7 @@ pub struct Claims { } #[test] -fn round_trip_sign_verification() { +fn round_trip_sign_verification_pk8() { let privkey = include_bytes!("private_ecdsa_key.pk8"); let encrypted = sign("hello world", Key::Pkcs8(&privkey[..]), Algorithm::ES256).unwrap(); let pubkey = include_bytes!("public_ecdsa_key.pk8"); @@ -22,6 +22,17 @@ fn round_trip_sign_verification() { assert!(is_valid); } +#[test] +fn round_trip_sign_verification_pem() { + let privkey_pem = decode_pem(include_str!("private_ecdsa_key.pem")).unwrap(); + let privkey = privkey_pem.as_key().unwrap(); + let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); + let pubkey_pem = decode_pem(include_str!("public_ecdsa_key.pem")).unwrap(); + let pubkey = pubkey_pem.as_key().unwrap(); + let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap(); + assert!(is_valid); +} + #[test] fn round_trip_claim() { let my_claims = Claims { diff --git a/tests/private_ecdsa_key.pem b/tests/private_ecdsa_key.pem new file mode 100644 index 0000000..4613a0d --- /dev/null +++ b/tests/private_ecdsa_key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWTFfCGljY6aw3Hrt +kHmPRiazukxPLb6ilpRAewjW8nihRANCAATDskChT+Altkm9X7MI69T3IUmrQU0L +950IxEzvw/x5BMEINRMrXLBJhqzO9Bm+d6JbqA21YQmd1Kt4RzLJR1W+ +-----END PRIVATE KEY----- diff --git a/tests/private_rsa_key.pem b/tests/private_rsa_key_pkcs1.pem similarity index 100% rename from tests/private_rsa_key.pem rename to tests/private_rsa_key_pkcs1.pem diff --git a/tests/private_rsa_key_pkcs8.pem b/tests/private_rsa_key_pkcs8.pem new file mode 100644 index 0000000..2df7451 --- /dev/null +++ b/tests/private_rsa_key_pkcs8.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJETqse41HRBsc +7cfcq3ak4oZWFCoZlcic525A3FfO4qW9BMtRO/iXiyCCHn8JhiL9y8j5JdVP2Q9Z +IpfElcFd3/guS9w+5RqQGgCR+H56IVUyHZWtTJbKPcwWXQdNUX0rBFcsBzCRESJL +eelOEdHIjG7LRkx5l/FUvlqsyHDVJEQsHwegZ8b8C0fz0EgT2MMEdn10t6Ur1rXz +jMB/wvCg8vG8lvciXmedyo9xJ8oMOh0wUEgxziVDMMovmC+aJctcHUAYubwoGN8T +yzcvnGqL7JSh36Pwy28iPzXZ2RLhAyJFU39vLaHdljwthUaupldlNyCfa6Ofy4qN +ctlUPlN1AgMBAAECggEAdESTQjQ70O8QIp1ZSkCYXeZjuhj081CK7jhhp/4ChK7J +GlFQZMwiBze7d6K84TwAtfQGZhQ7km25E1kOm+3hIDCoKdVSKch/oL54f/BK6sKl +qlIzQEAenho4DuKCm3I4yAw9gEc0DV70DuMTR0LEpYyXcNJY3KNBOTjN5EYQAR9s +2MeurpgK2MdJlIuZaIbzSGd+diiz2E6vkmcufJLtmYUT/k/ddWvEtz+1DnO6bRHh +xuuDMeJA/lGB/EYloSLtdyCF6sII6C6slJJtgfb0bPy7l8VtL5iDyz46IKyzdyzW +tKAn394dm7MYR1RlUBEfqFUyNK7C+pVMVoTwCC2V4QKBgQD64syfiQ2oeUlLYDm4 +CcKSP3RnES02bcTyEDFSuGyyS1jldI4A8GXHJ/lG5EYgiYa1RUivge4lJrlNfjyf +dV230xgKms7+JiXqag1FI+3mqjAgg4mYiNjaao8N8O3/PD59wMPeWYImsWXNyeHS +55rUKiHERtCcvdzKl4u35ZtTqQKBgQDNKnX2bVqOJ4WSqCgHRhOm386ugPHfy+8j +m6cicmUR46ND6ggBB03bCnEG9OtGisxTo/TuYVRu3WP4KjoJs2LD5fwdwJqpgtHl +yVsk45Y1Hfo+7M6lAuR8rzCi6kHHNb0HyBmZjysHWZsn79ZM+sQnLpgaYgQGRbKV +DZWlbw7g7QKBgQCl1u+98UGXAP1jFutwbPsx40IVszP4y5ypCe0gqgon3UiY/G+1 +zTLp79GGe/SjI2VpQ7AlW7TI2A0bXXvDSDi3/5Dfya9ULnFXv9yfvH1QwWToySpW +Kvd1gYSoiX84/WCtjZOr0e0HmLIb0vw0hqZA4szJSqoxQgvF22EfIWaIaQKBgQCf +34+OmMYw8fEvSCPxDxVvOwW2i7pvV14hFEDYIeZKW2W1HWBhVMzBfFB5SE8yaCQy +pRfOzj9aKOCm2FjjiErVNpkQoi6jGtLvScnhZAt/lr2TXTrl8OwVkPrIaN0bG/AS +aUYxmBPCpXu3UjhfQiWqFq/mFyzlqlgvuCc9g95HPQKBgAscKP8mLxdKwOgX8yFW +GcZ0izY/30012ajdHY+/QK5lsMoxTnn0skdS+spLxaS5ZEO4qvPVb8RAoCkWMMal +2pOhmquJQVDPDLuZHdrIiKiDM20dy9sMfHygWcZjQ4WSxf/J7T9canLZIXFhHAZT +3wc9h4G8BBCtWN2TN/LsGZdB +-----END PRIVATE KEY----- diff --git a/tests/public_ecdsa_key.pem b/tests/public_ecdsa_key.pem new file mode 100644 index 0000000..f8b9c8e --- /dev/null +++ b/tests/public_ecdsa_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEw7JAoU/gJbZJvV+zCOvU9yFJq0FN +C/edCMRM78P8eQTBCDUTK1ywSYaszvQZvneiW6gNtWEJndSreEcyyUdVvg== +-----END PUBLIC KEY----- diff --git a/tests/public_rsa_key_pkcs1.pem b/tests/public_rsa_key_pkcs1.pem new file mode 100644 index 0000000..99e2a40 --- /dev/null +++ b/tests/public_rsa_key_pkcs1.pem @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAyRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4 +l4sggh5/CYYi/cvI+SXVT9kPWSKXxJXBXd/4LkvcPuUakBoAkfh+eiFVMh2VrUyW +yj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG +/AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4l +QzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi+yUod+j8MtvIj812dkS4QMiRVN/by2h +3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/tests/public_rsa_key.pem b/tests/public_rsa_key_pkcs8.pem similarity index 100% rename from tests/public_rsa_key.pem rename to tests/public_rsa_key_pkcs8.pem diff --git a/tests/rsa.rs b/tests/rsa.rs index 3a32399..82e6cc4 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, Key, Validation}; +use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; const RSA_ALGORITHMS: &[Algorithm] = &[ Algorithm::RS256, @@ -23,7 +23,7 @@ pub struct Claims { } #[test] -fn round_trip_sign_verification() { +fn round_trip_sign_verification_der() { let privkey = include_bytes!("private_rsa_key.der"); for &alg in RSA_ALGORITHMS { let encrypted = sign("hello world", Key::Der(&privkey[..]), alg).unwrap(); @@ -34,6 +34,34 @@ fn round_trip_sign_verification() { } } +#[test] +fn round_trip_sign_verification_pem_pkcs1() { + let privkey_pem = decode_pem(include_str!("private_rsa_key_pkcs1.pem")).unwrap(); + let pubkey_pem = decode_pem(include_str!("public_rsa_key_pkcs1.pem")).unwrap(); + + for &alg in RSA_ALGORITHMS { + let privkey_key = privkey_pem.as_key().unwrap(); + let pubkey_key = pubkey_pem.as_key().unwrap(); + let encrypted = sign("hello world", privkey_key, alg).unwrap(); + let is_valid = verify(&encrypted, "hello world", pubkey_key, alg).unwrap(); + assert!(is_valid); + } +} + +#[test] +fn round_trip_sign_verification_pem_pkcs8() { + let privkey_pem = decode_pem(include_str!("private_rsa_key_pkcs8.pem")).unwrap(); + let pubkey_pem = decode_pem(include_str!("public_rsa_key_pkcs8.pem")).unwrap(); + + for &alg in RSA_ALGORITHMS { + let privkey_key = privkey_pem.as_key().unwrap(); + let pubkey_key = pubkey_pem.as_key().unwrap(); + let encrypted = sign("hello world", privkey_key, alg).unwrap(); + let is_valid = verify(&encrypted, "hello world", pubkey_key, alg).unwrap(); + assert!(is_valid); + } +} + #[test] fn round_trip_claim() { let my_claims = Claims { From b9a3e3086fa9b3f0399b333c9ddf136edd2e4a67 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 3 Nov 2019 12:36:52 +0000 Subject: [PATCH 02/24] Fix Option handling --- src/pem_decoder.rs | 71 ++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/src/pem_decoder.rs b/src/pem_decoder.rs index 32c9b7c..e525eb9 100644 --- a/src/pem_decoder.rs +++ b/src/pem_decoder.rs @@ -6,11 +6,32 @@ extern crate simple_asn1; use simple_asn1::{OID, BigUint}; +/// Supported PEM files for EC and RSA Public and Private Keys +#[derive(Debug, PartialEq)] +enum PemType { + ECPublicKey, + ECPrivateKey, + RSAPublicKey, + RSAPrivateKey, +} + +#[derive(Debug, PartialEq)] +enum PemEncodedWith { + PKCS1, + PKCS8, +} + +#[derive(Debug, PartialEq)] +enum Classification { + EC, + RSA, +} + /// The return type of a successful PEM encoded key with `decode_pem` -/// +/// /// This struct gives a way to parse a string to a key for use in jsonwebtoken. /// A struct is necessary as it provides the lifetime of the key -/// +/// /// PEM public private keys are encoded PKCS#1 or PKCS#8 /// You will find that with PKCS#8 RSA keys that the PKCS#1 content /// is embedded inside. This is what is provided to ring via `Key::Der` @@ -37,7 +58,7 @@ impl PemEncodedKey { Ok(asn1) => asn1, Err(_) => return Err(ErrorKind::InvalidKeyFormat)?, }; - + match content.tag.as_ref() { // This handles a PKCS#1 RSA Private key "RSA PRIVATE KEY" => Ok(PemEncodedKey { @@ -52,7 +73,7 @@ impl PemEncodedKey { pem_type: PemType::RSAPublicKey, encoded_with: PemEncodedWith::PKCS1, }), - + // No "EC PRIVATE KEY" // https://security.stackexchange.com/questions/84327/converting-ecc-private-key-to-pkcs1-format // "there is no such thing as a "PKCS#1 format" for elliptic curve (EC) keys" @@ -60,13 +81,13 @@ impl PemEncodedKey { // This handles PKCS#8 private keys "PRIVATE KEY" => { match classify_pem(&asn1_content) { - Option::Some(Classification::EC) => Ok(PemEncodedKey { + Some(Classification::EC) => Ok(PemEncodedKey { content: pem_contents, asn1: asn1_content, pem_type: PemType::ECPrivateKey, encoded_with: PemEncodedWith::PKCS8, }), - Option::Some(Classification::RSA) => Ok(PemEncodedKey { + Some(Classification::RSA) => Ok(PemEncodedKey { content: pem_contents, asn1: asn1_content, pem_type: PemType::RSAPrivateKey, @@ -79,13 +100,13 @@ impl PemEncodedKey { // This handles PKCS#8 public keys "PUBLIC KEY" => { match classify_pem(&asn1_content) { - Option::Some(Classification::EC) => Ok(PemEncodedKey { + Some(Classification::EC) => Ok(PemEncodedKey { content: pem_contents, asn1: asn1_content, pem_type: PemType::ECPublicKey, encoded_with: PemEncodedWith::PKCS8, }), - Option::Some(Classification::RSA) => Ok(PemEncodedKey { + Some(Classification::RSA) => Ok(PemEncodedKey { content: pem_contents, asn1: asn1_content, pem_type: PemType::RSAPublicKey, @@ -122,34 +143,10 @@ impl PemEncodedKey { } } -#[derive(Debug)] -#[derive(PartialEq)] -/// Supported PEM files for EC and RSA Public and Private Keys -enum PemType { - ECPublicKey, - ECPrivateKey, - RSAPublicKey, - RSAPrivateKey, -} - -#[derive(Debug)] -#[derive(PartialEq)] -enum PemEncodedWith { - PKCS1, - PKCS8, -} - -#[derive(Debug)] -#[derive(PartialEq)] -enum Classification { - EC, - RSA, -} - // This really just finds and returns the first bitstring or octet string // Which is the x coordinate for EC public keys // And the DER contents of an RSA key -// Though PKCS#11 keys shouldn't have anything else. +// Though PKCS#11 keys shouldn't have anything else. // It will get confusing with certificates. fn extract_first_bitstring(asn1: &Vec) -> Result<&[u8]> { for asn1_entry in asn1.iter() { @@ -186,13 +183,13 @@ fn classify_pem(asn1: &Vec) -> Option { } simple_asn1::ASN1Block::ObjectIdentifier(_, oid) => { if oid == ec_public_key_oid { - return Option::Some(Classification::EC); + return Some(Classification::EC); } else if oid == rsa_public_key_oid { - return Option::Some(Classification::RSA); + return Some(Classification::RSA); } } _ => {} } } - return Option::default(); -} \ No newline at end of file + None +} From 06bebeaae3e621601d140109b7aabcc537f56ba4 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 3 Nov 2019 12:55:36 +0000 Subject: [PATCH 03/24] cargo fmt --- src/lib.rs | 12 +-- src/pem_decoder.rs | 257 ++++++++++++++++++++++----------------------- src/validation.rs | 6 +- 3 files changed, 135 insertions(+), 140 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0243844..db07517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,17 +18,17 @@ mod crypto; pub mod errors; mod header; mod keys; +mod pem_decoder; mod serialization; mod validation; -mod pem_decoder; pub use algorithms::Algorithm; pub use crypto::{sign, verify}; pub use header::Header; pub use keys::Key; +pub use pem_decoder::PemEncodedKey; pub use serialization::TokenData; pub use validation::Validation; -pub use pem_decoder::PemEncodedKey; use serde::de::DeserializeOwned; use serde::ser::Serialize; @@ -172,21 +172,21 @@ pub fn decode_header(token: &str) -> Result
{ /// /// This must be a tagged PEM encoded key, tags start with `-----BEGIN ..-----` /// and end with a `-----END ..-----` -/// +/// /// ```rust /// use jsonwebtoken::{decode_pem, sign, verify, Algorithm}; -/// +/// /// let pem_content = "-----BEGIN PRIVATE KEY----- /// MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWTFfCGljY6aw3Hrt /// kHmPRiazukxPLb6ilpRAewjW8nihRANCAATDskChT+Altkm9X7MI69T3IUmrQU0L /// 950IxEzvw/x5BMEINRMrXLBJhqzO9Bm+d6JbqA21YQmd1Kt4RzLJR1W+ /// -----END PRIVATE KEY-----"; -/// +/// /// // First use decode_pem from jsonwebtoken /// let privkey_pem = decode_pem(pem_content).unwrap(); /// // If it decodes Ok, then you can start using it with a given algorithm /// let privkey = privkey_pem.as_key().unwrap(); -/// +/// /// // When using the as_key function, you do not need to wrap in Key::Der or Key::Pkcs8 /// // The same code can be used for public keys too. /// let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); diff --git a/src/pem_decoder.rs b/src/pem_decoder.rs index e525eb9..3ccaa6d 100644 --- a/src/pem_decoder.rs +++ b/src/pem_decoder.rs @@ -1,30 +1,30 @@ +use crate::errors::{ErrorKind, Result}; use crate::keys::Key; -use crate::errors::{Result, ErrorKind}; extern crate pem; extern crate simple_asn1; -use simple_asn1::{OID, BigUint}; +use simple_asn1::{BigUint, OID}; /// Supported PEM files for EC and RSA Public and Private Keys #[derive(Debug, PartialEq)] enum PemType { - ECPublicKey, - ECPrivateKey, - RSAPublicKey, - RSAPrivateKey, + ECPublicKey, + ECPrivateKey, + RSAPublicKey, + RSAPrivateKey, } #[derive(Debug, PartialEq)] enum PemEncodedWith { - PKCS1, - PKCS8, + PKCS1, + PKCS8, } #[derive(Debug, PartialEq)] enum Classification { - EC, - RSA, + EC, + RSA, } /// The return type of a successful PEM encoded key with `decode_pem` @@ -41,106 +41,101 @@ enum Classification { /// Documentation about these formats is at /// PKCS#1: https://tools.ietf.org/html/rfc8017 /// PKCS#8: https://tools.ietf.org/html/rfc5958 +#[derive(Debug)] pub struct PemEncodedKey { - content: Vec, - asn1: Vec, - pem_type: PemType, - encoded_with: PemEncodedWith, + content: Vec, + asn1: Vec, + pem_type: PemType, + encoded_with: PemEncodedWith, } impl PemEncodedKey { - /// Read the PEM file for later key use - pub fn read(input: &str) -> Result { - match pem::parse(input) { - Ok(content) => { - let pem_contents = content.contents; - let asn1_content = match simple_asn1::from_der(pem_contents.as_slice()) { - Ok(asn1) => asn1, - Err(_) => return Err(ErrorKind::InvalidKeyFormat)?, - }; + /// Read the PEM file for later key use + pub fn read(input: &str) -> Result { + match pem::parse(input) { + Ok(content) => { + let pem_contents = content.contents; + let asn1_content = match simple_asn1::from_der(pem_contents.as_slice()) { + Ok(asn1) => asn1, + Err(_) => return Err(ErrorKind::InvalidKeyFormat)?, + }; - match content.tag.as_ref() { - // This handles a PKCS#1 RSA Private key - "RSA PRIVATE KEY" => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::RSAPrivateKey, - encoded_with: PemEncodedWith::PKCS1, - }), - "RSA PUBLIC KEY" => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::RSAPublicKey, - encoded_with: PemEncodedWith::PKCS1, - }), + match content.tag.as_ref() { + // This handles a PKCS#1 RSA Private key + "RSA PRIVATE KEY" => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::RSAPrivateKey, + encoded_with: PemEncodedWith::PKCS1, + }), + "RSA PUBLIC KEY" => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::RSAPublicKey, + encoded_with: PemEncodedWith::PKCS1, + }), - // No "EC PRIVATE KEY" - // https://security.stackexchange.com/questions/84327/converting-ecc-private-key-to-pkcs1-format - // "there is no such thing as a "PKCS#1 format" for elliptic curve (EC) keys" + // No "EC PRIVATE KEY" + // https://security.stackexchange.com/questions/84327/converting-ecc-private-key-to-pkcs1-format + // "there is no such thing as a "PKCS#1 format" for elliptic curve (EC) keys" - // This handles PKCS#8 private keys - "PRIVATE KEY" => { - match classify_pem(&asn1_content) { - Some(Classification::EC) => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::ECPrivateKey, - encoded_with: PemEncodedWith::PKCS8, - }), - Some(Classification::RSA) => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::RSAPrivateKey, - encoded_with: PemEncodedWith::PKCS8, - }), - _ => return Err(ErrorKind::InvalidKeyFormat)?, + // This handles PKCS#8 private keys + "PRIVATE KEY" => match classify_pem(&asn1_content) { + Some(Classification::EC) => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::ECPrivateKey, + encoded_with: PemEncodedWith::PKCS8, + }), + Some(Classification::RSA) => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::RSAPrivateKey, + encoded_with: PemEncodedWith::PKCS8, + }), + _ => return Err(ErrorKind::InvalidKeyFormat)?, + }, + + // This handles PKCS#8 public keys + "PUBLIC KEY" => match classify_pem(&asn1_content) { + Some(Classification::EC) => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::ECPublicKey, + encoded_with: PemEncodedWith::PKCS8, + }), + Some(Classification::RSA) => Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type: PemType::RSAPublicKey, + encoded_with: PemEncodedWith::PKCS8, + }), + _ => return Err(ErrorKind::InvalidKeyFormat)?, + }, + + // Unknown type + _ => return Err(ErrorKind::InvalidKeyFormat)?, + } } - } - - // This handles PKCS#8 public keys - "PUBLIC KEY" => { - match classify_pem(&asn1_content) { - Some(Classification::EC) => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::ECPublicKey, - encoded_with: PemEncodedWith::PKCS8, - }), - Some(Classification::RSA) => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::RSAPublicKey, - encoded_with: PemEncodedWith::PKCS8, - }), - _ => return Err(ErrorKind::InvalidKeyFormat)?, - } - } - - // Unknown type - _ => return Err(ErrorKind::InvalidKeyFormat)?, + Err(_) => return Err(ErrorKind::InvalidKeyFormat)?, } - }, - Err(_) => return Err(ErrorKind::InvalidKeyFormat)?, } - } - /// This will do the initial parsing of a PEM file. - /// Supported tagged pems include "RSA PRIVATE KEY", "RSA PUBLIC KEY", - /// "PRIVATE KEY", "PUBLIC KEY" - /// PEMs with multiple tagged portions are not supported - pub fn as_key(&self) -> Result> { - match self.encoded_with { - PemEncodedWith::PKCS1 => Ok(Key::Der(self.content.as_slice())), - PemEncodedWith::PKCS8 => { - match self.pem_type { - PemType::RSAPrivateKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), - PemType::RSAPublicKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), - PemType::ECPrivateKey => Ok(Key::Pkcs8(self.content.as_slice())), - PemType::ECPublicKey => Ok(Key::Pkcs8(extract_first_bitstring(&self.asn1)?)), + /// This will do the initial parsing of a PEM file. + /// Supported tagged pems include "RSA PRIVATE KEY", "RSA PUBLIC KEY", + /// "PRIVATE KEY", "PUBLIC KEY" + /// PEMs with multiple tagged portions are not supported + pub fn as_key(&self) -> Result> { + match self.encoded_with { + PemEncodedWith::PKCS1 => Ok(Key::Der(self.content.as_slice())), + PemEncodedWith::PKCS8 => match self.pem_type { + PemType::RSAPrivateKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), + PemType::RSAPublicKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), + PemType::ECPrivateKey => Ok(Key::Pkcs8(self.content.as_slice())), + PemType::ECPublicKey => Ok(Key::Pkcs8(extract_first_bitstring(&self.asn1)?)), + }, } - }, } - } } // This really just finds and returns the first bitstring or octet string @@ -149,47 +144,47 @@ impl PemEncodedKey { // Though PKCS#11 keys shouldn't have anything else. // It will get confusing with certificates. fn extract_first_bitstring(asn1: &Vec) -> Result<&[u8]> { - for asn1_entry in asn1.iter() { - match asn1_entry { - simple_asn1::ASN1Block::Sequence(_, entries) => { - if let Ok(result) = extract_first_bitstring(entries) { - return Ok(result); + for asn1_entry in asn1.iter() { + match asn1_entry { + simple_asn1::ASN1Block::Sequence(_, entries) => { + if let Ok(result) = extract_first_bitstring(entries) { + return Ok(result); + } + } + simple_asn1::ASN1Block::BitString(_, _, value) => { + return Ok(value.as_ref()); + } + simple_asn1::ASN1Block::OctetString(_, value) => { + return Ok(value.as_ref()); + } + _ => (), } - } - simple_asn1::ASN1Block::BitString(_, _, value) => { - return Ok(value.as_ref()); - } - simple_asn1::ASN1Block::OctetString(_, value) => { - return Ok(value.as_ref()); - } - _ => () } - } - return Err(ErrorKind::InvalidEcdsaKey)? + return Err(ErrorKind::InvalidEcdsaKey)?; } fn classify_pem(asn1: &Vec) -> Option { - // These should be constant but the macro requires - // #![feature(const_vec_new)] - let ec_public_key_oid = simple_asn1::oid!(1,2,840,10045,2,1); - let rsa_public_key_oid = simple_asn1::oid!(1,2,840,113549,1,1,1); + // These should be constant but the macro requires + // #![feature(const_vec_new)] + let ec_public_key_oid = simple_asn1::oid!(1, 2, 840, 10045, 2, 1); + let rsa_public_key_oid = simple_asn1::oid!(1, 2, 840, 113549, 1, 1, 1); - for asn1_entry in asn1.iter() { - match asn1_entry { - simple_asn1::ASN1Block::Sequence(_, entries) => { - if let Some(classification) = classify_pem(entries) { - return Some(classification); + for asn1_entry in asn1.iter() { + match asn1_entry { + simple_asn1::ASN1Block::Sequence(_, entries) => { + if let Some(classification) = classify_pem(entries) { + return Some(classification); + } + } + simple_asn1::ASN1Block::ObjectIdentifier(_, oid) => { + if oid == ec_public_key_oid { + return Some(Classification::EC); + } else if oid == rsa_public_key_oid { + return Some(Classification::RSA); + } + } + _ => {} } - } - simple_asn1::ASN1Block::ObjectIdentifier(_, oid) => { - if oid == ec_public_key_oid { - return Some(Classification::EC); - } else if oid == rsa_public_key_oid { - return Some(Classification::RSA); - } - } - _ => {} } - } None } diff --git a/src/validation.rs b/src/validation.rs index 8c7b2ca..463249e 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -1,7 +1,7 @@ -use std::collections::HashSet; use chrono::Utc; use serde_json::map::Map; use serde_json::{from_value, Value}; +use std::collections::HashSet; use crate::algorithms::Algorithm; use crate::errors::{new_error, ErrorKind, Result}; @@ -141,8 +141,8 @@ pub fn validate(claims: &Map, options: &Validation) -> Result<()> if let Some(ref correct_aud) = options.aud { if let Some(aud) = claims.get("aud") { - let provided_aud: HashSet = from_value(aud.clone())?; - if provided_aud.intersection(correct_aud).count() == 0 { + let provided_aud: HashSet = from_value(aud.clone())?; + if provided_aud.intersection(correct_aud).count() == 0 { return Err(new_error(ErrorKind::InvalidAudience)); } } else { From caef740ad419bc575f6558ca61f7e354141ed5f1 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 3 Nov 2019 13:17:04 +0000 Subject: [PATCH 04/24] Refactoring + more idiomatic enum names --- src/keys.rs | 1 + src/pem_decoder.rs | 114 ++++++++++++++++++++++----------------------- 2 files changed, 57 insertions(+), 58 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index cbabfa4..0d2e6c4 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,5 +1,6 @@ /// The supported RSA key formats, see the documentation for ring::signature::RsaKeyPair /// for more information +#[derive(Debug, PartialEq)] pub enum Key<'a> { /// An unencrypted PKCS#8-encoded key. Can be used with both ECDSA and RSA /// algorithms when signing. See ring for information. diff --git a/src/pem_decoder.rs b/src/pem_decoder.rs index 3ccaa6d..0f08440 100644 --- a/src/pem_decoder.rs +++ b/src/pem_decoder.rs @@ -9,22 +9,22 @@ use simple_asn1::{BigUint, OID}; /// Supported PEM files for EC and RSA Public and Private Keys #[derive(Debug, PartialEq)] enum PemType { - ECPublicKey, - ECPrivateKey, - RSAPublicKey, - RSAPrivateKey, + EcPublicKey, + EcPrivateKey, + RsaPublicKey, + RsaPrivateKey, } #[derive(Debug, PartialEq)] -enum PemEncodedWith { - PKCS1, - PKCS8, +enum Standard { + Pkcs1, + Pkcs8, } #[derive(Debug, PartialEq)] enum Classification { - EC, - RSA, + Ec, + Rsa, } /// The return type of a successful PEM encoded key with `decode_pem` @@ -46,7 +46,7 @@ pub struct PemEncodedKey { content: Vec, asn1: Vec, pem_type: PemType, - encoded_with: PemEncodedWith, + standard: Standard, } impl PemEncodedKey { @@ -65,55 +65,51 @@ impl PemEncodedKey { "RSA PRIVATE KEY" => Ok(PemEncodedKey { content: pem_contents, asn1: asn1_content, - pem_type: PemType::RSAPrivateKey, - encoded_with: PemEncodedWith::PKCS1, + pem_type: PemType::RsaPrivateKey, + standard: Standard::Pkcs1, }), "RSA PUBLIC KEY" => Ok(PemEncodedKey { content: pem_contents, asn1: asn1_content, - pem_type: PemType::RSAPublicKey, - encoded_with: PemEncodedWith::PKCS1, + pem_type: PemType::RsaPublicKey, + standard: Standard::Pkcs1, }), // No "EC PRIVATE KEY" // https://security.stackexchange.com/questions/84327/converting-ecc-private-key-to-pkcs1-format // "there is no such thing as a "PKCS#1 format" for elliptic curve (EC) keys" - // This handles PKCS#8 private keys - "PRIVATE KEY" => match classify_pem(&asn1_content) { - Some(Classification::EC) => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::ECPrivateKey, - encoded_with: PemEncodedWith::PKCS8, - }), - Some(Classification::RSA) => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::RSAPrivateKey, - encoded_with: PemEncodedWith::PKCS8, - }), - _ => return Err(ErrorKind::InvalidKeyFormat)?, + // This handles PKCS#8 public & private keys + tag @ "PRIVATE KEY" | tag @ "PUBLIC KEY" => match classify_pem(&asn1_content) { + Some(c) => { + let is_private = tag == "PRIVATE KEY"; + let pem_type = match c { + Classification::Ec => { + if is_private { + PemType::EcPrivateKey + } else { + PemType::EcPublicKey + } + } + Classification::Rsa => { + if is_private { + PemType::RsaPrivateKey + } else { + PemType::RsaPublicKey + } + } + }; + Ok(PemEncodedKey { + content: pem_contents, + asn1: asn1_content, + pem_type, + standard: Standard::Pkcs8, + }) + } + None => return Err(ErrorKind::InvalidKeyFormat)?, }, - // This handles PKCS#8 public keys - "PUBLIC KEY" => match classify_pem(&asn1_content) { - Some(Classification::EC) => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::ECPublicKey, - encoded_with: PemEncodedWith::PKCS8, - }), - Some(Classification::RSA) => Ok(PemEncodedKey { - content: pem_contents, - asn1: asn1_content, - pem_type: PemType::RSAPublicKey, - encoded_with: PemEncodedWith::PKCS8, - }), - _ => return Err(ErrorKind::InvalidKeyFormat)?, - }, - - // Unknown type + // Unknown/unsupported type _ => return Err(ErrorKind::InvalidKeyFormat)?, } } @@ -126,13 +122,13 @@ impl PemEncodedKey { /// "PRIVATE KEY", "PUBLIC KEY" /// PEMs with multiple tagged portions are not supported pub fn as_key(&self) -> Result> { - match self.encoded_with { - PemEncodedWith::PKCS1 => Ok(Key::Der(self.content.as_slice())), - PemEncodedWith::PKCS8 => match self.pem_type { - PemType::RSAPrivateKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), - PemType::RSAPublicKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), - PemType::ECPrivateKey => Ok(Key::Pkcs8(self.content.as_slice())), - PemType::ECPublicKey => Ok(Key::Pkcs8(extract_first_bitstring(&self.asn1)?)), + match self.standard { + Standard::Pkcs1 => Ok(Key::Der(self.content.as_slice())), + Standard::Pkcs8 => match self.pem_type { + PemType::RsaPrivateKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), + PemType::RsaPublicKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), + PemType::EcPrivateKey => Ok(Key::Pkcs8(self.content.as_slice())), + PemType::EcPublicKey => Ok(Key::Pkcs8(extract_first_bitstring(&self.asn1)?)), }, } } @@ -160,9 +156,10 @@ fn extract_first_bitstring(asn1: &Vec) -> Result<&[u8]> _ => (), } } - return Err(ErrorKind::InvalidEcdsaKey)?; + Err(ErrorKind::InvalidEcdsaKey)? } +/// Find whether this is EC or RSA fn classify_pem(asn1: &Vec) -> Option { // These should be constant but the macro requires // #![feature(const_vec_new)] @@ -178,9 +175,10 @@ fn classify_pem(asn1: &Vec) -> Option { } simple_asn1::ASN1Block::ObjectIdentifier(_, oid) => { if oid == ec_public_key_oid { - return Some(Classification::EC); - } else if oid == rsa_public_key_oid { - return Some(Classification::RSA); + return Some(Classification::Ec); + } + if oid == rsa_public_key_oid { + return Some(Classification::Rsa); } } _ => {} From 210e96063d69a8c269465c473178d8b56fcf9ae3 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 3 Nov 2019 15:36:19 +0000 Subject: [PATCH 05/24] Reorganise tests --- CHANGELOG.md | 1 + Cargo.toml | 6 +- src/algorithms.rs | 19 +++ src/validation.rs | 24 +++ tests/{ecdsa.rs => ec/mod.rs} | 13 +- tests/{ => ec}/private_ecdsa_key.pem | 0 tests/{ => ec}/private_ecdsa_key.pk8 | Bin tests/{ => ec}/public_ecdsa_key.pem | 0 tests/{ => ec}/public_ecdsa_key.pk8 | 0 tests/hmac.rs | 134 ++++++++++++++++ tests/lib.rs | 177 +--------------------- tests/{rsa.rs => rsa/mod.rs} | 13 +- tests/{ => rsa}/private_rsa_key.der | Bin tests/{ => rsa}/private_rsa_key_pkcs1.pem | 0 tests/{ => rsa}/private_rsa_key_pkcs8.pem | 0 tests/{ => rsa}/public_rsa_key.der | Bin tests/{ => rsa}/public_rsa_key_pkcs1.pem | 0 tests/{ => rsa}/public_rsa_key_pkcs8.pem | 0 18 files changed, 193 insertions(+), 194 deletions(-) rename tests/{ecdsa.rs => ec/mod.rs} (83%) rename tests/{ => ec}/private_ecdsa_key.pem (100%) rename tests/{ => ec}/private_ecdsa_key.pk8 (100%) rename tests/{ => ec}/public_ecdsa_key.pem (100%) rename tests/{ => ec}/public_ecdsa_key.pk8 (100%) create mode 100644 tests/hmac.rs rename tests/{rsa.rs => rsa/mod.rs} (94%) rename tests/{ => rsa}/private_rsa_key.der (100%) rename tests/{ => rsa}/private_rsa_key_pkcs1.pem (100%) rename tests/{ => rsa}/private_rsa_key_pkcs8.pem (100%) rename tests/{ => rsa}/public_rsa_key.der (100%) rename tests/{ => rsa}/public_rsa_key_pkcs1.pem (100%) rename tests/{ => rsa}/public_rsa_key_pkcs8.pem (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd1df1f..a2aefd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Update to 2018 edition - Changed aud field type in Validation to `Option>`. Audience validation now tests for "any-of-these" audience membership. +- Add support for keys in PEM format ## 6.0.1 (2019-05-10) diff --git a/Cargo.toml b/Cargo.toml index 94d5a76..b294bd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,9 @@ serde_json = "1.0" serde_derive = "1.0" serde = "1.0" ring = { version = "0.16.5", features = ["std"] } -base64 = "0.10" +base64 = "0.11" +# For validation chrono = "0.4" +# For PEM decoding pem = "0.7" -simple_asn1 = "0.4.0" +simple_asn1 = "0.4" diff --git a/src/algorithms.rs b/src/algorithms.rs index 34f8556..6b05356 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -56,3 +56,22 @@ impl FromStr for Algorithm { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generate_algorithm_enum_from_str() { + assert!(Algorithm::from_str("HS256").is_ok()); + assert!(Algorithm::from_str("HS384").is_ok()); + assert!(Algorithm::from_str("HS512").is_ok()); + assert!(Algorithm::from_str("RS256").is_ok()); + assert!(Algorithm::from_str("RS384").is_ok()); + assert!(Algorithm::from_str("RS512").is_ok()); + assert!(Algorithm::from_str("PS256").is_ok()); + assert!(Algorithm::from_str("PS384").is_ok()); + assert!(Algorithm::from_str("PS512").is_ok()); + assert!(Algorithm::from_str("").is_err()); + } +} diff --git a/src/validation.rs b/src/validation.rs index 463249e..280fd72 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -403,4 +403,28 @@ mod tests { _ => assert!(false), }; } + + // https://github.com/Keats/jsonwebtoken/issues/51 + #[test] + fn does_validation_in_right_order() { + let mut claims = Map::new(); + claims.insert("exp".to_string(), to_value(Utc::now().timestamp() + 10000).unwrap()); + let v = Validation { + leeway: 5, + validate_exp: true, + iss: Some("iss no check".to_string()), + sub: Some("sub no check".to_string()), + ..Validation::default() + }; + let res = validate(&claims, &v); + // It errors because it needs to validate iss/sub which are missing + assert!(res.is_err()); + match res.unwrap_err().kind() { + &ErrorKind::InvalidIssuer => (), + t @ _ => { + println!("{:?}", t); + assert!(false) + } + }; + } } diff --git a/tests/ecdsa.rs b/tests/ec/mod.rs similarity index 83% rename from tests/ecdsa.rs rename to tests/ec/mod.rs index e4ffa34..8a15ecc 100644 --- a/tests/ecdsa.rs +++ b/tests/ec/mod.rs @@ -1,10 +1,6 @@ -extern crate jsonwebtoken; -#[macro_use] -extern crate serde_derive; -extern crate chrono; - use chrono::Utc; use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; +use serde_derive::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Claims { @@ -49,10 +45,3 @@ fn round_trip_claim() { assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); } - -#[test] -#[should_panic(expected = "InvalidKeyFormat")] -fn fails_with_non_pkcs8_key_format() { - let privkey = include_bytes!("private_rsa_key.der"); - let _encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::ES256).unwrap(); -} diff --git a/tests/private_ecdsa_key.pem b/tests/ec/private_ecdsa_key.pem similarity index 100% rename from tests/private_ecdsa_key.pem rename to tests/ec/private_ecdsa_key.pem diff --git a/tests/private_ecdsa_key.pk8 b/tests/ec/private_ecdsa_key.pk8 similarity index 100% rename from tests/private_ecdsa_key.pk8 rename to tests/ec/private_ecdsa_key.pk8 diff --git a/tests/public_ecdsa_key.pem b/tests/ec/public_ecdsa_key.pem similarity index 100% rename from tests/public_ecdsa_key.pem rename to tests/ec/public_ecdsa_key.pem diff --git a/tests/public_ecdsa_key.pk8 b/tests/ec/public_ecdsa_key.pk8 similarity index 100% rename from tests/public_ecdsa_key.pk8 rename to tests/ec/public_ecdsa_key.pk8 diff --git a/tests/hmac.rs b/tests/hmac.rs new file mode 100644 index 0000000..3c3f284 --- /dev/null +++ b/tests/hmac.rs @@ -0,0 +1,134 @@ +use chrono::Utc; +use jsonwebtoken::{ + dangerous_unsafe_decode, decode, decode_header, encode, sign, verify, Algorithm, Header, Key, + Validation, +}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Claims { + sub: String, + company: String, + exp: i64, +} + +#[test] +fn sign_hs256() { + let result = sign("hello world", Key::Hmac(b"secret"), Algorithm::HS256).unwrap(); + let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; + assert_eq!(result, expected); +} + +#[test] +fn verify_hs256() { + let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; + let valid = verify(sig, "hello world", Key::Hmac(b"secret"), Algorithm::HS256).unwrap(); + assert!(valid); +} + +#[test] +fn encode_with_custom_header() { + let my_claims = Claims { + sub: "b@b.com".to_string(), + company: "ACME".to_string(), + exp: Utc::now().timestamp() + 10000, + }; + let mut header = Header::default(); + header.kid = Some("kid".to_string()); + let token = encode(&header, &my_claims, Key::Hmac(b"secret")).unwrap(); + let token_data = + decode::(&token, Key::Hmac(b"secret"), &Validation::default()).unwrap(); + assert_eq!(my_claims, token_data.claims); + assert_eq!("kid", token_data.header.kid.unwrap()); +} + +#[test] +fn round_trip_claim() { + let my_claims = Claims { + sub: "b@b.com".to_string(), + company: "ACME".to_string(), + exp: Utc::now().timestamp() + 10000, + }; + let token = encode(&Header::default(), &my_claims, Key::Hmac(b"secret")).unwrap(); + let token_data = + decode::(&token, Key::Hmac(b"secret"), &Validation::default()).unwrap(); + assert_eq!(my_claims, token_data.claims); + assert!(token_data.header.kid.is_none()); +} + +#[test] +fn decode_token() { + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; + let claims = decode::(token, Key::Hmac(b"secret"), &Validation::default()); + println!("{:?}", claims); + claims.unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidToken")] +fn decode_token_missing_parts() { + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; + let claims = decode::(token, Key::Hmac(b"secret"), &Validation::default()); + claims.unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidSignature")] +fn decode_token_invalid_signature() { + let token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; + let claims = decode::(token, Key::Hmac(b"secret"), &Validation::default()); + claims.unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidAlgorithm")] +fn decode_token_wrong_algorithm() { + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; + let claims = decode::(token, Key::Hmac(b"secret"), &Validation::new(Algorithm::RS512)); + claims.unwrap(); +} + +#[test] +fn decode_token_with_bytes_secret() { + let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks"; + let claims = decode::(token, Key::Hmac(b"\x01\x02\x03"), &Validation::default()); + assert!(claims.is_ok()); +} + +#[test] +fn decode_header_only() { + let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S"; + let header = decode_header(token).unwrap(); + assert_eq!(header.alg, Algorithm::HS256); + assert_eq!(header.typ, Some("JWT".to_string())); +} + +#[test] +fn dangerous_unsafe_decode_token() { + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; + let claims = dangerous_unsafe_decode::(token); + claims.unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidToken")] +fn dangerous_unsafe_decode_token_missing_parts() { + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; + let claims = dangerous_unsafe_decode::(token); + claims.unwrap(); +} + +#[test] +fn dangerous_unsafe_decode_token_invalid_signature() { + let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.wrong"; + let claims = dangerous_unsafe_decode::(token); + claims.unwrap(); +} + +#[test] +fn dangerous_unsafe_decode_token_wrong_algorithm() { + let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.fLxey-hxAKX5rNHHIx1_Ch0KmrbiuoakDVbsJjLWrx8fbjKjrPuWMYEJzTU3SBnYgnZokC-wqSdqckXUOunC-g"; + let claims = dangerous_unsafe_decode::(token); + claims.unwrap(); +} diff --git a/tests/lib.rs b/tests/lib.rs index cce95a1..30241b4 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,175 +1,2 @@ -extern crate jsonwebtoken; -#[macro_use] -extern crate serde_derive; -extern crate chrono; - -use chrono::Utc; -use jsonwebtoken::{ - dangerous_unsafe_decode, decode, decode_header, encode, sign, verify, Algorithm, Header, Key, - Validation, -}; -use std::str::FromStr; - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct Claims { - sub: String, - company: String, - exp: i64, -} - -#[test] -fn sign_hs256() { - let result = sign("hello world", Key::Hmac(b"secret"), Algorithm::HS256).unwrap(); - let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; - assert_eq!(result, expected); -} - -#[test] -fn verify_hs256() { - let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; - let valid = verify(sig, "hello world", Key::Hmac(b"secret"), Algorithm::HS256).unwrap(); - assert!(valid); -} - -#[test] -fn encode_with_custom_header() { - let my_claims = Claims { - sub: "b@b.com".to_string(), - company: "ACME".to_string(), - exp: Utc::now().timestamp() + 10000, - }; - let mut header = Header::default(); - header.kid = Some("kid".to_string()); - let token = encode(&header, &my_claims, Key::Hmac(b"secret")).unwrap(); - let token_data = - decode::(&token, Key::Hmac(b"secret"), &Validation::default()).unwrap(); - assert_eq!(my_claims, token_data.claims); - assert_eq!("kid", token_data.header.kid.unwrap()); -} - -#[test] -fn round_trip_claim() { - let my_claims = Claims { - sub: "b@b.com".to_string(), - company: "ACME".to_string(), - exp: Utc::now().timestamp() + 10000, - }; - let token = encode(&Header::default(), &my_claims, Key::Hmac(b"secret")).unwrap(); - let token_data = - decode::(&token, Key::Hmac(b"secret"), &Validation::default()).unwrap(); - assert_eq!(my_claims, token_data.claims); - assert!(token_data.header.kid.is_none()); -} - -#[test] -fn decode_token() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; - let claims = decode::(token, Key::Hmac(b"secret"), &Validation::default()); - println!("{:?}", claims); - claims.unwrap(); -} - -#[test] -#[should_panic(expected = "InvalidToken")] -fn decode_token_missing_parts() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; - let claims = decode::(token, Key::Hmac(b"secret"), &Validation::default()); - claims.unwrap(); -} - -#[test] -#[should_panic(expected = "InvalidSignature")] -fn decode_token_invalid_signature() { - let token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; - let claims = decode::(token, Key::Hmac(b"secret"), &Validation::default()); - claims.unwrap(); -} - -#[test] -#[should_panic(expected = "InvalidAlgorithm")] -fn decode_token_wrong_algorithm() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; - let claims = decode::(token, Key::Hmac(b"secret"), &Validation::new(Algorithm::RS512)); - claims.unwrap(); -} - -#[test] -fn decode_token_with_bytes_secret() { - let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks"; - let claims = decode::(token, Key::Hmac(b"\x01\x02\x03"), &Validation::default()); - assert!(claims.is_ok()); -} - -#[test] -fn decode_header_only() { - let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S"; - let header = decode_header(token).unwrap(); - assert_eq!(header.alg, Algorithm::HS256); - assert_eq!(header.typ, Some("JWT".to_string())); -} - -#[test] -fn dangerous_unsafe_decode_token() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; - let claims = dangerous_unsafe_decode::(token); - claims.unwrap(); -} - -#[test] -#[should_panic(expected = "InvalidToken")] -fn dangerous_unsafe_decode_token_missing_parts() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; - let claims = dangerous_unsafe_decode::(token); - claims.unwrap(); -} - -#[test] -fn dangerous_unsafe_decode_token_invalid_signature() { - let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.wrong"; - let claims = dangerous_unsafe_decode::(token); - claims.unwrap(); -} - -#[test] -fn dangerous_unsafe_decode_token_wrong_algorithm() { - let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.fLxey-hxAKX5rNHHIx1_Ch0KmrbiuoakDVbsJjLWrx8fbjKjrPuWMYEJzTU3SBnYgnZokC-wqSdqckXUOunC-g"; - let claims = dangerous_unsafe_decode::(token); - claims.unwrap(); -} - -// https://github.com/Keats/jsonwebtoken/issues/51 -#[test] -fn does_validation_in_right_order() { - let my_claims = Claims { - sub: "b@b.com".to_string(), - company: "ACME".to_string(), - exp: Utc::now().timestamp() + 10000, - }; - let token = encode(&Header::default(), &my_claims, Key::Hmac(b"secret")).unwrap(); - let v = Validation { - leeway: 5, - validate_exp: true, - iss: Some("iss no check".to_string()), - sub: Some("sub no check".to_string()), - ..Validation::default() - }; - let res = decode::(&token, Key::Hmac(b"secret"), &v); - assert!(res.is_err()); - println!("{:?}", res); - //assert!(res.is_ok()); -} - -#[test] -fn generate_algorithm_enum_from_str() { - assert!(Algorithm::from_str("HS256").is_ok()); - assert!(Algorithm::from_str("HS384").is_ok()); - assert!(Algorithm::from_str("HS512").is_ok()); - assert!(Algorithm::from_str("RS256").is_ok()); - assert!(Algorithm::from_str("RS384").is_ok()); - assert!(Algorithm::from_str("RS512").is_ok()); - assert!(Algorithm::from_str("PS256").is_ok()); - assert!(Algorithm::from_str("PS384").is_ok()); - assert!(Algorithm::from_str("PS512").is_ok()); - assert!(Algorithm::from_str("").is_err()); -} +mod ec; +mod rsa; diff --git a/tests/rsa.rs b/tests/rsa/mod.rs similarity index 94% rename from tests/rsa.rs rename to tests/rsa/mod.rs index 82e6cc4..d453cfe 100644 --- a/tests/rsa.rs +++ b/tests/rsa/mod.rs @@ -1,10 +1,6 @@ -extern crate jsonwebtoken; -#[macro_use] -extern crate serde_derive; -extern crate chrono; - use chrono::Utc; use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; +use serde_derive::{Deserialize, Serialize}; const RSA_ALGORITHMS: &[Algorithm] = &[ Algorithm::RS256, @@ -126,3 +122,10 @@ fn rsa_modulus_exponent() { .unwrap(); assert!(is_valid); } + +#[test] +#[should_panic(expected = "InvalidKeyFormat")] +fn fails_with_non_pkcs8_key_format() { + let privkey = include_bytes!("private_rsa_key.der"); + let _encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::ES256).unwrap(); +} diff --git a/tests/private_rsa_key.der b/tests/rsa/private_rsa_key.der similarity index 100% rename from tests/private_rsa_key.der rename to tests/rsa/private_rsa_key.der diff --git a/tests/private_rsa_key_pkcs1.pem b/tests/rsa/private_rsa_key_pkcs1.pem similarity index 100% rename from tests/private_rsa_key_pkcs1.pem rename to tests/rsa/private_rsa_key_pkcs1.pem diff --git a/tests/private_rsa_key_pkcs8.pem b/tests/rsa/private_rsa_key_pkcs8.pem similarity index 100% rename from tests/private_rsa_key_pkcs8.pem rename to tests/rsa/private_rsa_key_pkcs8.pem diff --git a/tests/public_rsa_key.der b/tests/rsa/public_rsa_key.der similarity index 100% rename from tests/public_rsa_key.der rename to tests/rsa/public_rsa_key.der diff --git a/tests/public_rsa_key_pkcs1.pem b/tests/rsa/public_rsa_key_pkcs1.pem similarity index 100% rename from tests/public_rsa_key_pkcs1.pem rename to tests/rsa/public_rsa_key_pkcs1.pem diff --git a/tests/public_rsa_key_pkcs8.pem b/tests/rsa/public_rsa_key_pkcs8.pem similarity index 100% rename from tests/public_rsa_key_pkcs8.pem rename to tests/rsa/public_rsa_key_pkcs8.pem From 417e00780d333af58f3963d57999e06bcfd1030a Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 3 Nov 2019 15:46:08 +0000 Subject: [PATCH 06/24] Use serde with derive feature --- Cargo.toml | 3 +-- examples/custom_chrono.rs | 22 +++++++--------------- examples/custom_header.rs | 8 +++----- examples/validation.rs | 9 +++------ src/algorithms.rs | 1 + src/header.rs | 1 + src/lib.rs | 9 --------- tests/ec/mod.rs | 2 +- tests/hmac.rs | 2 +- tests/rsa/mod.rs | 2 +- 10 files changed, 19 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b294bd8..75d2314 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,7 @@ edition = "2018" [dependencies] serde_json = "1.0" -serde_derive = "1.0" -serde = "1.0" +serde = {version = "1.0", features = ["derive"] } ring = { version = "0.16.5", features = ["std"] } base64 = "0.11" # For validation diff --git a/examples/custom_chrono.rs b/examples/custom_chrono.rs index 2d3bcca..fa6d868 100644 --- a/examples/custom_chrono.rs +++ b/examples/custom_chrono.rs @@ -1,11 +1,6 @@ -extern crate jsonwebtoken as jwt; -extern crate serde; -#[macro_use] -extern crate serde_derive; -extern crate chrono; - use chrono::prelude::*; -use jwt::{Header, Key, Validation}; +use jsonwebtoken::{Header, Key, Validation}; +use serde::{Deserialize, Serialize}; const SECRET: &str = "some-secret"; @@ -44,9 +39,6 @@ mod jwt_numeric_date { #[cfg(test)] mod tests { - use super::*; - use jwt::{Header, Validation}; - const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.RTgha0S53MjPC2pMA4e2oMzaBxSY3DMjiYR2qFfV55A"; use super::super::{Claims, SECRET}; @@ -59,13 +51,13 @@ mod jwt_numeric_date { let claims = Claims { sub: sub.clone(), iat, exp }; - let token = jwt::encode(&Header::default(), &claims, Key::Hmac(SECRET.as_ref())) + let token = encode(&Header::default(), &claims, Key::Hmac(SECRET.as_ref())) .expect("Failed to encode claims"); assert_eq!(&token, EXPECTED_TOKEN); let decoded = - jwt::decode::(&token, Key::Hmac(SECRET.as_ref()), &Validation::default()) + decode::(&token, Key::Hmac(SECRET.as_ref()), &Validation::default()) .expect("Failed to decode token"); assert_eq!(decoded.claims, claims); @@ -77,7 +69,7 @@ mod jwt_numeric_date { let overflow_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjkyMjMzNzIwMzY4NTQ3NzYwMDB9.G2PKreA27U8_xOwuIeCYXacFYeR46f9FyENIZfCrvEc"; let decode_result = - jwt::decode::(&overflow_token, SECRET.as_ref(), &Validation::default()); + decode::(&overflow_token, SECRET.as_ref(), &Validation::default()); assert!(decode_result.is_err()); } @@ -91,12 +83,12 @@ fn main() -> Result<(), Box> { let claims = Claims { sub: sub.clone(), iat, exp }; - let token = jwt::encode(&Header::default(), &claims, Key::Hmac(SECRET.as_ref()))?; + let token = jsonwebtoken::encode(&Header::default(), &claims, Key::Hmac(SECRET.as_ref()))?; println!("serialized token: {}", &token); let token_data = - jwt::decode::(&token, Key::Hmac(SECRET.as_ref()), &Validation::default())?; + jsonwebtoken::decode::(&token, Key::Hmac(SECRET.as_ref()), &Validation::default())?; println!("token data:\n{:#?}", &token_data); Ok(()) diff --git a/examples/custom_header.rs b/examples/custom_header.rs index d4c94da..d4e5c58 100644 --- a/examples/custom_header.rs +++ b/examples/custom_header.rs @@ -1,9 +1,7 @@ -extern crate jsonwebtoken as jwt; -#[macro_use] -extern crate serde_derive; +use serde::{Deserialize, Serialize}; -use jwt::errors::ErrorKind; -use jwt::{decode, encode, Algorithm, Header, Key, Validation}; +use jsonwebtoken::errors::ErrorKind; +use jsonwebtoken::{decode, encode, Algorithm, Header, Key, Validation}; #[derive(Debug, Serialize, Deserialize)] struct Claims { diff --git a/examples/validation.rs b/examples/validation.rs index 552eef0..bfc6bcc 100644 --- a/examples/validation.rs +++ b/examples/validation.rs @@ -1,9 +1,6 @@ -extern crate jsonwebtoken as jwt; -#[macro_use] -extern crate serde_derive; - -use jwt::errors::ErrorKind; -use jwt::{decode, encode, Header, Key, Validation}; +use jsonwebtoken::errors::ErrorKind; +use jsonwebtoken::{decode, encode, Header, Key, Validation}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] struct Claims { diff --git a/src/algorithms.rs b/src/algorithms.rs index 6b05356..09c8b17 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,4 +1,5 @@ use crate::errors::{new_error, Error, ErrorKind, Result}; +use serde::{Deserialize, Serialize}; use std::str::FromStr; /// The algorithms supported for signing/verifying diff --git a/src/header.rs b/src/header.rs index 2a17da9..66f38d8 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,4 +1,5 @@ use crate::algorithms::Algorithm; +use serde::{Deserialize, Serialize}; /// A basic JWT header, the alg defaults to HS256 and typ is automatically /// set to `JWT`. All the other fields are optional. diff --git a/src/lib.rs b/src/lib.rs index db07517..6f9b1fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,15 +3,6 @@ //! Documentation: [stable](https://docs.rs/jsonwebtoken/) #![deny(missing_docs)] -#[macro_use] -extern crate serde_derive; -extern crate base64; -extern crate chrono; -extern crate ring; -extern crate serde; -extern crate serde_json; -extern crate simple_asn1; - mod algorithms; mod crypto; /// All the errors diff --git a/tests/ec/mod.rs b/tests/ec/mod.rs index 8a15ecc..cc3321e 100644 --- a/tests/ec/mod.rs +++ b/tests/ec/mod.rs @@ -1,6 +1,6 @@ use chrono::Utc; use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Claims { diff --git a/tests/hmac.rs b/tests/hmac.rs index 3c3f284..89cfaf7 100644 --- a/tests/hmac.rs +++ b/tests/hmac.rs @@ -3,7 +3,7 @@ use jsonwebtoken::{ dangerous_unsafe_decode, decode, decode_header, encode, sign, verify, Algorithm, Header, Key, Validation, }; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Claims { diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index d453cfe..8fde515 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,6 +1,6 @@ use chrono::Utc; use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; const RSA_ALGORITHMS: &[Algorithm] = &[ Algorithm::RS256, From 73d96357c359633699f5edd2cfed3b726fb414b4 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 3 Nov 2019 16:13:22 +0000 Subject: [PATCH 07/24] Simplify header decoding --- src/errors.rs | 26 ++++++++++++++------------ src/header.rs | 14 ++++++++++++-- src/lib.rs | 12 ++++++------ src/serialization.rs | 16 ++++------------ 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 317a5cb..0c8b148 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -45,7 +45,7 @@ pub enum ErrorKind { /// When a key is provided with an invalid format InvalidKeyFormat, - // validation error + // Validation errors /// When a token’s `exp` claim indicates that it has expired ExpiredSignature, /// When a token’s `iss` claim does not match the expected issuer @@ -91,6 +91,7 @@ impl StdError for Error { ErrorKind::InvalidSubject => "invalid subject", ErrorKind::ImmatureSignature => "immature signature", ErrorKind::InvalidAlgorithm => "algorithms don't match", + ErrorKind::InvalidAlgorithmName => "not a known algorithm", ErrorKind::Base64(ref err) => err.description(), ErrorKind::Json(ref err) => err.description(), ErrorKind::Utf8(ref err) => err.description(), @@ -111,6 +112,7 @@ impl StdError for Error { ErrorKind::InvalidSubject => None, ErrorKind::ImmatureSignature => None, ErrorKind::InvalidAlgorithm => None, + ErrorKind::InvalidAlgorithmName => None, ErrorKind::Base64(ref err) => Some(err), ErrorKind::Json(ref err) => Some(err), ErrorKind::Utf8(ref err) => Some(err), @@ -123,17 +125,17 @@ impl StdError for Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self.0 { - ErrorKind::InvalidToken => write!(f, "invalid token"), - ErrorKind::InvalidSignature => write!(f, "invalid signature"), - ErrorKind::InvalidEcdsaKey => write!(f, "invalid ECDSA key"), - ErrorKind::InvalidRsaKey => write!(f, "invalid RSA key"), - ErrorKind::ExpiredSignature => write!(f, "expired signature"), - ErrorKind::InvalidIssuer => write!(f, "invalid issuer"), - ErrorKind::InvalidAudience => write!(f, "invalid audience"), - ErrorKind::InvalidSubject => write!(f, "invalid subject"), - ErrorKind::ImmatureSignature => write!(f, "immature signature"), - ErrorKind::InvalidAlgorithm => write!(f, "algorithms don't match"), - ErrorKind::Base64(ref err) => write!(f, "base64 error: {}", err), + ErrorKind::InvalidToken + | ErrorKind::InvalidSignature + | ErrorKind::InvalidEcdsaKey + | ErrorKind::InvalidRsaKey + | ErrorKind::ExpiredSignature + | ErrorKind::InvalidIssuer + | ErrorKind::InvalidAudience + | ErrorKind::InvalidSubject + | ErrorKind::ImmatureSignature + | ErrorKind::InvalidAlgorithm + | ErrorKind::InvalidAlgorithmName => write!(f, "{}", self.description()), ErrorKind::Json(ref err) => write!(f, "JSON error: {}", err), ErrorKind::Utf8(ref err) => write!(f, "UTF-8 error: {}", err), ErrorKind::Crypto(ref err) => write!(f, "Crypto error: {}", err), diff --git a/src/header.rs b/src/header.rs index 66f38d8..73a2a1d 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,6 +1,8 @@ -use crate::algorithms::Algorithm; use serde::{Deserialize, Serialize}; +use crate::algorithms::Algorithm; +use crate::errors::Result; + /// A basic JWT header, the alg defaults to HS256 and typ is automatically /// set to `JWT`. All the other fields are optional. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -43,7 +45,7 @@ pub struct Header { impl Header { /// Returns a JWT header with the algorithm given - pub fn new(algorithm: Algorithm) -> Header { + pub fn new(algorithm: Algorithm) -> Self { Header { typ: Some("JWT".to_string()), alg: algorithm, @@ -54,6 +56,14 @@ impl Header { x5t: None, } } + + /// Converts an encoded part into the Header struct if possible + pub(crate) fn from_encoded(encoded_part: &str) -> Result { + let decoded = base64::decode_config(encoded_part, base64::URL_SAFE_NO_PAD)?; + let s = String::from_utf8(decoded)?; + + Ok(serde_json::from_str(&s)?) + } } impl Default for Header { diff --git a/src/lib.rs b/src/lib.rs index 6f9b1fa..0dc6cbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ use serde::de::DeserializeOwned; use serde::ser::Serialize; use crate::errors::{new_error, ErrorKind, Result}; -use crate::serialization::{from_jwt_part, from_jwt_part_claims, to_jwt_part}; +use crate::serialization::{encode_part, from_jwt_part_claims}; use crate::validation::validate; /// Encode the header and claims given and sign the payload using the algorithm from the header and the key @@ -51,8 +51,8 @@ use crate::validation::validate; /// let token = encode(&Header::default(), &my_claims, Key::Hmac("secret".as_ref())).unwrap(); /// ``` pub fn encode(header: &Header, claims: &T, key: Key) -> Result { - let encoded_header = to_jwt_part(&header)?; - let encoded_claims = to_jwt_part(&claims)?; + let encoded_header = encode_part(&header)?; + let encoded_claims = encode_part(&claims)?; let signing_input = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); let signature = sign(&*signing_input, key, header.alg)?; @@ -97,7 +97,7 @@ pub fn decode( ) -> 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)?; + let header = Header::from_encoded(header)?; if !verify(signature, signing_input, key, header.alg)? { return Err(new_error(ErrorKind::InvalidSignature)); @@ -135,7 +135,7 @@ pub fn decode( pub fn dangerous_unsafe_decode(token: &str) -> Result> { let (_, signing_input) = expect_two!(token.rsplitn(2, '.')); let (claims, header) = expect_two!(signing_input.rsplitn(2, '.')); - let header: Header = from_jwt_part(header)?; + let header = Header::from_encoded(header)?; let (decoded_claims, _): (T, _) = from_jwt_part_claims(claims)?; @@ -156,7 +156,7 @@ pub fn dangerous_unsafe_decode(token: &str) -> Result Result
{ let (_, signing_input) = expect_two!(token.rsplitn(2, '.')); let (_, header) = expect_two!(signing_input.rsplitn(2, '.')); - from_jwt_part(header) + Header::from_encoded(header) } /// Decode a PEM string to obtain its key diff --git a/src/serialization.rs b/src/serialization.rs index 2a47688..d1aafeb 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -16,18 +16,10 @@ pub struct TokenData { pub claims: T, } -/// Serializes to JSON and encodes to base64 -pub fn to_jwt_part(input: &T) -> Result { - let encoded = to_string(input)?; - Ok(base64::encode_config(encoded.as_bytes(), base64::URL_SAFE_NO_PAD)) -} - -/// Decodes from base64 and deserializes from JSON to a struct -pub fn from_jwt_part, T: DeserializeOwned>(encoded: B) -> Result { - let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?; - let s = String::from_utf8(decoded)?; - - Ok(from_str(&s)?) +/// Serializes a struct to JSON and encodes it in base64 +pub fn encode_part(input: &T) -> Result { + let json = to_string(input)?; + Ok(base64::encode_config(json.as_bytes(), base64::URL_SAFE_NO_PAD)) } /// Decodes from base64 and deserializes from JSON to a struct AND a hashmap From 53188e1f403cd46a84ccf7251372c4f14c07f31f Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 6 Nov 2019 07:58:49 -0600 Subject: [PATCH 08/24] Add functions to create pems and ders of the public keys (#108) --- src/lib.rs | 31 ++++++++++++++ src/pem_encoder.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++ tests/ec/mod.rs | 30 +++++++++++++- tests/rsa/mod.rs | 47 ++++++++++++++++++++- 4 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 src/pem_encoder.rs diff --git a/src/lib.rs b/src/lib.rs index 0dc6cbf..6785890 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub mod errors; mod header; mod keys; mod pem_decoder; +mod pem_encoder; mod serialization; mod validation; @@ -185,3 +186,33 @@ pub fn decode_header(token: &str) -> Result
{ pub fn decode_pem(content: &str) -> Result { PemEncodedKey::read(content) } + +/// TODO +pub fn encode_rsa_public_pkcs1_pem(modulus: &[u8], exponent: &[u8]) -> Result { + pem_encoder::encode_rsa_public_pkcs1_pem(modulus, exponent) +} + +/// TODO +pub fn encode_rsa_public_pkcs1_der(modulus: &[u8], exponent: &[u8]) -> Result> { + pem_encoder::encode_rsa_public_pkcs1_der(modulus, exponent) +} + +/// TODO +pub fn encode_rsa_public_pkcs8_pem(modulus: &[u8], exponent: &[u8]) -> Result { + pem_encoder::encode_rsa_public_pkcs8_pem(modulus, exponent) +} + +/// TODO +pub fn encode_rsa_public_pkcs8_der(modulus: &[u8], exponent: &[u8]) -> Result> { + pem_encoder::encode_rsa_public_pkcs8_der(modulus, exponent) +} + +/// TODO +pub fn encode_ec_public_pem(x_coordinate: &[u8]) -> Result { + pem_encoder::encode_ec_public_pem(x_coordinate) +} + +/// TODO +pub fn encode_ec_public_der(x_coordinate: &[u8]) -> Result> { + pem_encoder::encode_ec_public_der(x_coordinate) +} \ No newline at end of file diff --git a/src/pem_encoder.rs b/src/pem_encoder.rs new file mode 100644 index 0000000..0149259 --- /dev/null +++ b/src/pem_encoder.rs @@ -0,0 +1,100 @@ +use crate::errors::{ErrorKind, Result}; +use simple_asn1::{ASN1Block, BigUint, BigInt, OID}; +use pem::{Pem}; + +extern crate base64; +extern crate pem; +extern crate simple_asn1; + +pub fn encode_rsa_public_pkcs1_pem(modulus: &[u8], exponent: &[u8]) -> Result { + Ok(pem::encode(&Pem { + contents: encode_rsa_public_pkcs1_der(modulus, exponent)?, + tag: "RSA PUBLIC KEY".to_string(), + })) +} + +pub fn encode_rsa_public_pkcs1_der(modulus: &[u8], exponent: &[u8]) -> Result> { + match simple_asn1::to_der(&encode_rsa_public_pksc1_asn1(modulus, exponent)) { + Ok(bytes) => Ok(bytes), + Err(_) => return Err(ErrorKind::InvalidRsaKey)?, + } +} + +pub fn encode_rsa_public_pkcs8_pem(modulus: &[u8], exponent: &[u8]) -> Result { + Ok(pem::encode(&Pem { + contents: encode_rsa_public_pkcs8_der(modulus, exponent)?, + tag: "PUBLIC KEY".to_string(), + })) +} + +pub fn encode_rsa_public_pkcs8_der(modulus: &[u8], exponent: &[u8]) -> Result> { + match simple_asn1::to_der(&encode_rsa_public_pksc8_asn1(modulus, exponent)?) { + Ok(bytes) => Ok(bytes), + Err(_) => return Err(ErrorKind::InvalidRsaKey)?, + } +} + +pub fn encode_ec_public_pem(x: &[u8]) -> Result { + Ok(pem::encode(&Pem { + contents: encode_ec_public_der(x)?, + tag: "PUBLIC KEY".to_string(), + })) +} + +pub fn encode_ec_public_der(x: &[u8]) -> Result> { + match simple_asn1::to_der(&encode_ec_public_asn1(x)) { + Ok(bytes) => Ok(bytes), + Err(_) => return Err(ErrorKind::InvalidEcdsaKey)?, + } +} + +fn encode_rsa_public_pksc8_asn1(modulus: &[u8], exponent: &[u8]) -> Result { + let pksc1 = match simple_asn1::to_der(&encode_rsa_public_pksc1_asn1(modulus, exponent)) { + Ok(bytes) => bytes, + Err(_) => return Err(ErrorKind::InvalidRsaKey)?, + }; + Ok(ASN1Block::Sequence( + 0, + vec![ + ASN1Block::Sequence( + 0, + vec![ + // rsaEncryption (PKCS #1) + ASN1Block::ObjectIdentifier(0, simple_asn1::oid!(1,2,840,113549,1,1,1)), + ASN1Block::Null(0) + ] + ), + // the second parameter takes the count of bits + ASN1Block::BitString(0, pksc1.len() * 8, pksc1) + ], + )) +} + +fn encode_rsa_public_pksc1_asn1(modulus: &[u8], exponent: &[u8]) -> ASN1Block { + ASN1Block::Sequence( + 0, + vec![ + ASN1Block::Integer(0, BigInt::from_signed_bytes_be(modulus)), + ASN1Block::Integer(0, BigInt::from_signed_bytes_be(exponent)), + ], + ) +} + +fn encode_ec_public_asn1(x: &[u8]) -> ASN1Block { + ASN1Block::Sequence( + 0, + vec![ + ASN1Block::Sequence( + 0, + vec![ + // ecPublicKey (ANSI X9.62 public key type) + ASN1Block::ObjectIdentifier(0, simple_asn1::oid!(1, 2, 840, 10045, 2, 1)), + // prime256v1 (ANSI X9.62 named elliptic curve) + ASN1Block::ObjectIdentifier(0, simple_asn1::oid!(1, 2, 840, 10045, 3, 1, 7)), + ], + ), + // the second parameter takes the count of bits + ASN1Block::BitString(0, x.len() * 8, x.to_vec()), + ], + ) +} diff --git a/tests/ec/mod.rs b/tests/ec/mod.rs index cc3321e..d4fb16d 100644 --- a/tests/ec/mod.rs +++ b/tests/ec/mod.rs @@ -1,6 +1,7 @@ use chrono::Utc; -use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; +use jsonwebtoken::{decode, decode_pem, encode_ec_public_pem, encode_ec_public_der, encode, sign, verify, Algorithm, Header, Key, Validation}; use serde::{Deserialize, Serialize}; +use ring::{signature, signature::KeyPair}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Claims { @@ -45,3 +46,30 @@ fn round_trip_claim() { assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); } + +#[test] +fn public_key_encoding() { + let privkey_pem = decode_pem(include_str!("private_ecdsa_key.pem")).unwrap(); + let privkey = privkey_pem.as_key().unwrap(); + let alg = &signature::ECDSA_P256_SHA256_FIXED_SIGNING; + let ring_key = signature::EcdsaKeyPair::from_pkcs8(alg, match privkey { + Key::Pkcs8(bytes) => bytes, + _ => panic!("Unexpected") + }).unwrap(); + + let public_key_pem = encode_ec_public_pem(ring_key.public_key().as_ref()).unwrap(); + assert_eq!(include_str!("public_ecdsa_key.pem").trim(), public_key_pem.replace('\r', "").trim()); + + let public_key_der = encode_ec_public_der(ring_key.public_key().as_ref()).unwrap(); + // The stored ".pk8" key is just the x coordinate of the EC key + // It's not truly a pkcs8 formatted DER + // To get around that, a prepended binary specifies the EC key, EC name, + // and X coordinate length. The length is unlikely to change.. in the + // event that it does, look at the pem file (convert base64 to hex) and find + // where 0x03, 0x42 don't match up. 0x42 is the length. + let mut stored_pk8_der = vec![0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, + 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, + 0x07, 0x03, 0x42, 0x00]; + stored_pk8_der.extend(include_bytes!("public_ecdsa_key.pk8").to_vec()); + assert_eq!(stored_pk8_der, public_key_der); +} \ No newline at end of file diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 8fde515..c1c3962 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,6 +1,7 @@ use chrono::Utc; -use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; +use jsonwebtoken::{decode, decode_pem, encode_rsa_public_pkcs1_pem, encode_rsa_public_pkcs1_der, encode_rsa_public_pkcs8_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; use serde::{Deserialize, Serialize}; +use ring::{signature, signature::KeyPair}; const RSA_ALGORITHMS: &[Algorithm] = &[ Algorithm::RS256, @@ -129,3 +130,47 @@ fn fails_with_non_pkcs8_key_format() { let privkey = include_bytes!("private_rsa_key.der"); let _encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::ES256).unwrap(); } + +#[test] +fn public_key_encoding_pkcs1() { + let privkey_pem = decode_pem(include_str!("private_rsa_key_pkcs8.pem")).unwrap(); + let privkey = privkey_pem.as_key().unwrap(); + let ring_key = signature::RsaKeyPair::from_der(match privkey { + Key::Der(bytes) => bytes, + _ => panic!("Unexpected") + }).unwrap(); + let mut modulus = vec!(0); + modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); + let exponent = ring_key.public_key().exponent(); + + let public_key_pkcs1_pem = encode_rsa_public_pkcs1_pem( + modulus.as_ref(), + exponent.big_endian_without_leading_zero() + ).unwrap(); + assert_eq!(include_str!("public_rsa_key_pkcs1.pem").trim(), public_key_pkcs1_pem.replace('\r', "").trim()); + + let public_key_pkcs1_der = encode_rsa_public_pkcs1_der( + modulus.as_ref(), + exponent.big_endian_without_leading_zero() + ).unwrap(); + assert_eq!(include_bytes!("public_rsa_key.der").to_vec(), public_key_pkcs1_der); +} + +#[test] +fn public_key_encoding_pkcs8() { + let privkey_pem = decode_pem(include_str!("private_rsa_key_pkcs8.pem")).unwrap(); + let privkey = privkey_pem.as_key().unwrap(); + let ring_key = signature::RsaKeyPair::from_der(match privkey { + Key::Der(bytes) => bytes, + _ => panic!("Unexpected") + }).unwrap(); + let mut modulus = vec!(0); + modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); + let exponent = ring_key.public_key().exponent(); + + let public_key_pkcs8 = encode_rsa_public_pkcs8_pem( + modulus.as_ref(), + exponent.big_endian_without_leading_zero() + ).unwrap(); + assert_eq!(include_str!("public_rsa_key_pkcs8.pem").trim(), public_key_pkcs8.replace('\r', "").trim()); +} \ No newline at end of file From 382e4478cf4694f073c29e9ce570a6932887c9dc Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Wed, 6 Nov 2019 18:30:59 +0000 Subject: [PATCH 09/24] Move pem encoding tests --- src/lib.rs | 2 +- src/pem_encoder.rs | 120 +++++++++++++++++++++++++++++++++++++++++---- tests/ec/mod.rs | 30 +----------- tests/rsa/mod.rs | 47 +----------------- 4 files changed, 113 insertions(+), 86 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6785890..5a81b2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,4 +215,4 @@ pub fn encode_ec_public_pem(x_coordinate: &[u8]) -> Result { /// TODO pub fn encode_ec_public_der(x_coordinate: &[u8]) -> Result> { pem_encoder::encode_ec_public_der(x_coordinate) -} \ No newline at end of file +} diff --git a/src/pem_encoder.rs b/src/pem_encoder.rs index 0149259..101cef7 100644 --- a/src/pem_encoder.rs +++ b/src/pem_encoder.rs @@ -1,6 +1,6 @@ use crate::errors::{ErrorKind, Result}; -use simple_asn1::{ASN1Block, BigUint, BigInt, OID}; -use pem::{Pem}; +use pem::Pem; +use simple_asn1::{ASN1Block, BigInt, BigUint, OID}; extern crate base64; extern crate pem; @@ -35,10 +35,7 @@ pub fn encode_rsa_public_pkcs8_der(modulus: &[u8], exponent: &[u8]) -> Result Result { - Ok(pem::encode(&Pem { - contents: encode_ec_public_der(x)?, - tag: "PUBLIC KEY".to_string(), - })) + Ok(pem::encode(&Pem { contents: encode_ec_public_der(x)?, tag: "PUBLIC KEY".to_string() })) } pub fn encode_ec_public_der(x: &[u8]) -> Result> { @@ -60,12 +57,12 @@ fn encode_rsa_public_pksc8_asn1(modulus: &[u8], exponent: &[u8]) -> Result ASN1Block { ], ) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::decode_pem; + use crate::keys::Key; + use ring::{signature, signature::KeyPair}; + + #[test] + fn public_key_encoding_pkcs1() { + let privkey_pem = + decode_pem(include_str!("../tests/rsa/private_rsa_key_pkcs8.pem")).unwrap(); + let privkey = privkey_pem.as_key().unwrap(); + let ring_key = signature::RsaKeyPair::from_der(match privkey { + Key::Der(bytes) => bytes, + _ => panic!("Unexpected"), + }) + .unwrap(); + let mut modulus = vec![0]; + modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); + let exponent = ring_key.public_key().exponent(); + + let public_key_pkcs1_pem = encode_rsa_public_pkcs1_pem( + modulus.as_ref(), + exponent.big_endian_without_leading_zero(), + ) + .unwrap(); + assert_eq!( + include_str!("../tests/rsa/public_rsa_key_pkcs1.pem").trim(), + public_key_pkcs1_pem.replace('\r', "").trim() + ); + + let public_key_pkcs1_der = encode_rsa_public_pkcs1_der( + modulus.as_ref(), + exponent.big_endian_without_leading_zero(), + ) + .unwrap(); + assert_eq!( + include_bytes!("../tests/rsa/public_rsa_key.der").to_vec(), + public_key_pkcs1_der + ); + } + + #[test] + fn public_key_encoding_pkcs8() { + let privkey_pem = + decode_pem(include_str!("../tests/rsa/private_rsa_key_pkcs8.pem")).unwrap(); + let privkey = privkey_pem.as_key().unwrap(); + let ring_key = signature::RsaKeyPair::from_der(match privkey { + Key::Der(bytes) => bytes, + _ => panic!("Unexpected"), + }) + .unwrap(); + let mut modulus = vec![0]; + modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); + let exponent = ring_key.public_key().exponent(); + + let public_key_pkcs8 = encode_rsa_public_pkcs8_pem( + modulus.as_ref(), + exponent.big_endian_without_leading_zero(), + ) + .unwrap(); + assert_eq!( + include_str!("../tests/rsa/public_rsa_key_pkcs8.pem").trim(), + public_key_pkcs8.replace('\r', "").trim() + ); + } + + #[test] + fn public_key_encoding() { + let privkey_pem = decode_pem(include_str!("../tests/ec/private_ecdsa_key.pem")).unwrap(); + let privkey = privkey_pem.as_key().unwrap(); + let alg = &signature::ECDSA_P256_SHA256_FIXED_SIGNING; + let ring_key = signature::EcdsaKeyPair::from_pkcs8( + alg, + match privkey { + Key::Pkcs8(bytes) => bytes, + _ => panic!("Unexpected"), + }, + ) + .unwrap(); + + let public_key_pem = encode_ec_public_pem(ring_key.public_key().as_ref()).unwrap(); + assert_eq!( + include_str!("../tests/ec/public_ecdsa_key.pem").trim(), + public_key_pem.replace('\r', "").trim() + ); + + let public_key_der = encode_ec_public_der(ring_key.public_key().as_ref()).unwrap(); + // The stored ".pk8" key is just the x coordinate of the EC key + // It's not truly a pkcs8 formatted DER + // To get around that, a prepended binary specifies the EC key, EC name, + // and X coordinate length. The length is unlikely to change.. in the + // event that it does, look at the pem file (convert base64 to hex) and find + // where 0x03, 0x42 don't match up. 0x42 is the length. + let mut stored_pk8_der = vec![ + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, + 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, + ]; + stored_pk8_der.extend(include_bytes!("../tests/ec/public_ecdsa_key.pk8").to_vec()); + assert_eq!(stored_pk8_der, public_key_der); + } +} diff --git a/tests/ec/mod.rs b/tests/ec/mod.rs index d4fb16d..cc3321e 100644 --- a/tests/ec/mod.rs +++ b/tests/ec/mod.rs @@ -1,7 +1,6 @@ use chrono::Utc; -use jsonwebtoken::{decode, decode_pem, encode_ec_public_pem, encode_ec_public_der, encode, sign, verify, Algorithm, Header, Key, Validation}; +use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; use serde::{Deserialize, Serialize}; -use ring::{signature, signature::KeyPair}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Claims { @@ -46,30 +45,3 @@ fn round_trip_claim() { assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); } - -#[test] -fn public_key_encoding() { - let privkey_pem = decode_pem(include_str!("private_ecdsa_key.pem")).unwrap(); - let privkey = privkey_pem.as_key().unwrap(); - let alg = &signature::ECDSA_P256_SHA256_FIXED_SIGNING; - let ring_key = signature::EcdsaKeyPair::from_pkcs8(alg, match privkey { - Key::Pkcs8(bytes) => bytes, - _ => panic!("Unexpected") - }).unwrap(); - - let public_key_pem = encode_ec_public_pem(ring_key.public_key().as_ref()).unwrap(); - assert_eq!(include_str!("public_ecdsa_key.pem").trim(), public_key_pem.replace('\r', "").trim()); - - let public_key_der = encode_ec_public_der(ring_key.public_key().as_ref()).unwrap(); - // The stored ".pk8" key is just the x coordinate of the EC key - // It's not truly a pkcs8 formatted DER - // To get around that, a prepended binary specifies the EC key, EC name, - // and X coordinate length. The length is unlikely to change.. in the - // event that it does, look at the pem file (convert base64 to hex) and find - // where 0x03, 0x42 don't match up. 0x42 is the length. - let mut stored_pk8_der = vec![0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, - 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, - 0x07, 0x03, 0x42, 0x00]; - stored_pk8_der.extend(include_bytes!("public_ecdsa_key.pk8").to_vec()); - assert_eq!(stored_pk8_der, public_key_der); -} \ No newline at end of file diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index c1c3962..8fde515 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,7 +1,6 @@ use chrono::Utc; -use jsonwebtoken::{decode, decode_pem, encode_rsa_public_pkcs1_pem, encode_rsa_public_pkcs1_der, encode_rsa_public_pkcs8_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; +use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; use serde::{Deserialize, Serialize}; -use ring::{signature, signature::KeyPair}; const RSA_ALGORITHMS: &[Algorithm] = &[ Algorithm::RS256, @@ -130,47 +129,3 @@ fn fails_with_non_pkcs8_key_format() { let privkey = include_bytes!("private_rsa_key.der"); let _encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::ES256).unwrap(); } - -#[test] -fn public_key_encoding_pkcs1() { - let privkey_pem = decode_pem(include_str!("private_rsa_key_pkcs8.pem")).unwrap(); - let privkey = privkey_pem.as_key().unwrap(); - let ring_key = signature::RsaKeyPair::from_der(match privkey { - Key::Der(bytes) => bytes, - _ => panic!("Unexpected") - }).unwrap(); - let mut modulus = vec!(0); - modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); - let exponent = ring_key.public_key().exponent(); - - let public_key_pkcs1_pem = encode_rsa_public_pkcs1_pem( - modulus.as_ref(), - exponent.big_endian_without_leading_zero() - ).unwrap(); - assert_eq!(include_str!("public_rsa_key_pkcs1.pem").trim(), public_key_pkcs1_pem.replace('\r', "").trim()); - - let public_key_pkcs1_der = encode_rsa_public_pkcs1_der( - modulus.as_ref(), - exponent.big_endian_without_leading_zero() - ).unwrap(); - assert_eq!(include_bytes!("public_rsa_key.der").to_vec(), public_key_pkcs1_der); -} - -#[test] -fn public_key_encoding_pkcs8() { - let privkey_pem = decode_pem(include_str!("private_rsa_key_pkcs8.pem")).unwrap(); - let privkey = privkey_pem.as_key().unwrap(); - let ring_key = signature::RsaKeyPair::from_der(match privkey { - Key::Der(bytes) => bytes, - _ => panic!("Unexpected") - }).unwrap(); - let mut modulus = vec!(0); - modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); - let exponent = ring_key.public_key().exponent(); - - let public_key_pkcs8 = encode_rsa_public_pkcs8_pem( - modulus.as_ref(), - exponent.big_endian_without_leading_zero() - ).unwrap(); - assert_eq!(include_str!("public_rsa_key_pkcs8.pem").trim(), public_key_pkcs8.replace('\r', "").trim()); -} \ No newline at end of file From a6ea8c2c1a4bb6ad4778b6320f6c18a1ab4545f4 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Wed, 6 Nov 2019 18:41:51 +0000 Subject: [PATCH 10/24] clippy + fmt --- src/crypto.rs | 8 ++++---- src/pem_decoder.rs | 19 ++++++++++--------- src/pem_encoder.rs | 10 +++++----- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/crypto.rs b/src/crypto.rs index 3556fb8..804f082 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -10,7 +10,7 @@ use crate::keys::Key; fn sign_hmac(alg: hmac::Algorithm, key: Key, signing_input: &str) -> Result { let signing_key = match key { Key::Hmac(bytes) => hmac::Key::new(alg, bytes), - _ => return Err(ErrorKind::InvalidKeyFormat)?, + _ => return Err(ErrorKind::InvalidKeyFormat.into()), }; let digest = hmac::sign(&signing_key, signing_input.as_bytes()); @@ -49,7 +49,7 @@ fn sign_rsa( signature::RsaKeyPair::from_pkcs8(bytes).map_err(|_| ErrorKind::InvalidRsaKey)? } _ => { - return Err(ErrorKind::InvalidKeyFormat)?; + return Err(ErrorKind::InvalidKeyFormat.into()); } }; @@ -112,7 +112,7 @@ fn verify_ring_es( let bytes = match key { Key::Pkcs8(bytes) => bytes, _ => { - return Err(ErrorKind::InvalidKeyFormat)?; + return Err(ErrorKind::InvalidKeyFormat.into()); } }; verify_ring(alg, signature, signing_input, bytes) @@ -135,7 +135,7 @@ fn verify_ring_rsa( Ok(res.is_ok()) } - _ => Err(ErrorKind::InvalidKeyFormat)?, + _ => Err(ErrorKind::InvalidKeyFormat.into()), } } diff --git a/src/pem_decoder.rs b/src/pem_decoder.rs index 0f08440..c42b042 100644 --- a/src/pem_decoder.rs +++ b/src/pem_decoder.rs @@ -57,7 +57,7 @@ impl PemEncodedKey { let pem_contents = content.contents; let asn1_content = match simple_asn1::from_der(pem_contents.as_slice()) { Ok(asn1) => asn1, - Err(_) => return Err(ErrorKind::InvalidKeyFormat)?, + Err(_) => return Err(ErrorKind::InvalidKeyFormat.into()), }; match content.tag.as_ref() { @@ -106,14 +106,14 @@ impl PemEncodedKey { standard: Standard::Pkcs8, }) } - None => return Err(ErrorKind::InvalidKeyFormat)?, + None => Err(ErrorKind::InvalidKeyFormat.into()), }, // Unknown/unsupported type - _ => return Err(ErrorKind::InvalidKeyFormat)?, + _ => Err(ErrorKind::InvalidKeyFormat.into()), } } - Err(_) => return Err(ErrorKind::InvalidKeyFormat)?, + Err(_) => Err(ErrorKind::InvalidKeyFormat.into()), } } @@ -139,7 +139,7 @@ impl PemEncodedKey { // And the DER contents of an RSA key // Though PKCS#11 keys shouldn't have anything else. // It will get confusing with certificates. -fn extract_first_bitstring(asn1: &Vec) -> Result<&[u8]> { +fn extract_first_bitstring(asn1: &[simple_asn1::ASN1Block]) -> Result<&[u8]> { for asn1_entry in asn1.iter() { match asn1_entry { simple_asn1::ASN1Block::Sequence(_, entries) => { @@ -156,15 +156,16 @@ fn extract_first_bitstring(asn1: &Vec) -> Result<&[u8]> _ => (), } } - Err(ErrorKind::InvalidEcdsaKey)? + + Err(ErrorKind::InvalidEcdsaKey.into()) } /// Find whether this is EC or RSA -fn classify_pem(asn1: &Vec) -> Option { +fn classify_pem(asn1: &[simple_asn1::ASN1Block]) -> Option { // These should be constant but the macro requires // #![feature(const_vec_new)] - let ec_public_key_oid = simple_asn1::oid!(1, 2, 840, 10045, 2, 1); - let rsa_public_key_oid = simple_asn1::oid!(1, 2, 840, 113549, 1, 1, 1); + let ec_public_key_oid = simple_asn1::oid!(1, 2, 840, 10_045, 2, 1); + let rsa_public_key_oid = simple_asn1::oid!(1, 2, 840, 113_549, 1, 1, 1); for asn1_entry in asn1.iter() { match asn1_entry { diff --git a/src/pem_encoder.rs b/src/pem_encoder.rs index 101cef7..d719fc1 100644 --- a/src/pem_encoder.rs +++ b/src/pem_encoder.rs @@ -16,7 +16,7 @@ pub fn encode_rsa_public_pkcs1_pem(modulus: &[u8], exponent: &[u8]) -> Result Result> { match simple_asn1::to_der(&encode_rsa_public_pksc1_asn1(modulus, exponent)) { Ok(bytes) => Ok(bytes), - Err(_) => return Err(ErrorKind::InvalidRsaKey)?, + Err(_) => Err(ErrorKind::InvalidRsaKey.into()), } } @@ -30,7 +30,7 @@ pub fn encode_rsa_public_pkcs8_pem(modulus: &[u8], exponent: &[u8]) -> Result Result> { match simple_asn1::to_der(&encode_rsa_public_pksc8_asn1(modulus, exponent)?) { Ok(bytes) => Ok(bytes), - Err(_) => return Err(ErrorKind::InvalidRsaKey)?, + Err(_) => Err(ErrorKind::InvalidRsaKey.into()), } } @@ -41,14 +41,14 @@ pub fn encode_ec_public_pem(x: &[u8]) -> Result { pub fn encode_ec_public_der(x: &[u8]) -> Result> { match simple_asn1::to_der(&encode_ec_public_asn1(x)) { Ok(bytes) => Ok(bytes), - Err(_) => return Err(ErrorKind::InvalidEcdsaKey)?, + Err(_) => Err(ErrorKind::InvalidEcdsaKey.into()), } } fn encode_rsa_public_pksc8_asn1(modulus: &[u8], exponent: &[u8]) -> Result { let pksc1 = match simple_asn1::to_der(&encode_rsa_public_pksc1_asn1(modulus, exponent)) { Ok(bytes) => bytes, - Err(_) => return Err(ErrorKind::InvalidRsaKey)?, + Err(_) => return Err(ErrorKind::InvalidRsaKey.into()), }; Ok(ASN1Block::Sequence( 0, @@ -57,7 +57,7 @@ fn encode_rsa_public_pksc8_asn1(modulus: &[u8], exponent: &[u8]) -> Result Date: Wed, 6 Nov 2019 22:32:13 +0000 Subject: [PATCH 11/24] Remove Key approach in favour of &[u8] with pem --- examples/custom_chrono.rs | 13 ++- examples/custom_header.rs | 19 ++-- examples/validation.rs | 6 +- src/algorithms.rs | 4 +- src/crypto.rs | 71 ++++---------- src/errors.rs | 10 +- src/keys.rs | 16 ---- src/lib.rs | 36 +------- src/pem_decoder.rs | 63 ++++++++----- src/pem_encoder.rs | 190 +++++++++++++++++++------------------- src/serialization.rs | 3 +- tests/ec/mod.rs | 35 ++++--- tests/hmac.rs | 26 +++--- tests/rsa/mod.rs | 121 ++++++++++-------------- 14 files changed, 262 insertions(+), 351 deletions(-) delete mode 100644 src/keys.rs diff --git a/examples/custom_chrono.rs b/examples/custom_chrono.rs index fa6d868..e0b1185 100644 --- a/examples/custom_chrono.rs +++ b/examples/custom_chrono.rs @@ -1,5 +1,5 @@ use chrono::prelude::*; -use jsonwebtoken::{Header, Key, Validation}; +use jsonwebtoken::{Header, Validation}; use serde::{Deserialize, Serialize}; const SECRET: &str = "some-secret"; @@ -51,14 +51,13 @@ mod jwt_numeric_date { let claims = Claims { sub: sub.clone(), iat, exp }; - let token = encode(&Header::default(), &claims, Key::Hmac(SECRET.as_ref())) + let token = encode(&Header::default(), &claims, SECRET.as_ref()) .expect("Failed to encode claims"); assert_eq!(&token, EXPECTED_TOKEN); - let decoded = - decode::(&token, Key::Hmac(SECRET.as_ref()), &Validation::default()) - .expect("Failed to decode token"); + let decoded = decode::(&token, SECRET.as_ref(), &Validation::default()) + .expect("Failed to decode token"); assert_eq!(decoded.claims, claims); } @@ -83,12 +82,12 @@ fn main() -> Result<(), Box> { let claims = Claims { sub: sub.clone(), iat, exp }; - let token = jsonwebtoken::encode(&Header::default(), &claims, Key::Hmac(SECRET.as_ref()))?; + let token = jsonwebtoken::encode(&Header::default(), &claims, SECRET.as_ref())?; println!("serialized token: {}", &token); let token_data = - jsonwebtoken::decode::(&token, Key::Hmac(SECRET.as_ref()), &Validation::default())?; + jsonwebtoken::decode::(&token, SECRET.as_ref(), &Validation::default())?; println!("token data:\n{:#?}", &token_data); Ok(()) diff --git a/examples/custom_header.rs b/examples/custom_header.rs index d4e5c58..f298c8d 100644 --- a/examples/custom_header.rs +++ b/examples/custom_header.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use jsonwebtoken::errors::ErrorKind; -use jsonwebtoken::{decode, encode, Algorithm, Header, Key, Validation}; +use jsonwebtoken::{decode, encode, Algorithm, Header, Validation}; #[derive(Debug, Serialize, Deserialize)] struct Claims { @@ -19,20 +19,19 @@ fn main() { header.kid = Some("signing_key".to_owned()); header.alg = Algorithm::HS512; - let token = match encode(&header, &my_claims, Key::Hmac(key)) { + let token = match encode(&header, &my_claims, key) { Ok(t) => t, Err(_) => panic!(), // in practice you would return the error }; println!("{:?}", token); - let token_data = - match decode::(&token, Key::Hmac(key), &Validation::new(Algorithm::HS512)) { - Ok(c) => c, - Err(err) => match *err.kind() { - ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error - _ => panic!(), - }, - }; + let token_data = match decode::(&token, key, &Validation::new(Algorithm::HS512)) { + Ok(c) => c, + Err(err) => match *err.kind() { + ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error + _ => panic!(), + }, + }; println!("{:?}", token_data.claims); println!("{:?}", token_data.header); } diff --git a/examples/validation.rs b/examples/validation.rs index bfc6bcc..6c22d3f 100644 --- a/examples/validation.rs +++ b/examples/validation.rs @@ -1,5 +1,5 @@ use jsonwebtoken::errors::ErrorKind; -use jsonwebtoken::{decode, encode, Header, Key, Validation}; +use jsonwebtoken::{decode, encode, Header, Validation}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] @@ -13,13 +13,13 @@ fn main() { let my_claims = Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned(), exp: 10000000000 }; let key = b"secret"; - let token = match encode(&Header::default(), &my_claims, Key::Hmac(key)) { + let token = match encode(&Header::default(), &my_claims, key) { Ok(t) => t, Err(_) => panic!(), // in practice you would return the error }; let validation = Validation { sub: Some("b@b.com".to_string()), ..Validation::default() }; - let token_data = match decode::(&token, Key::Hmac(key), &validation) { + let token_data = match decode::(&token, key, &validation) { Ok(c) => c, Err(err) => match *err.kind() { ErrorKind::InvalidToken => panic!("Token is invalid"), // Example on how to handle a specific error diff --git a/src/algorithms.rs b/src/algorithms.rs index 09c8b17..6054125 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,4 +1,4 @@ -use crate::errors::{new_error, Error, ErrorKind, Result}; +use crate::errors::{Error, ErrorKind, Result}; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -53,7 +53,7 @@ impl FromStr for Algorithm { "PS384" => Ok(Algorithm::PS384), "PS512" => Ok(Algorithm::PS512), "RS512" => Ok(Algorithm::RS512), - _ => Err(new_error(ErrorKind::InvalidAlgorithmName)), + _ => Err(ErrorKind::InvalidAlgorithmName.into()), } } } diff --git a/src/crypto.rs b/src/crypto.rs index 804f082..ad95dc1 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -3,32 +3,23 @@ use ring::constant_time::verify_slices_are_equal; use ring::{hmac, rand, signature}; use crate::algorithms::Algorithm; -use crate::errors::{new_error, ErrorKind, Result}; -use crate::keys::Key; +use crate::errors::{ErrorKind, Result}; +use crate::pem_decoder::PemEncodedKey; /// The actual HS signing + encoding -fn sign_hmac(alg: hmac::Algorithm, key: Key, signing_input: &str) -> Result { - let signing_key = match key { - Key::Hmac(bytes) => hmac::Key::new(alg, bytes), - _ => return Err(ErrorKind::InvalidKeyFormat.into()), - }; - let digest = hmac::sign(&signing_key, signing_input.as_bytes()); - +fn sign_hmac(alg: hmac::Algorithm, key: &[u8], signing_input: &str) -> Result { + let digest = hmac::sign(&hmac::Key::new(alg, key), signing_input.as_bytes()); Ok(base64::encode_config::(&digest, base64::URL_SAFE_NO_PAD)) } /// The actual ECDSA signing + encoding fn sign_ecdsa( alg: &'static signature::EcdsaSigningAlgorithm, - key: Key, + key: &[u8], signing_input: &str, ) -> Result { - let signing_key = match key { - Key::Pkcs8(bytes) => signature::EcdsaKeyPair::from_pkcs8(alg, bytes)?, - _ => { - return Err(new_error(ErrorKind::InvalidKeyFormat)); - } - }; + let pem_key = PemEncodedKey::new(key)?; + let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?; let rng = rand::SystemRandom::new(); let sig = signing_key.sign(&rng, signing_input.as_bytes())?; Ok(base64::encode_config(&sig, base64::URL_SAFE_NO_PAD)) @@ -38,20 +29,12 @@ fn sign_ecdsa( /// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html fn sign_rsa( alg: &'static dyn signature::RsaEncoding, - key: Key, + key: &[u8], signing_input: &str, ) -> Result { - let key_pair = match key { - Key::Der(bytes) => { - signature::RsaKeyPair::from_der(bytes).map_err(|_| ErrorKind::InvalidRsaKey)? - } - Key::Pkcs8(bytes) => { - signature::RsaKeyPair::from_pkcs8(bytes).map_err(|_| ErrorKind::InvalidRsaKey)? - } - _ => { - return Err(ErrorKind::InvalidKeyFormat.into()); - } - }; + let pem_key = PemEncodedKey::new(key)?; + let key_pair = signature::RsaKeyPair::from_der(pem_key.as_rsa_key()?) + .map_err(|_| ErrorKind::InvalidRsaKey)?; let mut signature = vec![0; key_pair.public_modulus_len()]; let rng = rand::SystemRandom::new(); @@ -66,7 +49,7 @@ fn sign_rsa( /// the base64 url safe encoded of the result. /// /// Only use this function if you want to do something other than JWT. -pub fn sign(signing_input: &str, key: Key, algorithm: Algorithm) -> Result { +pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result { match algorithm { Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, signing_input), Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, signing_input), @@ -107,36 +90,20 @@ fn verify_ring_es( alg: &'static dyn signature::VerificationAlgorithm, signature: &str, signing_input: &str, - key: Key, + key: &[u8], ) -> Result { - let bytes = match key { - Key::Pkcs8(bytes) => bytes, - _ => { - return Err(ErrorKind::InvalidKeyFormat.into()); - } - }; - verify_ring(alg, signature, signing_input, bytes) + let pem_key = PemEncodedKey::new(key)?; + verify_ring(alg, signature, signing_input, pem_key.as_ec_public_key()?) } fn verify_ring_rsa( alg: &'static signature::RsaParameters, signature: &str, signing_input: &str, - key: Key, + key: &[u8], ) -> Result { - match key { - Key::Der(bytes) | Key::Pkcs8(bytes) => verify_ring(alg, signature, signing_input, bytes), - Key::ModulusExponent(n, e) => { - let public_key = signature::RsaPublicKeyComponents { n, e }; - - let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?; - - let res = public_key.verify(alg, signing_input.as_bytes(), &signature_bytes); - - Ok(res.is_ok()) - } - _ => Err(ErrorKind::InvalidKeyFormat.into()), - } + let pem_key = PemEncodedKey::new(key)?; + verify_ring(alg, signature, signing_input, pem_key.as_rsa_key()?) } /// Compares the signature given with a re-computed signature for HMAC or using the public key @@ -150,7 +117,7 @@ fn verify_ring_rsa( pub fn verify( signature: &str, signing_input: &str, - key: Key, + key: &[u8], algorithm: Algorithm, ) -> Result { match algorithm { diff --git a/src/errors.rs b/src/errors.rs index 0c8b148..3973139 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -92,11 +92,12 @@ impl StdError for Error { ErrorKind::ImmatureSignature => "immature signature", ErrorKind::InvalidAlgorithm => "algorithms don't match", ErrorKind::InvalidAlgorithmName => "not a known algorithm", + ErrorKind::InvalidKeyFormat => "invalid key format", + ErrorKind::__Nonexhaustive => "unknown error", ErrorKind::Base64(ref err) => err.description(), ErrorKind::Json(ref err) => err.description(), ErrorKind::Utf8(ref err) => err.description(), ErrorKind::Crypto(ref err) => err.description(), - _ => unreachable!(), } } @@ -113,11 +114,12 @@ impl StdError for Error { ErrorKind::ImmatureSignature => None, ErrorKind::InvalidAlgorithm => None, ErrorKind::InvalidAlgorithmName => None, + ErrorKind::InvalidKeyFormat => None, + ErrorKind::__Nonexhaustive => None, ErrorKind::Base64(ref err) => Some(err), ErrorKind::Json(ref err) => Some(err), ErrorKind::Utf8(ref err) => Some(err), ErrorKind::Crypto(ref err) => Some(err), - _ => unreachable!(), } } } @@ -135,11 +137,13 @@ impl fmt::Display for Error { | ErrorKind::InvalidSubject | ErrorKind::ImmatureSignature | ErrorKind::InvalidAlgorithm + | ErrorKind::InvalidKeyFormat | ErrorKind::InvalidAlgorithmName => write!(f, "{}", self.description()), ErrorKind::Json(ref err) => write!(f, "JSON error: {}", err), ErrorKind::Utf8(ref err) => write!(f, "UTF-8 error: {}", err), ErrorKind::Crypto(ref err) => write!(f, "Crypto error: {}", err), - _ => unreachable!(), + ErrorKind::Base64(ref err) => write!(f, "Base64 error: {}", err), + ErrorKind::__Nonexhaustive => write!(f, "Unknown error"), } } } diff --git a/src/keys.rs b/src/keys.rs deleted file mode 100644 index 0d2e6c4..0000000 --- a/src/keys.rs +++ /dev/null @@ -1,16 +0,0 @@ -/// The supported RSA key formats, see the documentation for ring::signature::RsaKeyPair -/// for more information -#[derive(Debug, PartialEq)] -pub enum Key<'a> { - /// An unencrypted PKCS#8-encoded key. Can be used with both ECDSA and RSA - /// algorithms when signing. See ring for information. - Pkcs8(&'a [u8]), - /// A binary DER-encoded ASN.1 key. Can only be used with RSA algorithms - /// when signing. See ring for more information - Der(&'a [u8]), - /// This is not a key format, but provided for convenience since HMAC is - /// a supported signing algorithm. - Hmac(&'a [u8]), - /// A Modulus/exponent for a RSA public key - ModulusExponent(&'a [u8], &'a [u8]), -} diff --git a/src/lib.rs b/src/lib.rs index 5a81b2f..c11b6a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,6 @@ mod crypto; /// All the errors pub mod errors; mod header; -mod keys; mod pem_decoder; mod pem_encoder; mod serialization; @@ -17,8 +16,6 @@ mod validation; pub use algorithms::Algorithm; pub use crypto::{sign, verify}; pub use header::Header; -pub use keys::Key; -pub use pem_decoder::PemEncodedKey; pub use serialization::TokenData; pub use validation::Validation; @@ -49,9 +46,9 @@ use crate::validation::validate; /// /// // my_claims is a struct that implements Serialize /// // This will create a JWT using HS256 as algorithm -/// let token = encode(&Header::default(), &my_claims, Key::Hmac("secret".as_ref())).unwrap(); +/// let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap(); /// ``` -pub fn encode(header: &Header, claims: &T, key: Key) -> Result { +pub fn encode(header: &Header, claims: &T, key: &[u8]) -> Result { let encoded_header = encode_part(&header)?; let encoded_claims = encode_part(&claims)?; let signing_input = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); @@ -93,7 +90,7 @@ macro_rules! expect_two { /// ``` pub fn decode( token: &str, - key: Key, + key: &[u8], validation: &Validation, ) -> Result> { let (signature, signing_input) = expect_two!(token.rsplitn(2, '.')); @@ -160,33 +157,6 @@ pub fn decode_header(token: &str) -> Result
{ Header::from_encoded(header) } -/// Decode a PEM string to obtain its key -/// -/// This must be a tagged PEM encoded key, tags start with `-----BEGIN ..-----` -/// and end with a `-----END ..-----` -/// -/// ```rust -/// use jsonwebtoken::{decode_pem, sign, verify, Algorithm}; -/// -/// let pem_content = "-----BEGIN PRIVATE KEY----- -/// MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWTFfCGljY6aw3Hrt -/// kHmPRiazukxPLb6ilpRAewjW8nihRANCAATDskChT+Altkm9X7MI69T3IUmrQU0L -/// 950IxEzvw/x5BMEINRMrXLBJhqzO9Bm+d6JbqA21YQmd1Kt4RzLJR1W+ -/// -----END PRIVATE KEY-----"; -/// -/// // First use decode_pem from jsonwebtoken -/// let privkey_pem = decode_pem(pem_content).unwrap(); -/// // If it decodes Ok, then you can start using it with a given algorithm -/// let privkey = privkey_pem.as_key().unwrap(); -/// -/// // When using the as_key function, you do not need to wrap in Key::Der or Key::Pkcs8 -/// // The same code can be used for public keys too. -/// let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); -/// ``` -pub fn decode_pem(content: &str) -> Result { - PemEncodedKey::read(content) -} - /// TODO pub fn encode_rsa_public_pkcs1_pem(modulus: &[u8], exponent: &[u8]) -> Result { pem_encoder::encode_rsa_public_pkcs1_pem(modulus, exponent) diff --git a/src/pem_decoder.rs b/src/pem_decoder.rs index c42b042..2c29f7d 100644 --- a/src/pem_decoder.rs +++ b/src/pem_decoder.rs @@ -1,5 +1,4 @@ use crate::errors::{ErrorKind, Result}; -use crate::keys::Key; extern crate pem; extern crate simple_asn1; @@ -9,10 +8,10 @@ use simple_asn1::{BigUint, OID}; /// Supported PEM files for EC and RSA Public and Private Keys #[derive(Debug, PartialEq)] enum PemType { - EcPublicKey, - EcPrivateKey, - RsaPublicKey, - RsaPrivateKey, + EcPublic, + EcPrivate, + RsaPublic, + RsaPrivate, } #[derive(Debug, PartialEq)] @@ -42,7 +41,7 @@ enum Classification { /// PKCS#1: https://tools.ietf.org/html/rfc8017 /// PKCS#8: https://tools.ietf.org/html/rfc5958 #[derive(Debug)] -pub struct PemEncodedKey { +pub(crate) struct PemEncodedKey { content: Vec, asn1: Vec, pem_type: PemType, @@ -51,7 +50,7 @@ pub struct PemEncodedKey { impl PemEncodedKey { /// Read the PEM file for later key use - pub fn read(input: &str) -> Result { + pub fn new(input: &[u8]) -> Result { match pem::parse(input) { Ok(content) => { let pem_contents = content.contents; @@ -65,13 +64,13 @@ impl PemEncodedKey { "RSA PRIVATE KEY" => Ok(PemEncodedKey { content: pem_contents, asn1: asn1_content, - pem_type: PemType::RsaPrivateKey, + pem_type: PemType::RsaPrivate, standard: Standard::Pkcs1, }), "RSA PUBLIC KEY" => Ok(PemEncodedKey { content: pem_contents, asn1: asn1_content, - pem_type: PemType::RsaPublicKey, + pem_type: PemType::RsaPublic, standard: Standard::Pkcs1, }), @@ -86,16 +85,16 @@ impl PemEncodedKey { let pem_type = match c { Classification::Ec => { if is_private { - PemType::EcPrivateKey + PemType::EcPrivate } else { - PemType::EcPublicKey + PemType::EcPublic } } Classification::Rsa => { if is_private { - PemType::RsaPrivateKey + PemType::RsaPrivate } else { - PemType::RsaPublicKey + PemType::RsaPublic } } }; @@ -117,18 +116,36 @@ impl PemEncodedKey { } } - /// This will do the initial parsing of a PEM file. - /// Supported tagged pems include "RSA PRIVATE KEY", "RSA PUBLIC KEY", - /// "PRIVATE KEY", "PUBLIC KEY" - /// PEMs with multiple tagged portions are not supported - pub fn as_key(&self) -> Result> { + /// Can only be PKCS8 + pub fn as_ec_private_key(&self) -> Result<&[u8]> { match self.standard { - Standard::Pkcs1 => Ok(Key::Der(self.content.as_slice())), + Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()), Standard::Pkcs8 => match self.pem_type { - PemType::RsaPrivateKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), - PemType::RsaPublicKey => Ok(Key::Der(extract_first_bitstring(&self.asn1)?)), - PemType::EcPrivateKey => Ok(Key::Pkcs8(self.content.as_slice())), - PemType::EcPublicKey => Ok(Key::Pkcs8(extract_first_bitstring(&self.asn1)?)), + PemType::EcPrivate => Ok(self.content.as_slice()), + _ => Err(ErrorKind::InvalidKeyFormat.into()), + }, + } + } + + /// Can only be PKCS8 + pub fn as_ec_public_key(&self) -> Result<&[u8]> { + match self.standard { + Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()), + Standard::Pkcs8 => match self.pem_type { + PemType::EcPublic => extract_first_bitstring(&self.asn1), + _ => Err(ErrorKind::InvalidKeyFormat.into()), + }, + } + } + + /// Can be PKCS1 or PKCS8 + pub fn as_rsa_key(&self) -> Result<&[u8]> { + match self.standard { + Standard::Pkcs1 => Ok(self.content.as_slice()), + Standard::Pkcs8 => match self.pem_type { + PemType::RsaPrivate => extract_first_bitstring(&self.asn1), + PemType::RsaPublic => extract_first_bitstring(&self.asn1), + _ => Err(ErrorKind::InvalidKeyFormat.into()), }, } } diff --git a/src/pem_encoder.rs b/src/pem_encoder.rs index d719fc1..ef7ba9d 100644 --- a/src/pem_encoder.rs +++ b/src/pem_encoder.rs @@ -99,102 +99,100 @@ fn encode_ec_public_asn1(x: &[u8]) -> ASN1Block { #[cfg(test)] mod tests { use super::*; - use crate::decode_pem; - use crate::keys::Key; use ring::{signature, signature::KeyPair}; - #[test] - fn public_key_encoding_pkcs1() { - let privkey_pem = - decode_pem(include_str!("../tests/rsa/private_rsa_key_pkcs8.pem")).unwrap(); - let privkey = privkey_pem.as_key().unwrap(); - let ring_key = signature::RsaKeyPair::from_der(match privkey { - Key::Der(bytes) => bytes, - _ => panic!("Unexpected"), - }) - .unwrap(); - let mut modulus = vec![0]; - modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); - let exponent = ring_key.public_key().exponent(); - - let public_key_pkcs1_pem = encode_rsa_public_pkcs1_pem( - modulus.as_ref(), - exponent.big_endian_without_leading_zero(), - ) - .unwrap(); - assert_eq!( - include_str!("../tests/rsa/public_rsa_key_pkcs1.pem").trim(), - public_key_pkcs1_pem.replace('\r', "").trim() - ); - - let public_key_pkcs1_der = encode_rsa_public_pkcs1_der( - modulus.as_ref(), - exponent.big_endian_without_leading_zero(), - ) - .unwrap(); - assert_eq!( - include_bytes!("../tests/rsa/public_rsa_key.der").to_vec(), - public_key_pkcs1_der - ); - } - - #[test] - fn public_key_encoding_pkcs8() { - let privkey_pem = - decode_pem(include_str!("../tests/rsa/private_rsa_key_pkcs8.pem")).unwrap(); - let privkey = privkey_pem.as_key().unwrap(); - let ring_key = signature::RsaKeyPair::from_der(match privkey { - Key::Der(bytes) => bytes, - _ => panic!("Unexpected"), - }) - .unwrap(); - let mut modulus = vec![0]; - modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); - let exponent = ring_key.public_key().exponent(); - - let public_key_pkcs8 = encode_rsa_public_pkcs8_pem( - modulus.as_ref(), - exponent.big_endian_without_leading_zero(), - ) - .unwrap(); - assert_eq!( - include_str!("../tests/rsa/public_rsa_key_pkcs8.pem").trim(), - public_key_pkcs8.replace('\r', "").trim() - ); - } - - #[test] - fn public_key_encoding() { - let privkey_pem = decode_pem(include_str!("../tests/ec/private_ecdsa_key.pem")).unwrap(); - let privkey = privkey_pem.as_key().unwrap(); - let alg = &signature::ECDSA_P256_SHA256_FIXED_SIGNING; - let ring_key = signature::EcdsaKeyPair::from_pkcs8( - alg, - match privkey { - Key::Pkcs8(bytes) => bytes, - _ => panic!("Unexpected"), - }, - ) - .unwrap(); - - let public_key_pem = encode_ec_public_pem(ring_key.public_key().as_ref()).unwrap(); - assert_eq!( - include_str!("../tests/ec/public_ecdsa_key.pem").trim(), - public_key_pem.replace('\r', "").trim() - ); - - let public_key_der = encode_ec_public_der(ring_key.public_key().as_ref()).unwrap(); - // The stored ".pk8" key is just the x coordinate of the EC key - // It's not truly a pkcs8 formatted DER - // To get around that, a prepended binary specifies the EC key, EC name, - // and X coordinate length. The length is unlikely to change.. in the - // event that it does, look at the pem file (convert base64 to hex) and find - // where 0x03, 0x42 don't match up. 0x42 is the length. - let mut stored_pk8_der = vec![ - 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, - 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, - ]; - stored_pk8_der.extend(include_bytes!("../tests/ec/public_ecdsa_key.pk8").to_vec()); - assert_eq!(stored_pk8_der, public_key_der); - } + // #[test] + // fn public_key_encoding_pkcs1() { + // let privkey_pem = + // decode_pem(include_str!("../tests/rsa/private_rsa_key_pkcs8.pem")).unwrap(); + // let privkey = privkey_pem.as_key().unwrap(); + // let ring_key = signature::RsaKeyPair::from_der(match privkey { + // Key::Der(bytes) => bytes, + // _ => panic!("Unexpected"), + // }) + // .unwrap(); + // let mut modulus = vec![0]; + // modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); + // let exponent = ring_key.public_key().exponent(); + // + // let public_key_pkcs1_pem = encode_rsa_public_pkcs1_pem( + // modulus.as_ref(), + // exponent.big_endian_without_leading_zero(), + // ) + // .unwrap(); + // assert_eq!( + // include_str!("../tests/rsa/public_rsa_key_pkcs1.pem").trim(), + // public_key_pkcs1_pem.replace('\r', "").trim() + // ); + // + // let public_key_pkcs1_der = encode_rsa_public_pkcs1_der( + // modulus.as_ref(), + // exponent.big_endian_without_leading_zero(), + // ) + // .unwrap(); + // assert_eq!( + // include_bytes!("../tests/rsa/public_rsa_key.der").to_vec(), + // public_key_pkcs1_der + // ); + // } + // + // #[test] + // fn public_key_encoding_pkcs8() { + // let privkey_pem = + // decode_pem(include_str!("../tests/rsa/private_rsa_key_pkcs8.pem")).unwrap(); + // let privkey = privkey_pem.as_key().unwrap(); + // let ring_key = signature::RsaKeyPair::from_der(match privkey { + // Key::Der(bytes) => bytes, + // _ => panic!("Unexpected"), + // }) + // .unwrap(); + // let mut modulus = vec![0]; + // modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); + // let exponent = ring_key.public_key().exponent(); + // + // let public_key_pkcs8 = encode_rsa_public_pkcs8_pem( + // modulus.as_ref(), + // exponent.big_endian_without_leading_zero(), + // ) + // .unwrap(); + // assert_eq!( + // include_str!("../tests/rsa/public_rsa_key_pkcs8.pem").trim(), + // public_key_pkcs8.replace('\r', "").trim() + // ); + // } + // + // #[test] + // fn public_key_encoding() { + // let privkey_pem = decode_pem(include_str!("../tests/ec/private_ecdsa_key.pem")).unwrap(); + // let privkey = privkey_pem.as_key().unwrap(); + // let alg = &signature::ECDSA_P256_SHA256_FIXED_SIGNING; + // let ring_key = signature::EcdsaKeyPair::from_pkcs8( + // alg, + // match privkey { + // Key::Pkcs8(bytes) => bytes, + // _ => panic!("Unexpected"), + // }, + // ) + // .unwrap(); + // + // let public_key_pem = encode_ec_public_pem(ring_key.public_key().as_ref()).unwrap(); + // assert_eq!( + // include_str!("../tests/ec/public_ecdsa_key.pem").trim(), + // public_key_pem.replace('\r', "").trim() + // ); + // + // let public_key_der = encode_ec_public_der(ring_key.public_key().as_ref()).unwrap(); + // // The stored ".pk8" key is just the x coordinate of the EC key + // // It's not truly a pkcs8 formatted DER + // // To get around that, a prepended binary specifies the EC key, EC name, + // // and X coordinate length. The length is unlikely to change.. in the + // // event that it does, look at the pem file (convert base64 to hex) and find + // // where 0x03, 0x42 don't match up. 0x42 is the length. + // let mut stored_pk8_der = vec![ + // 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, + // 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, + // ]; + // stored_pk8_der.extend(include_bytes!("../tests/ec/public_ecdsa_key.pk8").to_vec()); + // assert_eq!(stored_pk8_der, public_key_der); + // } } diff --git a/src/serialization.rs b/src/serialization.rs index d1aafeb..6c6df1f 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -22,7 +22,8 @@ pub fn encode_part(input: &T) -> Result { Ok(base64::encode_config(json.as_bytes(), base64::URL_SAFE_NO_PAD)) } -/// Decodes from base64 and deserializes from JSON to a struct AND a hashmap +/// Decodes from base64 and deserializes from JSON to a struct AND a hashmap of Value so we can +/// run validation on it pub fn from_jwt_part_claims, T: DeserializeOwned>( encoded: B, ) -> Result<(T, Map)> { diff --git a/tests/ec/mod.rs b/tests/ec/mod.rs index cc3321e..b02721c 100644 --- a/tests/ec/mod.rs +++ b/tests/ec/mod.rs @@ -1,5 +1,5 @@ use chrono::Utc; -use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; +use jsonwebtoken::{decode, encode, sign, verify, Algorithm, Header, Validation}; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -9,22 +9,21 @@ pub struct Claims { exp: i64, } -#[test] -fn round_trip_sign_verification_pk8() { - let privkey = include_bytes!("private_ecdsa_key.pk8"); - let encrypted = sign("hello world", Key::Pkcs8(&privkey[..]), Algorithm::ES256).unwrap(); - let pubkey = include_bytes!("public_ecdsa_key.pk8"); - let is_valid = verify(&encrypted, "hello world", Key::Pkcs8(pubkey), Algorithm::ES256).unwrap(); - assert!(is_valid); -} +// TODO: remove completely? +//#[test] +//fn round_trip_sign_verification_pk8() { +// let privkey = include_bytes!("private_ecdsa_key.pk8"); +// let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); +// let pubkey = include_bytes!("public_ecdsa_key.pk8"); +// let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap(); +// assert!(is_valid); +//} #[test] fn round_trip_sign_verification_pem() { - let privkey_pem = decode_pem(include_str!("private_ecdsa_key.pem")).unwrap(); - let privkey = privkey_pem.as_key().unwrap(); + let privkey = include_bytes!("private_ecdsa_key.pem"); let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); - let pubkey_pem = decode_pem(include_str!("public_ecdsa_key.pem")).unwrap(); - let pubkey = pubkey_pem.as_key().unwrap(); + let pubkey = include_bytes!("public_ecdsa_key.pem"); let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap(); assert!(is_valid); } @@ -36,12 +35,10 @@ fn round_trip_claim() { company: "ACME".to_string(), exp: Utc::now().timestamp() + 10000, }; - let privkey = include_bytes!("private_ecdsa_key.pk8"); - let token = - encode(&Header::new(Algorithm::ES256), &my_claims, Key::Pkcs8(&privkey[..])).unwrap(); - let pubkey = include_bytes!("public_ecdsa_key.pk8"); - let token_data = - decode::(&token, Key::Pkcs8(pubkey), &Validation::new(Algorithm::ES256)).unwrap(); + let privkey = include_bytes!("private_ecdsa_key.pem"); + let token = encode(&Header::new(Algorithm::ES256), &my_claims, privkey).unwrap(); + let pubkey = include_bytes!("public_ecdsa_key.pem"); + let token_data = decode::(&token, pubkey, &Validation::new(Algorithm::ES256)).unwrap(); assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); } diff --git a/tests/hmac.rs b/tests/hmac.rs index 89cfaf7..0a245fc 100644 --- a/tests/hmac.rs +++ b/tests/hmac.rs @@ -1,6 +1,6 @@ use chrono::Utc; use jsonwebtoken::{ - dangerous_unsafe_decode, decode, decode_header, encode, sign, verify, Algorithm, Header, Key, + dangerous_unsafe_decode, decode, decode_header, encode, sign, verify, Algorithm, Header, Validation, }; use serde::{Deserialize, Serialize}; @@ -14,7 +14,7 @@ pub struct Claims { #[test] fn sign_hs256() { - let result = sign("hello world", Key::Hmac(b"secret"), Algorithm::HS256).unwrap(); + let result = sign("hello world", b"secret", Algorithm::HS256).unwrap(); let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; assert_eq!(result, expected); } @@ -22,7 +22,7 @@ fn sign_hs256() { #[test] fn verify_hs256() { let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo"; - let valid = verify(sig, "hello world", Key::Hmac(b"secret"), Algorithm::HS256).unwrap(); + let valid = verify(sig, "hello world", b"secret", Algorithm::HS256).unwrap(); assert!(valid); } @@ -35,9 +35,8 @@ fn encode_with_custom_header() { }; let mut header = Header::default(); header.kid = Some("kid".to_string()); - let token = encode(&header, &my_claims, Key::Hmac(b"secret")).unwrap(); - let token_data = - decode::(&token, Key::Hmac(b"secret"), &Validation::default()).unwrap(); + let token = encode(&header, &my_claims, b"secret").unwrap(); + let token_data = decode::(&token, b"secret", &Validation::default()).unwrap(); assert_eq!(my_claims, token_data.claims); assert_eq!("kid", token_data.header.kid.unwrap()); } @@ -49,9 +48,8 @@ fn round_trip_claim() { company: "ACME".to_string(), exp: Utc::now().timestamp() + 10000, }; - let token = encode(&Header::default(), &my_claims, Key::Hmac(b"secret")).unwrap(); - let token_data = - decode::(&token, Key::Hmac(b"secret"), &Validation::default()).unwrap(); + let token = encode(&Header::default(), &my_claims, b"secret").unwrap(); + let token_data = decode::(&token, b"secret", &Validation::default()).unwrap(); assert_eq!(my_claims, token_data.claims); assert!(token_data.header.kid.is_none()); } @@ -59,7 +57,7 @@ fn round_trip_claim() { #[test] fn decode_token() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.9r56oF7ZliOBlOAyiOFperTGxBtPykRQiWNFxhDCW98"; - let claims = decode::(token, Key::Hmac(b"secret"), &Validation::default()); + let claims = decode::(token, b"secret", &Validation::default()); println!("{:?}", claims); claims.unwrap(); } @@ -68,7 +66,7 @@ fn decode_token() { #[should_panic(expected = "InvalidToken")] fn decode_token_missing_parts() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; - let claims = decode::(token, Key::Hmac(b"secret"), &Validation::default()); + let claims = decode::(token, b"secret", &Validation::default()); claims.unwrap(); } @@ -77,7 +75,7 @@ fn decode_token_missing_parts() { fn decode_token_invalid_signature() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong"; - let claims = decode::(token, Key::Hmac(b"secret"), &Validation::default()); + let claims = decode::(token, b"secret", &Validation::default()); claims.unwrap(); } @@ -85,14 +83,14 @@ fn decode_token_invalid_signature() { #[should_panic(expected = "InvalidAlgorithm")] fn decode_token_wrong_algorithm() { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY"; - let claims = decode::(token, Key::Hmac(b"secret"), &Validation::new(Algorithm::RS512)); + let claims = decode::(token, b"secret", &Validation::new(Algorithm::RS512)); claims.unwrap(); } #[test] fn decode_token_with_bytes_secret() { let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUiLCJleHAiOjI1MzI1MjQ4OTF9.Hm0yvKH25TavFPz7J_coST9lZFYH1hQo0tvhvImmaks"; - let claims = decode::(token, Key::Hmac(b"\x01\x02\x03"), &Validation::default()); + let claims = decode::(token, b"\x01\x02\x03", &Validation::default()); assert!(claims.is_ok()); } diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 8fde515..83c4fde 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,5 +1,5 @@ use chrono::Utc; -use jsonwebtoken::{decode, decode_pem, encode, sign, verify, Algorithm, Header, Key, Validation}; +use jsonwebtoken::{decode, encode, sign, verify, Algorithm, Header, Validation}; use serde::{Deserialize, Serialize}; const RSA_ALGORITHMS: &[Algorithm] = &[ @@ -18,42 +18,26 @@ pub struct Claims { exp: i64, } -#[test] -fn round_trip_sign_verification_der() { - let privkey = include_bytes!("private_rsa_key.der"); - for &alg in RSA_ALGORITHMS { - let encrypted = sign("hello world", Key::Der(&privkey[..]), alg).unwrap(); - let is_valid = - verify(&encrypted, "hello world", Key::Der(include_bytes!("public_rsa_key.der")), alg) - .unwrap(); - assert!(is_valid); - } -} - #[test] fn round_trip_sign_verification_pem_pkcs1() { - let privkey_pem = decode_pem(include_str!("private_rsa_key_pkcs1.pem")).unwrap(); - let pubkey_pem = decode_pem(include_str!("public_rsa_key_pkcs1.pem")).unwrap(); + let privkey_pem = include_bytes!("private_rsa_key_pkcs1.pem"); + let pubkey_pem = include_bytes!("public_rsa_key_pkcs1.pem"); for &alg in RSA_ALGORITHMS { - let privkey_key = privkey_pem.as_key().unwrap(); - let pubkey_key = pubkey_pem.as_key().unwrap(); - let encrypted = sign("hello world", privkey_key, alg).unwrap(); - let is_valid = verify(&encrypted, "hello world", pubkey_key, alg).unwrap(); + let encrypted = sign("hello world", privkey_pem, alg).unwrap(); + let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap(); assert!(is_valid); } } #[test] fn round_trip_sign_verification_pem_pkcs8() { - let privkey_pem = decode_pem(include_str!("private_rsa_key_pkcs8.pem")).unwrap(); - let pubkey_pem = decode_pem(include_str!("public_rsa_key_pkcs8.pem")).unwrap(); + let privkey_pem = include_bytes!("private_rsa_key_pkcs8.pem"); + let pubkey_pem = include_bytes!("public_rsa_key_pkcs8.pem"); for &alg in RSA_ALGORITHMS { - let privkey_key = privkey_pem.as_key().unwrap(); - let pubkey_key = pubkey_pem.as_key().unwrap(); - let encrypted = sign("hello world", privkey_key, alg).unwrap(); - let is_valid = verify(&encrypted, "hello world", pubkey_key, alg).unwrap(); + let encrypted = sign("hello world", privkey_pem, alg).unwrap(); + let is_valid = verify(&encrypted, "hello world", pubkey_pem, alg).unwrap(); assert!(is_valid); } } @@ -65,13 +49,13 @@ fn round_trip_claim() { company: "ACME".to_string(), exp: Utc::now().timestamp() + 10000, }; - let privkey = include_bytes!("private_rsa_key.der"); + let privkey = include_bytes!("private_rsa_key_pkcs1.pem"); for &alg in RSA_ALGORITHMS { - let token = encode(&Header::new(alg), &my_claims, Key::Der(&privkey[..])).unwrap(); + let token = encode(&Header::new(alg), &my_claims, privkey).unwrap(); let token_data = decode::( &token, - Key::Der(include_bytes!("public_rsa_key.der")), + include_bytes!("public_rsa_key_pkcs1.pem"), &Validation::new(alg), ) .unwrap(); @@ -80,52 +64,45 @@ fn round_trip_claim() { } } -#[test] -#[should_panic(expected = "InvalidRsaKey")] -fn fails_with_different_key_format() { - let privkey = include_bytes!("private_rsa_key.der"); - sign("hello world", Key::Pkcs8(&privkey[..]), Algorithm::RS256).unwrap(); -} - -#[test] -fn rsa_modulus_exponent() { - 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 privkey = include_bytes!("private_rsa_key.der"); - - let encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::RS256).unwrap(); - let is_valid = verify( - &encrypted, - "hello world", - Key::ModulusExponent(&modulus, &exponent), - Algorithm::RS256, - ) - .unwrap(); - assert!(is_valid); -} +//#[test] +//fn rsa_modulus_exponent() { +// 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 privkey = include_bytes!("private_rsa_key.der"); +// +// let encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::RS256).unwrap(); +// let is_valid = verify( +// &encrypted, +// "hello world", +// Key::ModulusExponent(&modulus, &exponent), +// Algorithm::RS256, +// ) +// .unwrap(); +// assert!(is_valid); +//} #[test] #[should_panic(expected = "InvalidKeyFormat")] fn fails_with_non_pkcs8_key_format() { - let privkey = include_bytes!("private_rsa_key.der"); - let _encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::ES256).unwrap(); + let _encrypted = + sign("hello world", include_bytes!("private_rsa_key_pkcs1.pem"), Algorithm::ES256).unwrap(); } From 34ea1941791ec2f01f8ac36268f8e016dfa6c679 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 8 Nov 2019 19:00:19 +0000 Subject: [PATCH 12/24] Move crypto to a dir --- src/crypto/ecdsa.rs | 18 +++ src/{crypto.rs => crypto/mod.rs} | 132 +++++++++------------- src/crypto/rsa.rs | 25 ++++ src/header.rs | 4 +- src/jwk.rs | 31 +++++ src/lib.rs | 1 + src/serialization.rs | 18 ++- tests/{ec => ecdsa}/mod.rs | 0 tests/{ec => ecdsa}/private_ecdsa_key.pem | 0 tests/{ec => ecdsa}/private_ecdsa_key.pk8 | Bin tests/{ec => ecdsa}/public_ecdsa_key.pem | 0 tests/{ec => ecdsa}/public_ecdsa_key.pk8 | 0 tests/lib.rs | 2 +- 13 files changed, 144 insertions(+), 87 deletions(-) create mode 100644 src/crypto/ecdsa.rs rename src/{crypto.rs => crypto/mod.rs} (62%) create mode 100644 src/crypto/rsa.rs create mode 100644 src/jwk.rs rename tests/{ec => ecdsa}/mod.rs (100%) rename tests/{ec => ecdsa}/private_ecdsa_key.pem (100%) rename tests/{ec => ecdsa}/private_ecdsa_key.pk8 (100%) rename tests/{ec => ecdsa}/public_ecdsa_key.pem (100%) rename tests/{ec => ecdsa}/public_ecdsa_key.pk8 (100%) diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs new file mode 100644 index 0000000..b1f00c9 --- /dev/null +++ b/src/crypto/ecdsa.rs @@ -0,0 +1,18 @@ +use ring::{rand, signature}; + +use crate::errors::{Result}; +use crate::pem_decoder::PemEncodedKey; +use crate::serialization::encode; + +/// The actual ECDSA signing + encoding +pub fn sign( + alg: &'static signature::EcdsaSigningAlgorithm, + key: &[u8], + signing_input: &str, +) -> Result { + let pem_key = PemEncodedKey::new(key)?; + let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?; + let rng = rand::SystemRandom::new(); + let out = signing_key.sign(&rng, signing_input.as_bytes())?; + Ok(encode(out.as_ref())) +} diff --git a/src/crypto.rs b/src/crypto/mod.rs similarity index 62% rename from src/crypto.rs rename to src/crypto/mod.rs index ad95dc1..3ab788a 100644 --- a/src/crypto.rs +++ b/src/crypto/mod.rs @@ -1,54 +1,26 @@ -use base64; use ring::constant_time::verify_slices_are_equal; -use ring::{hmac, rand, signature}; +use ring::{hmac, signature}; use crate::algorithms::Algorithm; -use crate::errors::{ErrorKind, Result}; +use crate::errors::{Result}; use crate::pem_decoder::PemEncodedKey; +use crate::serialization::{encode, decode}; + +pub(crate) mod rsa; +pub(crate) mod ecdsa; /// The actual HS signing + encoding -fn sign_hmac(alg: hmac::Algorithm, key: &[u8], signing_input: &str) -> Result { +pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], signing_input: &str) -> Result { let digest = hmac::sign(&hmac::Key::new(alg, key), signing_input.as_bytes()); - Ok(base64::encode_config::(&digest, base64::URL_SAFE_NO_PAD)) + Ok(encode(digest.as_ref())) } -/// The actual ECDSA signing + encoding -fn sign_ecdsa( - alg: &'static signature::EcdsaSigningAlgorithm, - key: &[u8], - signing_input: &str, -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?; - let rng = rand::SystemRandom::new(); - let sig = signing_key.sign(&rng, signing_input.as_bytes())?; - Ok(base64::encode_config(&sig, base64::URL_SAFE_NO_PAD)) -} - -/// The actual RSA signing + encoding -/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html -fn sign_rsa( - alg: &'static dyn signature::RsaEncoding, - key: &[u8], - signing_input: &str, -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - let key_pair = signature::RsaKeyPair::from_der(pem_key.as_rsa_key()?) - .map_err(|_| ErrorKind::InvalidRsaKey)?; - - let mut signature = vec![0; key_pair.public_modulus_len()]; - let rng = rand::SystemRandom::new(); - key_pair - .sign(alg, &rng, signing_input.as_bytes(), &mut signature) - .map_err(|_| ErrorKind::InvalidRsaKey)?; - - Ok(base64::encode_config::<[u8]>(&signature, base64::URL_SAFE_NO_PAD)) -} /// Take the payload of a JWT, sign it using the algorithm given and return /// the base64 url safe encoded of the result. /// /// Only use this function if you want to do something other than JWT. +/// `key` is the secret for HMAC and a pem encoded string otherwise pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result { match algorithm { Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, signing_input), @@ -56,56 +28,22 @@ pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result sign_hmac(hmac::HMAC_SHA512, key, signing_input), Algorithm::ES256 => { - sign_ecdsa(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, signing_input) + ecdsa::sign(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, signing_input) } Algorithm::ES384 => { - sign_ecdsa(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, signing_input) + ecdsa::sign(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, signing_input) } - Algorithm::RS256 => sign_rsa(&signature::RSA_PKCS1_SHA256, key, signing_input), - Algorithm::RS384 => sign_rsa(&signature::RSA_PKCS1_SHA384, key, signing_input), - Algorithm::RS512 => sign_rsa(&signature::RSA_PKCS1_SHA512, key, signing_input), + Algorithm::RS256 => rsa::sign(&signature::RSA_PKCS1_SHA256, key, signing_input), + Algorithm::RS384 => rsa::sign(&signature::RSA_PKCS1_SHA384, key, signing_input), + Algorithm::RS512 => rsa::sign(&signature::RSA_PKCS1_SHA512, key, signing_input), - Algorithm::PS256 => sign_rsa(&signature::RSA_PSS_SHA256, key, signing_input), - Algorithm::PS384 => sign_rsa(&signature::RSA_PSS_SHA384, key, signing_input), - Algorithm::PS512 => sign_rsa(&signature::RSA_PSS_SHA512, key, signing_input), + Algorithm::PS256 => rsa::sign(&signature::RSA_PSS_SHA256, key, signing_input), + Algorithm::PS384 => rsa::sign(&signature::RSA_PSS_SHA384, key, signing_input), + Algorithm::PS512 => rsa::sign(&signature::RSA_PSS_SHA512, key, signing_input), } } -/// See Ring docs for more details -fn verify_ring( - alg: &'static dyn signature::VerificationAlgorithm, - signature: &str, - signing_input: &str, - key: &[u8], -) -> Result { - let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?; - let public_key = signature::UnparsedPublicKey::new(alg, key); - let res = public_key.verify(signing_input.as_bytes(), &signature_bytes); - - Ok(res.is_ok()) -} - -fn verify_ring_es( - alg: &'static dyn signature::VerificationAlgorithm, - signature: &str, - signing_input: &str, - key: &[u8], -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - verify_ring(alg, signature, signing_input, pem_key.as_ec_public_key()?) -} - -fn verify_ring_rsa( - alg: &'static signature::RsaParameters, - signature: &str, - signing_input: &str, - key: &[u8], -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - verify_ring(alg, signature, signing_input, pem_key.as_rsa_key()?) -} - /// Compares the signature given with a re-computed signature for HMAC or using the public key /// for RSA. /// @@ -152,3 +90,39 @@ pub fn verify( } } } + +// TODO: see if we can remove stuff? + +/// See Ring docs for more details +fn verify_ring( + alg: &'static dyn signature::VerificationAlgorithm, + signature: &str, + signing_input: &str, + key: &[u8], +) -> Result { + let signature_bytes = decode(signature)?; + let public_key = signature::UnparsedPublicKey::new(alg, key); + let res = public_key.verify(signing_input.as_bytes(), &signature_bytes); + + Ok(res.is_ok()) +} + +fn verify_ring_es( + alg: &'static dyn signature::VerificationAlgorithm, + signature: &str, + signing_input: &str, + key: &[u8], +) -> Result { + let pem_key = PemEncodedKey::new(key)?; + verify_ring(alg, signature, signing_input, pem_key.as_ec_public_key()?) +} + +fn verify_ring_rsa( + alg: &'static signature::RsaParameters, + signature: &str, + signing_input: &str, + key: &[u8], +) -> Result { + let pem_key = PemEncodedKey::new(key)?; + verify_ring(alg, signature, signing_input, pem_key.as_rsa_key()?) +} diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs new file mode 100644 index 0000000..bc4a78c --- /dev/null +++ b/src/crypto/rsa.rs @@ -0,0 +1,25 @@ +use ring::{rand, signature}; + +use crate::errors::{ErrorKind, Result}; +use crate::pem_decoder::PemEncodedKey; +use crate::serialization::encode; + +/// The actual RSA signing + encoding +/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html +pub fn sign( + alg: &'static dyn signature::RsaEncoding, + key: &[u8], + signing_input: &str, +) -> Result { + let pem_key = PemEncodedKey::new(key)?; + let key_pair = signature::RsaKeyPair::from_der(pem_key.as_rsa_key()?) + .map_err(|_| ErrorKind::InvalidRsaKey)?; + + let mut signature = vec![0; key_pair.public_modulus_len()]; + let rng = rand::SystemRandom::new(); + key_pair + .sign(alg, &rng, signing_input.as_bytes(), &mut signature) + .map_err(|_| ErrorKind::InvalidRsaKey)?; + + Ok(encode(&signature)) +} diff --git a/src/header.rs b/src/header.rs index 73a2a1d..553dbb3 100644 --- a/src/header.rs +++ b/src/header.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use crate::algorithms::Algorithm; use crate::errors::Result; +use crate::serialization::decode; + /// A basic JWT header, the alg defaults to HS256 and typ is automatically /// set to `JWT`. All the other fields are optional. @@ -59,7 +61,7 @@ impl Header { /// Converts an encoded part into the Header struct if possible pub(crate) fn from_encoded(encoded_part: &str) -> Result { - let decoded = base64::decode_config(encoded_part, base64::URL_SAFE_NO_PAD)?; + let decoded = decode(encoded_part)?; let s = String::from_utf8(decoded)?; Ok(serde_json::from_str(&s)?) diff --git a/src/jwk.rs b/src/jwk.rs new file mode 100644 index 0000000..bc331f9 --- /dev/null +++ b/src/jwk.rs @@ -0,0 +1,31 @@ +use serde::de::DeserializeOwned; + +use crate::validation::{Validation, validate}; +use crate::header::Header; +use crate::errors::{new_error, ErrorKind, Result}; +use crate::serialization::{from_jwt_part_claims, TokenData}; + + +pub fn decode_rsa_jwk( + token: &str, + modulus: &[u8], + exponent: &[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_encoded(header)?; + + if !verify(signature, signing_input, key, 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 }) +} diff --git a/src/lib.rs b/src/lib.rs index c11b6a6..604f6fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ mod pem_decoder; mod pem_encoder; mod serialization; mod validation; +// mod jwk; pub use algorithms::Algorithm; pub use crypto::{sign, verify}; diff --git a/src/serialization.rs b/src/serialization.rs index 6c6df1f..0f814d4 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -1,4 +1,3 @@ -use base64; use serde::de::DeserializeOwned; use serde::ser::Serialize; use serde_json::map::Map; @@ -16,19 +15,26 @@ pub struct TokenData { pub claims: T, } +pub(crate) fn encode(input: &[u8]) -> String { + base64::encode_config(input, base64::URL_SAFE_NO_PAD) +} + +pub(crate) fn decode(input: &str) -> Result> { + base64::decode_config(input, base64::URL_SAFE_NO_PAD).map_err(|e| e.into()) +} + /// Serializes a struct to JSON and encodes it in base64 -pub fn encode_part(input: &T) -> Result { +pub(crate) fn encode_part(input: &T) -> Result { let json = to_string(input)?; - Ok(base64::encode_config(json.as_bytes(), base64::URL_SAFE_NO_PAD)) + Ok(encode(json.as_bytes())) } /// Decodes from base64 and deserializes from JSON to a struct AND a hashmap of Value so we can /// run validation on it -pub fn from_jwt_part_claims, T: DeserializeOwned>( +pub(crate) fn from_jwt_part_claims, T: DeserializeOwned>( encoded: B, ) -> Result<(T, Map)> { - let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?; - let s = String::from_utf8(decoded)?; + let s = String::from_utf8(decode(encoded.as_ref())?)?; let claims: T = from_str(&s)?; let map: Map<_, _> = from_str(&s)?; diff --git a/tests/ec/mod.rs b/tests/ecdsa/mod.rs similarity index 100% rename from tests/ec/mod.rs rename to tests/ecdsa/mod.rs diff --git a/tests/ec/private_ecdsa_key.pem b/tests/ecdsa/private_ecdsa_key.pem similarity index 100% rename from tests/ec/private_ecdsa_key.pem rename to tests/ecdsa/private_ecdsa_key.pem diff --git a/tests/ec/private_ecdsa_key.pk8 b/tests/ecdsa/private_ecdsa_key.pk8 similarity index 100% rename from tests/ec/private_ecdsa_key.pk8 rename to tests/ecdsa/private_ecdsa_key.pk8 diff --git a/tests/ec/public_ecdsa_key.pem b/tests/ecdsa/public_ecdsa_key.pem similarity index 100% rename from tests/ec/public_ecdsa_key.pem rename to tests/ecdsa/public_ecdsa_key.pem diff --git a/tests/ec/public_ecdsa_key.pk8 b/tests/ecdsa/public_ecdsa_key.pk8 similarity index 100% rename from tests/ec/public_ecdsa_key.pk8 rename to tests/ecdsa/public_ecdsa_key.pk8 diff --git a/tests/lib.rs b/tests/lib.rs index 30241b4..3760c89 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,2 +1,2 @@ -mod ec; +mod ecdsa; mod rsa; From b27981549f93a0f667e064cd3c7e2956a6a97277 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sat, 9 Nov 2019 11:42:40 +0000 Subject: [PATCH 13/24] Not working yet jwk decoding --- Cargo.toml | 2 +- src/crypto/ecdsa.rs | 2 +- src/crypto/mod.rs | 112 +++++++++++++++++------------------ src/decoding.rs | 136 +++++++++++++++++++++++++++++++++++++++++++ src/header.rs | 1 - src/jwk.rs | 31 ---------- src/lib.rs | 122 ++++---------------------------------- src/serialization.rs | 4 +- tests/rsa/mod.rs | 52 +++++------------ 9 files changed, 222 insertions(+), 240 deletions(-) create mode 100644 src/decoding.rs delete mode 100644 src/jwk.rs diff --git a/Cargo.toml b/Cargo.toml index 75d2314..de7847b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "jsonwebtoken" version = "7.0.0" -authors = ["Vincent Prouillet "] +authors = ["Vincent Prouillet "] license = "MIT" readme = "README.md" description = "Create and parse JWT in a strongly typed way." diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs index b1f00c9..28974e2 100644 --- a/src/crypto/ecdsa.rs +++ b/src/crypto/ecdsa.rs @@ -1,6 +1,6 @@ use ring::{rand, signature}; -use crate::errors::{Result}; +use crate::errors::Result; use crate::pem_decoder::PemEncodedKey; use crate::serialization::encode; diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 3ab788a..6ec34fc 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,46 +1,54 @@ use ring::constant_time::verify_slices_are_equal; use ring::{hmac, signature}; +use simple_asn1::BigUint; use crate::algorithms::Algorithm; -use crate::errors::{Result}; +use crate::errors::Result; use crate::pem_decoder::PemEncodedKey; -use crate::serialization::{encode, decode}; +use crate::serialization::{decode, encode}; -pub(crate) mod rsa; pub(crate) mod ecdsa; +pub(crate) mod rsa; /// The actual HS signing + encoding -pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], signing_input: &str) -> Result { - let digest = hmac::sign(&hmac::Key::new(alg, key), signing_input.as_bytes()); +pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &str) -> Result { + let digest = hmac::sign(&hmac::Key::new(alg, key), message.as_bytes()); Ok(encode(digest.as_ref())) } - /// Take the payload of a JWT, sign it using the algorithm given and return /// the base64 url safe encoded of the result. /// /// Only use this function if you want to do something other than JWT. /// `key` is the secret for HMAC and a pem encoded string otherwise -pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result { +pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result { match algorithm { - Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, signing_input), - Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, signing_input), - Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key, signing_input), + Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, message), + Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, message), + Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key, message), - Algorithm::ES256 => { - ecdsa::sign(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, signing_input) - } - Algorithm::ES384 => { - ecdsa::sign(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, signing_input) - } + Algorithm::ES256 => ecdsa::sign(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, message), + Algorithm::ES384 => ecdsa::sign(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, message), - Algorithm::RS256 => rsa::sign(&signature::RSA_PKCS1_SHA256, key, signing_input), - Algorithm::RS384 => rsa::sign(&signature::RSA_PKCS1_SHA384, key, signing_input), - Algorithm::RS512 => rsa::sign(&signature::RSA_PKCS1_SHA512, key, signing_input), + Algorithm::RS256 => rsa::sign(&signature::RSA_PKCS1_SHA256, key, message), + Algorithm::RS384 => rsa::sign(&signature::RSA_PKCS1_SHA384, key, message), + Algorithm::RS512 => rsa::sign(&signature::RSA_PKCS1_SHA512, key, message), - Algorithm::PS256 => rsa::sign(&signature::RSA_PSS_SHA256, key, signing_input), - Algorithm::PS384 => rsa::sign(&signature::RSA_PSS_SHA384, key, signing_input), - Algorithm::PS512 => rsa::sign(&signature::RSA_PSS_SHA512, key, signing_input), + Algorithm::PS256 => rsa::sign(&signature::RSA_PSS_SHA256, key, message), + Algorithm::PS384 => rsa::sign(&signature::RSA_PSS_SHA384, key, message), + Algorithm::PS512 => rsa::sign(&signature::RSA_PSS_SHA512, key, message), + } +} + +fn rsa_alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters { + match alg { + Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256, + Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384, + Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512, + Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256, + Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384, + Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512, + _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"), } } @@ -52,42 +60,21 @@ pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result Result { +/// For ECDSA/RSS, the `key` is the pem public key +pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) -> Result { match algorithm { Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { - // we just re-sign the data with the key and compare if they are equal - let signed = sign(signing_input, key, algorithm)?; + // we just re-sign the message with the key and compare if they are equal + let signed = sign(message, key, algorithm)?; Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok()) } Algorithm::ES256 => { - verify_ring_es(&signature::ECDSA_P256_SHA256_FIXED, signature, signing_input, key) + verify_ring_es(&signature::ECDSA_P256_SHA256_FIXED, signature, message, key) } Algorithm::ES384 => { - verify_ring_es(&signature::ECDSA_P384_SHA384_FIXED, signature, signing_input, key) - } - Algorithm::RS256 => { - verify_ring_rsa(&signature::RSA_PKCS1_2048_8192_SHA256, signature, signing_input, key) - } - Algorithm::RS384 => { - verify_ring_rsa(&signature::RSA_PKCS1_2048_8192_SHA384, signature, signing_input, key) - } - Algorithm::RS512 => { - verify_ring_rsa(&signature::RSA_PKCS1_2048_8192_SHA512, signature, signing_input, key) - } - Algorithm::PS256 => { - verify_ring_rsa(&signature::RSA_PSS_2048_8192_SHA256, signature, signing_input, key) - } - Algorithm::PS384 => { - verify_ring_rsa(&signature::RSA_PSS_2048_8192_SHA384, signature, signing_input, key) - } - Algorithm::PS512 => { - verify_ring_rsa(&signature::RSA_PSS_2048_8192_SHA512, signature, signing_input, key) + verify_ring_es(&signature::ECDSA_P384_SHA384_FIXED, signature, message, key) } + _ => verify_ring_rsa(rsa_alg_to_rsa_parameters(algorithm), signature, message, key), } } @@ -97,12 +84,12 @@ pub fn verify( fn verify_ring( alg: &'static dyn signature::VerificationAlgorithm, signature: &str, - signing_input: &str, + message: &str, key: &[u8], ) -> Result { let signature_bytes = decode(signature)?; let public_key = signature::UnparsedPublicKey::new(alg, key); - let res = public_key.verify(signing_input.as_bytes(), &signature_bytes); + let res = public_key.verify(message.as_bytes(), &signature_bytes); Ok(res.is_ok()) } @@ -110,19 +97,32 @@ fn verify_ring( fn verify_ring_es( alg: &'static dyn signature::VerificationAlgorithm, signature: &str, - signing_input: &str, + message: &str, key: &[u8], ) -> Result { let pem_key = PemEncodedKey::new(key)?; - verify_ring(alg, signature, signing_input, pem_key.as_ec_public_key()?) + verify_ring(alg, signature, message, pem_key.as_ec_public_key()?) } fn verify_ring_rsa( alg: &'static signature::RsaParameters, signature: &str, - signing_input: &str, + message: &str, key: &[u8], ) -> Result { let pem_key = PemEncodedKey::new(key)?; - verify_ring(alg, signature, signing_input, pem_key.as_rsa_key()?) + verify_ring(alg, signature, message, pem_key.as_rsa_key()?) +} + +pub fn verify_rsa_modulus_exponent( + alg: Algorithm, + signature: &str, + message: &str, + components: (&str, &str), +) -> Result { + let n = BigUint::from_bytes_be(&decode(components.0)?).to_bytes_be(); + let e = BigUint::from_bytes_be(&decode(components.1)?).to_bytes_be(); + let pubkey = signature::RsaPublicKeyComponents { n, e }; + let res = pubkey.verify(rsa_alg_to_rsa_parameters(alg), message.as_ref(), signature.as_ref()); + Ok(res.is_ok()) } diff --git a/src/decoding.rs b/src/decoding.rs new file mode 100644 index 0000000..0be1c4c --- /dev/null +++ b/src/decoding.rs @@ -0,0 +1,136 @@ +use serde::de::DeserializeOwned; + +use crate::crypto::{verify, verify_rsa_modulus_exponent}; +use crate::errors::{new_error, ErrorKind, Result}; +use crate::header::Header; +use crate::serialization::{from_jwt_part_claims, TokenData}; +use crate::validation::{validate, Validation}; + +/// Takes the result of a rsplit and ensure we only get 2 parts +/// Errors if we don't +macro_rules! expect_two { + ($iter:expr) => {{ + let mut i = $iter; + match (i.next(), i.next(), i.next()) { + (Some(first), Some(second), None) => (first, second), + _ => return Err(new_error(ErrorKind::InvalidToken)), + } + }}; +} + +/// Internal way to differentiate between public key types +enum DecodingKey<'a> { + SecretOrPem(&'a [u8]), + RsaModulusExponent { n: &'a str, e: &'a str }, +} + +fn _decode( + token: &str, + key: DecodingKey, + validation: &Validation, +) -> Result> { + let (signature, message) = expect_two!(token.rsplitn(2, '.')); + let (claims, header) = expect_two!(message.rsplitn(2, '.')); + let header = Header::from_encoded(header)?; + + if !validation.algorithms.contains(&header.alg) { + return Err(new_error(ErrorKind::InvalidAlgorithm)); + } + + let is_valid = match key { + DecodingKey::SecretOrPem(k) => verify(signature, message, k, header.alg), + DecodingKey::RsaModulusExponent { n, e } => { + verify_rsa_modulus_exponent(header.alg, signature, message, (n, e)) + } + }?; + + if !is_valid { + return Err(new_error(ErrorKind::InvalidSignature)); + } + + let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?; + validate(&claims_map, validation)?; + + 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 +/// use serde::{Deserialize, Serialize}; +/// 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_message = decode::(&token, "secret".as_ref(), &Validation::new(Algorithm::HS256)); +/// ``` +pub fn decode( + token: &str, + key: &[u8], + validation: &Validation, +) -> Result> { + _decode(token, DecodingKey::SecretOrPem(key), validation) +} + +/// TO TEST +pub fn decode_rsa_jwk( + token: &str, + modulus: &str, + exponent: &str, + validation: &Validation, +) -> Result> { + _decode(token, DecodingKey::RsaModulusExponent { n: modulus, e: exponent }, validation) +} + +/// 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. +/// +/// ```rust +/// use serde::{Deserialize, Serialize}; +/// use jsonwebtoken::{dangerous_unsafe_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_message = dangerous_unsafe_decode::(&token); +/// ``` +pub fn dangerous_unsafe_decode(token: &str) -> Result> { + let (_, message) = expect_two!(token.rsplitn(2, '.')); + let (claims, header) = expect_two!(message.rsplitn(2, '.')); + let header = Header::from_encoded(header)?; + + let (decoded_claims, _): (T, _) = from_jwt_part_claims(claims)?; + + Ok(TokenData { header, claims: decoded_claims }) +} + +/// Decode a token and return the Header. This is not doing any kind of validation: it is meant to be +/// used when you don't know which `alg` the token is using and want to find out. +/// +/// If the token has an invalid format, it will return an error. +/// +/// ```rust +/// use jsonwebtoken::decode_header; +/// +/// let token = "a.jwt.token".to_string(); +/// let header = decode_header(&token); +/// ``` +pub fn decode_header(token: &str) -> Result
{ + let (_, message) = expect_two!(token.rsplitn(2, '.')); + let (_, header) = expect_two!(message.rsplitn(2, '.')); + Header::from_encoded(header) +} diff --git a/src/header.rs b/src/header.rs index 553dbb3..0ced490 100644 --- a/src/header.rs +++ b/src/header.rs @@ -4,7 +4,6 @@ use crate::algorithms::Algorithm; use crate::errors::Result; use crate::serialization::decode; - /// A basic JWT header, the alg defaults to HS256 and typ is automatically /// set to `JWT`. All the other fields are optional. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/jwk.rs b/src/jwk.rs deleted file mode 100644 index bc331f9..0000000 --- a/src/jwk.rs +++ /dev/null @@ -1,31 +0,0 @@ -use serde::de::DeserializeOwned; - -use crate::validation::{Validation, validate}; -use crate::header::Header; -use crate::errors::{new_error, ErrorKind, Result}; -use crate::serialization::{from_jwt_part_claims, TokenData}; - - -pub fn decode_rsa_jwk( - token: &str, - modulus: &[u8], - exponent: &[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_encoded(header)?; - - if !verify(signature, signing_input, key, 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 }) -} diff --git a/src/lib.rs b/src/lib.rs index 604f6fc..fa963b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ mod algorithms; mod crypto; +mod decoding; /// All the errors pub mod errors; mod header; @@ -12,29 +13,26 @@ mod pem_decoder; mod pem_encoder; mod serialization; mod validation; -// mod jwk; pub use algorithms::Algorithm; pub use crypto::{sign, verify}; +pub use decoding::{dangerous_unsafe_decode, decode, decode_header, decode_rsa_jwk}; pub use header::Header; pub use serialization::TokenData; pub use validation::Validation; -use serde::de::DeserializeOwned; use serde::ser::Serialize; -use crate::errors::{new_error, ErrorKind, Result}; -use crate::serialization::{encode_part, from_jwt_part_claims}; -use crate::validation::validate; +use crate::errors::Result; +use crate::serialization::encode_part; /// Encode the header and claims given and sign the payload using the algorithm from the header and the key /// -/// ```rust,ignore -/// #[macro_use] -/// extern crate serde_derive; +/// ```rust +/// use serde::{Deserialize, Serialize}; /// use jsonwebtoken::{encode, Algorithm, Header}; /// -/// /// #[derive(Debug, Serialize, Deserialize)] +/// #[derive(Debug, Serialize, Deserialize)] /// struct Claims { /// sub: String, /// company: String @@ -52,110 +50,10 @@ use crate::validation::validate; pub fn encode(header: &Header, claims: &T, key: &[u8]) -> Result { let encoded_header = encode_part(&header)?; let encoded_claims = encode_part(&claims)?; - let signing_input = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); - let signature = sign(&*signing_input, key, header.alg)?; + let message = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); + let signature = sign(&*message, key, header.alg)?; - Ok([signing_input, signature].join(".")) -} - -/// Used in decode: takes the result of a rsplit and ensure we only get 2 parts -/// Errors if we don't -macro_rules! expect_two { - ($iter:expr) => {{ - let mut i = $iter; - match (i.next(), i.next(), i.next()) { - (Some(first), Some(second), None) => (first, second), - _ => return Err(new_error(ErrorKind::InvalidToken)), - } - }}; -} - -/// 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, Key::Hmac("secret"), &Validation::new(Algorithm::HS256)); -/// ``` -pub fn decode( - token: &str, - key: &[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_encoded(header)?; - - if !verify(signature, signing_input, key, 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. -/// -/// ```rust,ignore -/// #[macro_use] -/// extern crate serde_derive; -/// use jsonwebtoken::{dangerous_unsafe_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 = dangerous_unsafe_decode::(&token, &Validation::new(Algorithm::HS256)); -/// ``` -pub fn dangerous_unsafe_decode(token: &str) -> Result> { - let (_, signing_input) = expect_two!(token.rsplitn(2, '.')); - let (claims, header) = expect_two!(signing_input.rsplitn(2, '.')); - let header = Header::from_encoded(header)?; - - let (decoded_claims, _): (T, _) = from_jwt_part_claims(claims)?; - - Ok(TokenData { header, claims: decoded_claims }) -} - -/// Decode a token and return the Header. This is not doing any kind of validation: it is meant to be -/// used when you don't know which `alg` the token is using and want to find out. -/// -/// If the token has an invalid format, it will return an error. -/// -/// ```rust,ignore -/// use jsonwebtoken::decode_header; -/// -/// let token = "a.jwt.token".to_string(); -/// let header = decode_header(&token); -/// ``` -pub fn decode_header(token: &str) -> Result
{ - let (_, signing_input) = expect_two!(token.rsplitn(2, '.')); - let (_, header) = expect_two!(signing_input.rsplitn(2, '.')); - Header::from_encoded(header) + Ok([message, signature].join(".")) } /// TODO diff --git a/src/serialization.rs b/src/serialization.rs index 0f814d4..a3a7d4d 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -37,6 +37,6 @@ pub(crate) fn from_jwt_part_claims, T: DeserializeOwned>( let s = String::from_utf8(decode(encoded.as_ref())?)?; let claims: T = from_str(&s)?; - let map: Map<_, _> = from_str(&s)?; - Ok((claims, map)) + let validation_map: Map<_, _> = from_str(&s)?; + Ok((claims, validation_map)) } diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 83c4fde..6cdb98c 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,5 +1,5 @@ use chrono::Utc; -use jsonwebtoken::{decode, encode, sign, verify, Algorithm, Header, Validation}; +use jsonwebtoken::{decode, decode_rsa_jwk, encode, sign, verify, Algorithm, Header, Validation}; use serde::{Deserialize, Serialize}; const RSA_ALGORITHMS: &[Algorithm] = &[ @@ -64,41 +64,21 @@ fn round_trip_claim() { } } -//#[test] -//fn rsa_modulus_exponent() { -// 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 privkey = include_bytes!("private_rsa_key.der"); -// -// let encrypted = sign("hello world", Key::Der(&privkey[..]), Algorithm::RS256).unwrap(); -// let is_valid = verify( -// &encrypted, -// "hello world", -// Key::ModulusExponent(&modulus, &exponent), -// Algorithm::RS256, -// ) -// .unwrap(); -// assert!(is_valid); -//} +#[test] +fn rsa_modulus_exponent() { + let privkey = include_str!("private_rsa_key_pkcs1.pem"); + let my_claims = Claims { + sub: "b@b.com".to_string(), + company: "ACME".to_string(), + exp: Utc::now().timestamp() + 10000, + }; + let n = "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ"; + let e = "AQAB"; + + let encrypted = encode(&Header::new(Algorithm::RS256), &my_claims, privkey.as_ref()).unwrap(); + let res = decode_rsa_jwk::(&encrypted, n, e, &Validation::new(Algorithm::RS256)); + assert!(res.is_ok()); +} #[test] #[should_panic(expected = "InvalidKeyFormat")] From 614f3610a72b752d9241b06bcaf7c1f27910400a Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Mon, 11 Nov 2019 12:08:11 +0100 Subject: [PATCH 14/24] Fix stupid bug --- src/crypto/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 6ec34fc..8f624aa 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -120,9 +120,10 @@ pub fn verify_rsa_modulus_exponent( message: &str, components: (&str, &str), ) -> Result { + let signature_bytes = decode(signature)?; let n = BigUint::from_bytes_be(&decode(components.0)?).to_bytes_be(); let e = BigUint::from_bytes_be(&decode(components.1)?).to_bytes_be(); let pubkey = signature::RsaPublicKeyComponents { n, e }; - let res = pubkey.verify(rsa_alg_to_rsa_parameters(alg), message.as_ref(), signature.as_ref()); + let res = pubkey.verify(rsa_alg_to_rsa_parameters(alg), message.as_ref(), &signature_bytes); Ok(res.is_ok()) } From 8169ee3d9f15295202f683daf762522a6c093e2e Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Mon, 11 Nov 2019 19:47:35 +0100 Subject: [PATCH 15/24] Remove chrono from deps --- CHANGELOG.md | 1 - Cargo.toml | 14 ++++++++------ src/validation.rs | 37 +++++++++++++++++++++---------------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2aefd9..acd53a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ - Add support for PS256, PS384 and PS512 - Add support for verifying with modulus/exponent components for RSA -- Change API for both sign/verify to take a `Key` enum rather than bytes - Update to 2018 edition - Changed aud field type in Validation to `Option>`. Audience validation now tests for "any-of-these" audience membership. diff --git a/Cargo.toml b/Cargo.toml index de7847b..3ed0158 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,10 @@ version = "7.0.0" authors = ["Vincent Prouillet "] license = "MIT" readme = "README.md" -description = "Create and parse JWT in a strongly typed way." -homepage = "https://github.com/Keats/rust-jwt" -repository = "https://github.com/Keats/rust-jwt" -keywords = ["jwt", "web", "api", "token", "json"] +description = "Create and decode JWTs in a strongly typed way." +homepage = "https://github.com/Keats/jsonwebtoken" +repository = "https://github.com/Keats/jsonwebtoken" +keywords = ["jwt", "web", "api", "token", "json", "jwk"] edition = "2018" [dependencies] @@ -15,8 +15,10 @@ serde_json = "1.0" serde = {version = "1.0", features = ["derive"] } ring = { version = "0.16.5", features = ["std"] } base64 = "0.11" -# For validation -chrono = "0.4" # For PEM decoding pem = "0.7" simple_asn1 = "0.4" + +[dev-dependencies] +# For the custom chrono example +chrono = "0.4" diff --git a/src/validation.rs b/src/validation.rs index 280fd72..8488634 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -1,14 +1,15 @@ -use chrono::Utc; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::collections::HashSet; + use serde_json::map::Map; use serde_json::{from_value, Value}; -use std::collections::HashSet; use crate::algorithms::Algorithm; use crate::errors::{new_error, ErrorKind, Result}; /// Contains the various validations that are applied after decoding a token. /// -/// All time validation happen on UTC timestamps. +/// All time validation happen on UTC timestamps as seconds. /// /// ```rust /// use jsonwebtoken::Validation; @@ -30,7 +31,7 @@ pub struct Validation { /// account for clock skew. /// /// Defaults to `0`. - pub leeway: i64, + pub leeway: u64, /// Whether to validate the `exp` field. /// /// It will return an error if the time in the `exp` field is past. @@ -96,12 +97,17 @@ impl Default for Validation { } } +fn get_current_timestamp() -> u64 { + let start = SystemTime::now(); + start.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() +} + pub fn validate(claims: &Map, options: &Validation) -> Result<()> { - let now = Utc::now().timestamp(); + let now = get_current_timestamp(); if options.validate_exp { if let Some(exp) = claims.get("exp") { - if from_value::(exp.clone())? < now - options.leeway { + if from_value::(exp.clone())? < now - options.leeway { return Err(new_error(ErrorKind::ExpiredSignature)); } } else { @@ -111,7 +117,7 @@ pub fn validate(claims: &Map, options: &Validation) -> Result<()> if options.validate_nbf { if let Some(nbf) = claims.get("nbf") { - if from_value::(nbf.clone())? > now + options.leeway { + if from_value::(nbf.clone())? > now + options.leeway { return Err(new_error(ErrorKind::ImmatureSignature)); } } else { @@ -155,18 +161,17 @@ pub fn validate(claims: &Map, options: &Validation) -> Result<()> #[cfg(test)] mod tests { - use chrono::Utc; use serde_json::map::Map; use serde_json::to_value; - use super::{validate, Validation}; + use super::{validate, Validation, get_current_timestamp}; use crate::errors::ErrorKind; #[test] fn exp_in_future_ok() { let mut claims = Map::new(); - claims.insert("exp".to_string(), to_value(Utc::now().timestamp() + 10000).unwrap()); + claims.insert("exp".to_string(), to_value(get_current_timestamp() + 10000).unwrap()); let res = validate(&claims, &Validation::default()); assert!(res.is_ok()); } @@ -174,7 +179,7 @@ mod tests { #[test] fn exp_in_past_fails() { let mut claims = Map::new(); - claims.insert("exp".to_string(), to_value(Utc::now().timestamp() - 100000).unwrap()); + claims.insert("exp".to_string(), to_value(get_current_timestamp() - 100000).unwrap()); let res = validate(&claims, &Validation::default()); assert!(res.is_err()); @@ -187,7 +192,7 @@ mod tests { #[test] fn exp_in_past_but_in_leeway_ok() { let mut claims = Map::new(); - claims.insert("exp".to_string(), to_value(Utc::now().timestamp() - 500).unwrap()); + claims.insert("exp".to_string(), to_value(get_current_timestamp() - 500).unwrap()); let validation = Validation { leeway: 1000 * 60, ..Default::default() }; let res = validate(&claims, &validation); assert!(res.is_ok()); @@ -208,7 +213,7 @@ mod tests { #[test] fn nbf_in_past_ok() { let mut claims = Map::new(); - claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() - 10000).unwrap()); + claims.insert("nbf".to_string(), to_value(get_current_timestamp() - 10000).unwrap()); let validation = Validation { validate_exp: false, validate_nbf: true, ..Validation::default() }; let res = validate(&claims, &validation); @@ -218,7 +223,7 @@ mod tests { #[test] fn nbf_in_future_fails() { let mut claims = Map::new(); - claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() + 100000).unwrap()); + claims.insert("nbf".to_string(), to_value(get_current_timestamp() + 100000).unwrap()); let validation = Validation { validate_exp: false, validate_nbf: true, ..Validation::default() }; let res = validate(&claims, &validation); @@ -233,7 +238,7 @@ mod tests { #[test] fn nbf_in_future_but_in_leeway_ok() { let mut claims = Map::new(); - claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() + 500).unwrap()); + claims.insert("nbf".to_string(), to_value(get_current_timestamp() + 500).unwrap()); let validation = Validation { leeway: 1000 * 60, validate_nbf: true, @@ -408,7 +413,7 @@ mod tests { #[test] fn does_validation_in_right_order() { let mut claims = Map::new(); - claims.insert("exp".to_string(), to_value(Utc::now().timestamp() + 10000).unwrap()); + claims.insert("exp".to_string(), to_value(get_current_timestamp() + 10000).unwrap()); let v = Validation { leeway: 5, validate_exp: true, From 1f6d0ffb2c6afafdc765b934444ec80bb5f1bfcd Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Mon, 11 Nov 2019 20:16:34 +0100 Subject: [PATCH 16/24] Refactor decoding --- Cargo.toml | 3 ++ src/crypto/ecdsa.rs | 19 ++++++++--- src/crypto/mod.rs | 79 ++++++++++++++------------------------------ src/crypto/rsa.rs | 39 +++++++++++++++++++--- src/decoding.rs | 4 +-- src/header.rs | 4 +-- src/lib.rs | 6 ++-- src/serialization.rs | 10 +++--- 8 files changed, 89 insertions(+), 75 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3ed0158..aa66e7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,6 @@ simple_asn1 = "0.4" [dev-dependencies] # For the custom chrono example chrono = "0.4" + +[badges] +maintenance = { status = "passively-developed" } diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs index 28974e2..eb7aa05 100644 --- a/src/crypto/ecdsa.rs +++ b/src/crypto/ecdsa.rs @@ -2,17 +2,28 @@ use ring::{rand, signature}; use crate::errors::Result; use crate::pem_decoder::PemEncodedKey; -use crate::serialization::encode; +use crate::serialization::b64_encode; +use crate::algorithms::Algorithm; + + +/// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs. +pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static signature::EcdsaVerificationAlgorithm { + match alg { + Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED, + Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED, + _ => unreachable!("Tried to get EC signature for a non-EC algorithm"), + } +} /// The actual ECDSA signing + encoding pub fn sign( alg: &'static signature::EcdsaSigningAlgorithm, key: &[u8], - signing_input: &str, + message: &str, ) -> Result { let pem_key = PemEncodedKey::new(key)?; let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, pem_key.as_ec_private_key()?)?; let rng = rand::SystemRandom::new(); - let out = signing_key.sign(&rng, signing_input.as_bytes())?; - Ok(encode(out.as_ref())) + let out = signing_key.sign(&rng, message.as_bytes())?; + Ok(b64_encode(out.as_ref())) } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 8f624aa..4c1132d 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,19 +1,19 @@ use ring::constant_time::verify_slices_are_equal; use ring::{hmac, signature}; -use simple_asn1::BigUint; use crate::algorithms::Algorithm; use crate::errors::Result; use crate::pem_decoder::PemEncodedKey; -use crate::serialization::{decode, encode}; +use crate::serialization::{b64_decode, b64_encode}; pub(crate) mod ecdsa; pub(crate) mod rsa; /// The actual HS signing + encoding +/// Could be in its own file to match RSA/EC but it's 2 lines... pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &str) -> Result { let digest = hmac::sign(&hmac::Key::new(alg, key), message.as_bytes()); - Ok(encode(digest.as_ref())) + Ok(b64_encode(digest.as_ref())) } /// Take the payload of a JWT, sign it using the algorithm given and return @@ -40,27 +40,16 @@ pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result { } } -fn rsa_alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters { - match alg { - Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256, - Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384, - Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512, - Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256, - Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384, - Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512, - _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"), - } -} - /// Compares the signature given with a re-computed signature for HMAC or using the public key -/// for RSA. +/// for RSA/EC. /// /// Only use this function if you want to do something other than JWT. /// /// `signature` is the signature part of a jwt (text after the second '.') /// -/// `signing_input` is base64(header) + "." + base64(claims) -/// For ECDSA/RSS, the `key` is the pem public key +/// `message` is base64(header) + "." + base64(claims) +/// For ECDSA/RSA, the `key` is the pem public key. If you want to verify using the public key +/// components (modulus/exponent), use `verify_rsa_components` instead. pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) -> Result { match algorithm { Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => { @@ -68,17 +57,18 @@ pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) let signed = sign(message, key, algorithm)?; Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok()) } - Algorithm::ES256 => { - verify_ring_es(&signature::ECDSA_P256_SHA256_FIXED, signature, message, key) + Algorithm::ES256 | Algorithm::ES384 => { + let pem_key = PemEncodedKey::new(key)?; + verify_ring(ecdsa::alg_to_ec_verification(algorithm), signature, message, pem_key.as_ec_public_key()?) } - Algorithm::ES384 => { - verify_ring_es(&signature::ECDSA_P384_SHA384_FIXED, signature, message, key) - } - _ => verify_ring_rsa(rsa_alg_to_rsa_parameters(algorithm), signature, message, key), + // only RSAs left + _ => { + let pem_key = PemEncodedKey::new(key)?; + verify_ring(rsa::alg_to_rsa_parameters(algorithm), signature, message, pem_key.as_rsa_key()?) + }, } } -// TODO: see if we can remove stuff? /// See Ring docs for more details fn verify_ring( @@ -87,43 +77,24 @@ fn verify_ring( message: &str, key: &[u8], ) -> Result { - let signature_bytes = decode(signature)?; + let signature_bytes = b64_decode(signature)?; let public_key = signature::UnparsedPublicKey::new(alg, key); let res = public_key.verify(message.as_bytes(), &signature_bytes); Ok(res.is_ok()) } -fn verify_ring_es( - alg: &'static dyn signature::VerificationAlgorithm, - signature: &str, - message: &str, - key: &[u8], -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - verify_ring(alg, signature, message, pem_key.as_ec_public_key()?) -} - -fn verify_ring_rsa( - alg: &'static signature::RsaParameters, - signature: &str, - message: &str, - key: &[u8], -) -> Result { - let pem_key = PemEncodedKey::new(key)?; - verify_ring(alg, signature, message, pem_key.as_rsa_key()?) -} - -pub fn verify_rsa_modulus_exponent( - alg: Algorithm, +/// Verify the signature given using the (n, e) components of a RSA public key. +/// +/// `signature` is the signature part of a jwt (text after the second '.') +/// +/// `message` is base64(header) + "." + base64(claims) +pub fn verify_rsa_components( signature: &str, message: &str, components: (&str, &str), + alg: Algorithm, ) -> Result { - let signature_bytes = decode(signature)?; - let n = BigUint::from_bytes_be(&decode(components.0)?).to_bytes_be(); - let e = BigUint::from_bytes_be(&decode(components.1)?).to_bytes_be(); - let pubkey = signature::RsaPublicKeyComponents { n, e }; - let res = pubkey.verify(rsa_alg_to_rsa_parameters(alg), message.as_ref(), &signature_bytes); - Ok(res.is_ok()) + let signature_bytes = b64_decode(signature)?; + rsa::verify_from_components(&signature_bytes, message, components, alg) } diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs index bc4a78c..a7241a2 100644 --- a/src/crypto/rsa.rs +++ b/src/crypto/rsa.rs @@ -1,15 +1,32 @@ use ring::{rand, signature}; +use simple_asn1::BigUint; use crate::errors::{ErrorKind, Result}; use crate::pem_decoder::PemEncodedKey; -use crate::serialization::encode; +use crate::serialization::{b64_encode, b64_decode}; +use crate::algorithms::Algorithm; + + +/// Only used internally when validating RSA, to map from our enum to the Ring param structs. +pub(crate) fn alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters { + match alg { + Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256, + Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384, + Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512, + Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256, + Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384, + Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512, + _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"), + } +} + /// The actual RSA signing + encoding /// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html -pub fn sign( +pub(crate) fn sign( alg: &'static dyn signature::RsaEncoding, key: &[u8], - signing_input: &str, + message: &str, ) -> Result { let pem_key = PemEncodedKey::new(key)?; let key_pair = signature::RsaKeyPair::from_der(pem_key.as_rsa_key()?) @@ -18,8 +35,20 @@ pub fn sign( let mut signature = vec![0; key_pair.public_modulus_len()]; let rng = rand::SystemRandom::new(); key_pair - .sign(alg, &rng, signing_input.as_bytes(), &mut signature) + .sign(alg, &rng, message.as_bytes(), &mut signature) .map_err(|_| ErrorKind::InvalidRsaKey)?; - Ok(encode(&signature)) + Ok(b64_encode(&signature)) +} + +pub(crate) fn verify_from_components( + signature_bytes: &[u8], + message: &str, + components: (&str, &str), + alg: Algorithm) -> Result { + let n = BigUint::from_bytes_be(&b64_decode(components.0)?).to_bytes_be(); + let e = BigUint::from_bytes_be(&b64_decode(components.1)?).to_bytes_be(); + let pubkey = signature::RsaPublicKeyComponents { n, e }; + let res = pubkey.verify(alg_to_rsa_parameters(alg), message.as_ref(), &signature_bytes); + Ok(res.is_ok()) } diff --git a/src/decoding.rs b/src/decoding.rs index 0be1c4c..c2934cc 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -1,6 +1,6 @@ use serde::de::DeserializeOwned; -use crate::crypto::{verify, verify_rsa_modulus_exponent}; +use crate::crypto::{verify, verify_rsa_components}; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; use crate::serialization::{from_jwt_part_claims, TokenData}; @@ -40,7 +40,7 @@ fn _decode( let is_valid = match key { DecodingKey::SecretOrPem(k) => verify(signature, message, k, header.alg), DecodingKey::RsaModulusExponent { n, e } => { - verify_rsa_modulus_exponent(header.alg, signature, message, (n, e)) + verify_rsa_components(signature, message, (n, e), header.alg) } }?; diff --git a/src/header.rs b/src/header.rs index 0ced490..c6ac473 100644 --- a/src/header.rs +++ b/src/header.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::algorithms::Algorithm; use crate::errors::Result; -use crate::serialization::decode; +use crate::serialization::b64_decode; /// A basic JWT header, the alg defaults to HS256 and typ is automatically /// set to `JWT`. All the other fields are optional. @@ -60,7 +60,7 @@ impl Header { /// Converts an encoded part into the Header struct if possible pub(crate) fn from_encoded(encoded_part: &str) -> Result { - let decoded = decode(encoded_part)?; + let decoded = b64_decode(encoded_part)?; let s = String::from_utf8(decoded)?; Ok(serde_json::from_str(&s)?) diff --git a/src/lib.rs b/src/lib.rs index fa963b5..4e067f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ pub use validation::Validation; use serde::ser::Serialize; use crate::errors::Result; -use crate::serialization::encode_part; +use crate::serialization::b64_encode_part; /// Encode the header and claims given and sign the payload using the algorithm from the header and the key /// @@ -48,8 +48,8 @@ use crate::serialization::encode_part; /// let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap(); /// ``` pub fn encode(header: &Header, claims: &T, key: &[u8]) -> Result { - let encoded_header = encode_part(&header)?; - let encoded_claims = encode_part(&claims)?; + let encoded_header = b64_encode_part(&header)?; + let encoded_claims = b64_encode_part(&claims)?; let message = [encoded_header.as_ref(), encoded_claims.as_ref()].join("."); let signature = sign(&*message, key, header.alg)?; diff --git a/src/serialization.rs b/src/serialization.rs index a3a7d4d..22f83f4 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -15,18 +15,18 @@ pub struct TokenData { pub claims: T, } -pub(crate) fn encode(input: &[u8]) -> String { +pub(crate) fn b64_encode(input: &[u8]) -> String { base64::encode_config(input, base64::URL_SAFE_NO_PAD) } -pub(crate) fn decode(input: &str) -> Result> { +pub(crate) fn b64_decode(input: &str) -> Result> { base64::decode_config(input, base64::URL_SAFE_NO_PAD).map_err(|e| e.into()) } /// Serializes a struct to JSON and encodes it in base64 -pub(crate) fn encode_part(input: &T) -> Result { +pub(crate) fn b64_encode_part(input: &T) -> Result { let json = to_string(input)?; - Ok(encode(json.as_bytes())) + Ok(b64_encode(json.as_bytes())) } /// Decodes from base64 and deserializes from JSON to a struct AND a hashmap of Value so we can @@ -34,7 +34,7 @@ pub(crate) fn encode_part(input: &T) -> Result { pub(crate) fn from_jwt_part_claims, T: DeserializeOwned>( encoded: B, ) -> Result<(T, Map)> { - let s = String::from_utf8(decode(encoded.as_ref())?)?; + let s = String::from_utf8(b64_decode(encoded.as_ref())?)?; let claims: T = from_str(&s)?; let validation_map: Map<_, _> = from_str(&s)?; From 8e4757cb1dace5b616eff07d7b4274880a13d387 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Mon, 11 Nov 2019 20:29:57 +0100 Subject: [PATCH 17/24] More refactoring in the crypto mod --- src/crypto/ecdsa.rs | 18 ++++++++--- src/crypto/mod.rs | 73 ++++++++++++++++++++++++++------------------- src/crypto/rsa.rs | 22 ++++++++++---- src/decoding.rs | 4 +-- src/validation.rs | 4 +-- 5 files changed, 78 insertions(+), 43 deletions(-) diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs index eb7aa05..ba2f0ff 100644 --- a/src/crypto/ecdsa.rs +++ b/src/crypto/ecdsa.rs @@ -1,17 +1,27 @@ use ring::{rand, signature}; +use crate::algorithms::Algorithm; use crate::errors::Result; use crate::pem_decoder::PemEncodedKey; use crate::serialization::b64_encode; -use crate::algorithms::Algorithm; - /// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs. -pub(crate) fn alg_to_ec_verification(alg: Algorithm) -> &'static signature::EcdsaVerificationAlgorithm { +pub(crate) fn alg_to_ec_verification( + alg: Algorithm, +) -> &'static signature::EcdsaVerificationAlgorithm { match alg { Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED, Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED, - _ => unreachable!("Tried to get EC signature for a non-EC algorithm"), + _ => unreachable!("Tried to get EC alg for a non-EC algorithm"), + } +} + +/// Only used internally when signing EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs. +pub(crate) fn alg_to_ec_signing(alg: Algorithm) -> &'static signature::EcdsaSigningAlgorithm { + match alg { + Algorithm::ES256 => &signature::ECDSA_P256_SHA256_FIXED_SIGNING, + Algorithm::ES384 => &signature::ECDSA_P384_SHA384_FIXED_SIGNING, + _ => unreachable!("Tried to get EC alg for a non-EC algorithm"), } } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 4c1132d..6667ee4 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -27,19 +27,33 @@ pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result { Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, message), Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key, message), - Algorithm::ES256 => ecdsa::sign(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, message), - Algorithm::ES384 => ecdsa::sign(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, message), + Algorithm::ES256 | Algorithm::ES384 => { + ecdsa::sign(ecdsa::alg_to_ec_signing(algorithm), key, message) + } - Algorithm::RS256 => rsa::sign(&signature::RSA_PKCS1_SHA256, key, message), - Algorithm::RS384 => rsa::sign(&signature::RSA_PKCS1_SHA384, key, message), - Algorithm::RS512 => rsa::sign(&signature::RSA_PKCS1_SHA512, key, message), - - Algorithm::PS256 => rsa::sign(&signature::RSA_PSS_SHA256, key, message), - Algorithm::PS384 => rsa::sign(&signature::RSA_PSS_SHA384, key, message), - Algorithm::PS512 => rsa::sign(&signature::RSA_PSS_SHA512, key, message), + Algorithm::RS256 + | Algorithm::RS384 + | Algorithm::RS512 + | Algorithm::PS256 + | Algorithm::PS384 + | Algorithm::PS512 => rsa::sign(rsa::alg_to_rsa_signing(algorithm), key, message), } } +/// See Ring docs for more details +fn verify_ring( + alg: &'static dyn signature::VerificationAlgorithm, + signature: &str, + message: &str, + key: &[u8], +) -> Result { + let signature_bytes = b64_decode(signature)?; + let public_key = signature::UnparsedPublicKey::new(alg, key); + let res = public_key.verify(message.as_bytes(), &signature_bytes); + + Ok(res.is_ok()) +} + /// Compares the signature given with a re-computed signature for HMAC or using the public key /// for RSA/EC. /// @@ -59,31 +73,30 @@ pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) } Algorithm::ES256 | Algorithm::ES384 => { let pem_key = PemEncodedKey::new(key)?; - verify_ring(ecdsa::alg_to_ec_verification(algorithm), signature, message, pem_key.as_ec_public_key()?) + verify_ring( + ecdsa::alg_to_ec_verification(algorithm), + signature, + message, + pem_key.as_ec_public_key()?, + ) } - // only RSAs left - _ => { + Algorithm::RS256 + | Algorithm::RS384 + | Algorithm::RS512 + | Algorithm::PS256 + | Algorithm::PS384 + | Algorithm::PS512 => { let pem_key = PemEncodedKey::new(key)?; - verify_ring(rsa::alg_to_rsa_parameters(algorithm), signature, message, pem_key.as_rsa_key()?) - }, + verify_ring( + rsa::alg_to_rsa_parameters(algorithm), + signature, + message, + pem_key.as_rsa_key()?, + ) + } } } - -/// See Ring docs for more details -fn verify_ring( - alg: &'static dyn signature::VerificationAlgorithm, - signature: &str, - message: &str, - key: &[u8], -) -> Result { - let signature_bytes = b64_decode(signature)?; - let public_key = signature::UnparsedPublicKey::new(alg, key); - let res = public_key.verify(message.as_bytes(), &signature_bytes); - - Ok(res.is_ok()) -} - /// Verify the signature given using the (n, e) components of a RSA public key. /// /// `signature` is the signature part of a jwt (text after the second '.') @@ -96,5 +109,5 @@ pub fn verify_rsa_components( alg: Algorithm, ) -> Result { let signature_bytes = b64_decode(signature)?; - rsa::verify_from_components(&signature_bytes, message, components, alg) + rsa::verify_from_components(rsa::alg_to_rsa_parameters(alg), &signature_bytes, message, components) } diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs index a7241a2..95e946b 100644 --- a/src/crypto/rsa.rs +++ b/src/crypto/rsa.rs @@ -1,11 +1,10 @@ use ring::{rand, signature}; use simple_asn1::BigUint; +use crate::algorithms::Algorithm; use crate::errors::{ErrorKind, Result}; use crate::pem_decoder::PemEncodedKey; -use crate::serialization::{b64_encode, b64_decode}; -use crate::algorithms::Algorithm; - +use crate::serialization::{b64_decode, b64_encode}; /// Only used internally when validating RSA, to map from our enum to the Ring param structs. pub(crate) fn alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters { @@ -20,6 +19,18 @@ pub(crate) fn alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaPa } } +/// Only used internally when signing with RSA, to map from our enum to the Ring signing structs. +pub(crate) fn alg_to_rsa_signing(alg: Algorithm) -> &'static dyn signature::RsaEncoding { + match alg { + Algorithm::RS256 => &signature::RSA_PKCS1_SHA256, + Algorithm::RS384 => &signature::RSA_PKCS1_SHA384, + Algorithm::RS512 => &signature::RSA_PKCS1_SHA512, + Algorithm::PS256 => &signature::RSA_PSS_SHA256, + Algorithm::PS384 => &signature::RSA_PSS_SHA384, + Algorithm::PS512 => &signature::RSA_PSS_SHA512, + _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"), + } +} /// The actual RSA signing + encoding /// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html @@ -42,13 +53,14 @@ pub(crate) fn sign( } pub(crate) fn verify_from_components( + alg: &'static signature::RsaParameters, signature_bytes: &[u8], message: &str, components: (&str, &str), - alg: Algorithm) -> Result { +) -> Result { let n = BigUint::from_bytes_be(&b64_decode(components.0)?).to_bytes_be(); let e = BigUint::from_bytes_be(&b64_decode(components.1)?).to_bytes_be(); let pubkey = signature::RsaPublicKeyComponents { n, e }; - let res = pubkey.verify(alg_to_rsa_parameters(alg), message.as_ref(), &signature_bytes); + let res = pubkey.verify(alg, message.as_ref(), &signature_bytes); Ok(res.is_ok()) } diff --git a/src/decoding.rs b/src/decoding.rs index c2934cc..e79847d 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -118,8 +118,8 @@ pub fn dangerous_unsafe_decode(token: &str) -> Result Date: Thu, 14 Nov 2019 19:43:43 +0100 Subject: [PATCH 18/24] Update docs --- README.md | 116 +++++++++++++------------ benches/jwt.rs | 8 +- src/algorithms.rs | 2 +- src/crypto/ecdsa.rs | 2 +- src/crypto/mod.rs | 14 ++- src/crypto/rsa.rs | 2 +- src/decoding.rs | 43 +++++++-- src/errors.rs | 4 +- src/lib.rs | 49 +++-------- src/{pem_decoder.rs => pem/decoder.rs} | 3 - src/{pem_encoder.rs => pem/encoder.rs} | 16 ++-- src/pem/mod.rs | 34 ++++++++ src/serialization.rs | 10 --- src/validation.rs | 2 +- tests/ecdsa/mod.rs | 5 +- tests/hmac.rs | 4 +- tests/rsa/mod.rs | 7 +- 17 files changed, 175 insertions(+), 146 deletions(-) rename src/{pem_decoder.rs => pem/decoder.rs} (99%) rename src/{pem_encoder.rs => pem/encoder.rs} (93%) create mode 100644 src/pem/mod.rs diff --git a/README.md b/README.md index 2f5359d..9834cc3 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,7 @@ Add the following to Cargo.toml: ```toml jsonwebtoken = "7" -serde_derive = "1" -serde = "1" +serde = {version = "1.0", features = ["derive"] } ``` ## How to use @@ -18,11 +17,8 @@ Complete examples are available in the examples directory: a basic one and one w In terms of imports and structs: ```rust -extern crate jsonwebtoken as jwt; -#[macro_use] -extern crate serde_derive; - -use jwt::{encode, decode, Header, Algorithm, Validation}; +use serde::{Serialize, Deserialize}; +use jsonwebtoken::{encode, decode, Header, Algorithm, Validation}; /// Our claims struct, it needs to derive `Serialize` and/or `Deserialize` #[derive(Debug, Serialize, Deserialize)] @@ -33,7 +29,7 @@ struct Claims { } ``` -### Encoding +### Header The default algorithm is HS256. ```rust @@ -45,38 +41,84 @@ All the parameters from the RFC are supported but the default header only has `t If you want to set the `kid` parameter or change the algorithm for example: ```rust -let mut header = Header::default(); +let mut header = Header::new(Algorithm::HS512); header.kid = Some("blabla".to_owned()); -header.alg = Algorithm::HS512; let token = encode(&header, &my_claims, "secret".as_ref())?; ``` Look at `examples/custom_header.rs` for a full working example. +### Encoding + +```rust +// HS256 +let token = encode(&Header::default(), &my_claims, "secret".as_ref())?; +// RSA +let token = encode(&Header::new(Algorithm::RS256), &my_claims, include_str!("privkey.pem"))?; +``` +Encoding a JWT takes 3 parameters: + +- a header: the `Header` struct +- some claims: your own struct +- a key + +When using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using +RSA/EC, the key should always be the content of the private key in the PEM format. + ### Decoding + ```rust let token = decode::(&token, "secret".as_ref(), &Validation::default())?; -// token is a struct with 2 params: header and claims +// token is a struct with 2 fields: `header` and `claims` and `claims` is your own struct. ``` `decode` can error for a variety of reasons: - the token or its signature is invalid -- error while decoding base64 or the result of decoding base64 is not valid UTF-8 +- the token had invalid base64 - validation of at least one reserved claim failed -In some cases, for example if you don't know the algorithm used, you will want to only decode the header: +As with encoding, when using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using +RSA/EC, the key should always be the content of the public key in the PEM format. + +In some cases, for example if you don't know the algorithm used or need to grab the `kid`, you can decode only the header: ```rust let header = decode_header(&token)?; ``` -This does not perform any validation on the token. +This does not perform any signature verification/validations on the token so it could have been tampered with. + +You can also decode a token using the public key components of a RSA key in base64 format. +The main use-case is for JWK where your public key is a JSON format like so: + +```json +{ + "kty":"RSA", + "e":"AQAB", + "kid":"6a7a119f-0876-4f7e-8d0f-bf3ea1391dd8", + "n":"yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ" +} +``` + +```rust +let token = decode_rsa_components::(&token, jwk["n"], jwk["e"], &Validation::new(Algorithm::RS256))?; +// token is a struct with 2 fields: `header` and `claims` and `claims` is your own struct. +``` + +### Convertion .der to .pem + +You can use openssl for that: + +```bash +openssl rsa -inform DER -outform PEM -in mykey.der -out mykey.pem +``` + #### Validation This library validates automatically the `exp` claim. `nbf` is also validated if present. You can also validate the `sub`, `iss` and `aud` but those require setting the expected value in the `Validation` struct. Since validating time fields is always a bit tricky due to clock skew, -you can add some leeway to the `iat`, `exp` and `nbf` validation by setting a `leeway` parameter. +you can add some leeway to the `iat`, `exp` and `nbf` validation by setting the `leeway` field. Last but not least, you will need to set the algorithm(s) allowed for this token if you are not using `HS256`. @@ -112,44 +154,6 @@ This library currently supports the following: - ES256 - ES384 -### RSA -`jsonwebtoken` can read DER and PEM encoded keys. - -#### DER Encoded -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 `. - -```bash -// private key -$ openssl rsa -in private_rsa_key.pem -outform DER -out private_rsa_key.der -// public key -$ openssl rsa -in private_rsa_key.der -inform DER -RSAPublicKey_out -outform DER -out public_key.der -``` - -If you are getting an error with your public key, make sure you get it by using the command above to ensure -it is in the right format. - -#### PEM Encoded -To generate a PKCS#1 RSA key, run `openssl genrsa -out private_rsa_key_pkcs1.pem 2048` -To convert a PKCS#1 RSA key to a PKCS#8 RSA key, run `openssl pkcs8 -topk8 -inform pem -in private_rsa_key_pkcs1.pem -outform pem -nocrypt -out private_rsa_key_pkcs8.pem` - -To use a PEM encoded private / public keys, a pem struct is returned by `decode_pem`. -This carries the lifetime of the data inside. Finally to use the key like any other -use the `.as_key(alg)` function on the pem struct. -``` -let privkey_pem = decode_pem(pem_string_here).unwrap(); -let privkey = privkey_pem.as_key(Algorithm::RS256).unwrap(); -``` - -### ECDSA -`jsonwebtoken` can read PKCS#8 DER encoded private keys and public keys, as well as PEM encoded keys. Like RSA, to read a PEM key, you must use the pem decoder. - -To generate an EC key, you can do the following. - -```bash -// private key -openssl ecparam -genkey -name prime256v1 | openssl ec -out private_key.pem -// public key -openssl ec -in private_key.pem -pubout -out public_key.pem -``` +### RSA & ECDSA +By default, the `encode`/`decode` functions takes the PEM format since it is the most common. +RSA can also use the public key components modulus/exponent in base64 format for decoding. diff --git a/benches/jwt.rs b/benches/jwt.rs index 47ba571..9efe6e1 100644 --- a/benches/jwt.rs +++ b/benches/jwt.rs @@ -1,10 +1,8 @@ #![feature(test)] -extern crate jsonwebtoken as jwt; extern crate test; -#[macro_use] -extern crate serde_derive; -use jwt::{decode, encode, Header, Hmac, Validation}; +use jsonwebtoken::{decode, encode, Header, Validation}; +use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] struct Claims { @@ -16,7 +14,7 @@ struct Claims { fn bench_encode(b: &mut test::Bencher) { let claim = Claims { sub: "b@b.com".to_owned(), company: "ACME".to_owned() }; - b.iter(|| encode(&Header::default(), &claim, Hmac::from(b"secret"))); + b.iter(|| encode(&Header::default(), &claim, "secret".as_ref())); } #[bench] diff --git a/src/algorithms.rs b/src/algorithms.rs index 6054125..c9ff848 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -2,7 +2,7 @@ use crate::errors::{Error, ErrorKind, Result}; use serde::{Deserialize, Serialize}; use std::str::FromStr; -/// The algorithms supported for signing/verifying +/// The algorithms supported for signing/verifying JWTs #[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)] pub enum Algorithm { /// HMAC using SHA-256 diff --git a/src/crypto/ecdsa.rs b/src/crypto/ecdsa.rs index ba2f0ff..25099a7 100644 --- a/src/crypto/ecdsa.rs +++ b/src/crypto/ecdsa.rs @@ -2,7 +2,7 @@ use ring::{rand, signature}; use crate::algorithms::Algorithm; use crate::errors::Result; -use crate::pem_decoder::PemEncodedKey; +use crate::pem::decoder::PemEncodedKey; use crate::serialization::b64_encode; /// Only used internally when validating EC, to map from our enum to the Ring EcdsaVerificationAlgorithm structs. diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 6667ee4..98dc440 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -3,7 +3,7 @@ use ring::{hmac, signature}; use crate::algorithms::Algorithm; use crate::errors::Result; -use crate::pem_decoder::PemEncodedKey; +use crate::pem::decoder::PemEncodedKey; use crate::serialization::{b64_decode, b64_encode}; pub(crate) mod ecdsa; @@ -19,7 +19,8 @@ pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &str) -> Resu /// Take the payload of a JWT, sign it using the algorithm given and return /// the base64 url safe encoded of the result. /// -/// Only use this function if you want to do something other than JWT. +/// If you just want to encode a JWT, use `encode` instead. +/// /// `key` is the secret for HMAC and a pem encoded string otherwise pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result { match algorithm { @@ -57,7 +58,7 @@ fn verify_ring( /// Compares the signature given with a re-computed signature for HMAC or using the public key /// for RSA/EC. /// -/// Only use this function if you want to do something other than JWT. +/// If you just want to decode a JWT, use `decode` instead. /// /// `signature` is the signature part of a jwt (text after the second '.') /// @@ -109,5 +110,10 @@ pub fn verify_rsa_components( alg: Algorithm, ) -> Result { let signature_bytes = b64_decode(signature)?; - rsa::verify_from_components(rsa::alg_to_rsa_parameters(alg), &signature_bytes, message, components) + rsa::verify_from_components( + rsa::alg_to_rsa_parameters(alg), + &signature_bytes, + message, + components, + ) } diff --git a/src/crypto/rsa.rs b/src/crypto/rsa.rs index 95e946b..89da359 100644 --- a/src/crypto/rsa.rs +++ b/src/crypto/rsa.rs @@ -3,7 +3,7 @@ use simple_asn1::BigUint; use crate::algorithms::Algorithm; use crate::errors::{ErrorKind, Result}; -use crate::pem_decoder::PemEncodedKey; +use crate::pem::decoder::PemEncodedKey; use crate::serialization::{b64_decode, b64_encode}; /// Only used internally when validating RSA, to map from our enum to the Ring param structs. diff --git a/src/decoding.rs b/src/decoding.rs index e79847d..d740052 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -3,9 +3,18 @@ use serde::de::DeserializeOwned; use crate::crypto::{verify, verify_rsa_components}; use crate::errors::{new_error, ErrorKind, Result}; use crate::header::Header; -use crate::serialization::{from_jwt_part_claims, TokenData}; +use crate::serialization::from_jwt_part_claims; use crate::validation::{validate, Validation}; +/// The return type of a successful call to [decode](fn.decode.html). +#[derive(Debug)] +pub struct TokenData { + /// The decoded JWT header + pub header: Header, + /// The decoded JWT claims + pub claims: T, +} + /// Takes the result of a rsplit and ensure we only get 2 parts /// Errors if we don't macro_rules! expect_two { @@ -54,7 +63,7 @@ fn _decode( Ok(TokenData { header, claims: decoded_claims }) } -/// Decode a token into a struct containing 2 fields: `claims` and `header`. +/// Decode and validate a JWT using a secret for HS and a public PEM format for RSA/EC /// /// If the token or its signature is invalid or the claims fail validation, it will return an error. /// @@ -80,8 +89,27 @@ pub fn decode( _decode(token, DecodingKey::SecretOrPem(key), validation) } -/// TO TEST -pub fn decode_rsa_jwk( +/// Decode and validate a JWT using (n, e) base64 encoded public key components for RSA +/// +/// If the token or its signature is invalid or the claims fail validation, it will return an error. +/// +/// ```rust +/// use serde::{Deserialize, Serialize}; +/// use jsonwebtoken::{decode_rsa_components, Validation, Algorithm}; +/// +/// #[derive(Debug, Serialize, Deserialize)] +/// struct Claims { +/// sub: String, +/// company: String +/// } +/// +/// let modulus = "some-base64-data"; +/// let exponent = "some-base64-data"; +/// let token = "a.jwt.token".to_string(); +/// // Claims is a struct that implements Deserialize +/// let token_message = decode_rsa_components::(&token, &modulus, &exponent, &Validation::new(Algorithm::HS256)); +/// ``` +pub fn decode_rsa_components( token: &str, modulus: &str, exponent: &str, @@ -90,7 +118,7 @@ pub fn decode_rsa_jwk( _decode(token, DecodingKey::RsaModulusExponent { n: modulus, e: exponent }, validation) } -/// Decode a token without any signature validation into a struct containing 2 fields: `claims` and `header`. +/// Decode a JWT without any signature verification/validations. /// /// 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. /// @@ -118,10 +146,9 @@ pub fn dangerous_unsafe_decode(token: &str) -> Result "algorithms don't match", ErrorKind::InvalidAlgorithmName => "not a known algorithm", ErrorKind::InvalidKeyFormat => "invalid key format", - ErrorKind::__Nonexhaustive => "unknown error", ErrorKind::Base64(ref err) => err.description(), ErrorKind::Json(ref err) => err.description(), ErrorKind::Utf8(ref err) => err.description(), ErrorKind::Crypto(ref err) => err.description(), + ErrorKind::__Nonexhaustive => "unknown error", } } @@ -115,11 +115,11 @@ impl StdError for Error { ErrorKind::InvalidAlgorithm => None, ErrorKind::InvalidAlgorithmName => None, ErrorKind::InvalidKeyFormat => None, - ErrorKind::__Nonexhaustive => None, ErrorKind::Base64(ref err) => Some(err), ErrorKind::Json(ref err) => Some(err), ErrorKind::Utf8(ref err) => Some(err), ErrorKind::Crypto(ref err) => Some(err), + ErrorKind::__Nonexhaustive => None, } } } diff --git a/src/lib.rs b/src/lib.rs index 4e067f1..77d34ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,21 +4,21 @@ #![deny(missing_docs)] mod algorithms; -mod crypto; +/// Lower level functions, if you want to do something other than JWTs +pub mod crypto; mod decoding; -/// All the errors +/// All the errors that can be encountered while encoding/decoding JWTs pub mod errors; mod header; -mod pem_decoder; -mod pem_encoder; +mod pem; mod serialization; mod validation; pub use algorithms::Algorithm; -pub use crypto::{sign, verify}; -pub use decoding::{dangerous_unsafe_decode, decode, decode_header, decode_rsa_jwk}; +pub use decoding::{ + dangerous_unsafe_decode, decode, decode_header, decode_rsa_components, TokenData, +}; pub use header::Header; -pub use serialization::TokenData; pub use validation::Validation; use serde::ser::Serialize; @@ -26,7 +26,8 @@ use serde::ser::Serialize; use crate::errors::Result; use crate::serialization::b64_encode_part; -/// Encode the header and claims given and sign the payload using the algorithm from the header and the key +/// Encode the header and claims given and sign the payload using the algorithm from the header and the key. +/// If the algorithm given is RSA or EC, the key needs to be in the PEM format. /// /// ```rust /// use serde::{Deserialize, Serialize}; @@ -51,37 +52,7 @@ pub fn encode(header: &Header, claims: &T, key: &[u8]) -> Result Result { - pem_encoder::encode_rsa_public_pkcs1_pem(modulus, exponent) -} - -/// TODO -pub fn encode_rsa_public_pkcs1_der(modulus: &[u8], exponent: &[u8]) -> Result> { - pem_encoder::encode_rsa_public_pkcs1_der(modulus, exponent) -} - -/// TODO -pub fn encode_rsa_public_pkcs8_pem(modulus: &[u8], exponent: &[u8]) -> Result { - pem_encoder::encode_rsa_public_pkcs8_pem(modulus, exponent) -} - -/// TODO -pub fn encode_rsa_public_pkcs8_der(modulus: &[u8], exponent: &[u8]) -> Result> { - pem_encoder::encode_rsa_public_pkcs8_der(modulus, exponent) -} - -/// TODO -pub fn encode_ec_public_pem(x_coordinate: &[u8]) -> Result { - pem_encoder::encode_ec_public_pem(x_coordinate) -} - -/// TODO -pub fn encode_ec_public_der(x_coordinate: &[u8]) -> Result> { - pem_encoder::encode_ec_public_der(x_coordinate) -} diff --git a/src/pem_decoder.rs b/src/pem/decoder.rs similarity index 99% rename from src/pem_decoder.rs rename to src/pem/decoder.rs index 2c29f7d..db41156 100644 --- a/src/pem_decoder.rs +++ b/src/pem/decoder.rs @@ -1,8 +1,5 @@ use crate::errors::{ErrorKind, Result}; -extern crate pem; -extern crate simple_asn1; - use simple_asn1::{BigUint, OID}; /// Supported PEM files for EC and RSA Public and Private Keys diff --git a/src/pem_encoder.rs b/src/pem/encoder.rs similarity index 93% rename from src/pem_encoder.rs rename to src/pem/encoder.rs index ef7ba9d..f2c2159 100644 --- a/src/pem_encoder.rs +++ b/src/pem/encoder.rs @@ -2,43 +2,39 @@ use crate::errors::{ErrorKind, Result}; use pem::Pem; use simple_asn1::{ASN1Block, BigInt, BigUint, OID}; -extern crate base64; -extern crate pem; -extern crate simple_asn1; - -pub fn encode_rsa_public_pkcs1_pem(modulus: &[u8], exponent: &[u8]) -> Result { +pub(crate) fn encode_rsa_public_pkcs1_pem(modulus: &[u8], exponent: &[u8]) -> Result { Ok(pem::encode(&Pem { contents: encode_rsa_public_pkcs1_der(modulus, exponent)?, tag: "RSA PUBLIC KEY".to_string(), })) } -pub fn encode_rsa_public_pkcs1_der(modulus: &[u8], exponent: &[u8]) -> Result> { +pub(crate) fn encode_rsa_public_pkcs1_der(modulus: &[u8], exponent: &[u8]) -> Result> { match simple_asn1::to_der(&encode_rsa_public_pksc1_asn1(modulus, exponent)) { Ok(bytes) => Ok(bytes), Err(_) => Err(ErrorKind::InvalidRsaKey.into()), } } -pub fn encode_rsa_public_pkcs8_pem(modulus: &[u8], exponent: &[u8]) -> Result { +pub(crate) fn encode_rsa_public_pkcs8_pem(modulus: &[u8], exponent: &[u8]) -> Result { Ok(pem::encode(&Pem { contents: encode_rsa_public_pkcs8_der(modulus, exponent)?, tag: "PUBLIC KEY".to_string(), })) } -pub fn encode_rsa_public_pkcs8_der(modulus: &[u8], exponent: &[u8]) -> Result> { +pub(crate) fn encode_rsa_public_pkcs8_der(modulus: &[u8], exponent: &[u8]) -> Result> { match simple_asn1::to_der(&encode_rsa_public_pksc8_asn1(modulus, exponent)?) { Ok(bytes) => Ok(bytes), Err(_) => Err(ErrorKind::InvalidRsaKey.into()), } } -pub fn encode_ec_public_pem(x: &[u8]) -> Result { +pub(crate) fn encode_ec_public_pem(x: &[u8]) -> Result { Ok(pem::encode(&Pem { contents: encode_ec_public_der(x)?, tag: "PUBLIC KEY".to_string() })) } -pub fn encode_ec_public_der(x: &[u8]) -> Result> { +pub(crate) fn encode_ec_public_der(x: &[u8]) -> Result> { match simple_asn1::to_der(&encode_ec_public_asn1(x)) { Ok(bytes) => Ok(bytes), Err(_) => Err(ErrorKind::InvalidEcdsaKey.into()), diff --git a/src/pem/mod.rs b/src/pem/mod.rs new file mode 100644 index 0000000..c79f281 --- /dev/null +++ b/src/pem/mod.rs @@ -0,0 +1,34 @@ +pub(crate) mod decoder; +mod encoder; + +use crate::errors::Result; + +/// Encode (n, e) components into the public PKCS1 PEM format +pub fn encode_rsa_public_pkcs1_pem(modulus: &[u8], exponent: &[u8]) -> Result { + encoder::encode_rsa_public_pkcs1_pem(modulus, exponent) +} + +/// Encode (n, e) components into the public PKCS1 PEM format +pub fn encode_rsa_public_pkcs1_der(modulus: &[u8], exponent: &[u8]) -> Result> { + encoder::encode_rsa_public_pkcs1_der(modulus, exponent) +} + +/// TODO +pub fn encode_rsa_public_pkcs8_pem(modulus: &[u8], exponent: &[u8]) -> Result { + encoder::encode_rsa_public_pkcs8_pem(modulus, exponent) +} + +/// TODO +pub fn encode_rsa_public_pkcs8_der(modulus: &[u8], exponent: &[u8]) -> Result> { + encoder::encode_rsa_public_pkcs8_der(modulus, exponent) +} + +/// TODO +pub fn encode_ec_public_pem(x_coordinate: &[u8]) -> Result { + encoder::encode_ec_public_pem(x_coordinate) +} + +/// TODO +pub fn encode_ec_public_der(x_coordinate: &[u8]) -> Result> { + encoder::encode_ec_public_der(x_coordinate) +} diff --git a/src/serialization.rs b/src/serialization.rs index 22f83f4..23d0883 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -4,16 +4,6 @@ use serde_json::map::Map; use serde_json::{from_str, to_string, Value}; use crate::errors::Result; -use crate::header::Header; - -/// The return type of a successful call to decode -#[derive(Debug)] -pub struct TokenData { - /// The decoded JWT header - pub header: Header, - /// The decoded JWT claims - pub claims: T, -} pub(crate) fn b64_encode(input: &[u8]) -> String { base64::encode_config(input, base64::URL_SAFE_NO_PAD) diff --git a/src/validation.rs b/src/validation.rs index 9afbf2a..48d79c7 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -7,7 +7,7 @@ use serde_json::{from_value, Value}; use crate::algorithms::Algorithm; use crate::errors::{new_error, ErrorKind, Result}; -/// Contains the various validations that are applied after decoding a token. +/// Contains the various validations that are applied after decoding a JWT. /// /// All time validation happen on UTC timestamps as seconds. /// diff --git a/tests/ecdsa/mod.rs b/tests/ecdsa/mod.rs index b02721c..b9754c1 100644 --- a/tests/ecdsa/mod.rs +++ b/tests/ecdsa/mod.rs @@ -1,5 +1,8 @@ use chrono::Utc; -use jsonwebtoken::{decode, encode, sign, verify, Algorithm, Header, Validation}; +use jsonwebtoken::{ + crypto::{sign, verify}, + decode, encode, Algorithm, Header, Validation, +}; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] diff --git a/tests/hmac.rs b/tests/hmac.rs index 0a245fc..98e2d56 100644 --- a/tests/hmac.rs +++ b/tests/hmac.rs @@ -1,7 +1,7 @@ use chrono::Utc; use jsonwebtoken::{ - dangerous_unsafe_decode, decode, decode_header, encode, sign, verify, Algorithm, Header, - Validation, + crypto::{sign, verify}, + dangerous_unsafe_decode, decode, decode_header, encode, Algorithm, Header, Validation, }; use serde::{Deserialize, Serialize}; diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 6cdb98c..f93ed96 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -1,5 +1,8 @@ use chrono::Utc; -use jsonwebtoken::{decode, decode_rsa_jwk, encode, sign, verify, Algorithm, Header, Validation}; +use jsonwebtoken::{ + crypto::{sign, verify}, + decode, decode_rsa_components, encode, Algorithm, Header, Validation, +}; use serde::{Deserialize, Serialize}; const RSA_ALGORITHMS: &[Algorithm] = &[ @@ -76,7 +79,7 @@ fn rsa_modulus_exponent() { let e = "AQAB"; let encrypted = encode(&Header::new(Algorithm::RS256), &my_claims, privkey.as_ref()).unwrap(); - let res = decode_rsa_jwk::(&encrypted, n, e, &Validation::new(Algorithm::RS256)); + let res = decode_rsa_components::(&encrypted, n, e, &Validation::new(Algorithm::RS256)); assert!(res.is_ok()); } From 6e8d4a4be6aa16fdd23f5c5786c55d45dcc7975a Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 14 Nov 2019 19:48:38 +0100 Subject: [PATCH 19/24] Remove pem encoding for now --- src/pem/encoder.rs | 194 --------------------------------------------- src/pem/mod.rs | 33 -------- 2 files changed, 227 deletions(-) delete mode 100644 src/pem/encoder.rs diff --git a/src/pem/encoder.rs b/src/pem/encoder.rs deleted file mode 100644 index f2c2159..0000000 --- a/src/pem/encoder.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::errors::{ErrorKind, Result}; -use pem::Pem; -use simple_asn1::{ASN1Block, BigInt, BigUint, OID}; - -pub(crate) fn encode_rsa_public_pkcs1_pem(modulus: &[u8], exponent: &[u8]) -> Result { - Ok(pem::encode(&Pem { - contents: encode_rsa_public_pkcs1_der(modulus, exponent)?, - tag: "RSA PUBLIC KEY".to_string(), - })) -} - -pub(crate) fn encode_rsa_public_pkcs1_der(modulus: &[u8], exponent: &[u8]) -> Result> { - match simple_asn1::to_der(&encode_rsa_public_pksc1_asn1(modulus, exponent)) { - Ok(bytes) => Ok(bytes), - Err(_) => Err(ErrorKind::InvalidRsaKey.into()), - } -} - -pub(crate) fn encode_rsa_public_pkcs8_pem(modulus: &[u8], exponent: &[u8]) -> Result { - Ok(pem::encode(&Pem { - contents: encode_rsa_public_pkcs8_der(modulus, exponent)?, - tag: "PUBLIC KEY".to_string(), - })) -} - -pub(crate) fn encode_rsa_public_pkcs8_der(modulus: &[u8], exponent: &[u8]) -> Result> { - match simple_asn1::to_der(&encode_rsa_public_pksc8_asn1(modulus, exponent)?) { - Ok(bytes) => Ok(bytes), - Err(_) => Err(ErrorKind::InvalidRsaKey.into()), - } -} - -pub(crate) fn encode_ec_public_pem(x: &[u8]) -> Result { - Ok(pem::encode(&Pem { contents: encode_ec_public_der(x)?, tag: "PUBLIC KEY".to_string() })) -} - -pub(crate) fn encode_ec_public_der(x: &[u8]) -> Result> { - match simple_asn1::to_der(&encode_ec_public_asn1(x)) { - Ok(bytes) => Ok(bytes), - Err(_) => Err(ErrorKind::InvalidEcdsaKey.into()), - } -} - -fn encode_rsa_public_pksc8_asn1(modulus: &[u8], exponent: &[u8]) -> Result { - let pksc1 = match simple_asn1::to_der(&encode_rsa_public_pksc1_asn1(modulus, exponent)) { - Ok(bytes) => bytes, - Err(_) => return Err(ErrorKind::InvalidRsaKey.into()), - }; - Ok(ASN1Block::Sequence( - 0, - vec![ - ASN1Block::Sequence( - 0, - vec![ - // rsaEncryption (PKCS #1) - ASN1Block::ObjectIdentifier(0, simple_asn1::oid!(1, 2, 840, 113_549, 1, 1, 1)), - ASN1Block::Null(0), - ], - ), - // the second parameter takes the count of bits - ASN1Block::BitString(0, pksc1.len() * 8, pksc1), - ], - )) -} - -fn encode_rsa_public_pksc1_asn1(modulus: &[u8], exponent: &[u8]) -> ASN1Block { - ASN1Block::Sequence( - 0, - vec![ - ASN1Block::Integer(0, BigInt::from_signed_bytes_be(modulus)), - ASN1Block::Integer(0, BigInt::from_signed_bytes_be(exponent)), - ], - ) -} - -fn encode_ec_public_asn1(x: &[u8]) -> ASN1Block { - ASN1Block::Sequence( - 0, - vec![ - ASN1Block::Sequence( - 0, - vec![ - // ecPublicKey (ANSI X9.62 public key type) - ASN1Block::ObjectIdentifier(0, simple_asn1::oid!(1, 2, 840, 10045, 2, 1)), - // prime256v1 (ANSI X9.62 named elliptic curve) - ASN1Block::ObjectIdentifier(0, simple_asn1::oid!(1, 2, 840, 10045, 3, 1, 7)), - ], - ), - // the second parameter takes the count of bits - ASN1Block::BitString(0, x.len() * 8, x.to_vec()), - ], - ) -} - -#[cfg(test)] -mod tests { - use super::*; - use ring::{signature, signature::KeyPair}; - - // #[test] - // fn public_key_encoding_pkcs1() { - // let privkey_pem = - // decode_pem(include_str!("../tests/rsa/private_rsa_key_pkcs8.pem")).unwrap(); - // let privkey = privkey_pem.as_key().unwrap(); - // let ring_key = signature::RsaKeyPair::from_der(match privkey { - // Key::Der(bytes) => bytes, - // _ => panic!("Unexpected"), - // }) - // .unwrap(); - // let mut modulus = vec![0]; - // modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); - // let exponent = ring_key.public_key().exponent(); - // - // let public_key_pkcs1_pem = encode_rsa_public_pkcs1_pem( - // modulus.as_ref(), - // exponent.big_endian_without_leading_zero(), - // ) - // .unwrap(); - // assert_eq!( - // include_str!("../tests/rsa/public_rsa_key_pkcs1.pem").trim(), - // public_key_pkcs1_pem.replace('\r', "").trim() - // ); - // - // let public_key_pkcs1_der = encode_rsa_public_pkcs1_der( - // modulus.as_ref(), - // exponent.big_endian_without_leading_zero(), - // ) - // .unwrap(); - // assert_eq!( - // include_bytes!("../tests/rsa/public_rsa_key.der").to_vec(), - // public_key_pkcs1_der - // ); - // } - // - // #[test] - // fn public_key_encoding_pkcs8() { - // let privkey_pem = - // decode_pem(include_str!("../tests/rsa/private_rsa_key_pkcs8.pem")).unwrap(); - // let privkey = privkey_pem.as_key().unwrap(); - // let ring_key = signature::RsaKeyPair::from_der(match privkey { - // Key::Der(bytes) => bytes, - // _ => panic!("Unexpected"), - // }) - // .unwrap(); - // let mut modulus = vec![0]; - // modulus.extend(ring_key.public_key().modulus().big_endian_without_leading_zero()); - // let exponent = ring_key.public_key().exponent(); - // - // let public_key_pkcs8 = encode_rsa_public_pkcs8_pem( - // modulus.as_ref(), - // exponent.big_endian_without_leading_zero(), - // ) - // .unwrap(); - // assert_eq!( - // include_str!("../tests/rsa/public_rsa_key_pkcs8.pem").trim(), - // public_key_pkcs8.replace('\r', "").trim() - // ); - // } - // - // #[test] - // fn public_key_encoding() { - // let privkey_pem = decode_pem(include_str!("../tests/ec/private_ecdsa_key.pem")).unwrap(); - // let privkey = privkey_pem.as_key().unwrap(); - // let alg = &signature::ECDSA_P256_SHA256_FIXED_SIGNING; - // let ring_key = signature::EcdsaKeyPair::from_pkcs8( - // alg, - // match privkey { - // Key::Pkcs8(bytes) => bytes, - // _ => panic!("Unexpected"), - // }, - // ) - // .unwrap(); - // - // let public_key_pem = encode_ec_public_pem(ring_key.public_key().as_ref()).unwrap(); - // assert_eq!( - // include_str!("../tests/ec/public_ecdsa_key.pem").trim(), - // public_key_pem.replace('\r', "").trim() - // ); - // - // let public_key_der = encode_ec_public_der(ring_key.public_key().as_ref()).unwrap(); - // // The stored ".pk8" key is just the x coordinate of the EC key - // // It's not truly a pkcs8 formatted DER - // // To get around that, a prepended binary specifies the EC key, EC name, - // // and X coordinate length. The length is unlikely to change.. in the - // // event that it does, look at the pem file (convert base64 to hex) and find - // // where 0x03, 0x42 don't match up. 0x42 is the length. - // let mut stored_pk8_der = vec![ - // 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, - // 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, - // ]; - // stored_pk8_der.extend(include_bytes!("../tests/ec/public_ecdsa_key.pk8").to_vec()); - // assert_eq!(stored_pk8_der, public_key_der); - // } -} diff --git a/src/pem/mod.rs b/src/pem/mod.rs index c79f281..99753de 100644 --- a/src/pem/mod.rs +++ b/src/pem/mod.rs @@ -1,34 +1 @@ pub(crate) mod decoder; -mod encoder; - -use crate::errors::Result; - -/// Encode (n, e) components into the public PKCS1 PEM format -pub fn encode_rsa_public_pkcs1_pem(modulus: &[u8], exponent: &[u8]) -> Result { - encoder::encode_rsa_public_pkcs1_pem(modulus, exponent) -} - -/// Encode (n, e) components into the public PKCS1 PEM format -pub fn encode_rsa_public_pkcs1_der(modulus: &[u8], exponent: &[u8]) -> Result> { - encoder::encode_rsa_public_pkcs1_der(modulus, exponent) -} - -/// TODO -pub fn encode_rsa_public_pkcs8_pem(modulus: &[u8], exponent: &[u8]) -> Result { - encoder::encode_rsa_public_pkcs8_pem(modulus, exponent) -} - -/// TODO -pub fn encode_rsa_public_pkcs8_der(modulus: &[u8], exponent: &[u8]) -> Result> { - encoder::encode_rsa_public_pkcs8_der(modulus, exponent) -} - -/// TODO -pub fn encode_ec_public_pem(x_coordinate: &[u8]) -> Result { - encoder::encode_ec_public_pem(x_coordinate) -} - -/// TODO -pub fn encode_ec_public_der(x_coordinate: &[u8]) -> Result> { - encoder::encode_ec_public_der(x_coordinate) -} From dd642bed1dd9eba3f82e86fb87fcb3d86baa4560 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 14 Nov 2019 20:00:47 +0100 Subject: [PATCH 20/24] Use GH actions --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8b7c4fe --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: ci +on: [push, pull_request] + +jobs: + tests: + name: Tests + runs-on: ${{ matrix.os }} + strategy: + matrix: + build: [pinned, stable, nightly] + include: + - build: pinned + os: ubuntu-18.04 + rust: 1.34.0 + - build: stable + os: ubuntu-18.04 + rust: stable + - build: nightly + os: ubuntu-18.04 + rust: nightly + steps: + - uses: actions/checkout@v1 + - name: Install Rust + uses: hecrj/setup-rust-action@v1 + with: + rust-version: ${{ matrix.rust }} + + - name: Build System Info + run: rustc --version + + - name: Run tests + run: cargo test From 3fe0bc1f83d06413ea9f8ced046c08ec6124f8a6 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 14 Nov 2019 20:24:38 +0100 Subject: [PATCH 21/24] Update README --- README.md | 59 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 9834cc3..2e0f694 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [API documentation on docs.rs](https://docs.rs/jsonwebtoken/) +See [JSON Web Tokens](https://en.wikipedia.org/wiki/JSON_Web_Token) for more information on what are JSON Web Tokens. + ## Installation Add the following to Cargo.toml: @@ -12,6 +14,24 @@ jsonwebtoken = "7" serde = {version = "1.0", features = ["derive"] } ``` +The minimum required Rust version is 1.34. + +## Algorithms +This library currently supports the following: + +- HS256 +- HS384 +- HS512 +- RS256 +- RS384 +- RS512 +- PS256 +- PS384 +- PS512 +- ES256 +- ES384 + + ## How to use Complete examples are available in the examples directory: a basic one and one with a custom header. @@ -30,7 +50,7 @@ struct Claims { ``` ### Header -The default algorithm is HS256. +The default algorithm is HS256, which uses a shared secret. ```rust let token = encode(&Header::default(), &my_claims, "secret".as_ref())?; @@ -59,7 +79,7 @@ Encoding a JWT takes 3 parameters: - a header: the `Header` struct - some claims: your own struct -- a key +- a key/secret When using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using RSA/EC, the key should always be the content of the private key in the PEM format. @@ -67,8 +87,8 @@ RSA/EC, the key should always be the content of the private key in the PEM forma ### Decoding ```rust +// `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct. let token = decode::(&token, "secret".as_ref(), &Validation::default())?; -// token is a struct with 2 fields: `header` and `claims` and `claims` is your own struct. ``` `decode` can error for a variety of reasons: @@ -79,16 +99,16 @@ let token = decode::(&token, "secret".as_ref(), &Validation::default())? As with encoding, when using HS256, HS2384 or HS512, the key is always a shared secret like in the example above. When using RSA/EC, the key should always be the content of the public key in the PEM format. -In some cases, for example if you don't know the algorithm used or need to grab the `kid`, you can decode only the header: +In some cases, for example if you don't know the algorithm used or need to grab the `kid`, you can choose to decode only the header: ```rust let header = decode_header(&token)?; ``` -This does not perform any signature verification/validations on the token so it could have been tampered with. +This does not perform any signature verification or validate the token claims. You can also decode a token using the public key components of a RSA key in base64 format. -The main use-case is for JWK where your public key is a JSON format like so: +The main use-case is for JWK where your public key is in a JSON format like so: ```json { @@ -100,11 +120,11 @@ The main use-case is for JWK where your public key is a JSON format like so: ``` ```rust +// `token` is a struct with 2 fields: `header` and `claims` where `claims` is your own struct. let token = decode_rsa_components::(&token, jwk["n"], jwk["e"], &Validation::new(Algorithm::RS256))?; -// token is a struct with 2 fields: `header` and `claims` and `claims` is your own struct. ``` -### Convertion .der to .pem +### Converting .der to .pem You can use openssl for that: @@ -113,8 +133,8 @@ openssl rsa -inform DER -outform PEM -in mykey.der -out mykey.pem ``` -#### Validation -This library validates automatically the `exp` claim. `nbf` is also validated if present. You can also validate the `sub`, `iss` and `aud` but +## Validation +This library validates automatically the `exp` claim and `nbf` is validated if present. You can also validate the `sub`, `iss` and `aud` but those require setting the expected value in the `Validation` struct. Since validating time fields is always a bit tricky due to clock skew, @@ -138,22 +158,3 @@ let mut validation = Validation::default(); validation.set_audience(&"Me"); // string validation.set_audience(&["Me", "You"]); // array of strings ``` - -## Algorithms -This library currently supports the following: - -- HS256 -- HS384 -- HS512 -- RS256 -- RS384 -- RS512 -- PS256 -- PS384 -- PS512 -- ES256 -- ES384 - -### RSA & ECDSA -By default, the `encode`/`decode` functions takes the PEM format since it is the most common. -RSA can also use the public key components modulus/exponent in base64 format for decoding. From b35719b55540f611c71c069a3c7986c18b23a9da Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 14 Nov 2019 20:32:03 +0100 Subject: [PATCH 22/24] Point to validation example --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2e0f694..2d26f9b 100644 --- a/README.md +++ b/README.md @@ -158,3 +158,5 @@ let mut validation = Validation::default(); validation.set_audience(&"Me"); // string validation.set_audience(&["Me", "You"]); // array of strings ``` + +Look at `examples/validation.rs` for a full working example. From 6121db3d077102a6da6a92d6cd023c4db4f2b0c6 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 14 Nov 2019 20:35:20 +0100 Subject: [PATCH 23/24] Min version is 1.36? --- .github/workflows/ci.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b7c4fe..83bdfe8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: include: - build: pinned os: ubuntu-18.04 - rust: 1.34.0 + rust: 1.36.0 - build: stable os: ubuntu-18.04 rust: stable diff --git a/README.md b/README.md index 2d26f9b..cd78b9d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ jsonwebtoken = "7" serde = {version = "1.0", features = ["derive"] } ``` -The minimum required Rust version is 1.34. +The minimum required Rust version is 1.36. ## Algorithms This library currently supports the following: From d550c5f3188e726dcd212565fe4ed96db68b11c9 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 15 Nov 2019 20:16:38 +0100 Subject: [PATCH 24/24] Add more tests and document SEC1 lack of support --- README.md | 8 ++++++++ src/pem/decoder.rs | 2 ++ tests/ecdsa/mod.rs | 23 +++++++++++++++++++---- tests/ecdsa/private_jwtio.pem | 6 ++++++ tests/ecdsa/private_jwtio_pkcs8.pem | 6 ++++++ tests/ecdsa/public_jwtio.pem | 5 +++++ tests/rsa/mod.rs | 19 +++++++++++++++++++ tests/rsa/private_jwtio.pem | 27 +++++++++++++++++++++++++++ tests/rsa/public_jwtio.pem | 9 +++++++++ 9 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 tests/ecdsa/private_jwtio.pem create mode 100644 tests/ecdsa/private_jwtio_pkcs8.pem create mode 100644 tests/ecdsa/public_jwtio.pem create mode 100644 tests/rsa/private_jwtio.pem create mode 100644 tests/rsa/public_jwtio.pem diff --git a/README.md b/README.md index cd78b9d..a1cbce6 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,14 @@ You can use openssl for that: openssl rsa -inform DER -outform PEM -in mykey.der -out mykey.pem ``` +### Convert SEC1 private key to PKCS8 +`jsonwebtoken` currently only supports PKCS8 format for private EC keys. If your key has `BEGIN EC PRIVATE KEY` at the top, +this is a SEC1 type and can be converted to PKCS8 like so: + +```bash +openssl pkcs8 -topk8 -nocrypt -in sec1.pem -out pkcs8.pem +``` + ## Validation This library validates automatically the `exp` claim and `nbf` is validated if present. You can also validate the `sub`, `iss` and `aud` but diff --git a/src/pem/decoder.rs b/src/pem/decoder.rs index db41156..ded5ac5 100644 --- a/src/pem/decoder.rs +++ b/src/pem/decoder.rs @@ -13,7 +13,9 @@ enum PemType { #[derive(Debug, PartialEq)] enum Standard { + // Only for RSA Pkcs1, + // RSA/EC Pkcs8, } diff --git a/tests/ecdsa/mod.rs b/tests/ecdsa/mod.rs index b9754c1..d90242e 100644 --- a/tests/ecdsa/mod.rs +++ b/tests/ecdsa/mod.rs @@ -25,23 +25,38 @@ pub struct Claims { #[test] fn round_trip_sign_verification_pem() { let privkey = include_bytes!("private_ecdsa_key.pem"); - let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); let pubkey = include_bytes!("public_ecdsa_key.pem"); + let encrypted = sign("hello world", privkey, Algorithm::ES256).unwrap(); let is_valid = verify(&encrypted, "hello world", pubkey, Algorithm::ES256).unwrap(); assert!(is_valid); } #[test] fn round_trip_claim() { + let privkey = include_bytes!("private_ecdsa_key.pem"); + let pubkey = include_bytes!("public_ecdsa_key.pem"); let my_claims = Claims { sub: "b@b.com".to_string(), company: "ACME".to_string(), exp: Utc::now().timestamp() + 10000, }; - let privkey = include_bytes!("private_ecdsa_key.pem"); let token = encode(&Header::new(Algorithm::ES256), &my_claims, privkey).unwrap(); - let pubkey = include_bytes!("public_ecdsa_key.pem"); let token_data = decode::(&token, pubkey, &Validation::new(Algorithm::ES256)).unwrap(); assert_eq!(my_claims, token_data.claims); - assert!(token_data.header.kid.is_none()); +} + +// https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken +#[test] +fn roundtrip_with_jwtio_example() { + // We currently do not support SEC1 so we use the converted PKCS8 formatted + let privkey = include_bytes!("private_jwtio_pkcs8.pem"); + let pubkey = include_bytes!("public_jwtio.pem"); + let my_claims = Claims { + sub: "b@b.com".to_string(), + company: "ACME".to_string(), + exp: Utc::now().timestamp() + 10000, + }; + let token = encode(&Header::new(Algorithm::ES384), &my_claims, privkey).unwrap(); + let token_data = decode::(&token, pubkey, &Validation::new(Algorithm::ES384)).unwrap(); + assert_eq!(my_claims, token_data.claims); } diff --git a/tests/ecdsa/private_jwtio.pem b/tests/ecdsa/private_jwtio.pem new file mode 100644 index 0000000..6e8b0cc --- /dev/null +++ b/tests/ecdsa/private_jwtio.pem @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCAHpFQ62QnGCEvYh/pE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhske +enT+rAyyPhGgBwYFK4EEACKhZANiAAQLW5ZJePZzMIPAxMtZXkEWbDF0zo9f2n4+ +T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw8lE5IPUWpgu553SteKigiKLU +PeNpbqmYZUkWGh3MLfVzLmx85ii2vMU= +-----END EC PRIVATE KEY----- diff --git a/tests/ecdsa/private_jwtio_pkcs8.pem b/tests/ecdsa/private_jwtio_pkcs8.pem new file mode 100644 index 0000000..893d58e --- /dev/null +++ b/tests/ecdsa/private_jwtio_pkcs8.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCAHpFQ62QnGCEvYh/p +E9QmR1C9aLcDItRbslbmhen/h1tt8AyMhskeenT+rAyyPhGhZANiAAQLW5ZJePZz +MIPAxMtZXkEWbDF0zo9f2n4+T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw +8lE5IPUWpgu553SteKigiKLUPeNpbqmYZUkWGh3MLfVzLmx85ii2vMU= +-----END PRIVATE KEY----- diff --git a/tests/ecdsa/public_jwtio.pem b/tests/ecdsa/public_jwtio.pem new file mode 100644 index 0000000..81eac4e --- /dev/null +++ b/tests/ecdsa/public_jwtio.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+ +Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii +1D3jaW6pmGVJFhodzC31cy5sfOYotrzF +-----END PUBLIC KEY----- diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index f93ed96..9b3301e 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -89,3 +89,22 @@ fn fails_with_non_pkcs8_key_format() { let _encrypted = sign("hello world", include_bytes!("private_rsa_key_pkcs1.pem"), Algorithm::ES256).unwrap(); } + +// https://jwt.io/ is often used for examples so ensure their example works with jsonwebtoken +#[test] +fn roundtrip_with_jwtio_example_jey() { + let privkey_pem = include_bytes!("private_jwtio.pem"); + let pubkey_pem = include_bytes!("public_jwtio.pem"); + + 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, privkey_pem).unwrap(); + let token_data = decode::(&token, pubkey_pem, &Validation::new(alg)).unwrap(); + assert_eq!(my_claims, token_data.claims); + } +} diff --git a/tests/rsa/private_jwtio.pem b/tests/rsa/private_jwtio.pem new file mode 100644 index 0000000..61056a5 --- /dev/null +++ b/tests/rsa/private_jwtio.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw +kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr +m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi +NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV +3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2 +QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs +kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go +amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM ++bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9 +D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC +0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y +lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+ +hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp +bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X ++jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B +BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC +2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx +QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz +5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9 +Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0 +NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j +8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma +3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K +y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB +jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE= +-----END RSA PRIVATE KEY----- diff --git a/tests/rsa/public_jwtio.pem b/tests/rsa/public_jwtio.pem new file mode 100644 index 0000000..12301e0 --- /dev/null +++ b/tests/rsa/public_jwtio.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv +vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc +aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy +tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0 +e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb +V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9 +MwIDAQAB +-----END PUBLIC KEY-----