From 3e5e1e12bfe8a2e921a7369474cb62fd01836e34 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 31 Aug 2021 20:11:52 -0700 Subject: [PATCH] Basic serde support for non-secret types --- CHANGELOG.md | 2 + Cargo.toml | 4 + umbral-pre/Cargo.toml | 6 +- umbral-pre/src/capsule.rs | 32 ++++++ umbral-pre/src/capsule_frag.rs | 34 +++++++ umbral-pre/src/key_frag.rs | 34 +++++++ umbral-pre/src/keys.rs | 55 +++++++++++ umbral-pre/src/lib.rs | 1 + umbral-pre/src/serde.rs | 175 +++++++++++++++++++++++++++++++++ 9 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 umbral-pre/src/serde.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd27438..d9013de5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `VerifiedCapsuleFrag::from_verified_bytes()`. ([#63]) - Added `SecretKeyFactory::secret_key_factory_by_label()`. ([#64]) - Added `SecretKeyFactory::from_secure_randomness()` and `seed_size()`. ([#64]) +- `serde` support for `Capsule`, `CapsuleFrag`, `KeyFrag`, `PublicKey`, and `Signature`. ([#67]) ### Fixed @@ -40,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#63]: https://github.com/nucypher/rust-umbral/pull/63 [#64]: https://github.com/nucypher/rust-umbral/pull/64 [#65]: https://github.com/nucypher/rust-umbral/pull/65 +[#67]: https://github.com/nucypher/rust-umbral/pull/67 ## [0.2.0] - 2021-06-14 diff --git a/Cargo.toml b/Cargo.toml index 50afcba6..68ad18ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,7 @@ members = [ "umbral-pre-wasm", "umbral-pre-python", ] + +# Prevents feature conflicts between [dependencies] and [dev-dependencies] +# Will be the default in 2021 edition. +resolver = "2" diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index 037cb12b..ba6c129d 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -14,7 +14,9 @@ k256 = { version = "0.9", default-features = false, features = ["ecdsa", "arithm sha2 = { version = "0.9", default-features = false } chacha20poly1305 = { version = "0.8", features = ["xchacha20poly1305"] } hkdf = { version = "0.11", default-features = false } -hex = { version = "0.4", default-features = false } +hex = { version = "0.4", default-features = false, features = ["alloc"] } +serde = { version = "1", default-features = false } +base64 = { version = "0.13", default-features = false, features = ["alloc"] } # These packages are among the dependencies of the packages above. # Their versions should be updated when the main packages above are updated. @@ -32,6 +34,8 @@ zeroize = "1.3" [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } +serde_json = "1" +rmp-serde = "0.15" [features] default = ["default-rng"] diff --git a/umbral-pre/src/capsule.rs b/umbral-pre/src/capsule.rs index 49e53e04..ac9620a2 100644 --- a/umbral-pre/src/capsule.rs +++ b/umbral-pre/src/capsule.rs @@ -4,6 +4,7 @@ use core::fmt; use generic_array::sequence::Concat; use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use typenum::op; use crate::capsule_frag::CapsuleFrag; @@ -12,6 +13,7 @@ use crate::hashing_ds::{hash_capsule_points, hash_to_polynomial_arg, hash_to_sha use crate::keys::{PublicKey, SecretKey}; use crate::params::Parameters; use crate::secret_box::SecretBox; +use crate::serde::{serde_deserialize, serde_serialize, Representation}; use crate::traits::{ fmt_public, ConstructionError, DeserializableFromArray, HasTypeName, RepresentableAsArray, SerializableToArray, @@ -84,6 +86,24 @@ impl DeserializableFromArray for Capsule { } } +impl Serialize for Capsule { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_serialize(self, serializer, Representation::Base64) + } +} + +impl<'de> Deserialize<'de> for Capsule { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer, Representation::Base64) + } +} + impl HasTypeName for Capsule { fn type_name() -> &'static str { "Capsule" @@ -243,6 +263,8 @@ mod tests { use rand_core::OsRng; use super::{Capsule, OpenReencryptedError}; + use crate::serde::tests::{check_deserialization, check_serialization}; + use crate::serde::Representation; use crate::{ encrypt, generate_kfrags, reencrypt, DeserializableFromArray, SecretKey, SerializableToArray, Signer, @@ -324,4 +346,14 @@ mod tests { Err(OpenReencryptedError::ValidationFailed) ); } + + #[test] + fn test_serde_serialization() { + let delegating_sk = SecretKey::random(); + let delegating_pk = delegating_sk.public_key(); + let (capsule, _key_seed) = Capsule::from_public_key(&mut OsRng, &delegating_pk); + + check_serialization(&capsule, Representation::Base64); + check_deserialization(&capsule); + } } diff --git a/umbral-pre/src/capsule_frag.rs b/umbral-pre/src/capsule_frag.rs index 1db6398d..1e413cf4 100644 --- a/umbral-pre/src/capsule_frag.rs +++ b/umbral-pre/src/capsule_frag.rs @@ -3,6 +3,7 @@ use core::fmt; use generic_array::sequence::Concat; use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use typenum::op; use crate::capsule::Capsule; @@ -10,6 +11,7 @@ use crate::curve::{CurvePoint, CurveScalar}; use crate::hashing_ds::{hash_to_cfrag_verification, kfrag_signature_message}; use crate::key_frag::{KeyFrag, KeyFragID}; use crate::keys::{PublicKey, Signature}; +use crate::serde::{serde_deserialize, serde_serialize, Representation}; use crate::traits::{ fmt_public, ConstructionError, DeserializableFromArray, DeserializationError, HasTypeName, RepresentableAsArray, SerializableToArray, @@ -154,6 +156,24 @@ impl DeserializableFromArray for CapsuleFrag { } } +impl Serialize for CapsuleFrag { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_serialize(self, serializer, Representation::Base64) + } +} + +impl<'de> Deserialize<'de> for CapsuleFrag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer, Representation::Base64) + } +} + impl HasTypeName for CapsuleFrag { fn type_name() -> &'static str { "CapsuleFrag" @@ -329,6 +349,8 @@ mod tests { use alloc::vec::Vec; use super::{CapsuleFrag, VerifiedCapsuleFrag}; + use crate::serde::tests::{check_deserialization, check_serialization}; + use crate::serde::Representation; use crate::{ encrypt, generate_kfrags, reencrypt, Capsule, DeserializableFromArray, PublicKey, SecretKey, SerializableToArray, Signer, @@ -388,4 +410,16 @@ mod tests { assert_eq!(verified_cfrag_back, verified_cfrag); } } + + #[test] + fn test_serde_serialization() { + let (_delegating_pk, _receiving_pk, _verifying_pk, _capsule, verified_cfrags) = + prepare_cfrags(); + + let vcfrag = verified_cfrags[0].clone(); + let cfrag = CapsuleFrag::from_array(&vcfrag.to_array()).unwrap(); + + check_serialization(&cfrag, Representation::Base64); + check_deserialization(&cfrag); + } } diff --git a/umbral-pre/src/key_frag.rs b/umbral-pre/src/key_frag.rs index ba7847c7..8a4b6299 100644 --- a/umbral-pre/src/key_frag.rs +++ b/umbral-pre/src/key_frag.rs @@ -5,12 +5,14 @@ use core::fmt; use generic_array::sequence::Concat; use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use typenum::{op, U32}; use crate::curve::{CurvePoint, CurveScalar}; use crate::hashing_ds::{hash_to_polynomial_arg, hash_to_shared_secret, kfrag_signature_message}; use crate::keys::{PublicKey, SecretKey, Signature, Signer}; use crate::params::Parameters; +use crate::serde::{serde_deserialize, serde_serialize, Representation}; use crate::traits::{ fmt_public, ConstructionError, DeserializableFromArray, DeserializationError, HasTypeName, RepresentableAsArray, SerializableToArray, @@ -201,6 +203,24 @@ impl DeserializableFromArray for KeyFrag { } } +impl Serialize for KeyFrag { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_serialize(self, serializer, Representation::Base64) + } +} + +impl<'de> Deserialize<'de> for KeyFrag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer, Representation::Base64) + } +} + impl HasTypeName for KeyFrag { fn type_name() -> &'static str { "KeyFrag" @@ -471,6 +491,8 @@ mod tests { use rand_core::OsRng; use super::{KeyFrag, KeyFragBase, KeyFragVerificationError, VerifiedKeyFrag}; + use crate::serde::tests::{check_deserialization, check_serialization}; + use crate::serde::Representation; use crate::{DeserializableFromArray, PublicKey, SecretKey, SerializableToArray, Signer}; fn prepare_kfrags( @@ -539,4 +561,16 @@ mod tests { } } } + + #[test] + fn test_serde_serialization() { + let (_delegating_pk, _receiving_pk, _verifying_pk, verified_kfrags) = + prepare_kfrags(true, true); + + let vkfrag = verified_kfrags[0].clone(); + let kfrag = KeyFrag::from_array(&vkfrag.to_array()).unwrap(); + + check_serialization(&kfrag, Representation::Base64); + check_deserialization(&kfrag); + } } diff --git a/umbral-pre/src/keys.rs b/umbral-pre/src/keys.rs index 8ad363c8..1ae1ac64 100644 --- a/umbral-pre/src/keys.rs +++ b/umbral-pre/src/keys.rs @@ -8,6 +8,7 @@ use ecdsa::{Signature as BackendSignature, SignatureSize, SigningKey, VerifyingK use elliptic_curve::{PublicKey as BackendPublicKey, SecretKey as BackendSecretKey}; use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use signature::{DigestVerifier, RandomizedDigestSigner, Signature as SignatureTrait}; use typenum::{Unsigned, U32, U64}; @@ -18,6 +19,7 @@ use crate::curve::{BackendNonZeroScalar, CurvePoint, CurveScalar, CurveType}; use crate::dem::kdf; use crate::hashing::{BackendDigest, Hash, ScalarDigest}; use crate::secret_box::{CanBeZeroizedOnDrop, SecretBox}; +use crate::serde::{serde_deserialize, serde_serialize, Representation}; use crate::traits::{ fmt_public, fmt_secret, ConstructionError, DeserializableFromArray, HasTypeName, RepresentableAsArray, SerializableToArray, SerializableToSecretArray, SizeMismatchError, @@ -47,6 +49,24 @@ impl DeserializableFromArray for Signature { } } +impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_serialize(self, serializer, Representation::Base64) + } +} + +impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer, Representation::Base64) + } +} + impl Signature { /// Verifies that the given message was signed with the secret counterpart of the given key. /// The message is hashed internally. @@ -238,6 +258,24 @@ impl DeserializableFromArray for PublicKey { } } +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_serialize(self, serializer, Representation::Hex) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer, Representation::Hex) + } +} + impl HasTypeName for PublicKey { fn type_name() -> &'static str { "PublicKey" @@ -382,6 +420,8 @@ impl fmt::Display for SecretKeyFactory { mod tests { use super::{PublicKey, SecretKey, SecretKeyFactory, Signer}; + use crate::serde::tests::{check_deserialization, check_serialization}; + use crate::serde::Representation; use crate::{DeserializableFromArray, SerializableToArray, SerializableToSecretArray}; #[test] @@ -433,4 +473,19 @@ mod tests { assert_eq!(pk, vk); assert!(signature.verify(&vk, message)); } + + #[test] + fn test_serde_serialization() { + let sk = SecretKey::random(); + let pk = sk.public_key(); + let message = b"asdafdahsfdasdfasd"; + let signer = Signer::new(&sk); + let signature = signer.sign(message); + + check_serialization(&pk, Representation::Hex); + check_deserialization(&pk); + + check_serialization(&signature, Representation::Base64); + check_deserialization(&signature); + } } diff --git a/umbral-pre/src/lib.rs b/umbral-pre/src/lib.rs index 4b7839b1..1255eb00 100644 --- a/umbral-pre/src/lib.rs +++ b/umbral-pre/src/lib.rs @@ -116,6 +116,7 @@ mod keys; mod params; mod pre; mod secret_box; +mod serde; mod traits; pub use capsule::{Capsule, OpenReencryptedError}; diff --git a/umbral-pre/src/serde.rs b/umbral-pre/src/serde.rs new file mode 100644 index 00000000..00123957 --- /dev/null +++ b/umbral-pre/src/serde.rs @@ -0,0 +1,175 @@ +use core::fmt; +use core::marker::PhantomData; + +use serde::{de, Deserializer, Serializer}; + +use crate::traits::{DeserializableFromArray, HasTypeName, SerializableToArray}; + +pub(crate) enum Representation { + Base64, + Hex, +} + +// We cannot have a generic implementation of Serialize over everything +// that supports SerializableToArray, so we have to use this helper function +// and define implementations manually. +pub(crate) fn serde_serialize( + obj: &T, + serializer: S, + representation: Representation, +) -> Result +where + T: SerializableToArray, + S: Serializer, +{ + if serializer.is_human_readable() { + let repr = match representation { + Representation::Base64 => base64::encode(obj.to_array().as_ref()), + Representation::Hex => hex::encode(obj.to_array().as_ref()), + }; + serializer.serialize_str(&repr) + } else { + serializer.serialize_bytes(obj.to_array().as_ref()) + } +} + +struct B64Visitor(PhantomData); + +impl<'de, T> de::Visitor<'de> for B64Visitor +where + T: DeserializableFromArray + HasTypeName, +{ + type Value = T; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "b64-encoded {} bytes", T::type_name()) + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + let bytes = base64::decode(v).map_err(de::Error::custom)?; + T::from_bytes(&bytes).map_err(de::Error::custom) + } +} + +struct HexVisitor(PhantomData); + +impl<'de, T> de::Visitor<'de> for HexVisitor +where + T: DeserializableFromArray + HasTypeName, +{ + type Value = T; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "hex-encoded {} bytes", T::type_name()) + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + let bytes = hex::decode(v).map_err(de::Error::custom)?; + T::from_bytes(&bytes).map_err(de::Error::custom) + } +} + +struct BytesVisitor(PhantomData); + +impl<'de, T> de::Visitor<'de> for BytesVisitor +where + T: DeserializableFromArray + HasTypeName, +{ + type Value = T; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} bytes", T::type_name()) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + T::from_bytes(v).map_err(de::Error::custom) + } +} + +// We cannot have a generic implementation of Deerialize over everything +// that supports DeserializableFromArray, so we have to use this helper function +// and define implementations manually. +pub(crate) fn serde_deserialize<'de, T, D>( + deserializer: D, + representation: Representation, +) -> Result +where + D: Deserializer<'de>, + T: DeserializableFromArray + HasTypeName, +{ + if deserializer.is_human_readable() { + match representation { + Representation::Base64 => deserializer.deserialize_str(B64Visitor::(PhantomData)), + Representation::Hex => deserializer.deserialize_str(HexVisitor::(PhantomData)), + } + } else { + deserializer.deserialize_bytes(BytesVisitor::(PhantomData)) + } +} + +#[cfg(test)] +pub(crate) mod tests { + + use core::fmt; + + use serde::de::DeserializeOwned; + use serde::Serialize; + use serde_json; + + use super::Representation; + use crate::SerializableToArray; + + /// A helper function that checks that serialization to a human-readable format + /// uses b64 encoding, and serialization to a binary format contains plain bytes of the object. + pub(crate) fn check_serialization(obj: &T, representation: Representation) + where + T: SerializableToArray + fmt::Debug + PartialEq + Serialize, + { + // Check serialization to JSON (human-readable) + + let serialized = serde_json::to_string(obj).unwrap(); + + let substr = match representation { + Representation::Base64 => base64::encode(obj.to_array().as_ref()), + Representation::Hex => hex::encode(obj.to_array().as_ref()), + }; + + // check that the serialization contains the properly encoded bytestring + assert!(serialized.contains(&substr)); + + // Check serialization to MessagePack (binary) + + let serialized = rmp_serde::to_vec(obj).unwrap(); + let bytes = obj.to_array(); + // check that the serialization contains the bytestring + assert!(serialized + .windows(bytes.len()) + .any(move |sub_slice| sub_slice == bytes.as_ref())); + } + + pub(crate) fn check_deserialization(obj: &T) + where + T: SerializableToArray + fmt::Debug + PartialEq + Serialize + DeserializeOwned, + { + // Check serialization to JSON (human-readable) + + let serialized = serde_json::to_string(obj).unwrap(); + let deserialized: T = serde_json::from_str(&serialized).unwrap(); + assert_eq!(obj, &deserialized); + + // Check serialization to MessagePack (binary) + + let serialized = rmp_serde::to_vec(obj).unwrap(); + let deserialized: T = rmp_serde::from_read(&*serialized).unwrap(); + assert_eq!(obj, &deserialized); + } +}