diff --git a/Cargo.toml b/Cargo.toml index cf491d0..c029b3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ slow-hash = ["argon2"] bench = [] u64_backend = ["curve25519-dalek/u64_backend"] u32_backend = ["curve25519-dalek/u32_backend"] -serialize = ["serde", "base64"] +serialize = ["serde", "base64", "generic-array/serde", "curve25519-dalek/serde"] [dependencies] argon2 = { version = "0.2", optional = true } @@ -28,7 +28,7 @@ generic-bytes = { version = "0.1.0" } hkdf = "0.11.0" hmac = "0.11.0" rand = "0.8" -serde = { version = "1", optional = true } +serde = { version = "1", features = ["derive"], optional = true } subtle = { version = "2.3.0", default-features = false } thiserror = "1.0.22" zeroize = { version = "1.1.1", features = ["zeroize_derive"] } diff --git a/src/envelope.rs b/src/envelope.rs index 5dcad9d..e1f71cb 100644 --- a/src/envelope.rs +++ b/src/envelope.rs @@ -61,7 +61,7 @@ fn recover_keys_internal( Ok(client_static_keypair) } -#[derive(Clone, Copy, PartialEq, Zeroize)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Zeroize)] #[zeroize(drop)] pub(crate) enum InnerEnvelopeMode { Zero = 0, @@ -105,6 +105,8 @@ impl Clone for Envelope { } } +impl_debug_eq_hash_for!(struct Envelope, [mode, nonce, hmac]); + // Note that this struct represents an envelope that has been "opened" with the asssociated // key. This key is also used to derive the export_key parameter, which is technically // unrelated to the envelope's encrypted and authenticated contents. diff --git a/src/impls.rs b/src/impls.rs new file mode 100644 index 0000000..1faba3c --- /dev/null +++ b/src/impls.rs @@ -0,0 +1,97 @@ +macro_rules! impl_debug_eq_hash_for { + (struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { + impl$(<$($gen$(: $bound)?),+>)? std::fmt::Debug for $name$(<$($gen),+>)? + $(where $($type: std::fmt::Debug,)+)? + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("$name") + .field("$field1", &self.$field1) + $(.field("$field2", &self.$field2))* + .finish() + } + } + + impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)? + $(where $($type: Eq,)+)? + {} + + impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)? + $(where $($type: PartialEq,)+)? + { + fn eq(&self, other: &Self) -> bool { + PartialEq::eq(&self.$field1, &other.$field1) + $(&& PartialEq::eq(&self.$field2, &other.$field2))* + } + } + + impl$(<$($gen$(: $bound)?),+>)? std::hash::Hash for $name$(<$($gen),+>)? + $(where $($type: std::hash::Hash,)+)? + { + fn hash(&self, state: &mut H) { + std::hash::Hash::hash(&self.$field1, state); + $(std::hash::Hash::hash(&self.$field2, state);)* + } + } + }; + (tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { + impl$(<$($gen$(: $bound)?),+>)? std::fmt::Debug for $name$(<$($gen),+>)? + $(where $($type: std::fmt::Debug,)+)? + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("$name") + .field(&self.$field1) + $(.field(&self.$field2))* + .finish() + } + } + + impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)? + $(where $($type: Eq,)+)? + {} + + impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)? + $(where $($type: PartialEq,)+)? + { + fn eq(&self, other: &Self) -> bool { + PartialEq::eq(&self.$field1, &other.$field1) + $(&& PartialEq::eq(&self.$field2, &other.$field2))* + } + } + + impl$(<$($gen$(: $bound)?),+>)? std::hash::Hash for $name$(<$($gen),+>)? + $(where $($type: std::hash::Hash,)+)? + { + fn hash(&self, state: &mut H) { + std::hash::Hash::hash(&self.$field1, state); + $(std::hash::Hash::hash(&self.$field2, state);)* + } + } + }; +} + +macro_rules! impl_clone_for { + (struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { + impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)? + $(where $($type: Clone,)+)? + { + fn clone(&self) -> Self { + Self { + $field1: self.$field1.clone(), + $($field2: self.$field2.clone(),)* + } + } + } + }; + (tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => { + impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)? + $(where $($type: Clone,)+)? + { + fn clone(&self) -> Self { + Self( + self.$field1.clone(), + $(self.$field2.clone(),)* + ) + } + } + }; +} diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs index 7a9f66e..96856e9 100644 --- a/src/key_exchange/tripledh.rs +++ b/src/key_exchange/tripledh.rs @@ -207,7 +207,8 @@ impl KeyExchange for TripleDH { } /// The client state produced after the first key exchange message -#[derive(PartialEq, Eq, Zeroize, Clone)] +#[derive(PartialEq, Eq, Debug, Hash, Zeroize, Clone)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] #[zeroize(drop)] pub struct Ke1State { client_e_sk: PrivateKey, @@ -215,7 +216,8 @@ pub struct Ke1State { } /// The first key exchange message -#[derive(PartialEq, Eq, Clone)] +#[derive(PartialEq, Eq, Debug, Hash, Clone)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] pub struct Ke1Message { pub(crate) client_nonce: GenericArray, pub(crate) client_e_pk: PublicKey, @@ -272,7 +274,9 @@ impl FromBytes for Ke1Message { } } /// The server state produced after the second key exchange message -#[derive(Clone)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serialize", serde(bound = ""))] pub struct Ke2State> { km3: GenericArray, hashed_transcript: GenericArray, @@ -315,7 +319,9 @@ impl> ToBytesWithPointers for Ke2State { } /// The second key exchange message -#[derive(Clone)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serialize", serde(bound = ""))] pub struct Ke2Message> { server_nonce: GenericArray, server_e_pk: PublicKey, @@ -398,7 +404,9 @@ type TripleDHDerivationResult = ( ); /// The third key exchange message -#[derive(Clone)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serialize", serde(bound = ""))] pub struct Ke3Message> { mac: GenericArray, } diff --git a/src/keypair.rs b/src/keypair.rs index 093cf83..6bdcf82 100644 --- a/src/keypair.rs +++ b/src/keypair.rs @@ -35,13 +35,22 @@ pub trait SizedBytesExt: SizedBytes { impl SizedBytesExt for T where T: SizedBytes {} /// A Keypair trait with public-private verification -#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] pub struct KeyPair { pk: PublicKey, sk: PrivateKey, _g: PhantomData, } +impl_clone_for!( + struct KeyPair, + [pk, sk, _g], +); +impl_debug_eq_hash_for!( + struct KeyPair, + [pk, sk, _g], +); + // This can't be derived because of the use of a phantom parameter impl Zeroize for KeyPair { fn zeroize(&mut self) { @@ -154,7 +163,8 @@ impl KeyPair { type KeyLen = U32; /// A minimalist key type built around a \[u8; 32\] -#[derive(Debug, PartialEq, Eq, Clone, Zeroize)] +#[derive(Debug, PartialEq, Eq, Clone, Hash, Zeroize)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] // Ensure Key material is zeroed after use. #[zeroize(drop)] #[repr(transparent)] @@ -181,7 +191,8 @@ impl Key { } /// Wrapper around a Key to enforce that it's a private one. -#[derive(Debug, PartialEq, Eq, Clone, Zeroize)] +#[derive(Debug, PartialEq, Eq, Clone, Hash, Zeroize)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] // Ensure Key material is zeroed after use. #[zeroize(drop)] #[repr(transparent)] @@ -208,7 +219,8 @@ impl SizedBytes for PrivateKey { } /// Wrapper around a Key to enforce that it's a public one. -#[derive(Debug, PartialEq, Eq, Clone, Zeroize)] +#[derive(Debug, PartialEq, Eq, Clone, Hash, Zeroize)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] // Ensure Key material is zeroed after use. #[zeroize(drop)] #[repr(transparent)] diff --git a/src/lib.rs b/src/lib.rs index af2f639..a99f406 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -747,6 +747,8 @@ compile_error!( // Error types pub mod errors; +#[macro_use] +mod impls; #[macro_use] mod serialization; diff --git a/src/messages.rs b/src/messages.rs index 2cbdb7d..04bf4fc 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -46,6 +46,8 @@ impl Clone for RegistrationRequest { } } +impl_debug_eq_hash_for!(struct RegistrationRequest, [alpha], [CS::Group]); + impl RegistrationRequest { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -90,6 +92,12 @@ impl Clone for RegistrationResponse { } } +impl_debug_eq_hash_for!( + struct RegistrationResponse, + [beta, server_s_pk], + [CS::Group], +); + impl RegistrationResponse { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -136,16 +144,14 @@ pub struct RegistrationUpload { pub(crate) client_s_pk: PublicKey, } -// Cannot be derived because it would require for CS to be Clone. -impl Clone for RegistrationUpload { - fn clone(&self) -> Self { - Self { - envelope: self.envelope.clone(), - masking_key: self.masking_key.clone(), - client_s_pk: self.client_s_pk.clone(), - } - } -} +impl_clone_for!( + struct RegistrationUpload, + [envelope, masking_key, client_s_pk], +); +impl_debug_eq_hash_for!( + struct RegistrationUpload, + [envelope, masking_key, client_s_pk], +); impl RegistrationUpload { /// Serialization into bytes @@ -211,6 +217,15 @@ impl Clone for CredentialRequest { } } +impl_debug_eq_hash_for!( + struct CredentialRequest, + [alpha, ke1_message], + [ + CS::Group, + >::KE1Message + ], +); + impl CredentialRequest { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -266,6 +281,15 @@ impl Clone for CredentialResponse { } } +impl_debug_eq_hash_for!( + struct CredentialResponse, + [beta, masking_nonce, masked_response, ke2_message], + [ + CS::Group, + >::KE2Message, + ], +); + impl CredentialResponse { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -336,14 +360,12 @@ pub struct CredentialFinalization { pub(crate) ke3_message: >::KE3Message, } -// Cannot be derived because it would require for CS to be Clone. -impl Clone for CredentialFinalization { - fn clone(&self) -> Self { - Self { - ke3_message: self.ke3_message.clone(), - } - } -} +impl_clone_for!(struct CredentialFinalization, [ke3_message]); +impl_debug_eq_hash_for!( + struct CredentialFinalization, + [ke3_message], + [>::KE3Message], +); impl CredentialFinalization { /// Serialization into bytes diff --git a/src/opaque.rs b/src/opaque.rs index fbce901..6cf87fd 100644 --- a/src/opaque.rs +++ b/src/opaque.rs @@ -36,6 +36,7 @@ const STR_OPRF_KEY: &[u8] = b"OprfKey"; // ============ /// The state elements the server holds upon setup +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] pub struct ServerSetup { oprf_seed: GenericArray::OutputSize>, keypair: KeyPair, @@ -84,6 +85,16 @@ impl ServerSetup { } } +// Cannot be derived because it would require for CS to be bound. +impl_clone_for!( + struct ServerSetup, + [oprf_seed, keypair, fake_keypair], +); +impl_debug_eq_hash_for!( + struct ServerSetup, + [oprf_seed, oprf_seed, fake_keypair], +); + // Registration // ============ @@ -93,14 +104,12 @@ pub struct ClientRegistration { pub(crate) token: oprf::Token, } -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ClientRegistration { - fn clone(&self) -> Self { - Self { - token: self.token.clone(), - } - } -} +impl_clone_for!(struct ClientRegistration, [token]); +impl_debug_eq_hash_for!( + struct ClientRegistration, + [token], + [oprf::Token], +); impl ClientRegistration { /// Serialization into bytes @@ -306,12 +315,11 @@ impl Clone for ServerRegistrationStartResult { /// The state elements the server holds to record a registration pub struct ServerRegistration(RegistrationUpload); -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ServerRegistration { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} +impl_clone_for!(tuple ServerRegistration, [0]); +impl_debug_eq_hash_for!( + tuple ServerRegistration, + [0], +); impl ServerRegistration { /// Serialization into bytes @@ -377,6 +385,14 @@ impl_serialize_and_deserialize_for!(ServerRegistration); // ===== /// The state elements the client holds to perform a login +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + feature = "serialize", + serde(bound( + deserialize = "oprf::Token: serde::Deserialize<'de>, >::KE1State: serde::Deserialize<'de>", + serialize = "oprf::Token: serde::Serialize, >::KE1State: serde::Serialize" + )) +)] pub struct ClientLogin { /// token containing the client's password and the blinding factor token: oprf::Token, @@ -384,16 +400,12 @@ pub struct ClientLogin { serialized_credential_request: Vec, } -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ClientLogin { - fn clone(&self) -> Self { - Self { - token: self.token.clone(), - ke1_state: self.ke1_state.clone(), - serialized_credential_request: self.serialized_credential_request.clone(), - } - } -} +impl_clone_for!(struct ClientLogin, [token, ke1_state, serialized_credential_request]); +impl_debug_eq_hash_for!( + struct ClientLogin, + [token, ke1_state, serialized_credential_request], + [oprf::Token, >::KE1State], +); impl ClientLogin { /// Serialization into bytes @@ -627,15 +639,12 @@ pub struct ServerLogin { _cs: PhantomData, } -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ServerLogin { - fn clone(&self) -> Self { - Self { - ke2_state: self.ke2_state.clone(), - _cs: PhantomData, - } - } -} +impl_clone_for!(struct ServerLogin, [ke2_state, _cs]); +impl_debug_eq_hash_for!( + struct ServerLogin, + [ke2_state, _cs], + [>::KE2State], +); /// Optional parameters for server login start #[derive(Clone)] diff --git a/src/oprf.rs b/src/oprf.rs index bc3ec4b..b6b51cf 100644 --- a/src/oprf.rs +++ b/src/oprf.rs @@ -12,12 +12,14 @@ use generic_array::GenericArray; use rand::{CryptoRng, RngCore}; /// Used to store the OPRF input and blinding factor -#[derive(Clone)] pub struct Token { pub(crate) data: Vec, pub(crate) blind: Grp::Scalar, } +impl_clone_for!(struct Token, [data, blind]); +impl_debug_eq_hash_for!(struct Token, [data, blind], [Grp::Scalar]); + static STR_VOPRF: &[u8] = b"VOPRF06-HashToGroup-"; static STR_VOPRF_FINALIZE: &[u8] = b"VOPRF06-Finalize-"; static MODE_BASE: u8 = 0x00;