From 9d42ff9ee0f75777edaccd391172903a02b5967e Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Mon, 10 Jan 2022 17:20:20 +0100 Subject: [PATCH 1/5] crypto_box: Add optional serde support With the `serde` feature, serialization and deserialization is implemented for `PublicKey`. --- .github/workflows/crypto_box.yml | 1 + Cargo.lock | 49 +++++++++++++++++++++ crypto_box/Cargo.toml | 15 +++++++ crypto_box/src/lib.rs | 75 +++++++++++++++++++++++++++++++- 4 files changed, 138 insertions(+), 2 deletions(-) diff --git a/.github/workflows/crypto_box.yml b/.github/workflows/crypto_box.yml index e1543a2..ba46f0f 100644 --- a/.github/workflows/crypto_box.yml +++ b/.github/workflows/crypto_box.yml @@ -54,3 +54,4 @@ jobs: override: true - run: cargo test --release --features std - run: cargo test --release --features std,heapless + - run: cargo test --release --features std,serde diff --git a/Cargo.lock b/Cargo.lock index 196a443..e4bdc73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,15 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "blake2" version = "0.10.0" @@ -111,10 +120,13 @@ dependencies = [ name = "crypto_box" version = "0.7.0" dependencies = [ + "bincode", "chacha20", "chacha20poly1305", + "rand", "rand_core 0.6.3", "salsa20", + "serde", "x25519-dalek", "xsalsa20poly1305", "zeroize", @@ -317,6 +329,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro2" version = "1.0.33" @@ -335,6 +353,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.3", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -353,6 +393,15 @@ dependencies = [ "getrandom 0.2.3", ] +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.3", +] + [[package]] name = "salsa20" version = "0.9.0" diff --git a/crypto_box/Cargo.toml b/crypto_box/Cargo.toml index 4e936d5..ed46360 100644 --- a/crypto_box/Cargo.toml +++ b/crypto_box/Cargo.toml @@ -25,10 +25,25 @@ x25519-dalek = { version = "1", default-features = false } xsalsa20poly1305 = { version = "0.8", default-features = false, features = ["rand_core"] } zeroize = { version = ">=1, <1.5", default-features = false } +[dependencies.serde_crate] +package = "serde" +optional = true +version = "1" +default-features = false + +[dev-dependencies] +bincode = "1" +rand = "0.8" + [features] default = ["alloc", "u64_backend"] +serde = ["serde_crate"] std = ["rand_core/std", "xsalsa20poly1305/std"] alloc = ["xsalsa20poly1305/alloc"] heapless = ["xsalsa20poly1305/heapless"] u32_backend = ["x25519-dalek/u32_backend"] u64_backend = ["x25519-dalek/u64_backend"] + +[package.metadata.docs.rs] +features = ["serde"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/crypto_box/src/lib.rs b/crypto_box/src/lib.rs index 9bb9518..072d040 100644 --- a/crypto_box/src/lib.rs +++ b/crypto_box/src/lib.rs @@ -207,6 +207,12 @@ use xsalsa20poly1305::aead::{ use xsalsa20poly1305::XSalsa20Poly1305; use zeroize::{Zeroize, Zeroizing}; +#[cfg(feature = "serde")] +use serde_crate::{ + de::{Deserialize, Deserializer}, + ser::{Serialize, Serializer}, +}; + /// Size of a `crypto_box` public or secret key in bytes. pub const KEY_SIZE: usize = 32; @@ -215,7 +221,7 @@ pub const KEY_SIZE: usize = 32; /// Implemented as an alias for [`GenericArray`]. pub type Tag = GenericArray; -/// `crypto_box` secret key +/// A `crypto_box` secret key. #[derive(Clone)] pub struct SecretKey([u8; KEY_SIZE]); @@ -265,7 +271,9 @@ impl Drop for SecretKey { } } -/// `crypto_box` public key +/// A `crypto_box` public key. +/// +/// This type can be serialized if the `serde` feature is enabled. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct PublicKey([u8; KEY_SIZE]); @@ -294,6 +302,39 @@ impl From<[u8; KEY_SIZE]> for PublicKey { } } +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + <[u8]>::serialize(&self.0, serializer) + } +} + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use core::convert::TryInto; + use serde_crate::de::Error; + + // Deserialize slice + let slice = <&[u8]>::deserialize(deserializer)?; + + // Convert to array (with length check) + let array: [u8; KEY_SIZE] = slice + .try_into() + .map_err(|_| Error::invalid_length(slice.len(), &"a 32-byte public key"))?; + + Ok(PublicKey::from(array)) + } +} + macro_rules! impl_aead_in_place { ($box:ty, $nonce_size:ty, $tag_size:ty, $ct_overhead:ty) => { impl AeadCore for $box { @@ -415,3 +456,33 @@ impl ChaChaBox { } impl_aead_in_place!(ChaChaBox, U24, U16, U0); + +#[cfg(test)] +mod tests { + #[test] + #[cfg(feature = "serde")] + fn test_public_key_serialization() { + use super::PublicKey; + use rand_core::RngCore; + + // Random PK bytes + let mut public_key_bytes = [0; 32]; + let mut rng = rand::thread_rng(); + rng.fill_bytes(&mut public_key_bytes); + + // Create public key + let public_key = PublicKey::from(public_key_bytes); + + // Round-trip serialize + let serialized = + bincode::serialize(&public_key).expect("Public key could not be serialized"); + let deserialized: PublicKey = + bincode::deserialize(&serialized).expect("Public key could not be deserialized"); + + // Deserialized public key should equal the original public key + assert_eq!( + deserialized, public_key, + "Deserialized public key does not match original" + ); + } +} From a5196a1dbe296bf08ae17bbbabaf987a560e9697 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Tue, 11 Jan 2022 17:44:29 +0100 Subject: [PATCH 2/5] crypto_box: SeqAccess based deserializer for PublicKey --- crypto_box/src/lib.rs | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/crypto_box/src/lib.rs b/crypto_box/src/lib.rs index 072d040..254faa5 100644 --- a/crypto_box/src/lib.rs +++ b/crypto_box/src/lib.rs @@ -320,18 +320,35 @@ impl<'de> Deserialize<'de> for PublicKey { where D: Deserializer<'de>, { - use core::convert::TryInto; - use serde_crate::de::Error; + use serde_crate::de::{Error, SeqAccess, Visitor}; - // Deserialize slice - let slice = <&[u8]>::deserialize(deserializer)?; + struct PublicKeyVisitor; - // Convert to array (with length check) - let array: [u8; KEY_SIZE] = slice - .try_into() - .map_err(|_| Error::invalid_length(slice.len(), &"a 32-byte public key"))?; + impl<'de> Visitor<'de> for PublicKeyVisitor { + type Value = PublicKey; - Ok(PublicKey::from(array)) + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a 32-byte public key") + } + + fn visit_seq(self, mut seq: S) -> Result + where + S: SeqAccess<'de>, + { + let mut key_bytes = [0; KEY_SIZE]; + for i in 0..KEY_SIZE { + key_bytes[i] = match seq.next_element()? { + Some(val) => val, + None => { + return Err(Error::invalid_length(i - 1, &self)); + } + } + } + Ok(PublicKey::from(key_bytes)) + } + } + + deserializer.deserialize_seq(PublicKeyVisitor) } } From 7451e4b6186bd0522f605ef41c0842b445bf1e3c Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Tue, 11 Jan 2022 17:47:11 +0100 Subject: [PATCH 3/5] crypto_box: Add serde roundtrip test for rmp --- Cargo.lock | 37 +++++++++++++++++++++++++++++++++++++ crypto_box/Cargo.toml | 1 + crypto_box/src/lib.rs | 12 ++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4bdc73..543ff4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,12 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "bincode" version = "1.3.3" @@ -125,6 +131,7 @@ dependencies = [ "chacha20poly1305", "rand", "rand_core 0.6.3", + "rmp-serde", "salsa20", "serde", "x25519-dalek", @@ -306,6 +313,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "opaque-debug" version = "0.3.0" @@ -402,6 +418,27 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "rmp" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" +dependencies = [ + "byteorder", + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723ecff9ad04f4ad92fe1c8ca6c20d2196d9286e9c60727c4cb5511629260e9d" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "salsa20" version = "0.9.0" diff --git a/crypto_box/Cargo.toml b/crypto_box/Cargo.toml index ed46360..d0de870 100644 --- a/crypto_box/Cargo.toml +++ b/crypto_box/Cargo.toml @@ -34,6 +34,7 @@ default-features = false [dev-dependencies] bincode = "1" rand = "0.8" +rmp-serde = "0.15" [features] default = ["alloc", "u64_backend"] diff --git a/crypto_box/src/lib.rs b/crypto_box/src/lib.rs index 254faa5..4b9a033 100644 --- a/crypto_box/src/lib.rs +++ b/crypto_box/src/lib.rs @@ -490,13 +490,21 @@ mod tests { // Create public key let public_key = PublicKey::from(public_key_bytes); - // Round-trip serialize + // Round-trip serialize with bincode let serialized = bincode::serialize(&public_key).expect("Public key could not be serialized"); let deserialized: PublicKey = bincode::deserialize(&serialized).expect("Public key could not be deserialized"); + assert_eq!( + deserialized, public_key, + "Deserialized public key does not match original" + ); - // Deserialized public key should equal the original public key + // Round-trip serialize with rmp (msgpack) + let serialized = + rmp_serde::to_vec_named(&public_key).expect("Public key could not be serialized"); + let deserialized: PublicKey = + rmp_serde::from_slice(&serialized).expect("Public key could not be deserialized"); assert_eq!( deserialized, public_key, "Deserialized public key does not match original" From 5d870ca021a9546d5634a4c1534bd6677b482300 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Tue, 11 Jan 2022 18:19:59 +0100 Subject: [PATCH 4/5] crypto_box: Allow deserializing from bytes as well --- crypto_box/src/lib.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crypto_box/src/lib.rs b/crypto_box/src/lib.rs index 4b9a033..4983e75 100644 --- a/crypto_box/src/lib.rs +++ b/crypto_box/src/lib.rs @@ -320,6 +320,7 @@ impl<'de> Deserialize<'de> for PublicKey { where D: Deserializer<'de>, { + use core::convert::TryInto; use serde_crate::de::{Error, SeqAccess, Visitor}; struct PublicKeyVisitor; @@ -346,9 +347,20 @@ impl<'de> Deserialize<'de> for PublicKey { } Ok(PublicKey::from(key_bytes)) } + + fn visit_bytes(self, bytes: &[u8]) -> Result + where + E: Error, + { + // Convert to array (with length check) + let array: [u8; KEY_SIZE] = bytes + .try_into() + .map_err(|_| Error::invalid_length(bytes.len(), &self))?; + Ok(PublicKey::from(array)) + } } - deserializer.deserialize_seq(PublicKeyVisitor) + deserializer.deserialize_bytes(PublicKeyVisitor) } } From f846f24798ea4bcd2703dfb161e5b94eb04a33f6 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Tue, 11 Jan 2022 18:22:27 +0100 Subject: [PATCH 5/5] crypto_box: Use serialize_bytes for PublicKey This allows serializers to use a more efficient byte array representation. Otherwise sequence based serialization would be used. --- crypto_box/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_box/src/lib.rs b/crypto_box/src/lib.rs index 4983e75..22ca238 100644 --- a/crypto_box/src/lib.rs +++ b/crypto_box/src/lib.rs @@ -309,7 +309,7 @@ impl Serialize for PublicKey { where S: Serializer, { - <[u8]>::serialize(&self.0, serializer) + serializer.serialize_bytes(&self.0) } }