From 65a0c2f98d592bd820a14900266cf909f7add949 Mon Sep 17 00:00:00 2001 From: Kevin Lewi Date: Sat, 25 Sep 2021 16:36:00 -0700 Subject: [PATCH] General cleanups and reorganizing code (#236) --- README.md | 3 +- src/envelope.rs | 177 ++++---- src/group/ristretto.rs | 4 + src/group/x25519.rs | 4 + src/impls.rs | 67 +++ src/key_exchange/tripledh.rs | 479 ++++++++++---------- src/lib.rs | 15 +- src/messages.rs | 259 +++++------ src/opaque.rs | 844 ++++++++++++++++++----------------- src/oprf.rs | 2 +- src/serialization/mod.rs | 67 --- src/serialization/tests.rs | 2 +- 12 files changed, 976 insertions(+), 947 deletions(-) diff --git a/README.md b/README.md index 88377a2..b537776 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ To learn more about contributing to this project, [see this document](./CONTRIBU #### Acknowledgments Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for improving -this implementation. +this implementation. Additional credit goes to @daxpedda for adding no_std support, p256 support, and making other general +improvements to the library. License diff --git a/src/envelope.rs b/src/envelope.rs index 6054bf1..ccf2d39 100644 --- a/src/envelope.rs +++ b/src/envelope.rs @@ -26,45 +26,8 @@ const STR_AUTH_KEY: &[u8] = b"AuthKey"; const STR_EXPORT_KEY: &[u8] = b"ExportKey"; const STR_PRIVATE_KEY: &[u8] = b"PrivateKey"; const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: &[u8] = b"OPAQUE-DeriveAuthKeyPair"; - const NONCE_LEN: usize = 32; -fn build_inner_envelope_internal( - random_pwd: &[u8], - nonce: &[u8], -) -> Result, ProtocolError> { - let h = Hkdf::::new(None, random_pwd); - let mut keypair_seed = vec![0u8; ::ScalarLen::USIZE]; - h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed) - .map_err(|_| InternalError::HkdfError)?; - let client_static_keypair = KeyPair::::from_private_key_slice( - &CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::( - &keypair_seed[..], - STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, - )?), - )?; - - Ok(client_static_keypair.public().clone()) -} - -fn recover_keys_internal( - random_pwd: &[u8], - nonce: &[u8], -) -> Result, ProtocolError> { - let h = Hkdf::::new(None, random_pwd); - let mut keypair_seed = vec![0u8; ::ScalarLen::USIZE]; - h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed) - .map_err(|_| InternalError::HkdfError)?; - let client_static_keypair = KeyPair::::from_private_key_slice( - &CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::( - &keypair_seed[..], - STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, - )?), - )?; - - Ok(client_static_keypair) -} - #[derive(Clone, Debug, Eq, Hash, PartialEq, Zeroize)] #[zeroize(drop)] pub(crate) enum InnerEnvelopeMode { @@ -151,58 +114,6 @@ type SealResult = ( ); impl Envelope { - fn hmac_key_size() -> usize { - ::OutputSize::USIZE - } - - fn export_key_size() -> usize { - ::OutputSize::USIZE - } - - pub(crate) fn len() -> usize { - ::OutputSize::USIZE + NONCE_LEN - } - - pub(crate) fn serialize(&self) -> Vec { - [&self.nonce[..], &self.hmac[..]].concat() - } - pub(crate) fn deserialize(bytes: &[u8]) -> Result { - let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this? - - if bytes.len() < NONCE_LEN { - return Err(ProtocolError::SerializationError); - } - let nonce = bytes[..NONCE_LEN].to_vec(); - - let remainder = match mode { - InnerEnvelopeMode::Zero => { - return Err(InternalError::IncompatibleEnvelopeModeError.into()) - } - InnerEnvelopeMode::Internal => bytes[NONCE_LEN..].to_vec(), - }; - - let hmac_key_size = Self::hmac_key_size(); - let hmac = check_slice_size(&remainder, hmac_key_size, "hmac_key_size")?; - - Ok(Self { - mode, - nonce, - hmac: GenericArray::clone_from_slice(hmac), - }) - } - - // Creates a dummy envelope object that serializes to the all-zeros byte string - pub(crate) fn dummy() -> Self { - Self { - mode: InnerEnvelopeMode::Zero, - nonce: vec![0u8; NONCE_LEN], - hmac: GenericArray::clone_from_slice(&vec![ - 0u8; - ::OutputSize::USIZE - ]), - } - } - #[allow(clippy::type_complexity)] pub(crate) fn seal( rng: &mut R, @@ -330,6 +241,58 @@ impl Envelope { }) } + // Creates a dummy envelope object that serializes to the all-zeros byte string + pub(crate) fn dummy() -> Self { + Self { + mode: InnerEnvelopeMode::Zero, + nonce: vec![0u8; NONCE_LEN], + hmac: GenericArray::clone_from_slice(&vec![ + 0u8; + ::OutputSize::USIZE + ]), + } + } + + fn hmac_key_size() -> usize { + ::OutputSize::USIZE + } + + fn export_key_size() -> usize { + ::OutputSize::USIZE + } + + pub(crate) fn len() -> usize { + ::OutputSize::USIZE + NONCE_LEN + } + + pub(crate) fn serialize(&self) -> Vec { + [&self.nonce[..], &self.hmac[..]].concat() + } + pub(crate) fn deserialize(bytes: &[u8]) -> Result { + let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this? + + if bytes.len() < NONCE_LEN { + return Err(ProtocolError::SerializationError); + } + let nonce = bytes[..NONCE_LEN].to_vec(); + + let remainder = match mode { + InnerEnvelopeMode::Zero => { + return Err(InternalError::IncompatibleEnvelopeModeError.into()) + } + InnerEnvelopeMode::Internal => bytes[NONCE_LEN..].to_vec(), + }; + + let hmac_key_size = Self::hmac_key_size(); + let hmac = check_slice_size(&remainder, hmac_key_size, "hmac_key_size")?; + + Ok(Self { + mode, + nonce, + hmac: GenericArray::clone_from_slice(hmac), + }) + } + #[cfg(test)] pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> { vec![(self.hmac.as_ptr(), self.hmac.len())] @@ -353,6 +316,42 @@ impl Drop for Envelope { // Helper functions +fn build_inner_envelope_internal( + random_pwd: &[u8], + nonce: &[u8], +) -> Result, ProtocolError> { + let h = Hkdf::::new(None, random_pwd); + let mut keypair_seed = vec![0u8; ::ScalarLen::USIZE]; + h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed) + .map_err(|_| InternalError::HkdfError)?; + let client_static_keypair = KeyPair::::from_private_key_slice( + &CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::( + &keypair_seed[..], + STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, + )?), + )?; + + Ok(client_static_keypair.public().clone()) +} + +fn recover_keys_internal( + random_pwd: &[u8], + nonce: &[u8], +) -> Result, ProtocolError> { + let h = Hkdf::::new(None, random_pwd); + let mut keypair_seed = vec![0u8; ::ScalarLen::USIZE]; + h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed) + .map_err(|_| InternalError::HkdfError)?; + let client_static_keypair = KeyPair::::from_private_key_slice( + &CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::( + &keypair_seed[..], + STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, + )?), + )?; + + Ok(client_static_keypair) +} + fn construct_aad(id_u: &[u8], id_s: &[u8], server_s_pk: &[u8]) -> Vec { [server_s_pk, id_s, id_u].concat() } diff --git a/src/group/ristretto.rs b/src/group/ristretto.rs index 64c0b72..73267da 100644 --- a/src/group/ristretto.rs +++ b/src/group/ristretto.rs @@ -54,6 +54,7 @@ impl Group for RistrettoPoint { ) -> Result { Ok(Scalar::from_bytes_mod_order(*scalar_bits.as_ref())) } + fn random_nonzero_scalar(rng: &mut R) -> Self::Scalar { loop { let scalar = { @@ -78,9 +79,11 @@ impl Group for RistrettoPoint { } } } + fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray { scalar.to_bytes().into() } + fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar { scalar.invert() } @@ -94,6 +97,7 @@ impl Group for RistrettoPoint { .decompress() .ok_or(InternalError::PointError) } + // serialization of a group element fn to_arr(&self) -> GenericArray { self.compress().to_bytes().into() diff --git a/src/group/x25519.rs b/src/group/x25519.rs index 228c223..fddfe46 100644 --- a/src/group/x25519.rs +++ b/src/group/x25519.rs @@ -29,6 +29,7 @@ impl Group for MontgomeryPoint { ) -> Result { Ok(Scalar::from_bytes_mod_order(*scalar_bits.as_ref())) } + fn random_nonzero_scalar(rng: &mut R) -> Self::Scalar { loop { let scalar = { @@ -53,9 +54,11 @@ impl Group for MontgomeryPoint { } } } + fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray { scalar.to_bytes().into() } + fn scalar_invert(_scalar: &Self::Scalar) -> Self::Scalar { unreachable!("this algorithm should only be used as the `KeGroup`") } @@ -67,6 +70,7 @@ impl Group for MontgomeryPoint { ) -> Result { Ok(Self(*element_bits.as_ref())) } + // serialization of a group element fn to_arr(&self) -> GenericArray { self.to_bytes().into() diff --git a/src/impls.rs b/src/impls.rs index e9b842f..48fbde6 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -100,3 +100,70 @@ macro_rules! impl_clone_for { } }; } + +/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits. +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: core::marker::PhantomData, + } + impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor { + type Value = $t; + fn expecting( + &self, + formatter: &mut core::fmt::Formatter, + ) -> core::fmt::Result { + formatter.write_str(core::concat!( + "the byte representation of a ", + core::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), + &core::concat!( + "invalid byte sequence for ", + core::stringify!($t) + ), + ) + }) + } + } + deserializer.deserialize_bytes(ByteVisitor:: { + marker: core::marker::PhantomData, + }) + } + } + } + }; +} diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs index 365e0a5..f85c889 100644 --- a/src/key_exchange/tripledh.rs +++ b/src/key_exchange/tripledh.rs @@ -31,8 +31,12 @@ use hmac::{Hmac, Mac, NewMac}; use rand::{CryptoRng, RngCore}; use zeroize::Zeroize; -pub(crate) type NonceLen = U32; +/////////////// +// Constants // +// ========= // +/////////////// +pub(crate) type NonceLen = U32; static STR_RFC: &[u8] = b"RFCXXXX"; static STR_CLIENT_MAC: &[u8] = b"ClientMAC"; static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret"; @@ -40,10 +44,72 @@ static STR_SERVER_MAC: &[u8] = b"ServerMAC"; static STR_SESSION_KEY: &[u8] = b"SessionKey"; static STR_OPAQUE: &[u8] = b"OPAQUE-"; +//////////////////////////// +// High-level API Structs // +// ====================== // +//////////////////////////// + #[allow(clippy::upper_case_acronyms)] /// The Triple Diffie-Hellman key exchange implementation pub struct TripleDH; +/// The client state produced after the first key exchange message +#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] +pub struct Ke1State { + client_e_sk: PrivateKey, + client_nonce: GenericArray, +} + +impl_clone_for!( + struct Ke1State, + [client_e_sk, client_nonce], +); +impl_debug_eq_hash_for!( + struct Ke1State, + [client_e_sk, client_nonce], +); + +/// The first key exchange message +#[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, +} + +/// The server state produced after the second key exchange message +#[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, + session_key: GenericArray, +} + +/// The second key exchange message +#[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, + mac: GenericArray, +} + +/// The third key exchange message +#[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, +} + +//////////////////////////////// +// High-level Implementations // +// ========================== // +//////////////////////////////// + impl KeyExchange for TripleDH { type KE1State = Ke1State; type KE2State = Ke2State<::OutputSize>; @@ -213,207 +279,10 @@ impl KeyExchange for TripleDH { } } -/// The client state produced after the first key exchange message -#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] -pub struct Ke1State { - client_e_sk: PrivateKey, - client_nonce: GenericArray, -} - -impl_clone_for!( - struct Ke1State, - [client_e_sk, client_nonce], -); -impl_debug_eq_hash_for!( - struct Ke1State, - [client_e_sk, client_nonce], -); - -// This can't be derived because of the use of a generic parameter -impl Zeroize for Ke1State { - fn zeroize(&mut self) { - self.client_e_sk.zeroize(); - self.client_nonce.zeroize(); - } -} - -impl Drop for Ke1State { - fn drop(&mut self) { - self.zeroize(); - } -} - -/// The first key exchange message -#[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, -} - -impl FromBytes for Ke1State { - fn from_bytes(bytes: &[u8]) -> Result { - let key_len = ::ElemLen::USIZE; - - let nonce_len = NonceLen::USIZE; - let checked_bytes = check_slice_size_atleast(bytes, key_len + nonce_len, "ke1_state")?; - - Ok(Self { - client_e_sk: PrivateKey::from_bytes(&checked_bytes[..key_len])?, - client_nonce: GenericArray::clone_from_slice( - &checked_bytes[key_len..key_len + nonce_len], - ), - }) - } -} - -impl ToBytesWithPointers for Ke1State { - fn to_bytes(&self) -> Vec { - let output: Vec = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat(); - output - } - - #[cfg(test)] - fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> { - vec![ - (self.client_e_sk.as_ptr(), G::ScalarLen::USIZE), - (self.client_nonce.as_ptr(), NonceLen::USIZE), - ] - } -} - -impl ToBytes for Ke1Message { - fn to_bytes(&self) -> Vec { - [&self.client_nonce[..], &self.client_e_pk.to_arr()].concat() - } -} - -impl FromBytes for Ke1Message { - fn from_bytes(ke1_message_bytes: &[u8]) -> Result { - let nonce_len = NonceLen::USIZE; - let checked_nonce = check_slice_size( - ke1_message_bytes, - nonce_len + ::ElemLen::USIZE, - "ke1_message nonce", - )?; - - Ok(Self { - client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]), - client_e_pk: PublicKey::from_bytes(&checked_nonce[nonce_len..])?, - }) - } -} -/// The server state produced after the second key exchange message -#[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, - session_key: GenericArray, -} - -// This can't be derived because of the use of a phantom parameter -impl> Zeroize for Ke2State { - fn zeroize(&mut self) { - self.km3.zeroize(); - self.hashed_transcript.zeroize(); - self.session_key.zeroize(); - } -} - -impl> Drop for Ke2State { - fn drop(&mut self) { - self.zeroize(); - } -} - -impl> ToBytesWithPointers for Ke2State { - fn to_bytes(&self) -> Vec { - [ - &self.km3[..], - &self.hashed_transcript[..], - &self.session_key[..], - ] - .concat() - } - - #[cfg(test)] - fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> { - vec![ - (self.km3.as_ptr(), HashLen::USIZE), - (self.hashed_transcript.as_ptr(), HashLen::USIZE), - (self.session_key.as_ptr(), HashLen::USIZE), - ] - } -} - -/// The second key exchange message -#[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, - mac: GenericArray, -} - -impl> FromBytes for Ke2State { - fn from_bytes(input: &[u8]) -> Result { - let hash_len = HashLen::USIZE; - let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?; - - Ok(Self { - km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]), - hashed_transcript: GenericArray::clone_from_slice( - &checked_bytes[hash_len..2 * hash_len], - ), - session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]), - }) - } -} - -impl> ToBytes for Ke2Message { - fn to_bytes(&self) -> Vec { - [&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat() - } -} - -impl> Ke2Message { - fn to_bytes_without_info_or_mac(&self) -> Vec { - [&self.server_nonce[..], &self.server_e_pk.to_arr()].concat() - } -} - -impl> FromBytes for Ke2Message { - fn from_bytes(input: &[u8]) -> Result { - let key_len = ::ElemLen::USIZE; - let nonce_len = NonceLen::USIZE; - let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?; - - let unchecked_server_e_pk = check_slice_size_atleast( - &checked_nonce[nonce_len..], - key_len, - "ke2_message server_e_pk", - )?; - let checked_mac = check_slice_size( - &unchecked_server_e_pk[key_len..], - HashLen::USIZE, - "ke1_message mac", - )?; - - // Check the public key bytes - let server_e_pk = KeyPair::::check_public_key(PublicKey::from_bytes( - &unchecked_server_e_pk[..key_len], - )?)?; - - Ok(Self { - server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]), - server_e_pk: PublicKey::from_bytes(&server_e_pk)?, - mac: GenericArray::clone_from_slice(checked_mac), - }) - } -} +///////////////////////// +// Convenience Structs // +//==================== // +///////////////////////// #[allow(clippy::upper_case_acronyms)] // The triple of public and private components used in the 3DH computation @@ -442,29 +311,10 @@ type TripleDHDerivationResult = ( Vec, ); -/// The third key exchange message -#[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, -} - -impl> ToBytes for Ke3Message { - fn to_bytes(&self) -> Vec { - self.mac.to_vec() - } -} - -impl> FromBytes for Ke3Message { - fn from_bytes(bytes: &[u8]) -> Result { - let checked_bytes = check_slice_size(bytes, HashLen::USIZE, "ke3_message")?; - - Ok(Self { - mac: GenericArray::clone_from_slice(checked_bytes), - }) - } -} +//////////////////////////////////////////////// +// Helper functions and Trait Implementations // +// ========================================== // +//////////////////////////////////////////////// // Helper functions @@ -577,3 +427,182 @@ fn generate_nonce(rng: &mut R) -> GenericArray FromBytes for Ke1State { + fn from_bytes(bytes: &[u8]) -> Result { + let key_len = ::ElemLen::USIZE; + + let nonce_len = NonceLen::USIZE; + let checked_bytes = check_slice_size_atleast(bytes, key_len + nonce_len, "ke1_state")?; + + Ok(Self { + client_e_sk: PrivateKey::from_bytes(&checked_bytes[..key_len])?, + client_nonce: GenericArray::clone_from_slice( + &checked_bytes[key_len..key_len + nonce_len], + ), + }) + } +} + +impl ToBytesWithPointers for Ke1State { + fn to_bytes(&self) -> Vec { + let output: Vec = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat(); + output + } + + #[cfg(test)] + fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> { + vec![ + (self.client_e_sk.as_ptr(), G::ScalarLen::USIZE), + (self.client_nonce.as_ptr(), NonceLen::USIZE), + ] + } +} + +impl FromBytes for Ke1Message { + fn from_bytes(ke1_message_bytes: &[u8]) -> Result { + let nonce_len = NonceLen::USIZE; + let checked_nonce = check_slice_size( + ke1_message_bytes, + nonce_len + ::ElemLen::USIZE, + "ke1_message nonce", + )?; + + Ok(Self { + client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]), + client_e_pk: PublicKey::from_bytes(&checked_nonce[nonce_len..])?, + }) + } +} + +impl ToBytes for Ke1Message { + fn to_bytes(&self) -> Vec { + [&self.client_nonce[..], &self.client_e_pk.to_arr()].concat() + } +} + +impl> FromBytes for Ke2State { + fn from_bytes(input: &[u8]) -> Result { + let hash_len = HashLen::USIZE; + let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?; + + Ok(Self { + km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]), + hashed_transcript: GenericArray::clone_from_slice( + &checked_bytes[hash_len..2 * hash_len], + ), + session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]), + }) + } +} + +impl> ToBytesWithPointers for Ke2State { + fn to_bytes(&self) -> Vec { + [ + &self.km3[..], + &self.hashed_transcript[..], + &self.session_key[..], + ] + .concat() + } + + #[cfg(test)] + fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> { + vec![ + (self.km3.as_ptr(), HashLen::USIZE), + (self.hashed_transcript.as_ptr(), HashLen::USIZE), + (self.session_key.as_ptr(), HashLen::USIZE), + ] + } +} + +impl> FromBytes for Ke2Message { + fn from_bytes(input: &[u8]) -> Result { + let key_len = ::ElemLen::USIZE; + let nonce_len = NonceLen::USIZE; + let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?; + + let unchecked_server_e_pk = check_slice_size_atleast( + &checked_nonce[nonce_len..], + key_len, + "ke2_message server_e_pk", + )?; + let checked_mac = check_slice_size( + &unchecked_server_e_pk[key_len..], + HashLen::USIZE, + "ke1_message mac", + )?; + + // Check the public key bytes + let server_e_pk = KeyPair::::check_public_key(PublicKey::from_bytes( + &unchecked_server_e_pk[..key_len], + )?)?; + + Ok(Self { + server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]), + server_e_pk: PublicKey::from_bytes(&server_e_pk)?, + mac: GenericArray::clone_from_slice(checked_mac), + }) + } +} + +impl> ToBytes for Ke2Message { + fn to_bytes(&self) -> Vec { + [&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat() + } +} + +impl> Ke2Message { + fn to_bytes_without_info_or_mac(&self) -> Vec { + [&self.server_nonce[..], &self.server_e_pk.to_arr()].concat() + } +} + +impl> FromBytes for Ke3Message { + fn from_bytes(bytes: &[u8]) -> Result { + let checked_bytes = check_slice_size(bytes, HashLen::USIZE, "ke3_message")?; + + Ok(Self { + mac: GenericArray::clone_from_slice(checked_bytes), + }) + } +} + +impl> ToBytes for Ke3Message { + fn to_bytes(&self) -> Vec { + self.mac.to_vec() + } +} + +// Zeroize on drop implementations + +// This can't be derived because of the use of a generic parameter +impl Zeroize for Ke1State { + fn zeroize(&mut self) { + self.client_e_sk.zeroize(); + self.client_nonce.zeroize(); + } +} + +impl Drop for Ke1State { + fn drop(&mut self) { + self.zeroize(); + } +} + +// This can't be derived because of the use of a phantom parameter +impl> Zeroize for Ke2State { + fn zeroize(&mut self) { + self.km3.zeroize(); + self.hashed_transcript.zeroize(); + self.session_key.zeroize(); + } +} + +impl> Drop for Ke2State { + fn drop(&mut self) { + self.zeroize(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 65f4ceb..28c55ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -843,28 +843,21 @@ pub mod errors; mod impls; #[macro_use] mod serialization; - -// High-level API -mod opaque; - -mod messages; - pub mod ciphersuite; mod envelope; -pub mod hash; - pub mod group; - +pub mod hash; pub mod key_exchange; pub mod keypair; +mod messages; +mod opaque; +pub mod slow_hash; #[cfg(feature = "bench")] pub mod oprf; #[cfg(not(feature = "bench"))] mod oprf; -pub mod slow_hash; - #[cfg(test)] mod tests; diff --git a/src/messages.rs b/src/messages.rs index c47649d..4f79cbd 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -22,8 +22,10 @@ use digest::Digest; use generic_array::{typenum::Unsigned, GenericArray}; use rand::{CryptoRng, RngCore}; -// Messages -// ========= +//////////////////////////// +// High-level API Structs // +// ====================== // +//////////////////////////// /// The message sent by the client to the server, to initiate registration pub struct RegistrationRequest { @@ -31,24 +33,62 @@ pub struct RegistrationRequest { pub(crate) alpha: CS::OprfGroup, } +/// The answer sent by the server to the user, upon reception of the +/// registration attempt +pub struct RegistrationResponse { + /// The server's oprf output + pub(crate) beta: CS::OprfGroup, + /// Server's static public key + pub(crate) server_s_pk: PublicKey, +} + +/// The final message from the client, containing sealed cryptographic +/// identifiers +pub struct RegistrationUpload { + /// The "envelope" generated by the user, containing sealed + /// cryptographic identifiers + pub(crate) envelope: Envelope, + /// The masking key used to mask the envelope + pub(crate) masking_key: GenericArray::OutputSize>, + /// The user's public key + pub(crate) client_s_pk: PublicKey, +} + +/// The message sent by the user to the server, to initiate registration +pub struct CredentialRequest { + /// blinded password information + pub(crate) alpha: CS::OprfGroup, + pub(crate) ke1_message: >::KE1Message, +} + +/// The answer sent by the server to the user, upon reception of the +/// login attempt +pub struct CredentialResponse { + /// the server's oprf output + pub(crate) beta: CS::OprfGroup, + pub(crate) masking_nonce: Vec, + pub(crate) masked_response: Vec, + pub(crate) ke2_message: >::KE2Message, +} + +/// The answer sent by the client to the server, upon reception of the +/// sealed envelope +pub struct CredentialFinalization { + pub(crate) ke3_message: >::KE3Message, +} + +//////////////////////////////// +// High-level Implementations // +// ========================== // +//////////////////////////////// + impl RegistrationRequest { /// Only used for testing purposes #[cfg(test)] pub fn get_alpha_for_testing(&self) -> CS::OprfGroup { self.alpha } -} -// Cannot be derived because it would require for CS to be Clone. -impl Clone for RegistrationRequest { - fn clone(&self) -> Self { - Self { alpha: self.alpha } - } -} - -impl_debug_eq_hash_for!(struct RegistrationRequest, [alpha], [CS::OprfGroup]); - -impl RegistrationRequest { /// Serialization into bytes pub fn serialize(&self) -> Vec { self.alpha.to_arr().to_vec() @@ -71,33 +111,6 @@ 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 { - /// The server's oprf output - pub(crate) beta: CS::OprfGroup, - /// Server's static public key - pub(crate) server_s_pk: PublicKey, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for RegistrationResponse { - fn clone(&self) -> Self { - Self { - beta: self.beta, - server_s_pk: self.server_s_pk.clone(), - } - } -} - -impl_debug_eq_hash_for!( - struct RegistrationResponse, - [beta, server_s_pk], - [CS::OprfGroup], -); - impl RegistrationResponse { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -140,29 +153,6 @@ impl RegistrationResponse { } } -impl_serialize_and_deserialize_for!(RegistrationResponse); - -/// The final message from the client, containing sealed cryptographic -/// identifiers -pub struct RegistrationUpload { - /// The "envelope" generated by the user, containing sealed - /// cryptographic identifiers - pub(crate) envelope: Envelope, - /// The masking key used to mask the envelope - pub(crate) masking_key: GenericArray::OutputSize>, - /// The user's public key - pub(crate) client_s_pk: PublicKey, -} - -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 pub fn serialize(&self) -> Vec { @@ -208,34 +198,6 @@ 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 - pub(crate) alpha: CS::OprfGroup, - pub(crate) ke1_message: >::KE1Message, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for CredentialRequest { - fn clone(&self) -> Self { - Self { - alpha: self.alpha, - ke1_message: self.ke1_message.clone(), - } - } -} - -impl_debug_eq_hash_for!( - struct CredentialRequest, - [alpha, ke1_message], - [ - CS::OprfGroup, - >::KE1Message - ], -); - impl CredentialRequest { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -273,39 +235,6 @@ 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 { - /// the server's oprf output - pub(crate) beta: CS::OprfGroup, - pub(crate) masking_nonce: Vec, - pub(crate) masked_response: Vec, - pub(crate) ke2_message: >::KE2Message, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for CredentialResponse { - fn clone(&self) -> Self { - Self { - beta: self.beta, - masking_nonce: self.masking_nonce.clone(), - masked_response: self.masked_response.clone(), - ke2_message: self.ke2_message.clone(), - } - } -} - -impl_debug_eq_hash_for!( - struct CredentialResponse, - [beta, masking_nonce, masked_response, ke2_message], - [ - CS::OprfGroup, - >::KE2Message, - ], -); - impl CredentialResponse { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -380,21 +309,6 @@ 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 { - pub(crate) ke3_message: >::KE3Message, -} - -impl_clone_for!(struct CredentialFinalization, [ke3_message]); -impl_debug_eq_hash_for!( - struct CredentialFinalization, - [ke3_message], - [>::KE3Message], -); - impl CredentialFinalization { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -411,4 +325,71 @@ impl CredentialFinalization { } } +/////////////////////////// +// Trait Implementations // +// ===================== // +/////////////////////////// + +impl_clone_for!( + struct RegistrationRequest, + [alpha], +); +impl_debug_eq_hash_for!(struct RegistrationRequest, [alpha], [CS::OprfGroup]); +impl_serialize_and_deserialize_for!(RegistrationRequest); + +impl_clone_for!( + struct RegistrationResponse, + [beta, server_s_pk], +); +impl_debug_eq_hash_for!( + struct RegistrationResponse, + [beta, server_s_pk], + [CS::OprfGroup], +); +impl_serialize_and_deserialize_for!(RegistrationResponse); + +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_serialize_and_deserialize_for!(RegistrationUpload); + +impl_clone_for!( + struct CredentialRequest, + [alpha, ke1_message], +); +impl_debug_eq_hash_for!( + struct CredentialRequest, + [alpha, ke1_message], + [ + CS::OprfGroup, + >::KE1Message + ], +); +impl_serialize_and_deserialize_for!(CredentialRequest); + +impl_clone_for!( + struct CredentialResponse, + [beta, masking_nonce, masked_response, ke2_message], +); +impl_debug_eq_hash_for!( + struct CredentialResponse, + [beta, masking_nonce, masked_response, ke2_message], + [ + CS::OprfGroup, + >::KE2Message, + ], +); +impl_serialize_and_deserialize_for!(CredentialResponse); + +impl_clone_for!(struct CredentialFinalization, [ke3_message]); +impl_debug_eq_hash_for!( + struct CredentialFinalization, + [ke3_message], + [>::KE3Message], +); impl_serialize_and_deserialize_for!(CredentialFinalization); diff --git a/src/opaque.rs b/src/opaque.rs index 1591f6c..b18f178 100644 --- a/src/opaque.rs +++ b/src/opaque.rs @@ -28,13 +28,20 @@ use hkdf::Hkdf; use rand::{CryptoRng, RngCore}; use zeroize::Zeroize; +/////////////// +// Constants // +// ========= // +/////////////// + const STR_CREDENTIAL_RESPONSE_PAD: &[u8] = b"CredentialResponsePad"; const STR_MASKING_KEY: &[u8] = b"MaskingKey"; const STR_OPRF_KEY: &[u8] = b"OprfKey"; const STR_OPAQUE_DERIVE_KEY_PAIR: &[u8] = b"OPAQUE-DeriveKeyPair"; -// Server Setup -// ============ +//////////////////////////// +// High-level API Structs // +// ====================== // +//////////////////////////// /// The state elements the server holds upon setup #[cfg_attr( @@ -54,6 +61,86 @@ pub struct ServerSetup< pub(crate) fake_keypair: KeyPair, } +// 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], +); + +/// The state elements the client holds to register itself +pub struct ClientRegistration { + alpha: CS::OprfGroup, + /// token containing the client's password and the blinding factor + pub(crate) token: oprf::Token, +} + +impl_clone_for!(struct ClientRegistration, [token, alpha]); +impl_debug_eq_hash_for!( + struct ClientRegistration, + [token], + [oprf::Token], +); +impl_serialize_and_deserialize_for!(ClientRegistration); + +/// The state elements the server holds to record a registration +pub struct ServerRegistration(RegistrationUpload); + +impl_clone_for!(tuple ServerRegistration, [0]); +impl_debug_eq_hash_for!( + tuple ServerRegistration, + [0], +); +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, + ke1_state: >::KE1State, + serialized_credential_request: Vec, +} + +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], +); + +/// The state elements the server holds to record a login +pub struct ServerLogin { + ke2_state: >::KE2State, + _cs: PhantomData, +} + +impl_clone_for!(struct ServerLogin, [ke2_state, _cs]); +impl_debug_eq_hash_for!( + struct ServerLogin, + [ke2_state, _cs], + [>::KE2State], +); +impl_serialize_and_deserialize_for!(ServerLogin); + +//////////////////////////////// +// High-level Implementations // +// ========================== // +//////////////////////////////// + +// Server Setup +// ============ + impl ServerSetup> { /// Generate a new instance of server setup pub fn new(rng: &mut R) -> Self { @@ -108,33 +195,9 @@ 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 // ============ -/// The state elements the client holds to register itself -pub struct ClientRegistration { - alpha: CS::OprfGroup, - /// token containing the client's password and the blinding factor - pub(crate) token: oprf::Token, -} - -impl_clone_for!(struct ClientRegistration, [token, alpha]); -impl_debug_eq_hash_for!( - struct ClientRegistration, - [token], - [oprf::Token], -); - impl ClientRegistration { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -188,85 +251,7 @@ impl ClientRegistration { /* cannot provide raw pointer to self.token.blind until this is exposed in curve25519_dalek::scalar::Scalar */ ] } -} -impl_serialize_and_deserialize_for!(ClientRegistration); - -/// Options for specifying custom identifiers -#[derive(Clone)] -pub enum Identifiers { - /// Supply only a client identifier - ClientIdentifier(Vec), - /// Supply only a server identifier - ServerIdentifier(Vec), - /// Supply a client and server identifier - ClientAndServerIdentifiers(Vec, Vec), -} - -pub(crate) fn bytestrings_from_identifiers( - ids: &Option, - client_s_pk: &[u8], - server_s_pk: &[u8], -) -> Result<(Vec, Vec), ProtocolError> { - let (client_identity, server_identity): (Vec, Vec) = match ids { - None => (client_s_pk.to_vec(), server_s_pk.to_vec()), - Some(Identifiers::ClientIdentifier(id_u)) => (id_u.clone(), server_s_pk.to_vec()), - Some(Identifiers::ServerIdentifier(id_s)) => (client_s_pk.to_vec(), id_s.clone()), - Some(Identifiers::ClientAndServerIdentifiers(id_u, id_s)) => (id_u.clone(), id_s.clone()), - }; - Ok(( - serialize(&client_identity, 2)?, - serialize(&server_identity, 2)?, - )) -} - -/// Optional parameters for client registration finish -#[derive(Clone)] -pub struct ClientRegistrationFinishParameters<'h, CS: CipherSuite> { - /// Specifying the identifiers idU and idS - pub identifiers: Option, - /// Specifying a configuration for the slow hash - pub slow_hash: Option<&'h CS::SlowHash>, -} - -impl<'h, CS: CipherSuite> Default for ClientRegistrationFinishParameters<'h, CS> { - fn default() -> Self { - Self { - identifiers: None, - slow_hash: None, - } - } -} - -impl<'h, CS: CipherSuite> ClientRegistrationFinishParameters<'h, CS> { - /// Create a new [`ClientRegistrationFinishParameters`] - pub fn new(identifiers: Option, slow_hash: Option<&'h CS::SlowHash>) -> Self { - Self { - identifiers, - slow_hash, - } - } -} - -/// Contains the fields that are returned by a client registration start -pub struct ClientRegistrationStartResult { - /// The registration request message to be sent to the server - pub message: RegistrationRequest, - /// The client state that must be persisted in order to complete registration - pub state: ClientRegistration, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ClientRegistrationStartResult { - fn clone(&self) -> Self { - Self { - message: self.message.clone(), - state: self.state.clone(), - } - } -} - -impl ClientRegistration { /// Returns an initial "blinded" request to send to the server, as well as a ClientRegistration pub fn start( blinding_factor_rng: &mut R, @@ -280,45 +265,7 @@ impl ClientRegistration { state: Self { alpha, token }, }) } -} -/// Contains the fields that are returned by a client registration finish -pub struct ClientRegistrationFinishResult { - /// The registration upload message to be sent to the server - pub message: RegistrationUpload, - /// The export key output by client registration - pub export_key: GenericArray::OutputSize>, - /// The server's static public key - pub server_s_pk: PublicKey, - /// Instance of the ClientRegistration, only used in tests for checking zeroize - #[cfg(test)] - pub state: ClientRegistration, - /// AuthKey, only used in tests - #[cfg(test)] - pub auth_key: Vec, - /// Password derived key, only used in tests - #[cfg(test)] - pub randomized_pwd: GenericArray::OutputSize>, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ClientRegistrationFinishResult { - fn clone(&self) -> Self { - Self { - message: self.message.clone(), - export_key: self.export_key.clone(), - server_s_pk: self.server_s_pk.clone(), - #[cfg(test)] - state: self.state.clone(), - #[cfg(test)] - auth_key: self.auth_key.clone(), - #[cfg(test)] - randomized_pwd: self.randomized_pwd.clone(), - } - } -} - -impl ClientRegistration { /// "Unblinds" the server's answer and returns a final message containing /// cryptographic identifiers, to be sent to the server on setup finalization pub fn finish( @@ -366,36 +313,6 @@ impl ClientRegistration { } } -/// Contains the fields that are returned by a server registration start. -/// Note that there is no state output in this step -pub struct ServerRegistrationStartResult { - /// The registration resposne message to send to the client - pub message: RegistrationResponse, - /// OPRF key, only used in tests - #[cfg(test)] - pub oprf_key: GenericArray::ScalarLen>, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ServerRegistrationStartResult { - fn clone(&self) -> Self { - Self { - message: self.message.clone(), - #[cfg(test)] - oprf_key: self.oprf_key.clone(), - } - } -} - -/// The state elements the server holds to record a registration -pub struct ServerRegistration(RegistrationUpload); - -impl_clone_for!(tuple ServerRegistration, [0]); -impl_debug_eq_hash_for!( - tuple ServerRegistration, - [0], -); - impl ServerRegistration { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -456,34 +373,9 @@ impl ServerRegistration { } } -impl_serialize_and_deserialize_for!(ServerRegistration); - // Login // ===== -/// 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, - ke1_state: >::KE1State, - serialized_credential_request: Vec, -} - -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 pub fn serialize(&self) -> Result, ProtocolError> { @@ -543,99 +435,6 @@ impl ClientLogin { } } -/// Contains the fields that are returned by a client login start -pub struct ClientLoginStartResult { - /// The message to send to the server to begin the login protocol - pub message: CredentialRequest, - /// The state that the client must keep in order to complete the protocol - pub state: ClientLogin, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ClientLoginStartResult { - fn clone(&self) -> Self { - Self { - message: self.message.clone(), - state: self.state.clone(), - } - } -} - -/// Optional parameters for client login finish -#[derive(Clone)] -pub struct ClientLoginFinishParameters<'h, CS: CipherSuite> { - /// Specifying a context field that the server must agree on - pub context: Option>, - /// Specifying a user identifier and server identifier that will be matched against the server - pub identifiers: Option, - /// Specifying a configuration for the slow hash - pub slow_hash: Option<&'h CS::SlowHash>, -} - -impl<'h, CS: CipherSuite> Default for ClientLoginFinishParameters<'h, CS> { - fn default() -> Self { - Self { - context: None, - identifiers: None, - slow_hash: None, - } - } -} - -impl<'h, CS: CipherSuite> ClientLoginFinishParameters<'h, CS> { - /// Create a new [`ClientLoginFinishParameters`] - pub fn new( - context: Option>, - identifiers: Option, - slow_hash: Option<&'h CS::SlowHash>, - ) -> Self { - Self { - context, - identifiers, - slow_hash, - } - } -} - -/// Contains the fields that are returned by a client login finish -pub struct ClientLoginFinishResult { - /// The message to send to the server to complete the protocol - pub message: CredentialFinalization, - /// The session key - pub session_key: Vec, - /// The client-side export key - pub export_key: GenericArray::OutputSize>, - /// The server's static public key - pub server_s_pk: PublicKey, - /// Instance of the ClientLogin, only used in tests for checking zeroize - #[cfg(test)] - pub state: ClientLogin, - /// Handshake secret, only used in tests - #[cfg(test)] - pub handshake_secret: Vec, - /// Client MAC key, only used in tests - #[cfg(test)] - pub client_mac_key: GenericArray::OutputSize>, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ClientLoginFinishResult { - fn clone(&self) -> Self { - Self { - message: self.message.clone(), - session_key: self.session_key.clone(), - export_key: self.export_key.clone(), - server_s_pk: self.server_s_pk.clone(), - #[cfg(test)] - state: self.state.clone(), - #[cfg(test)] - handshake_secret: self.handshake_secret.clone(), - #[cfg(test)] - client_mac_key: self.client_mac_key.clone(), - } - } -} - impl ClientLogin { /// Returns an initial "blinded" password request to send to the server, as well as a ClientLogin pub fn start( @@ -743,93 +542,6 @@ impl ClientLogin { } } -/// The state elements the server holds to record a login -pub struct ServerLogin { - ke2_state: >::KE2State, - _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)] -pub enum ServerLoginStartParameters { - /// Specifying a context field that the client must agree on - WithContext(Vec), - /// Specifying a user identifier and server identifier that will be matched against the client - WithIdentifiers(Identifiers), - /// Specifying a context field that the client must agree on, - /// along with a user identifier and and server identifier that will be matched against the client - /// (in that order) - WithContextAndIdentifiers(Vec, Identifiers), -} - -impl Default for ServerLoginStartParameters { - fn default() -> Self { - Self::WithContext(Vec::new()) - } -} - -/// Contains the fields that are returned by a server login start -pub struct ServerLoginStartResult { - /// The message to send back to the client - pub message: CredentialResponse, - /// The state that the server must keep in order to finish the protocl - pub state: ServerLogin, - /// Handshake secret, only used in tests - #[cfg(test)] - pub handshake_secret: Vec, - /// Server MAC key, only used in tests - #[cfg(test)] - pub server_mac_key: GenericArray::OutputSize>, - /// OPRF key, only used in tests - #[cfg(test)] - pub oprf_key: GenericArray::ScalarLen>, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ServerLoginStartResult { - fn clone(&self) -> Self { - Self { - message: self.message.clone(), - state: self.state.clone(), - #[cfg(test)] - handshake_secret: self.handshake_secret.clone(), - #[cfg(test)] - server_mac_key: self.server_mac_key.clone(), - #[cfg(test)] - oprf_key: self.oprf_key.clone(), - } - } -} - -/// Contains the fields that are returned by a server login finish -pub struct ServerLoginFinishResult { - /// The session key between client and server - pub session_key: Vec, - _cs: PhantomData, - /// Instance of the ClientRegistration, only used in tests for checking zeroize - #[cfg(test)] - pub state: ServerLogin, -} - -// Cannot be derived because it would require for CS to be Clone. -impl Clone for ServerLoginFinishResult { - fn clone(&self) -> Self { - Self { - session_key: self.session_key.clone(), - _cs: PhantomData, - #[cfg(test)] - state: self.state.clone(), - } - } -} - impl ServerLogin { /// Serialization into bytes pub fn serialize(&self) -> Vec { @@ -964,68 +676,297 @@ impl ServerLogin { } } -impl_serialize_and_deserialize_for!(ServerLogin); +///////////////////////// +// Convenience Structs // +//==================== // +///////////////////////// -// Zeroize on drop implementations +/// Options for specifying custom identifiers +#[derive(Clone)] +pub enum Identifiers { + /// Supply only a client identifier + ClientIdentifier(Vec), + /// Supply only a server identifier + ServerIdentifier(Vec), + /// Supply a client and server identifier + ClientAndServerIdentifiers(Vec, Vec), +} -// This can't be derived because of the use of a phantom parameter -impl Zeroize for ClientRegistration { - fn zeroize(&mut self) { - self.token.data.zeroize(); - self.token.blind.zeroize(); +/// Optional parameters for client registration finish +#[derive(Clone)] +pub struct ClientRegistrationFinishParameters<'h, CS: CipherSuite> { + /// Specifying the identifiers idU and idS + pub identifiers: Option, + /// Specifying a configuration for the slow hash + pub slow_hash: Option<&'h CS::SlowHash>, +} + +impl<'h, CS: CipherSuite> Default for ClientRegistrationFinishParameters<'h, CS> { + fn default() -> Self { + Self { + identifiers: None, + slow_hash: None, + } } } -impl Drop for ClientRegistration { - fn drop(&mut self) { - self.zeroize(); +impl<'h, CS: CipherSuite> ClientRegistrationFinishParameters<'h, CS> { + /// Create a new [`ClientRegistrationFinishParameters`] + pub fn new(identifiers: Option, slow_hash: Option<&'h CS::SlowHash>) -> Self { + Self { + identifiers, + slow_hash, + } } } -// This can't be derived because of the use of a phantom parameter -impl Zeroize for ServerRegistration { - fn zeroize(&mut self) { - self.0.envelope.zeroize(); - self.0.masking_key.zeroize(); - self.0.client_s_pk.zeroize(); +/// Contains the fields that are returned by a client registration start +pub struct ClientRegistrationStartResult { + /// The registration request message to be sent to the server + pub message: RegistrationRequest, + /// The client state that must be persisted in order to complete registration + pub state: ClientRegistration, +} + +// Cannot be derived because it would require for CS to be Clone. +impl Clone for ClientRegistrationStartResult { + fn clone(&self) -> Self { + Self { + message: self.message.clone(), + state: self.state.clone(), + } } } -impl Drop for ServerRegistration { - fn drop(&mut self) { - self.zeroize(); +/// Contains the fields that are returned by a client registration finish +pub struct ClientRegistrationFinishResult { + /// The registration upload message to be sent to the server + pub message: RegistrationUpload, + /// The export key output by client registration + pub export_key: GenericArray::OutputSize>, + /// The server's static public key + pub server_s_pk: PublicKey, + /// Instance of the ClientRegistration, only used in tests for checking zeroize + #[cfg(test)] + pub state: ClientRegistration, + /// AuthKey, only used in tests + #[cfg(test)] + pub auth_key: Vec, + /// Password derived key, only used in tests + #[cfg(test)] + pub randomized_pwd: GenericArray::OutputSize>, +} + +// Cannot be derived because it would require for CS to be Clone. +impl Clone for ClientRegistrationFinishResult { + fn clone(&self) -> Self { + Self { + message: self.message.clone(), + export_key: self.export_key.clone(), + server_s_pk: self.server_s_pk.clone(), + #[cfg(test)] + state: self.state.clone(), + #[cfg(test)] + auth_key: self.auth_key.clone(), + #[cfg(test)] + randomized_pwd: self.randomized_pwd.clone(), + } } } -// This can't be derived because of the use of a phantom parameter -impl Zeroize for ClientLogin { - fn zeroize(&mut self) { - self.token.data.zeroize(); - self.token.blind.zeroize(); - self.ke1_state.zeroize(); - self.serialized_credential_request.zeroize(); +/// Contains the fields that are returned by a server registration start. +/// Note that there is no state output in this step +pub struct ServerRegistrationStartResult { + /// The registration resposne message to send to the client + pub message: RegistrationResponse, + /// OPRF key, only used in tests + #[cfg(test)] + pub oprf_key: GenericArray::ScalarLen>, +} + +// Cannot be derived because it would require for CS to be Clone. +impl Clone for ServerRegistrationStartResult { + fn clone(&self) -> Self { + Self { + message: self.message.clone(), + #[cfg(test)] + oprf_key: self.oprf_key.clone(), + } } } -impl Drop for ClientLogin { - fn drop(&mut self) { - self.zeroize(); +/// Contains the fields that are returned by a client login start +pub struct ClientLoginStartResult { + /// The message to send to the server to begin the login protocol + pub message: CredentialRequest, + /// The state that the client must keep in order to complete the protocol + pub state: ClientLogin, +} + +// Cannot be derived because it would require for CS to be Clone. +impl Clone for ClientLoginStartResult { + fn clone(&self) -> Self { + Self { + message: self.message.clone(), + state: self.state.clone(), + } } } -// This can't be derived because of the use of a phantom parameter -impl Zeroize for ServerLogin { - fn zeroize(&mut self) { - self.ke2_state.zeroize(); +/// Optional parameters for client login finish +#[derive(Clone)] +pub struct ClientLoginFinishParameters<'h, CS: CipherSuite> { + /// Specifying a context field that the server must agree on + pub context: Option>, + /// Specifying a user identifier and server identifier that will be matched against the server + pub identifiers: Option, + /// Specifying a configuration for the slow hash + pub slow_hash: Option<&'h CS::SlowHash>, +} + +impl<'h, CS: CipherSuite> Default for ClientLoginFinishParameters<'h, CS> { + fn default() -> Self { + Self { + context: None, + identifiers: None, + slow_hash: None, + } } } -impl Drop for ServerLogin { - fn drop(&mut self) { - self.zeroize(); +impl<'h, CS: CipherSuite> ClientLoginFinishParameters<'h, CS> { + /// Create a new [`ClientLoginFinishParameters`] + pub fn new( + context: Option>, + identifiers: Option, + slow_hash: Option<&'h CS::SlowHash>, + ) -> Self { + Self { + context, + identifiers, + slow_hash, + } } } +/// Contains the fields that are returned by a client login finish +pub struct ClientLoginFinishResult { + /// The message to send to the server to complete the protocol + pub message: CredentialFinalization, + /// The session key + pub session_key: Vec, + /// The client-side export key + pub export_key: GenericArray::OutputSize>, + /// The server's static public key + pub server_s_pk: PublicKey, + /// Instance of the ClientLogin, only used in tests for checking zeroize + #[cfg(test)] + pub state: ClientLogin, + /// Handshake secret, only used in tests + #[cfg(test)] + pub handshake_secret: Vec, + /// Client MAC key, only used in tests + #[cfg(test)] + pub client_mac_key: GenericArray::OutputSize>, +} + +// Cannot be derived because it would require for CS to be Clone. +impl Clone for ClientLoginFinishResult { + fn clone(&self) -> Self { + Self { + message: self.message.clone(), + session_key: self.session_key.clone(), + export_key: self.export_key.clone(), + server_s_pk: self.server_s_pk.clone(), + #[cfg(test)] + state: self.state.clone(), + #[cfg(test)] + handshake_secret: self.handshake_secret.clone(), + #[cfg(test)] + client_mac_key: self.client_mac_key.clone(), + } + } +} + +/// Contains the fields that are returned by a server login finish +pub struct ServerLoginFinishResult { + /// The session key between client and server + pub session_key: Vec, + _cs: PhantomData, + /// Instance of the ClientRegistration, only used in tests for checking zeroize + #[cfg(test)] + pub state: ServerLogin, +} + +// Cannot be derived because it would require for CS to be Clone. +impl Clone for ServerLoginFinishResult { + fn clone(&self) -> Self { + Self { + session_key: self.session_key.clone(), + _cs: PhantomData, + #[cfg(test)] + state: self.state.clone(), + } + } +} + +/// Optional parameters for server login start +#[derive(Clone)] +pub enum ServerLoginStartParameters { + /// Specifying a context field that the client must agree on + WithContext(Vec), + /// Specifying a user identifier and server identifier that will be matched against the client + WithIdentifiers(Identifiers), + /// Specifying a context field that the client must agree on, + /// along with a user identifier and and server identifier that will be matched against the client + /// (in that order) + WithContextAndIdentifiers(Vec, Identifiers), +} + +impl Default for ServerLoginStartParameters { + fn default() -> Self { + Self::WithContext(Vec::new()) + } +} + +/// Contains the fields that are returned by a server login start +pub struct ServerLoginStartResult { + /// The message to send back to the client + pub message: CredentialResponse, + /// The state that the server must keep in order to finish the protocl + pub state: ServerLogin, + /// Handshake secret, only used in tests + #[cfg(test)] + pub handshake_secret: Vec, + /// Server MAC key, only used in tests + #[cfg(test)] + pub server_mac_key: GenericArray::OutputSize>, + /// OPRF key, only used in tests + #[cfg(test)] + pub oprf_key: GenericArray::ScalarLen>, +} + +// Cannot be derived because it would require for CS to be Clone. +impl Clone for ServerLoginStartResult { + fn clone(&self) -> Self { + Self { + message: self.message.clone(), + state: self.state.clone(), + #[cfg(test)] + handshake_secret: self.handshake_secret.clone(), + #[cfg(test)] + server_mac_key: self.server_mac_key.clone(), + #[cfg(test)] + oprf_key: self.oprf_key.clone(), + } + } +} + +//////////////////////////////////////////////// +// Helper functions and Trait Implementations // +// ========================================== // +//////////////////////////////////////////////// + // Helper functions fn get_password_derived_key( @@ -1107,3 +1048,80 @@ fn unmask_response( Ok((server_s_pk, envelope)) } + +pub(crate) fn bytestrings_from_identifiers( + ids: &Option, + client_s_pk: &[u8], + server_s_pk: &[u8], +) -> Result<(Vec, Vec), ProtocolError> { + let (client_identity, server_identity): (Vec, Vec) = match ids { + None => (client_s_pk.to_vec(), server_s_pk.to_vec()), + Some(Identifiers::ClientIdentifier(id_u)) => (id_u.clone(), server_s_pk.to_vec()), + Some(Identifiers::ServerIdentifier(id_s)) => (client_s_pk.to_vec(), id_s.clone()), + Some(Identifiers::ClientAndServerIdentifiers(id_u, id_s)) => (id_u.clone(), id_s.clone()), + }; + Ok(( + serialize(&client_identity, 2)?, + serialize(&server_identity, 2)?, + )) +} + +// Zeroize on drop implementations + +// This can't be derived because of the use of a phantom parameter +impl Zeroize for ClientRegistration { + fn zeroize(&mut self) { + self.token.data.zeroize(); + self.token.blind.zeroize(); + } +} + +impl Drop for ClientRegistration { + fn drop(&mut self) { + self.zeroize(); + } +} + +// This can't be derived because of the use of a phantom parameter +impl Zeroize for ServerRegistration { + fn zeroize(&mut self) { + self.0.envelope.zeroize(); + self.0.masking_key.zeroize(); + self.0.client_s_pk.zeroize(); + } +} + +impl Drop for ServerRegistration { + fn drop(&mut self) { + self.zeroize(); + } +} + +// This can't be derived because of the use of a phantom parameter +impl Zeroize for ClientLogin { + fn zeroize(&mut self) { + self.token.data.zeroize(); + self.token.blind.zeroize(); + self.ke1_state.zeroize(); + self.serialized_credential_request.zeroize(); + } +} + +impl Drop for ClientLogin { + fn drop(&mut self) { + self.zeroize(); + } +} + +// This can't be derived because of the use of a phantom parameter +impl Zeroize for ServerLogin { + fn zeroize(&mut self) { + self.ke2_state.zeroize(); + } +} + +impl Drop for ServerLogin { + fn drop(&mut self) { + self.zeroize(); + } +} diff --git a/src/oprf.rs b/src/oprf.rs index e21fb26..9bd9dc3 100644 --- a/src/oprf.rs +++ b/src/oprf.rs @@ -76,7 +76,7 @@ fn finalize_after_unblind( } //////////////////////// -// Benchmarking shims // +// Benchmarking Shims // //////////////////////// #[cfg(feature = "bench")] diff --git a/src/serialization/mod.rs b/src/serialization/mod.rs index 6a79d1e..076a4ad 100644 --- a/src/serialization/mod.rs +++ b/src/serialization/mod.rs @@ -64,73 +64,6 @@ pub(crate) fn tokenize( )) } -/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits. -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: core::marker::PhantomData, - } - impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor { - type Value = $t; - fn expecting( - &self, - formatter: &mut core::fmt::Formatter, - ) -> core::fmt::Result { - formatter.write_str(core::concat!( - "the byte representation of a ", - core::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), - &core::concat!( - "invalid byte sequence for ", - core::stringify!($t) - ), - ) - }) - } - } - deserializer.deserialize_bytes(ByteVisitor:: { - marker: core::marker::PhantomData, - }) - } - } - } - }; -} - #[cfg(test)] mod tests; diff --git a/src/serialization/tests.rs b/src/serialization/tests.rs index 5cf17b1..950b2a1 100644 --- a/src/serialization/tests.rs +++ b/src/serialization/tests.rs @@ -257,7 +257,7 @@ fn credential_response_roundtrip() { } #[test] -fn login_third_message_roundtrip() { +fn credential_finalization_roundtrip() { let mut rng = OsRng; let mut mac = [0u8; MAC_SIZE]; rng.fill_bytes(&mut mac);