From 13f05eb17463f3c29ee01e7ef552fb6cb1a4c787 Mon Sep 17 00:00:00 2001 From: EliseZeroTwo Date: Mon, 18 Apr 2022 12:02:44 +0200 Subject: [PATCH 01/11] chore: update dependencies --- .rustfmt.toml | 2 +- Cargo.toml | 8 ++++---- src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index afb030d..0e3f40f 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -18,7 +18,7 @@ fn_single_line = false where_single_line = false imports_indent = "Block" imports_layout = "Mixed" -merge_imports = true +imports_granularity="Crate" reorder_imports = true reorder_modules = true reorder_impl_items = false diff --git a/Cargo.toml b/Cargo.toml index 673ac0d..47d9fbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,14 @@ edition = "2018" base64 = "0.13" bitflags = "1.2" generic-array = "0.14" -jsonwebtoken = { version = "8.0", optional = true } +jsonwebtoken = { version = "8.1", optional = true } num-bigint = { version = "0.4", optional = true } -p256 = { version = "0.9", optional = true, features = ["arithmetic"] } +p256 = { version = "0.10", optional = true, features = ["arithmetic"] } rand = { version = "0.8", optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -yasna = { version = "0.4", optional = true, features = ["num-bigint"] } +yasna = { version = "0.5", optional = true, features = ["num-bigint"] } zeroize = { version = "1.4", features = ["zeroize_derive"] } [features] @@ -29,7 +29,7 @@ jwt-convert = ["pkcs-convert", "jsonwebtoken"] generate = ["p256", "rand"] [dev-dependencies] -jsonwebtoken = "8.0" +jsonwebtoken = "8.1" [package.metadata.docs.rs] all-features = true diff --git a/src/lib.rs b/src/lib.rs index 2e8f014..2700697 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -417,7 +417,7 @@ impl Key { let sk = elliptic_curve::SecretKey::random(&mut rand::thread_rng()); let sk_scalar = p256::Scalar::from(&sk); - let pk = p256::ProjectivePoint::generator() * sk_scalar; + let pk = p256::ProjectivePoint::GENERATOR * sk_scalar; let pk_bytes = &pk .to_affine() .to_encoded_point(false /* compress */) From bd382dd08f99c782e1e1cb884888ce54ec902cce Mon Sep 17 00:00:00 2001 From: EliseZeroTwo Date: Mon, 18 Apr 2022 15:01:41 +0200 Subject: [PATCH 02/11] feat: add more algorithms --- .gitignore | 2 +- Cargo.toml | 12 +- README.md | 2 +- src/lib.rs | 81 ++++++++++- src/tests.rs | 377 +++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 456 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 96ef6c0..869df07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /target -Cargo.lock +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 47d9fbb..cf89747 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,15 +13,15 @@ edition = "2018" base64 = "0.13" bitflags = "1.2" generic-array = "0.14" -jsonwebtoken = { version = "8.1", optional = true } -num-bigint = { version = "0.4", optional = true } +jsonwebtoken = { version = "8.1", optional = true } +num-bigint = { version = "0.4", optional = true } p256 = { version = "0.10", optional = true, features = ["arithmetic"] } -rand = { version = "0.8", optional = true } -serde = { version = "1.0", features = ["derive"] } +rand = { version = "0.8", optional = true } +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -yasna = { version = "0.5", optional = true, features = ["num-bigint"] } -zeroize = { version = "1.4", features = ["zeroize_derive"] } +yasna = { version = "0.5", optional = true, features = ["num-bigint"] } +zeroize = { version = "1.4", features = ["zeroize_derive"] } [features] pkcs-convert = ["num-bigint", "yasna"] diff --git a/README.md b/README.md index 97624ee..b6902bc 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ tl;dr: get keys into a format that can be used by other crates; be as safe as possible while doing so. -- Serialization and deserialization of _Required_ and _Recommended_ key types (HS256, RS256, ES256) +- Serialization and deserialization of _Required_ and _Recommended_ key types (HS256, HS384, HS512 RS256, RS384, RS512, ES256, ES384) - Conversion to PEM for interop with existing JWT libraries (e.g., [jsonwebtoken](https://crates.io/crates/jsonwebtoken)) - Key generation (particularly useful for testing) diff --git a/src/lib.rs b/src/lib.rs index 2700697..4c889e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ mod utils; use std::{borrow::Cow, fmt}; -use generic_array::typenum::U32; +use generic_array::typenum::{U32, U48}; use serde::{Deserialize, Serialize}; pub use byte_array::ByteArray; @@ -164,8 +164,18 @@ impl JsonWebKey { curve: Curve::P256 { .. }, }, ) + | ( + ES384, + EC { + curve: Curve::P384 { .. }, + }, + ) | (RS256, RSA { .. }) + | (RS384, RSA { .. }) + | (RS512, RSA { .. }) | (HS256, Symmetric { .. }) => Ok(()), + (HS384, Symmetric { .. }) => Ok(()), + (HS512, Symmetric { .. }) => Ok(()), _ => Err(Error::MismatchedAlgorithm), } } @@ -230,6 +240,10 @@ impl Key { curve: Curve::P256 { d: Some(_), .. }, .. } + | Self::EC { + curve: Curve::P384 { d: Some(_), .. }, + .. + } | Self::RSA { private: Some(_), .. @@ -253,6 +267,15 @@ impl Key { d: None, }, }, + Self::EC { + curve: Curve::P384 { x, y, .. }, + } => Self::EC { + curve: Curve::P384 { + x: x.clone(), + y: y.clone(), + d: None, + }, + }, Self::RSA { public, .. } => Self::RSA { public: public.clone(), private: None, @@ -307,6 +330,34 @@ impl Key { None => pkcs8::write_public(oids, write_public), } } + Self::EC { + curve: Curve::P384 { d, x, y }, + } => { + let ec_public_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1]); + let prime384v1_oid = ObjectIdentifier::from_slice(&[1, 3, 132, 0, 34]); + let oids = &[Some(&ec_public_oid), Some(&prime384v1_oid)]; + + let write_public = |writer: DERWriter<'_>| { + let public_bytes: Vec = [0x04 /* uncompressed */] + .iter() + .chain(x.iter()) + .chain(y.iter()) + .copied() + .collect(); + writer.write_bitvec_bytes(&public_bytes, 8 * (48 * 2 + 1)); + }; + + match d { + Some(private_point) => { + pkcs8::write_private(oids, |writer: &mut DERWriterSeq<'_>| { + writer.next().write_i8(1); // version + writer.next().write_bytes(&**private_point); + writer.next().write_tagged(Tag::context(1), write_public); + }) + } + None => pkcs8::write_public(oids, write_public), + } + } Self::RSA { public, private } => { let rsa_encryption_oid = ObjectIdentifier::from_slice(&[ 1, 2, 840, 113549, 1, 1, 1, // rsaEncryption @@ -448,13 +499,29 @@ pub enum Curve { /// The curve point y coordinate. y: ByteArray, }, + /// Parameters of the prime384v1 (P384) curve. + #[serde(rename = "P-384")] + P384 { + /// The private scalar. + #[serde(skip_serializing_if = "Option::is_none")] + d: Option>, + /// The curve point x coordinate. + x: ByteArray, + /// The curve point y coordinate. + y: ByteArray, + }, } impl fmt::Debug for Curve { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::P256 { x, y, .. } => f - .debug_struct("Curve:P256") + .debug_struct("Curve::P256") + .field("x", x) + .field("y", y) + .finish(), + Self::P384 { x, y, .. } => f + .debug_struct("Curve::P384") .field("x", x) .field("y", y) .finish(), @@ -537,8 +604,13 @@ pub enum KeyUse { #[allow(clippy::upper_case_acronyms)] pub enum Algorithm { HS256, + HS384, + HS512, RS256, + RS384, + RS512, ES256, + ES384, } #[cfg(feature = "jwt-convert")] @@ -549,8 +621,13 @@ const _IMPL_JWT_CONVERSIONS: () = { fn from(alg: Algorithm) -> Self { match alg { Algorithm::HS256 => Self::HS256, + Algorithm::HS384 => Self::HS384, + Algorithm::HS512 => Self::HS512, Algorithm::ES256 => Self::ES256, + Algorithm::ES384 => Self::ES384, Algorithm::RS256 => Self::RS256, + Algorithm::RS384 => Self::RS384, + Algorithm::RS512 => Self::RS512, } } } diff --git a/src/tests.rs b/src/tests.rs index ec0e775..83d764e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -16,9 +16,52 @@ static P256_JWK_FIXTURE: &str = r#"{ "alg": "ES256" }"#; -static RSA_JWK_FIXTURE: &str = r#"{ +#[cfg(feature = "pkcs-convert")] +static P384_JWK_FIXTURE: &str = r#"{ + "kty": "EC", + "d": "qzp82B9d-COgCfNp-u33PYAcggkgoC5n3Unxc-yhiawKdrDSVw65NwqgscQAxNI3", + "use": "sig", + "crv": "P-384", + "kid": "sig-1650284838", + "x": "3VdrPTk7esh1TmQqE0LNswQVY1kGCD0dnxhaPc6Ei9nXSCUQf46lLUoXg00zWYPM", + "y": "z3P8BMHgDX9wiVkM-cTU0zj_N8uijd1hPhzXC4TSf-FgeMvP7QUqEYImAoEaGMkZ", + "alg": "ES384" + }"#; + +static RSA256_JWK_FIXTURE: &str = r#"{ "p": "6AQ4yHef17an_i5LQPHNIxzpH65xWOSf_qCB7q-lXyM", "kty": "RSA", + "alg": "RS256", + "q": "tSVfpefCsf1iWmAs1zYvxdEsUiv0VMEuQBtbTijj_OE", + "d": "Qdp8a8Df5TlMaaloXApNF_3eu8sLHNWbXdg70e5YVTAs0WUfaIf5c3n96RrDDAzmMEwgKnJ7A1NJ9Nlzz4Z0AQ", + "e": "AQAB", + "use": "enc", + "qi": "adhQHH8IGXFfLEMnZ5t_TeCp5zgSwQktJ2lmylxUG0M", + "dp": "qVnLiKeoSG_Olz17OGBGd4a2sqVFnrjh_51wuaQDdTk", + "dq": "GL_Ec6xYg2z1FRfyyGyU1lgf0BJFTZcfNI8ISIN5ssE", + "key_ops": ["wrapKey"], + "n": "pCzbcd9kjvg5rfGHdEMWnXo49zbB6FLQ-m0B0BvVp0aojVWYa0xujC-ZP7ZhxByPxyc2PazwFJJi9ivZ_ggRww" + }"#; + +static RSA384_JWK_FIXTURE: &str = r#"{ + "p": "6AQ4yHef17an_i5LQPHNIxzpH65xWOSf_qCB7q-lXyM", + "kty": "RSA", + "alg": "RS384", + "q": "tSVfpefCsf1iWmAs1zYvxdEsUiv0VMEuQBtbTijj_OE", + "d": "Qdp8a8Df5TlMaaloXApNF_3eu8sLHNWbXdg70e5YVTAs0WUfaIf5c3n96RrDDAzmMEwgKnJ7A1NJ9Nlzz4Z0AQ", + "e": "AQAB", + "use": "enc", + "qi": "adhQHH8IGXFfLEMnZ5t_TeCp5zgSwQktJ2lmylxUG0M", + "dp": "qVnLiKeoSG_Olz17OGBGd4a2sqVFnrjh_51wuaQDdTk", + "dq": "GL_Ec6xYg2z1FRfyyGyU1lgf0BJFTZcfNI8ISIN5ssE", + "key_ops": ["wrapKey"], + "n": "pCzbcd9kjvg5rfGHdEMWnXo49zbB6FLQ-m0B0BvVp0aojVWYa0xujC-ZP7ZhxByPxyc2PazwFJJi9ivZ_ggRww" + }"#; + +static RSA512_JWK_FIXTURE: &str = r#"{ + "p": "6AQ4yHef17an_i5LQPHNIxzpH65xWOSf_qCB7q-lXyM", + "kty": "RSA", + "alg": "RS512", "q": "tSVfpefCsf1iWmAs1zYvxdEsUiv0VMEuQBtbTijj_OE", "d": "Qdp8a8Df5TlMaaloXApNF_3eu8sLHNWbXdg70e5YVTAs0WUfaIf5c3n96RrDDAzmMEwgKnJ7A1NJ9Nlzz4Z0AQ", "e": "AQAB", @@ -162,9 +205,95 @@ fn serialize_hs256() { ); } +#[test] +fn deserialize_hs384() { + let jwk_str = r#"{ + "kty": "oct", + "k": "tAON6Q", + "alg": "HS384", + "key_ops": ["verify", "sign"] + }"#; + let jwk = JsonWebKey::from_str(jwk_str).unwrap(); + assert_eq!( + jwk, + JsonWebKey { + key: Box::new(Key::Symmetric { + // The parameters were decoded using a 10-liner Rust script. + key: vec![180, 3, 141, 233].into(), + }), + algorithm: Some(Algorithm::HS384), + key_id: None, + key_ops: KeyOps::SIGN | KeyOps::VERIFY, + key_use: None, + x5: Default::default(), + } + ); +} + +#[test] +fn serialize_hs384() { + let jwk = JsonWebKey { + key: Box::new(Key::Symmetric { + key: vec![42; 16].into(), + }), + key_id: None, + algorithm: Some(Algorithm::HS384), + key_ops: KeyOps::empty(), + key_use: None, + x5: Default::default(), + }; + assert_eq!( + jwk.to_string(), + r#"{"kty":"oct","k":"KioqKioqKioqKioqKioqKg","alg":"HS384"}"# + ); +} + +#[test] +fn deserialize_hs512() { + let jwk_str = r#"{ + "kty": "oct", + "k": "tAON6Q", + "alg": "HS512", + "key_ops": ["verify", "sign"] + }"#; + let jwk = JsonWebKey::from_str(jwk_str).unwrap(); + assert_eq!( + jwk, + JsonWebKey { + key: Box::new(Key::Symmetric { + // The parameters were decoded using a 10-liner Rust script. + key: vec![180, 3, 141, 233].into(), + }), + algorithm: Some(Algorithm::HS512), + key_id: None, + key_ops: KeyOps::SIGN | KeyOps::VERIFY, + key_use: None, + x5: Default::default(), + } + ); +} + +#[test] +fn serialize_hs512() { + let jwk = JsonWebKey { + key: Box::new(Key::Symmetric { + key: vec![42; 16].into(), + }), + key_id: None, + algorithm: Some(Algorithm::HS512), + key_ops: KeyOps::empty(), + key_use: None, + x5: Default::default(), + }; + assert_eq!( + jwk.to_string(), + r#"{"kty":"oct","k":"KioqKioqKioqKioqKioqKg","alg":"HS512"}"# + ); +} + #[test] fn deserialize_rs256() { - let jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap(); + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); assert_eq!( jwk, JsonWebKey { @@ -227,7 +356,7 @@ fn deserialize_rs256() { ) }) }), - algorithm: None, + algorithm: Some(Algorithm::RS256), key_id: None, key_ops: KeyOps::WRAP_KEY, key_use: Some(KeyUse::Encryption), @@ -237,7 +366,7 @@ fn deserialize_rs256() { } #[test] -fn serialize_rs256() { +fn serialize_rs() { let jwk = JsonWebKey { key: Box::new(Key::RSA { public: RsaPublic { @@ -265,6 +394,154 @@ fn serialize_rs256() { ); } +#[test] +fn deserialize_rs384() { + let jwk = JsonWebKey::from_str(RSA384_JWK_FIXTURE).unwrap(); + assert_eq!( + jwk, + JsonWebKey { + key: Box::new(Key::RSA { + public: RsaPublic { + e: PublicExponent, + n: vec![ + 164, 44, 219, 113, 223, 100, 142, 248, 57, 173, 241, 135, 116, 67, 22, 157, + 122, 56, 247, 54, 193, 232, 82, 208, 250, 109, 1, 208, 27, 213, 167, 70, + 168, 141, 85, 152, 107, 76, 110, 140, 47, 153, 63, 182, 97, 196, 28, 143, + 199, 39, 54, 61, 172, 240, 20, 146, 98, 246, 43, 217, 254, 8, 17, 195 + ] + .into() + }, + private: Some(RsaPrivate { + d: vec![ + 65, 218, 124, 107, 192, 223, 229, 57, 76, 105, 169, 104, 92, 10, 77, 23, + 253, 222, 187, 203, 11, 28, 213, 155, 93, 216, 59, 209, 238, 88, 85, 48, + 44, 209, 101, 31, 104, 135, 249, 115, 121, 253, 233, 26, 195, 12, 12, 230, + 48, 76, 32, 42, 114, 123, 3, 83, 73, 244, 217, 115, 207, 134, 116, 1 + ] + .into(), + p: Some( + vec![ + 232, 4, 56, 200, 119, 159, 215, 182, 167, 254, 46, 75, 64, 241, 205, + 35, 28, 233, 31, 174, 113, 88, 228, 159, 254, 160, 129, 238, 175, 165, + 95, 35 + ] + .into() + ), + q: Some( + vec![ + 181, 37, 95, 165, 231, 194, 177, 253, 98, 90, 96, 44, 215, 54, 47, 197, + 209, 44, 82, 43, 244, 84, 193, 46, 64, 27, 91, 78, 40, 227, 252, 225 + ] + .into() + ), + dp: Some( + vec![ + 169, 89, 203, 136, 167, 168, 72, 111, 206, 151, 61, 123, 56, 96, 70, + 119, 134, 182, 178, 165, 69, 158, 184, 225, 255, 157, 112, 185, 164, 3, + 117, 57 + ] + .into() + ), + dq: Some( + vec![ + 24, 191, 196, 115, 172, 88, 131, 108, 245, 21, 23, 242, 200, 108, 148, + 214, 88, 31, 208, 18, 69, 77, 151, 31, 52, 143, 8, 72, 131, 121, 178, + 193 + ] + .into() + ), + qi: Some( + vec![ + 105, 216, 80, 28, 127, 8, 25, 113, 95, 44, 67, 39, 103, 155, 127, 77, + 224, 169, 231, 56, 18, 193, 9, 45, 39, 105, 102, 202, 92, 84, 27, 67 + ] + .into() + ) + }) + }), + algorithm: Some(Algorithm::RS384), + key_id: None, + key_ops: KeyOps::WRAP_KEY, + key_use: Some(KeyUse::Encryption), + x5: Default::default(), + } + ); +} + +#[test] +fn deserialize_rs512() { + let jwk = JsonWebKey::from_str(RSA512_JWK_FIXTURE).unwrap(); + assert_eq!( + jwk, + JsonWebKey { + key: Box::new(Key::RSA { + public: RsaPublic { + e: PublicExponent, + n: vec![ + 164, 44, 219, 113, 223, 100, 142, 248, 57, 173, 241, 135, 116, 67, 22, 157, + 122, 56, 247, 54, 193, 232, 82, 208, 250, 109, 1, 208, 27, 213, 167, 70, + 168, 141, 85, 152, 107, 76, 110, 140, 47, 153, 63, 182, 97, 196, 28, 143, + 199, 39, 54, 61, 172, 240, 20, 146, 98, 246, 43, 217, 254, 8, 17, 195 + ] + .into() + }, + private: Some(RsaPrivate { + d: vec![ + 65, 218, 124, 107, 192, 223, 229, 57, 76, 105, 169, 104, 92, 10, 77, 23, + 253, 222, 187, 203, 11, 28, 213, 155, 93, 216, 59, 209, 238, 88, 85, 48, + 44, 209, 101, 31, 104, 135, 249, 115, 121, 253, 233, 26, 195, 12, 12, 230, + 48, 76, 32, 42, 114, 123, 3, 83, 73, 244, 217, 115, 207, 134, 116, 1 + ] + .into(), + p: Some( + vec![ + 232, 4, 56, 200, 119, 159, 215, 182, 167, 254, 46, 75, 64, 241, 205, + 35, 28, 233, 31, 174, 113, 88, 228, 159, 254, 160, 129, 238, 175, 165, + 95, 35 + ] + .into() + ), + q: Some( + vec![ + 181, 37, 95, 165, 231, 194, 177, 253, 98, 90, 96, 44, 215, 54, 47, 197, + 209, 44, 82, 43, 244, 84, 193, 46, 64, 27, 91, 78, 40, 227, 252, 225 + ] + .into() + ), + dp: Some( + vec![ + 169, 89, 203, 136, 167, 168, 72, 111, 206, 151, 61, 123, 56, 96, 70, + 119, 134, 182, 178, 165, 69, 158, 184, 225, 255, 157, 112, 185, 164, 3, + 117, 57 + ] + .into() + ), + dq: Some( + vec![ + 24, 191, 196, 115, 172, 88, 131, 108, 245, 21, 23, 242, 200, 108, 148, + 214, 88, 31, 208, 18, 69, 77, 151, 31, 52, 143, 8, 72, 131, 121, 178, + 193 + ] + .into() + ), + qi: Some( + vec![ + 105, 216, 80, 28, 127, 8, 25, 113, 95, 44, 67, 39, 103, 155, 127, 77, + 224, 169, 231, 56, 18, 193, 9, 45, 39, 105, 102, 202, 92, 84, 27, 67 + ] + .into() + ) + }) + }), + algorithm: Some(Algorithm::RS512), + key_id: None, + key_ops: KeyOps::WRAP_KEY, + key_use: Some(KeyUse::Encryption), + x5: Default::default(), + } + ); +} + #[test] fn mismatched_algorithm() { macro_rules! assert_mismatched_alg { @@ -333,10 +610,43 @@ LTXKRomXYnav0N1ONhmgedy1q0QTo0KsqZdB0kk+c3NkRfycGZl17cBjiQ== ); } +#[cfg(feature = "pkcs-convert")] +#[test] +fn p384_private_to_pem() { + let jwk = JsonWebKey::from_str(P384_JWK_FIXTURE).unwrap(); + #[rustfmt::skip] + assert_eq!( + jwk.key.to_pem(), +"-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCrOnzYH134I6AJ82n6 +7fc9gByCCSCgLmfdSfFz7KGJrAp2sNJXDrk3CqCxxADE0jehZANiAATdV2s9OTt6 +yHVOZCoTQs2zBBVjWQYIPR2fGFo9zoSL2ddIJRB/jqUtSheDTTNZg8zPc/wEweAN +f3CJWQz5xNTTOP83y6KN3WE+HNcLhNJ/4WB4y8/tBSoRgiYCgRoYyRk= +-----END PRIVATE KEY----- +" + ); +} + +#[cfg(feature = "pkcs-convert")] +#[test] +fn p384_public_to_pem() { + let jwk = JsonWebKey::from_str(P384_JWK_FIXTURE).unwrap(); + #[rustfmt::skip] + assert_eq!( + jwk.key.to_public().unwrap().to_pem(), +"-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3VdrPTk7esh1TmQqE0LNswQVY1kGCD0d +nxhaPc6Ei9nXSCUQf46lLUoXg00zWYPMz3P8BMHgDX9wiVkM+cTU0zj/N8uijd1h +PhzXC4TSf+FgeMvP7QUqEYImAoEaGMkZ +-----END PUBLIC KEY----- +" + ); +} + #[cfg(feature = "pkcs-convert")] #[test] fn rsa_private_to_pem() { - let jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap(); + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); #[rustfmt::skip] assert_eq!( jwk.key.to_pem(), @@ -357,7 +667,7 @@ J2lmylxUG0M= #[cfg(feature = "pkcs-convert")] #[test] fn rsa_public_to_pem() { - let jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap(); + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); assert_eq!( jwk.key.to_public().unwrap().to_pem(), "-----BEGIN PUBLIC KEY----- @@ -368,6 +678,57 @@ MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKQs23HfZI74Oa3xh3RDFp16OPc2wehS ); } +#[cfg(feature = "pkcs-convert")] +#[test] +fn rs256_private_to_encoding_key_via_pem() { + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); + let pem = jwk.key.to_pem(); + + jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes()).unwrap(); +} + +#[cfg(feature = "pkcs-convert")] +#[test] +fn rs256_public_to_decoding_key_via_pem() { + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); + let pem = jwk.key.to_public().unwrap().to_pem(); + jsonwebtoken::DecodingKey::from_rsa_pem(pem.as_bytes()).unwrap(); +} + +#[cfg(feature = "pkcs-convert")] +#[test] +fn rs384_private_to_encoding_key_via_pem() { + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); + let pem = jwk.key.to_pem(); + + jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes()).unwrap(); +} + +#[cfg(feature = "pkcs-convert")] +#[test] +fn rs384_public_to_decoding_key_via_pem() { + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); + let pem = jwk.key.to_public().unwrap().to_pem(); + jsonwebtoken::DecodingKey::from_rsa_pem(pem.as_bytes()).unwrap(); +} + +#[cfg(feature = "pkcs-convert")] +#[test] +fn rs512_private_to_encoding_key_via_pem() { + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); + let pem = jwk.key.to_pem(); + + jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes()).unwrap(); +} + +#[cfg(feature = "pkcs-convert")] +#[test] +fn rs512_public_to_decoding_key_via_pem() { + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); + let pem = jwk.key.to_public().unwrap().to_pem(); + jsonwebtoken::DecodingKey::from_rsa_pem(pem.as_bytes()).unwrap(); +} + #[cfg(feature = "pkcs-convert")] #[test] fn oct_to_pem() { @@ -407,7 +768,7 @@ fn ec_is_private() { #[test] fn rsa_is_private() { - let private_jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap(); + let private_jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); assert!(private_jwk.key.is_private()); assert!(!private_jwk.key.to_public().unwrap().is_private()); @@ -424,7 +785,7 @@ fn rsa_is_private() { #[test] fn x509_params() { - let private_jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap(); + let private_jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); assert!(private_jwk.key.is_private()); assert!(!private_jwk.key.to_public().unwrap().is_private()); From afc800478ecf720ebc9639c8db76f530f1517d39 Mon Sep 17 00:00:00 2001 From: EliseZeroTwo Date: Mon, 18 Apr 2022 16:26:22 +0200 Subject: [PATCH 03/11] feat: derive Hash on Algorithm and KeyUse --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4c889e6..e3d537a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -592,7 +592,7 @@ impl fmt::Debug for RsaPrivate { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] pub enum KeyUse { #[serde(rename = "sig")] Signing, @@ -600,7 +600,7 @@ pub enum KeyUse { Encryption, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] #[allow(clippy::upper_case_acronyms)] pub enum Algorithm { HS256, From 7c64f9566e886fc1ceca6099dd5bd2ecebacd67f Mon Sep 17 00:00:00 2001 From: EliseZeroTwo Date: Sun, 24 Apr 2022 09:03:45 +0200 Subject: [PATCH 04/11] fix: DecodingKey building for RSA keys --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index e3d537a..2c3f37c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -667,7 +667,8 @@ const _IMPL_JWT_CONVERSIONS: () = { .unwrap() } Self::RSA { .. } => { - jwt::DecodingKey::from_rsa_pem(self.to_pem().as_bytes()).unwrap() + jwt::DecodingKey::from_rsa_pem(self.to_public().unwrap().to_pem().as_bytes()) + .unwrap() } } } From 238dc88218d5d1c21ab9099cd3802cf51f78e24c Mon Sep 17 00:00:00 2001 From: Matteo Nardi Date: Thu, 2 Nov 2023 15:26:01 +0100 Subject: [PATCH 05/11] Update jsonwebtoken v9.0 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce02d98..772847d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" base64 = "0.13" bitflags = "1.2" generic-array = "0.14" -jsonwebtoken = { version = "8.0", optional = true } +jsonwebtoken = { version = "9.0", optional = true } num-bigint = { version = "0.4", optional = true } p256 = { version = "0.10", optional = true, features = ["arithmetic"] } rand = { version = "0.8", optional = true } @@ -31,7 +31,7 @@ generate = ["p256", "rand"] thumbprint = ["sha2"] [dev-dependencies] -jsonwebtoken = "8.0" +jsonwebtoken = "9.0" [package.metadata.docs.rs] all-features = true From eecfc5aee83bb3e3b22c9f4a328264463bf1d116 Mon Sep 17 00:00:00 2001 From: Matteo Nardi Date: Thu, 2 Nov 2023 15:27:30 +0100 Subject: [PATCH 06/11] Fix clippy warnings --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a0301f9..d948f3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,7 @@ impl JsonWebKey { } pub fn set_algorithm(&mut self, alg: Algorithm) -> Result<(), Error> { - Self::validate_algorithm(alg, &*self.key)?; + Self::validate_algorithm(alg, &self.key)?; self.algorithm = Some(alg); Ok(()) } @@ -180,7 +180,7 @@ impl std::str::FromStr for JsonWebKey { Some(alg) => alg, None => return Ok(jwk), }; - Self::validate_algorithm(alg, &*jwk.key).map(|_| jwk) + Self::validate_algorithm(alg, &jwk.key).map(|_| jwk) } } @@ -337,7 +337,7 @@ impl Key { Some(private_point) => { pkcs8::write_private(oids, |writer: &mut DERWriterSeq<'_>| { writer.next().write_i8(1); // version - writer.next().write_bytes(&**private_point); + writer.next().write_bytes(private_point); // The following tagged value is optional. OpenSSL produces it, // but many tools, including jwt.io and `jsonwebtoken`, don't like it, // so we don't include it. From c1d9e1c1bffa26231f7886b47ec310f7553bb0a3 Mon Sep 17 00:00:00 2001 From: Matteo Nardi Date: Thu, 2 Nov 2023 16:30:37 +0100 Subject: [PATCH 07/11] Update base64 v0.21 --- Cargo.toml | 2 +- src/lib.rs | 3 ++- src/utils.rs | 13 +++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 772847d..b4d55ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" edition = "2021" [dependencies] -base64 = "0.13" +base64 = "0.21" bitflags = "1.2" generic-array = "0.14" jsonwebtoken = { version = "9.0", optional = true } diff --git a/src/lib.rs b/src/lib.rs index d948f3b..a9fabe9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -411,8 +411,9 @@ impl Key { /// If this key is asymmetric, encodes it as PKCS#8 with PEM armoring. #[cfg(feature = "pkcs-convert")] pub fn try_to_pem(&self) -> Result { + use base64::{engine::general_purpose::STANDARD, Engine}; use std::fmt::Write; - let der_b64 = base64::encode(self.try_to_der()?); + let der_b64 = STANDARD.encode(self.try_to_der()?); let key_ty = if self.is_private() { "PRIVATE" } else { diff --git a/src/utils.rs b/src/utils.rs index b829ed9..9282469 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,19 +1,24 @@ +use base64::{ + alphabet::URL_SAFE, + engine::{general_purpose::NO_PAD, GeneralPurpose}, + Engine, +}; use serde::{ de::{self, Deserialize, Deserializer}, ser::{Serialize, Serializer}, }; use zeroize::Zeroizing; -fn base64_config() -> base64::Config { - base64::Config::new(base64::CharacterSet::UrlSafe, false /* pad */) +fn base64_config() -> GeneralPurpose { + GeneralPurpose::new(&URL_SAFE, NO_PAD) } pub(crate) fn base64_encode(bytes: impl AsRef<[u8]>) -> String { - base64::encode_config(bytes, base64_config()) + base64_config().encode(bytes) } fn base64_decode(b64: impl AsRef<[u8]>) -> Result, base64::DecodeError> { - base64::decode_config(b64, base64_config()) + base64_config().decode(b64) } pub(crate) mod serde_base64 { From 6c0447ca699ef5800ec2835f5238dc34ce2f85fb Mon Sep 17 00:00:00 2001 From: Matteo Nardi Date: Thu, 2 Nov 2023 16:40:05 +0100 Subject: [PATCH 08/11] Update p256 v0.13 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b4d55ec..dfaf215 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ bitflags = "1.2" generic-array = "0.14" jsonwebtoken = { version = "9.0", optional = true } num-bigint = { version = "0.4", optional = true } -p256 = { version = "0.10", optional = true, features = ["arithmetic"] } +p256 = { version = "0.13", optional = true, features = ["arithmetic"] } rand = { version = "0.8", optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" From 507a6fbd535aef1e98899614be643b50cd3fae70 Mon Sep 17 00:00:00 2001 From: Matteo Nardi Date: Thu, 2 Nov 2023 16:44:13 +0100 Subject: [PATCH 09/11] Update bitflags v2.4 --- Cargo.toml | 2 +- src/key_ops.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dfaf215..1d8dfda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" [dependencies] base64 = "0.21" -bitflags = "1.2" +bitflags = "2.4" generic-array = "0.14" jsonwebtoken = { version = "9.0", optional = true } num-bigint = { version = "0.4", optional = true } diff --git a/src/key_ops.rs b/src/key_ops.rs index f2521f0..722afb2 100644 --- a/src/key_ops.rs +++ b/src/key_ops.rs @@ -6,7 +6,7 @@ use serde::{ macro_rules! impl_key_ops { ($(($key_op:ident, $const_name:ident, $i:literal)),+,) => { bitflags::bitflags! { - #[derive(Default)] + #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct KeyOps: u16 { $(const $const_name = $i;)* } From 41c8233533633cd9e5efd280ac7b6e041395a795 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sun, 3 Dec 2023 22:02:52 +0100 Subject: [PATCH 10/11] fix: to_decoding_key should use to_public for RSA --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a0301f9..c5343f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -635,7 +635,8 @@ const _IMPL_JWT_CONVERSIONS: () = { .unwrap() } Self::RSA { .. } => { - jwt::DecodingKey::from_rsa_pem(self.to_pem().as_bytes()).unwrap() + jwt::DecodingKey::from_rsa_pem(self.to_public().unwrap().to_pem().as_bytes()) + .unwrap() } } } From 4e1873d98fe08e4ff4e376e945f1f60c0de92041 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sun, 17 Dec 2023 21:46:35 +0100 Subject: [PATCH 11/11] fix: optional features --- src/lib.rs | 27 +++++++++++++++++++++------ src/tests/mod.rs | 4 ++-- src/tests/pkcs_convert.rs | 4 ++-- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 39b31bc..7c2a805 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,7 +246,19 @@ impl Key { use serde::ser::{SerializeStruct, Serializer}; let mut s = serde_json::Serializer::new(Vec::new()); match self { - Self::EC { curve, x, y, .. } => { + Self::EC { + curve: curve @ Curve::P256 { x, y, .. }, + } => { + let mut ss = s.serialize_struct("", 4)?; + ss.serialize_field("crv", curve.name())?; + ss.serialize_field("kty", "EC")?; + ss.serialize_field("x", x)?; + ss.serialize_field("y", y)?; + ss.end()?; + } + Self::EC { + curve: curve @ Curve::P384 { x, y, .. }, + } => { let mut ss = s.serialize_struct("", 4)?; ss.serialize_field("crv", curve.name())?; ss.serialize_field("kty", "EC")?; @@ -342,7 +354,9 @@ impl Key { } Ok(match self { - Self::EC { d, x, y, .. } => { + Self::EC { + curve: Curve::P256 { d, x, y }, + } => { let ec_public_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1]); let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]); let oids = &[Some(&ec_public_oid), Some(&prime256v1_oid)]; @@ -521,10 +535,11 @@ impl Key { let (x_bytes, y_bytes) = pk_bytes.split_at(32); Self::EC { - curve: Curve::P256, - d: Some(sk_scalar.to_bytes().into()), - x: ByteArray::from_slice(x_bytes), - y: ByteArray::from_slice(y_bytes), + curve: Curve::P256 { + d: Some(sk_scalar.to_bytes().into()), + x: ByteArray::from_slice(x_bytes), + y: ByteArray::from_slice(y_bytes), + }, } } } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 44d9376..3ab46c4 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -737,14 +737,14 @@ fn rs512_public_to_decoding_key_via_pem() { #[cfg(feature = "pkcs-convert")] #[test] fn oct_to_pem() { - let jwk = JsonWebKey::from_str(OCT_FIXTURE).unwrap(); + let jwk = JsonWebKey::from_str(OCT_JWK_FIXTURE).unwrap(); assert!(jwk.key.try_to_pem().is_err()); } #[cfg(feature = "pkcs-convert")] #[test] fn oct_to_public() { - let jwk = JsonWebKey::from_str(OCT_FIXTURE).unwrap(); + let jwk = JsonWebKey::from_str(OCT_JWK_FIXTURE).unwrap(); assert!(jwk.key.to_public().is_none()); } diff --git a/src/tests/pkcs_convert.rs b/src/tests/pkcs_convert.rs index 66d53bd..f544948 100644 --- a/src/tests/pkcs_convert.rs +++ b/src/tests/pkcs_convert.rs @@ -32,7 +32,7 @@ LTXKRomXYnav0N1ONhmgedy1q0QTo0KsqZdB0kk+c3NkRfycGZl17cBjiQ== #[test] fn rsa_private_to_pem() { - let jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap(); + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); #[rustfmt::skip] assert_eq!( jwk.key.to_pem(), @@ -52,7 +52,7 @@ J2lmylxUG0M= #[test] fn rsa_public_to_pem() { - let jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap(); + let jwk = JsonWebKey::from_str(RSA256_JWK_FIXTURE).unwrap(); assert_eq!( jwk.key.to_public().unwrap().to_pem(), "-----BEGIN PUBLIC KEY-----