From 51e56c59d0fc9333d0d747cabf9018375acb586d 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 --- umbral-pre/Cargo.toml | 4 + umbral-pre/src/capsule.rs | 31 ++++++++ umbral-pre/src/capsule_frag.rs | 44 +++++++++++ umbral-pre/src/key_frag.rs | 44 +++++++++++ umbral-pre/src/keys.rs | 54 ++++++++++++++ umbral-pre/src/lib.rs | 1 + umbral-pre/src/serde.rs | 131 +++++++++++++++++++++++++++++++++ 7 files changed, 309 insertions(+) create mode 100644 umbral-pre/src/serde.rs diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index 037cb12b..270295dc 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -15,6 +15,8 @@ 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 } +serde = "1" +base64 = "0.13" # 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 df0786a9..73aee2ef 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}; 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) + } +} + +impl<'de> Deserialize<'de> for Capsule { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer) + } +} + impl HasTypeName for Capsule { fn type_name() -> &'static str { "Capsule" @@ -243,6 +263,7 @@ mod tests { use rand_core::OsRng; use super::{Capsule, OpenReencryptedError}; + use crate::serde::tests::{check_deserialization, check_serialization}; use crate::{ encrypt, generate_kfrags, reencrypt, DeserializableFromArray, SecretKey, SerializableToArray, Signer, @@ -324,4 +345,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); + check_deserialization(&capsule); + } } diff --git a/umbral-pre/src/capsule_frag.rs b/umbral-pre/src/capsule_frag.rs index fc4d01ee..68fcc24e 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}; 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) + } +} + +impl<'de> Deserialize<'de> for CapsuleFrag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer) + } +} + impl HasTypeName for CapsuleFrag { fn type_name() -> &'static str { "CapsuleFrag" @@ -289,6 +309,15 @@ impl SerializableToArray for VerifiedCapsuleFrag { } } +impl Serialize for VerifiedCapsuleFrag { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_serialize(self, serializer) + } +} + impl HasTypeName for VerifiedCapsuleFrag { fn type_name() -> &'static str { "VerifiedCapsuleFrag" @@ -329,6 +358,7 @@ mod tests { use alloc::vec::Vec; use super::{CapsuleFrag, VerifiedCapsuleFrag}; + use crate::serde::tests::{check_deserialization, check_serialization}; use crate::{ encrypt, generate_kfrags, reencrypt, Capsule, DeserializableFromArray, PublicKey, SecretKey, SerializableToArray, Signer, @@ -388,4 +418,18 @@ 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(&vcfrag); + + check_serialization(&cfrag); + check_deserialization(&cfrag); + } } diff --git a/umbral-pre/src/key_frag.rs b/umbral-pre/src/key_frag.rs index fa6c7c01..e0e4c23c 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}; 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) + } +} + +impl<'de> Deserialize<'de> for KeyFrag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer) + } +} + impl HasTypeName for KeyFrag { fn type_name() -> &'static str { "KeyFrag" @@ -355,6 +375,15 @@ impl SerializableToArray for VerifiedKeyFrag { } } +impl Serialize for VerifiedKeyFrag { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_serialize(self, serializer) + } +} + impl HasTypeName for VerifiedKeyFrag { fn type_name() -> &'static str { "VerifiedKeyFrag" @@ -471,6 +500,7 @@ mod tests { use rand_core::OsRng; use super::{KeyFrag, KeyFragBase, KeyFragVerificationError, VerifiedKeyFrag}; + use crate::serde::tests::{check_deserialization, check_serialization}; use crate::{DeserializableFromArray, PublicKey, SecretKey, SerializableToArray, Signer}; fn prepare_kfrags( @@ -539,4 +569,18 @@ 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(&vkfrag); + + check_serialization(&kfrag); + check_deserialization(&kfrag); + } } diff --git a/umbral-pre/src/keys.rs b/umbral-pre/src/keys.rs index bf24e6e8..35903568 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}; 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) + } +} + +impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer) + } +} + 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) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde_deserialize(deserializer) + } +} + impl HasTypeName for PublicKey { fn type_name() -> &'static str { "PublicKey" @@ -382,6 +420,7 @@ impl fmt::Display for SecretKeyFactory { mod tests { use super::{PublicKey, SecretKey, SecretKeyFactory, Signer}; + use crate::serde::tests::{check_deserialization, check_serialization}; use crate::{DeserializableFromArray, SerializableToArray, SerializableToSecretArray}; #[test] @@ -433,4 +472,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); + check_deserialization(&pk); + + check_serialization(&signature); + 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..cb6d5131 --- /dev/null +++ b/umbral-pre/src/serde.rs @@ -0,0 +1,131 @@ +use core::fmt; +use core::marker::PhantomData; + +use serde::{de, Deserializer, Serializer}; + +use crate::traits::{DeserializableFromArray, HasTypeName, SerializableToArray}; + +// 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) -> Result +where + T: SerializableToArray, + S: Serializer, +{ + if serializer.is_human_readable() { + let b64 = base64::encode(obj.to_array().as_ref()); + serializer.serialize_str(&b64) + } 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 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) -> Result +where + D: Deserializer<'de>, + T: DeserializableFromArray + HasTypeName, +{ + if deserializer.is_human_readable() { + deserializer.deserialize_str(B64Visitor::(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 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) + where + T: SerializableToArray + fmt::Debug + PartialEq + Serialize, + { + // Check serialization to JSON (human-readable) + + let serialized = serde_json::to_string(obj).unwrap(); + + let b64 = base64::encode(obj.to_array().as_ref()); + // check that the serialization contains the b64-encoded bytestring + assert!(serialized.contains(&b64)); + + // 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); + } +}