From e211b40430cb8c0658c50d20ed2540a19f569e42 Mon Sep 17 00:00:00 2001 From: Christopher Hotchkiss Date: Thu, 20 Feb 2025 16:44:06 -0800 Subject: [PATCH 1/3] Added optional serde support --- Cargo.toml | 18 ++++++++++++++-- scripts/test.sh | 3 +++ src/secure/key.rs | 55 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b1d19441..786f72af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "cookie" version = "0.18.1" -authors = ["Sergio Benitez ", "Alex Crichton "] +authors = [ + "Sergio Benitez ", + "Alex Crichton ", +] edition = "2018" license = "MIT OR Apache-2.0" repository = "https://github.com/SergioBenitez/cookie-rs" @@ -19,9 +22,15 @@ secure = ["private", "signed", "key-expansion"] private = ["aes-gcm", "base64", "rand", "subtle"] signed = ["hmac", "sha2", "base64", "rand", "subtle"] key-expansion = ["sha2", "hkdf"] +serde = ["dep:serde", "dep:serde_bytes"] [dependencies] -time = { version = "0.3", default-features = false, features = ["std", "parsing", "formatting", "macros"] } +time = { version = "0.3", default-features = false, features = [ + "std", + "parsing", + "formatting", + "macros", +] } percent-encoding = { version = "2.0", optional = true } # dependencies for secure (private/signed) functionality @@ -32,9 +41,14 @@ base64 = { version = "0.22", optional = true } rand = { version = "0.8", optional = true } hkdf = { version = "0.12.0", optional = true } subtle = { version = "2.3", optional = true } +serde = { version = "1.0.218", optional = true, features = ["derive"] } +serde_bytes = { version = "0.11.15", optional = true } [build-dependencies] version_check = "0.9.4" +[dev-dependencies] +serde_test = "1.0.177" + [package.metadata.docs.rs] all-features = true diff --git a/scripts/test.sh b/scripts/test.sh index 894d468e..c57c73d4 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -11,6 +11,9 @@ cargo test --verbose --features secure cargo test --verbose --features 'private,key-expansion' cargo test --verbose --features 'signed,key-expansion' cargo test --verbose --features 'secure,percent-encode' +cargo test --verbose --features 'private,key-expansion,serde' +cargo test --verbose --features 'signed,key-expansion,serde' +cargo test --verbose --features 'secure,percent-encode,serde' cargo test --verbose cargo test --verbose --no-default-features diff --git a/src/secure/key.rs b/src/secure/key.rs index 8b62a933..08c1d56d 100644 --- a/src/secure/key.rs +++ b/src/secure/key.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; const SIGNING_KEY_LEN: usize = 32; @@ -16,9 +18,15 @@ const_assert!(crate::secure::private::KEY_LEN == ENCRYPTION_KEY_LEN); /// [`PrivateJar`](crate::PrivateJar) and [`SignedJar`](crate::SignedJar). A /// single instance of a `Key` can be used for both a `PrivateJar` and a /// `SignedJar` simultaneously with no notable security implications. -#[cfg_attr(all(nightly, doc), doc(cfg(any(feature = "private", feature = "signed"))))] +#[cfg_attr( + all(nightly, doc), + doc(cfg(any(feature = "private", feature = "signed"))) +)] #[derive(Clone)] -pub struct Key([u8; COMBINED_KEY_LENGTH /* SIGNING | ENCRYPTION */]); +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Key( + #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; COMBINED_KEY_LENGTH], +); impl PartialEq for Key { fn eq(&self, other: &Self) -> bool { @@ -95,14 +103,18 @@ impl Key { #[cfg_attr(all(nightly, doc), doc(cfg(feature = "key-expansion")))] pub fn derive_from(master_key: &[u8]) -> Self { if master_key.len() < 32 { - panic!("bad master key length: expected >= 32 bytes, found {}", master_key.len()); + panic!( + "bad master key length: expected >= 32 bytes, found {}", + master_key.len() + ); } // Expand the master key into two HKDF generated keys. const KEYS_INFO: &[u8] = b"COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; let mut both_keys = [0; COMBINED_KEY_LENGTH]; let hk = hkdf::Hkdf::::from_prk(master_key).expect("key length prechecked"); - hk.expand(KEYS_INFO, &mut both_keys).expect("expand into keys"); + hk.expand(KEYS_INFO, &mut both_keys) + .expect("expand into keys"); Key::from(&both_keys) } @@ -192,7 +204,10 @@ impl Key { } /// An error indicating an issue with generating or constructing a key. -#[cfg_attr(all(nightly, doc), doc(cfg(any(feature = "private", feature = "signed"))))] +#[cfg_attr( + all(nightly, doc), + doc(cfg(any(feature = "private", feature = "signed"))) +)] #[derive(Debug)] #[non_exhaustive] pub enum KeyError { @@ -202,14 +217,17 @@ pub enum KeyError { TooShort(usize), } -impl std::error::Error for KeyError { } +impl std::error::Error for KeyError {} impl std::fmt::Display for KeyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { KeyError::TooShort(n) => { - write!(f, "key material is too short: expected >= {} bytes, got {} bytes", - COMBINED_KEY_LENGTH, n) + write!( + f, + "key material is too short: expected >= {} bytes, got {} bytes", + COMBINED_KEY_LENGTH, n + ) } } } @@ -311,4 +329,25 @@ mod test { assert_eq!(format!("{:?}", key), "Key"); } + + #[cfg(feature = "serde")] + static serde_key: [u8; 64] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + ]; + + #[test] + #[cfg(feature = "serde")] + fn roundtrip_serialize() { + let key = Key::from(&serde_key); + + serde_test::assert_tokens( + &key, + &[ + serde_test::Token::NewtypeStruct { name: "Key" }, + serde_test::Token::Bytes(&serde_key), + ], + ); + } } From cd2325c002df30930eda12c6e40280d505030791 Mon Sep 17 00:00:00 2001 From: Christopher Hotchkiss Date: Thu, 20 Feb 2025 17:05:09 -0800 Subject: [PATCH 2/3] Updated the serde dependency to be less prescriptive on the serde version. --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 786f72af..bf73d2ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,14 +41,14 @@ base64 = { version = "0.22", optional = true } rand = { version = "0.8", optional = true } hkdf = { version = "0.12.0", optional = true } subtle = { version = "2.3", optional = true } -serde = { version = "1.0.218", optional = true, features = ["derive"] } -serde_bytes = { version = "0.11.15", optional = true } +serde = { version = "1.0", optional = true, features = ["derive"] } +serde_bytes = { version = "0.11", optional = true } [build-dependencies] version_check = "0.9.4" [dev-dependencies] -serde_test = "1.0.177" +serde_test = "1.0" [package.metadata.docs.rs] all-features = true From 0daba184c6af6338c9b5170fa8f33555f08a4444 Mon Sep 17 00:00:00 2001 From: Christopher Hotchkiss Date: Thu, 20 Feb 2025 21:01:55 -0800 Subject: [PATCH 3/3] Reverted all formatting only changes to match requests on other pull requests. --- Cargo.toml | 12 ++---------- src/secure/key.rs | 27 +++++++-------------------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf73d2ed..2327f8fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "cookie" version = "0.18.1" -authors = [ - "Sergio Benitez ", - "Alex Crichton ", -] +authors = ["Sergio Benitez ", "Alex Crichton "] edition = "2018" license = "MIT OR Apache-2.0" repository = "https://github.com/SergioBenitez/cookie-rs" @@ -25,12 +22,7 @@ key-expansion = ["sha2", "hkdf"] serde = ["dep:serde", "dep:serde_bytes"] [dependencies] -time = { version = "0.3", default-features = false, features = [ - "std", - "parsing", - "formatting", - "macros", -] } +time = { version = "0.3", default-features = false, features = ["std", "parsing", "formatting", "macros"] } percent-encoding = { version = "2.0", optional = true } # dependencies for secure (private/signed) functionality diff --git a/src/secure/key.rs b/src/secure/key.rs index 08c1d56d..f4a265f8 100644 --- a/src/secure/key.rs +++ b/src/secure/key.rs @@ -18,10 +18,7 @@ const_assert!(crate::secure::private::KEY_LEN == ENCRYPTION_KEY_LEN); /// [`PrivateJar`](crate::PrivateJar) and [`SignedJar`](crate::SignedJar). A /// single instance of a `Key` can be used for both a `PrivateJar` and a /// `SignedJar` simultaneously with no notable security implications. -#[cfg_attr( - all(nightly, doc), - doc(cfg(any(feature = "private", feature = "signed"))) -)] +#[cfg_attr(all(nightly, doc), doc(cfg(any(feature = "private", feature = "signed"))))] #[derive(Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Key( @@ -103,18 +100,14 @@ impl Key { #[cfg_attr(all(nightly, doc), doc(cfg(feature = "key-expansion")))] pub fn derive_from(master_key: &[u8]) -> Self { if master_key.len() < 32 { - panic!( - "bad master key length: expected >= 32 bytes, found {}", - master_key.len() - ); + panic!("bad master key length: expected >= 32 bytes, found {}", master_key.len()); } // Expand the master key into two HKDF generated keys. const KEYS_INFO: &[u8] = b"COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; let mut both_keys = [0; COMBINED_KEY_LENGTH]; let hk = hkdf::Hkdf::::from_prk(master_key).expect("key length prechecked"); - hk.expand(KEYS_INFO, &mut both_keys) - .expect("expand into keys"); + hk.expand(KEYS_INFO, &mut both_keys).expect("expand into keys"); Key::from(&both_keys) } @@ -204,10 +197,7 @@ impl Key { } /// An error indicating an issue with generating or constructing a key. -#[cfg_attr( - all(nightly, doc), - doc(cfg(any(feature = "private", feature = "signed"))) -)] +#[cfg_attr(all(nightly, doc), doc(cfg(any(feature = "private", feature = "signed"))))] #[derive(Debug)] #[non_exhaustive] pub enum KeyError { @@ -217,17 +207,14 @@ pub enum KeyError { TooShort(usize), } -impl std::error::Error for KeyError {} +impl std::error::Error for KeyError { } impl std::fmt::Display for KeyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { KeyError::TooShort(n) => { - write!( - f, - "key material is too short: expected >= {} bytes, got {} bytes", - COMBINED_KEY_LENGTH, n - ) + write!(f, "key material is too short: expected >= {} bytes, got {} bytes", + COMBINED_KEY_LENGTH, n) } } }