From 055e76a1151098ec84efe6a7bf9f7f7372fef5ee Mon Sep 17 00:00:00 2001 From: Marcelin Dupraz Date: Thu, 10 Jun 2021 21:48:38 +0200 Subject: [PATCH] Implement serde serialization and deserialization to follow Rust's standards. --- Cargo.lock | 11 +++++++ Cargo.toml | 6 +++- src/messages.rs | 13 ++++++++ src/opaque.rs | 9 ++++++ src/serialization/mod.rs | 69 ++++++++++++++++++++++++++++++++++++++++ src/tests/full_test.rs | 36 +++++++++++++++++++++ 6 files changed, 143 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index fbeb2af..10b95e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -562,6 +571,7 @@ version = "0.5.1-pre.1" dependencies = [ "anyhow", "base64", + "bincode", "chacha20poly1305", "criterion", "curve25519-dalek", @@ -577,6 +587,7 @@ dependencies = [ "rand 0.8.3", "rustyline", "scrypt", + "serde", "serde_json", "sha2", "subtle", diff --git a/Cargo.toml b/Cargo.toml index ac6248e..5a5b17a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,15 @@ edition = "2018" readme = "README.md" [features] -default = ["u64_backend"] +default = ["u64_backend", "serialize"] slow-hash = ["scrypt"] bench = [] u64_backend = ["curve25519-dalek/u64_backend"] u32_backend = ["curve25519-dalek/u32_backend"] +serialize = ["serde", "base64"] [dependencies] +base64 = { version = "0.13", optional = true } curve25519-dalek = { version = "3.0.0", default-features = false, features = ["std"] } digest = "0.9.0" displaydoc = "0.1.7" @@ -26,6 +28,7 @@ hkdf = "0.10.0" hmac = "0.10.1" rand = "0.8" scrypt = { version = "0.5.0", optional = true } +serde = { version = "1", optional = true } subtle = { version = "2.3.0", default-features = false } thiserror = "1.0.22" zeroize = { version = "1.1.1", features = ["zeroize_derive"] } @@ -33,6 +36,7 @@ zeroize = { version = "1.1.1", features = ["zeroize_derive"] } [dev-dependencies] anyhow = "1.0.35" base64 = "0.13.0" +bincode = "1" chacha20poly1305 = "0.7.1" criterion = "0.3.3" hex = "0.4.2" diff --git a/src/messages.rs b/src/messages.rs index 692ec4a..42c9807 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -13,6 +13,7 @@ use crate::{ PakeError, ProtocolError, }, group::Group, + impl_serialize_and_deserialize_for, key_exchange::traits::{KeyExchange, ToBytes}, keypair::{Key, KeyPair, SizedBytesExt}, }; @@ -47,6 +48,8 @@ impl RegistrationRequest { } } +impl_serialize_and_deserialize_for!(RegistrationRequest); + /// The answer sent by the server to the user, upon reception of the /// registration attempt pub struct RegistrationResponse { @@ -81,6 +84,8 @@ impl RegistrationResponse { } } +impl_serialize_and_deserialize_for!(RegistrationResponse); + /// The final message from the client, containing sealed cryptographic /// identifiers pub struct RegistrationUpload { @@ -122,6 +127,8 @@ impl RegistrationUpload { } } +impl_serialize_and_deserialize_for!(RegistrationUpload); + /// The message sent by the user to the server, to initiate registration pub struct CredentialRequest { /// blinded password information @@ -159,6 +166,8 @@ impl CredentialRequest { } } +impl_serialize_and_deserialize_for!(CredentialRequest); + /// The answer sent by the server to the user, upon reception of the /// login attempt pub struct CredentialResponse { @@ -229,6 +238,8 @@ impl CredentialResponse { } } +impl_serialize_and_deserialize_for!(CredentialResponse); + /// The answer sent by the client to the server, upon reception of the /// sealed envelope pub struct CredentialFinalization { @@ -248,3 +259,5 @@ impl CredentialFinalization { Ok(Self { ke3_message }) } } + +impl_serialize_and_deserialize_for!(CredentialFinalization); diff --git a/src/opaque.rs b/src/opaque.rs index 35c6f6d..8c640a1 100644 --- a/src/opaque.rs +++ b/src/opaque.rs @@ -11,6 +11,7 @@ use crate::{ errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError}, group::Group, hash::Hash, + impl_serialize_and_deserialize_for, key_exchange::traits::{KeyExchange, ToBytesWithPointers}, keypair::{Key, KeyPair, SizedBytesExt}, map_to_curve::GroupWithMapToCurve, @@ -83,6 +84,8 @@ impl ClientRegistration { } } +impl_serialize_and_deserialize_for!(ClientRegistration); + /// Optional parameters for client registration finish pub enum ClientRegistrationFinishParameters { /// Specifying the identifiers idU and idS (corresponding to custom identifier mode) @@ -385,6 +388,8 @@ impl ServerRegistration { } } +impl_serialize_and_deserialize_for!(ServerRegistration); + // Login // ===== @@ -454,6 +459,8 @@ impl ClientLogin { } } +impl_serialize_and_deserialize_for!(ClientLogin); + /// Optional parameters for client login start pub enum ClientLoginStartParameters { /// Specifying a plaintext info field that will be sent to the server @@ -880,6 +887,8 @@ impl ServerLogin { } } +impl_serialize_and_deserialize_for!(ServerLogin); + // Zeroize on drop implementations // This can't be derived because of the use of a phantom parameter diff --git a/src/serialization/mod.rs b/src/serialization/mod.rs index e05462b..79d02b7 100644 --- a/src/serialization/mod.rs +++ b/src/serialization/mod.rs @@ -53,5 +53,74 @@ pub(crate) fn tokenize(input: &[u8], size_bytes: usize) -> Result<(Vec, Vec< )) } +/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits. +#[cfg(feature = "serialize")] +#[macro_export] +macro_rules! impl_serialize_and_deserialize_for { + ($t:ident) => { + #[cfg(feature = "serialize")] + impl serde::Serialize for $t { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(&base64::encode(&self.serialize())) + } else { + serializer.serialize_bytes(&self.serialize()) + } + } + } + + #[cfg(feature = "serialize")] + impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = <&str>::deserialize(deserializer)?; + $t::::deserialize(&base64::decode(s).map_err(serde::de::Error::custom)?) + .map_err(serde::de::Error::custom) + } else { + struct ByteVisitor { + marker: std::marker::PhantomData, + } + impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor { + type Value = $t; + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str(std::concat!( + "the byte representation of a ", + std::stringify!($t) + )) + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: serde::de::Error, + { + $t::::deserialize(value).map_err(|_| { + serde::de::Error::invalid_value( + serde::de::Unexpected::Bytes(value), + &std::concat!( + "invalid byte sequence for ", + std::stringify!($t) + ), + ) + }) + } + } + deserializer.deserialize_bytes(ByteVisitor:: { + marker: std::marker::PhantomData, + }) + } + } + } + }; +} + #[cfg(test)] mod tests; diff --git a/src/tests/full_test.rs b/src/tests/full_test.rs index 85fc6f3..55c8355 100644 --- a/src/tests/full_test.rs +++ b/src/tests/full_test.rs @@ -450,6 +450,42 @@ fn test_registration_request() -> Result<(), ProtocolError> { Ok(()) } +#[cfg(feature = "serialize")] +#[test] +fn test_serialization() -> Result<(), ProtocolError> { + let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); + let mut rng = CycleRng::new(parameters.blinding_factor.to_vec()); + let client_registration_start_result = + ClientRegistration::::start(&mut rng, ¶meters.password)?; + { + // Test the json serialization (human-readable, base64). + let registration_request_json = + serde_json::to_string(&client_registration_start_result.message).unwrap(); + assert_eq!( + registration_request_json, + r#""FLqG5TAYzlUH0r+y2YrT9g4wLYJr/zQQpexmnI4e8X0=""# + ); + let registration_request: RegistrationRequest = + serde_json::from_str(®istration_request_json).unwrap(); + assert_eq!( + hex::encode(client_registration_start_result.message.serialize()), + hex::encode(registration_request.serialize()), + ); + } + { + // Test the bincode serialization (binary). + let registration_request_bin = + bincode::serialize(&client_registration_start_result.message).unwrap(); + assert_eq!(registration_request_bin.len(), 40); + let registration_request: RegistrationRequest = + bincode::deserialize(®istration_request_bin).unwrap(); + assert_eq!( + hex::encode(client_registration_start_result.message.serialize()), + hex::encode(registration_request.serialize()), + ); + } + Ok(()) +} #[test] fn test_registration_response() -> Result<(), ProtocolError> { let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());