From 3c555e82ae2d5a9488834394e7d05d7a87a9ca5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Sat, 19 Sep 2020 19:16:27 -0400 Subject: [PATCH 1/5] Make tripleDH manipulate GenericArray over Vec upgrade some of the structure contents of the KE types --- src/group.rs | 6 +- src/hash.rs | 13 +--- src/key_exchange/tripledh.rs | 137 ++++++++++++++++++----------------- src/keypair.rs | 29 -------- src/tests/opaque_ke_test.rs | 5 +- 5 files changed, 76 insertions(+), 114 deletions(-) diff --git a/src/group.rs b/src/group.rs index 88fba28..551403c 100644 --- a/src/group.rs +++ b/src/group.rs @@ -86,8 +86,7 @@ impl Group for RistrettoPoint { element_bits: &GenericArray, ) -> Result { CompressedRistretto::from_slice(element_bits) - .decompress() - .ok_or_else(|| InternalPakeError::PointError) + .decompress().ok_or(InternalPakeError::PointError) } // serialization of a group element fn to_arr(&self) -> GenericArray { @@ -131,8 +130,7 @@ impl Group for EdwardsPoint { element_bits: &GenericArray, ) -> Result { let point = CompressedEdwardsY::from_slice(element_bits) - .decompress() - .ok_or_else(|| InternalPakeError::PointError)?; + .decompress().ok_or(InternalPakeError::PointError)?; if point.is_small_order() { return Err(InternalPakeError::SubGroupError); diff --git a/src/hash.rs b/src/hash.rs index dfe6f9e..03a591e 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -4,19 +4,10 @@ // LICENSE file in the root directory of this source tree. use digest::{BlockInput, FixedOutput, Reset, Update}; -use generic_array::ArrayLength; /// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC // Associated types could be simplified when they are made as defaults: // https://github.com/rust-lang/rust/issues/29661 -pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone { - /// The block size for the hash function - type BlockSize: ArrayLength; - /// The output size of the hash function - type OutputSize: ArrayLength; -} +pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {} -impl Hash for T { - type BlockSize = T::BlockSize; - type OutputSize = T::OutputSize; -} +impl Hash for T {} diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs index 138c69f..af9e61a 100644 --- a/src/key_exchange/tripledh.rs +++ b/src/key_exchange/tripledh.rs @@ -9,13 +9,9 @@ use crate::{ hash::Hash, key_exchange::traits::{KeyExchange, ToBytes}, keypair::{Key, KeyPair, SizedBytes}, - sized_bytes_using_constant_and_try_from, -}; -use digest::Digest; -use generic_array::{ - typenum::{U64, U96}, - GenericArray, }; +use digest::{Digest, FixedOutput}; +use generic_array::{typenum::U32, ArrayLength, GenericArray}; use hkdf::Hkdf; use hmac::{Hmac, Mac, NewMac}; use rand_core::{CryptoRng, RngCore}; @@ -24,6 +20,7 @@ use std::convert::TryFrom; const KEY_LEN: usize = 32; pub(crate) const NONCE_LEN: usize = 32; +pub(crate) type NonceLen = U32; const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN; const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN; @@ -33,22 +30,25 @@ static STR_3DH: &[u8] = b"3DH keys"; pub struct TripleDH; impl KeyExchange for TripleDH { - type KE1State = KE1State; - type KE2State = KE2State; + type KE1State = KE1State<::OutputSize>; + type KE2State = KE2State<::OutputSize>; type KE1Message = KE1Message; - type KE2Message = KE2Message; - type KE3Message = KE3Message; + type KE2Message = KE2Message<::OutputSize>; + type KE3Message = KE3Message<::OutputSize>; fn generate_ke1>( l1_component: Vec, rng: &mut R, ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> { let client_e_kp = KeyFormat::generate_random(rng)?; - let mut client_nonce = [0u8; NONCE_LEN]; - rng.fill_bytes(&mut client_nonce); + let client_nonce: GenericArray = { + let mut client_nonce_bytes = [0u8; NONCE_LEN]; + rng.fill_bytes(&mut client_nonce_bytes); + client_nonce_bytes.into() + }; let ke1_message = KE1Message { - client_nonce: client_nonce.to_vec(), + client_nonce, client_e_pk: client_e_kp.public().clone(), }; @@ -60,8 +60,8 @@ impl KeyExchange for TripleDH { Ok(( KE1State { client_e_sk: client_e_kp.private().clone(), - client_nonce: client_nonce.to_vec(), - hashed_l1: hashed_l1.to_vec(), + client_nonce, + hashed_l1, }, ke1_message, )) @@ -76,8 +76,11 @@ impl KeyExchange for TripleDH { server_s_sk: KeyFormat::Repr, ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> { let server_e_kp = KeyFormat::generate_random(rng)?; - let mut server_nonce = [0u8; NONCE_LEN]; - rng.fill_bytes(&mut server_nonce); + let server_nonce: GenericArray = { + let mut server_nonce_bytes = [0u8; NONCE_LEN]; + rng.fill_bytes(&mut server_nonce_bytes); + server_nonce_bytes.into() + }; let (shared_secret, km2, km3) = derive_3dh_keys::( TripleDHComponents { @@ -115,14 +118,14 @@ impl KeyExchange for TripleDH { Ok(( KE2State { - km3: km3.to_vec(), - hashed_transcript: hashed_transcript.to_vec(), - shared_secret: shared_secret.to_vec(), + km3, + hashed_transcript, + shared_secret, }, KE2Message { - server_nonce: server_nonce.to_vec(), + server_nonce, server_e_pk: server_e_kp.public().clone(), - mac: mac.finalize().into_bytes().to_vec(), + mac: mac.finalize().into_bytes(), }, )) } @@ -165,7 +168,7 @@ impl KeyExchange for TripleDH { Hmac::::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?; server_mac.update(&hashed_transcript); - if ke2_message.mac != server_mac.finalize().into_bytes().to_vec() { + if ke2_message.mac != server_mac.finalize().into_bytes() { return Err(ProtocolError::VerificationError( PakeError::KeyExchangeMacValidationError, )); @@ -178,7 +181,7 @@ impl KeyExchange for TripleDH { Ok(( shared_secret.to_vec(), KE3Message { - mac: client_mac.finalize().into_bytes().to_vec(), + mac: client_mac.finalize().into_bytes(), }, )) } @@ -191,7 +194,7 @@ impl KeyExchange for TripleDH { Hmac::::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?; client_mac.update(&ke2_state.hashed_transcript); - if ke3_message.mac != client_mac.finalize().into_bytes().to_vec() { + if ke3_message.mac != client_mac.finalize().into_bytes() { return Err(ProtocolError::VerificationError( PakeError::KeyExchangeMacValidationError, )); @@ -211,34 +214,40 @@ impl KeyExchange for TripleDH { /// The client state produced after the first key exchange message #[derive(PartialEq, Eq)] -pub struct KE1State { +pub struct KE1State> { client_e_sk: Key, - client_nonce: Vec, - hashed_l1: Vec, + client_nonce: GenericArray, + hashed_l1: GenericArray, } /// The first key exchange message #[derive(PartialEq, Eq)] pub struct KE1Message { - pub(crate) client_nonce: Vec, + pub(crate) client_nonce: GenericArray, pub(crate) client_e_pk: Key, } -impl TryFrom> for KE1State { +impl> TryFrom> for KE1State { type Error = InternalPakeError; fn try_from(bytes: Vec) -> Result { - let checked_bytes = check_slice_size(&bytes, KE1_STATE_LEN, "ke1_state")?; + let checked_bytes = check_slice_size( + &bytes, + KEY_LEN + NONCE_LEN + HashLen::to_usize(), + "ke1_state", + )?; Ok(Self { client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?, - client_nonce: checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN].to_vec(), - hashed_l1: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(), + client_nonce: GenericArray::clone_from_slice( + &checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN], + ), + hashed_l1: GenericArray::clone_from_slice(&checked_bytes[KEY_LEN + NONCE_LEN..]), }) } } -impl ToBytes for KE1State { +impl> ToBytes for KE1State { fn to_bytes(&self) -> Vec { let output: Vec = [ &self.client_e_sk.to_arr(), @@ -250,8 +259,6 @@ impl ToBytes for KE1State { } } -sized_bytes_using_constant_and_try_from!(KE1State, U96); - impl ToBytes for KE1Message { fn to_bytes(&self) -> Vec { [&self.client_nonce[..], &self.client_e_pk.to_arr()].concat() @@ -266,29 +273,27 @@ impl TryFrom> for KE1Message { check_slice_size(&ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?; Ok(Self { - client_nonce: checked_bytes[..NONCE_LEN].to_vec(), + client_nonce: GenericArray::clone_from_slice(&checked_bytes[..NONCE_LEN]), client_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..])?, }) } } -sized_bytes_using_constant_and_try_from!(KE1Message, U64); - /// The server state produced after the second key exchange message -pub struct KE2State { - km3: Vec, - hashed_transcript: Vec, - shared_secret: Vec, +pub struct KE2State> { + km3: GenericArray, + hashed_transcript: GenericArray, + shared_secret: GenericArray, } /// The second key exchange message -pub struct KE2Message { - server_nonce: Vec, +pub struct KE2Message> { + server_nonce: GenericArray, server_e_pk: Key, - mac: Vec, + mac: GenericArray, } -impl ToBytes for KE2State { +impl> ToBytes for KE2State { fn to_bytes(&self) -> Vec { let output: Vec = [ &self.km3[..], @@ -300,21 +305,21 @@ impl ToBytes for KE2State { } } -impl TryFrom> for KE2State { +impl> TryFrom> for KE2State { type Error = ProtocolError; fn try_from(ke1_message_bytes: Vec) -> Result { let checked_bytes = check_slice_size(&ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?; Ok(Self { - km3: checked_bytes[..KEY_LEN].to_vec(), - hashed_transcript: checked_bytes[KEY_LEN..2 * KEY_LEN].to_vec(), - shared_secret: checked_bytes[2 * KEY_LEN..].to_vec(), + km3: GenericArray::clone_from_slice(&checked_bytes[..KEY_LEN]), + hashed_transcript: GenericArray::clone_from_slice(&checked_bytes[KEY_LEN..2 * KEY_LEN]), + shared_secret: GenericArray::clone_from_slice(&checked_bytes[2 * KEY_LEN..]), }) } } -impl ToBytes for KE2Message { +impl> ToBytes for KE2Message { fn to_bytes(&self) -> Vec { let output: Vec = [ &self.server_nonce[..], @@ -326,16 +331,16 @@ impl ToBytes for KE2Message { } } -impl TryFrom> for KE2Message { +impl> TryFrom> for KE2Message { type Error = ProtocolError; fn try_from(ke2_message_bytes: Vec) -> Result { let checked_bytes = check_slice_size(&ke2_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?; Ok(Self { - server_nonce: checked_bytes[..NONCE_LEN].to_vec(), + server_nonce: GenericArray::clone_from_slice(&checked_bytes[..NONCE_LEN]), server_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..NONCE_LEN + KEY_LEN])?, - mac: checked_bytes[NONCE_LEN + KEY_LEN..].to_vec(), + mac: GenericArray::clone_from_slice(&checked_bytes[NONCE_LEN + KEY_LEN..]), }) } } @@ -352,17 +357,17 @@ struct TripleDHComponents { // Consists of a shared secret, followed by two mac keys type TripleDHDerivationResult = ( - GenericArray::OutputSize>, - GenericArray::OutputSize>, - GenericArray::OutputSize>, + GenericArray::OutputSize>, + GenericArray::OutputSize>, + GenericArray::OutputSize>, ); // Internal function which takes the public and private components of the client and server keypairs, along // with some auxiliary metadata, to produce the shared secret and two MAC keys fn derive_3dh_keys, D: Hash>( dh: TripleDHComponents, - client_nonce: &[u8], - server_nonce: &[u8], + client_nonce: &GenericArray, + server_nonce: &GenericArray, client_s_pk: KeyFormat::Repr, server_s_pk: KeyFormat::Repr, ) -> Result, ProtocolError> { @@ -395,24 +400,24 @@ fn derive_3dh_keys, D: Hash>( } /// The third key exchange message -pub struct KE3Message { - mac: Vec, +pub struct KE3Message> { + mac: GenericArray, } -impl ToBytes for KE3Message { +impl> ToBytes for KE3Message { fn to_bytes(&self) -> Vec { - self.mac.clone() + self.mac.to_vec() } } -impl TryFrom> for KE3Message { +impl> TryFrom> for KE3Message { type Error = ProtocolError; fn try_from(bytes: Vec) -> Result { let checked_bytes = check_slice_size(&bytes, KEY_LEN, "ke3_message")?; Ok(Self { - mac: checked_bytes.to_vec(), + mac: GenericArray::clone_from_slice(&checked_bytes), }) } } diff --git a/src/keypair.rs b/src/keypair.rs index d68db58..a425808 100644 --- a/src/keypair.rs +++ b/src/keypair.rs @@ -91,35 +91,6 @@ trait KeyPairExt: KeyPair + Debug { #[cfg(test)] impl KeyPairExt for KP where KP: KeyPair + Debug {} -/// This assumes you have defined: -/// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T` -/// - an `fn to_bytes(&self) -> Vec` in an `impl T` block -/// and it both of the above to produce a sensible SizedBytes implementation -/// -/// Because SizedBytes has a strong notion of size, and TryFrom/to_bytes does -/// not, it's better to use the macro below rather than this one, where possible. -#[macro_export] -macro_rules! sized_bytes_using_constant_and_try_from { - ($sized_type: ident, $len: ident) => { - impl SizedBytes for $sized_type { - type Len = $len; - - fn to_arr(&self) -> generic_array::GenericArray { - generic_array::GenericArray::clone_from_slice(&self.to_bytes()) - } - - fn from_bytes(bytes: &[u8]) -> Result { - let checked_bytes = check_slice_size( - bytes, - ::to_usize(), - "bytes", - )?; - std::convert::TryFrom::try_from(checked_bytes.to_vec()) - } - } - }; -} - /// This assumes you have defined a SizedBytes instance for a `T`, and defines: /// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T` /// - an `fn to_bytes(&self) -> Vec` in an `impl T` block diff --git a/src/tests/opaque_ke_test.rs b/src/tests/opaque_ke_test.rs index dc984a8..a79d23b 100644 --- a/src/tests/opaque_ke_test.rs +++ b/src/tests/opaque_ke_test.rs @@ -558,10 +558,7 @@ fn test_complete_flow( hex::encode(login_export_key) ); } else { - let res = match client_login_result { - Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true, - _ => false, - }; + let res = matches!(client_login_result, Err(ProtocolError::VerificationError(PakeError::InvalidLoginError))); assert!(res); } From 3c2a2086065ca108c6f6ab3c2cbf17405cec4eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Sat, 19 Sep 2020 19:16:30 -0400 Subject: [PATCH 2/5] surface the potential breakages out of copy_from_slice --- src/elligator/mod.rs | 5 +++-- src/group.rs | 17 +++++++++++------ src/keypair.rs | 9 +++++---- src/opaque.rs | 19 ++++++------------- src/tests/opaque_ke_test.rs | 7 ++++++- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/elligator/mod.rs b/src/elligator/mod.rs index 2df11f7..164020d 100644 --- a/src/elligator/mod.rs +++ b/src/elligator/mod.rs @@ -58,6 +58,7 @@ pub fn hash_to_point(bytes: &[u8]) -> EdwardsPoint { #[cfg(test)] mod tests { use super::*; + use std::convert::TryInto; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Signal tests from // @@ -73,8 +74,8 @@ mod tests { #[test] fn elligator_correct() { let bytes: Vec = (0u8..32u8).collect(); - let mut bits_in = [0u8; 32]; - bits_in.copy_from_slice(&bytes); + let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken"); + let fe = FieldElement51::from_bytes(&bits_in); let eg = elligator_signal(&fe); assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT); diff --git a/src/group.rs b/src/group.rs index 551403c..732260f 100644 --- a/src/group.rs +++ b/src/group.rs @@ -19,6 +19,7 @@ use generic_array::{ ArrayLength, GenericArray, }; use rand_core::{CryptoRng, RngCore}; +use std::convert::TryInto; use std::ops::Mul; use zeroize::Zeroize; @@ -86,7 +87,8 @@ impl Group for RistrettoPoint { element_bits: &GenericArray, ) -> Result { CompressedRistretto::from_slice(element_bits) - .decompress().ok_or(InternalPakeError::PointError) + .decompress() + .ok_or(InternalPakeError::PointError) } // serialization of a group element fn to_arr(&self) -> GenericArray { @@ -96,8 +98,9 @@ impl Group for RistrettoPoint { type UniformBytesLen = U64; fn hash_to_curve(uniform_bytes: &GenericArray) -> Self { - let mut bits = [0u8; 64]; - bits.copy_from_slice(&uniform_bytes); + let bits: [u8; 64] = (&uniform_bytes[..]) + .try_into() + .expect("GenericArray has a type-level length"); RistrettoPoint::from_uniform_bytes(&bits) } @@ -130,7 +133,8 @@ impl Group for EdwardsPoint { element_bits: &GenericArray, ) -> Result { let point = CompressedEdwardsY::from_slice(element_bits) - .decompress().ok_or(InternalPakeError::PointError)?; + .decompress() + .ok_or(InternalPakeError::PointError)?; if point.is_small_order() { return Err(InternalPakeError::SubGroupError); @@ -190,8 +194,9 @@ mod tests { ]; fn deserialize_point(pt: &[u8]) -> Result { - let mut bytes = [0u8; 32]; - bytes.copy_from_slice(&pt[..32]); + let bytes: [u8; 32] = (&pt[..32]) + .try_into() + .expect("Slice pattern invariant broken"); curve25519_dalek::edwards::CompressedEdwardsY(bytes) .decompress() diff --git a/src/keypair.rs b/src/keypair.rs index a425808..0c6bd8b 100644 --- a/src/keypair.rs +++ b/src/keypair.rs @@ -16,6 +16,7 @@ use proptest::prelude::*; #[cfg(test)] use rand::{rngs::StdRng, SeedableRng}; use rand_core::{CryptoRng, RngCore}; +use std::convert::TryInto; use std::fmt::Debug; use x25519_dalek::{PublicKey, StaticSecret}; @@ -216,15 +217,15 @@ impl KeyPair for X25519KeyPair { } fn public_from_private(secret: &Self::Repr) -> Self::Repr { - let mut secret_data = [0u8; 32]; - secret_data.copy_from_slice(&secret.0[..]); + let secret_data: [u8; 32] = (&secret.0[..]) + .try_into() + .expect("Keypair::Repr invariant broken"); let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES; Key(::x25519_dalek::x25519(secret_data, base_data).to_vec()) } fn check_public_key(key: Self::Repr) -> Result { - let mut key_bytes = [0u8; 32]; - key_bytes.copy_from_slice(&key); + let key_bytes: [u8; 32] = (&key[..]).try_into().expect("Key invariant broken"); let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes) .to_edwards(1) .ok_or(InternalPakeError::PointError)?; diff --git a/src/opaque.rs b/src/opaque.rs index d98eacb..f65efb4 100644 --- a/src/opaque.rs +++ b/src/opaque.rs @@ -533,14 +533,12 @@ where /// byte representation for the server's registration state pub fn to_bytes(&self) -> Vec { let mut output: Vec = CS::Group::scalar_as_bytes(&self.oprf_key).to_vec(); - match &self.client_s_pk { - Some(v) => output.extend_from_slice(&v.to_arr()), - None => {} - }; - match &self.envelope { - Some(v) => output.extend_from_slice(&v.to_bytes()), - None => {} - }; + self.client_s_pk + .iter() + .for_each(|v| output.extend_from_slice(&v)); + self.envelope + .iter() + .for_each(|v| output.extend_from_slice(&v.to_bytes())); output } @@ -641,8 +639,6 @@ where /// The state elements the client holds to perform a login pub struct ClientLogin { - /// A choice of the keypair type - _key_format: PhantomData, /// A blinding factor, which is used to mask (and unmask) secret /// information before transmission blinding_factor: ::Scalar, @@ -675,7 +671,6 @@ impl TryFrom<&[u8]> for ClientLogin { )?; let password = bytes[scalar_len + ke1_state_size..].to_vec(); Ok(Self { - _key_format: PhantomData, blinding_factor, password, ke1_state, @@ -745,7 +740,6 @@ impl ClientLogin { Ok(( l1, Self { - _key_format: PhantomData, blinding_factor, password: password.to_vec(), ke1_state, @@ -986,7 +980,6 @@ impl ServerLogin { } // Helper functions - fn get_password_derived_key, D: Hash>( password: Vec, beta: G, diff --git a/src/tests/opaque_ke_test.rs b/src/tests/opaque_ke_test.rs index a79d23b..c13988d 100644 --- a/src/tests/opaque_ke_test.rs +++ b/src/tests/opaque_ke_test.rs @@ -558,7 +558,12 @@ fn test_complete_flow( hex::encode(login_export_key) ); } else { - let res = matches!(client_login_result, Err(ProtocolError::VerificationError(PakeError::InvalidLoginError))); + let res = matches!( + client_login_result, + Err(ProtocolError::VerificationError( + PakeError::InvalidLoginError + )) + ); assert!(res); } From 887b4577fa65f1dd0f61c5a0e949d9e809474f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Sat, 19 Sep 2020 19:16:33 -0400 Subject: [PATCH 3/5] fix misuse of constants in KE2Message --- src/key_exchange/tripledh.rs | 11 +++++++---- src/map_to_curve.rs | 5 ++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs index af9e61a..3ddbf5d 100644 --- a/src/key_exchange/tripledh.rs +++ b/src/key_exchange/tripledh.rs @@ -11,7 +11,10 @@ use crate::{ keypair::{Key, KeyPair, SizedBytes}, }; use digest::{Digest, FixedOutput}; -use generic_array::{typenum::U32, ArrayLength, GenericArray}; +use generic_array::{ + typenum::{Unsigned, U32}, + ArrayLength, GenericArray, +}; use hkdf::Hkdf; use hmac::{Hmac, Mac, NewMac}; use rand_core::{CryptoRng, RngCore}; @@ -22,7 +25,6 @@ const KEY_LEN: usize = 32; pub(crate) const NONCE_LEN: usize = 32; pub(crate) type NonceLen = U32; const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN; -const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN; static STR_3DH: &[u8] = b"3DH keys"; @@ -208,7 +210,7 @@ impl KeyExchange for TripleDH { } fn ke2_message_size() -> usize { - KE2_MESSAGE_LEN + NONCE_LEN + KEY_LEN + <::OutputSize as Unsigned>::to_usize() } } @@ -335,7 +337,8 @@ impl> TryFrom> for KE2Message { type Error = ProtocolError; fn try_from(ke2_message_bytes: Vec) -> Result { - let checked_bytes = check_slice_size(&ke2_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?; + let ke2_message_len = NONCE_LEN + KEY_LEN + HashLen::to_usize(); + let checked_bytes = check_slice_size(&ke2_message_bytes, ke2_message_len, "ke2_message")?; Ok(Self { server_nonce: GenericArray::clone_from_slice(&checked_bytes[..NONCE_LEN]), diff --git a/src/map_to_curve.rs b/src/map_to_curve.rs index 3d8819c..a7667db 100644 --- a/src/map_to_curve.rs +++ b/src/map_to_curve.rs @@ -9,7 +9,6 @@ use crate::group::Group; use curve25519_dalek::{edwards::EdwardsPoint, ristretto::RistrettoPoint}; -use generic_array::GenericArray; use hkdf::Hkdf; use sha2::{Sha256, Sha512}; @@ -22,13 +21,13 @@ pub trait GroupWithMapToCurve: Group { impl GroupWithMapToCurve for RistrettoPoint { fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self { let (hashed_input, _) = Hkdf::::extract(pepper, password); - ::hash_to_curve(GenericArray::from_slice(&hashed_input)) + ::hash_to_curve(&hashed_input) } } impl GroupWithMapToCurve for EdwardsPoint { fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self { let (hashed_input, _) = Hkdf::::extract(pepper, password); - ::hash_to_curve(GenericArray::from_slice(&hashed_input)) + ::hash_to_curve(&hashed_input) } } From 786bc51fdd11854a56bf074d6eeb939e0f0d23da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Sat, 19 Sep 2020 19:16:35 -0400 Subject: [PATCH 4/5] Normalize generic parameters for LoginSecondMessage (aka use the CipherSuite just like the others) Repair the generics in KE trait: it makes sense that generate_ke[1-3] should operate on the same key representation. --- src/ciphersuite.rs | 2 +- src/key_exchange/traits.rs | 8 +-- src/key_exchange/tripledh.rs | 8 +-- src/opaque.rs | 123 +++++++++++++++-------------------- src/tests/opaque_ke_test.rs | 22 +++---- src/tests/serialization.rs | 8 ++- 6 files changed, 74 insertions(+), 97 deletions(-) diff --git a/src/ciphersuite.rs b/src/ciphersuite.rs index ad7b1ac..63f20dc 100644 --- a/src/ciphersuite.rs +++ b/src/ciphersuite.rs @@ -34,7 +34,7 @@ pub trait CipherSuite { /// A keypair type composed of public and private components type KeyFormat: KeyPair + PartialEq; /// A key exchange protocol - type KeyExchange: KeyExchange; + type KeyExchange: KeyExchange; /// The main hash function use (for HKDF computations and hashing transcripts) type Hash: Hash; /// A slow hashing function, typically used for password hashing diff --git a/src/key_exchange/traits.rs b/src/key_exchange/traits.rs index 543df0f..1786bec 100644 --- a/src/key_exchange/traits.rs +++ b/src/key_exchange/traits.rs @@ -12,19 +12,19 @@ use rand_core::{CryptoRng, RngCore}; use std::convert::TryFrom; -pub trait KeyExchange { +pub trait KeyExchange> { type KE1State: TryFrom, Error = InternalPakeError> + ToBytes; type KE2State: TryFrom, Error = ProtocolError> + ToBytes; type KE1Message: TryFrom, Error = InternalPakeError> + ToBytes; type KE2Message: TryFrom, Error = ProtocolError> + ToBytes; type KE3Message: TryFrom, Error = ProtocolError> + ToBytes; - fn generate_ke1>( + fn generate_ke1( l1_component: Vec, rng: &mut R, ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>; - fn generate_ke2>( + fn generate_ke2( rng: &mut R, l1_bytes: Vec, l2_bytes: Vec, @@ -33,7 +33,7 @@ pub trait KeyExchange { server_s_sk: KeyFormat::Repr, ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError>; - fn generate_ke3>( + fn generate_ke3( l2_component: Vec, ke2_message: Self::KE2Message, ke1_state: &Self::KE1State, diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs index 3ddbf5d..ad4ec97 100644 --- a/src/key_exchange/tripledh.rs +++ b/src/key_exchange/tripledh.rs @@ -31,14 +31,14 @@ static STR_3DH: &[u8] = b"3DH keys"; /// The Triple Diffie-Hellman key exchange implementation pub struct TripleDH; -impl KeyExchange for TripleDH { +impl> KeyExchange for TripleDH { type KE1State = KE1State<::OutputSize>; type KE2State = KE2State<::OutputSize>; type KE1Message = KE1Message; type KE2Message = KE2Message<::OutputSize>; type KE3Message = KE3Message<::OutputSize>; - fn generate_ke1>( + fn generate_ke1( l1_component: Vec, rng: &mut R, ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> { @@ -69,7 +69,7 @@ impl KeyExchange for TripleDH { )) } - fn generate_ke2>( + fn generate_ke2( rng: &mut R, l1_bytes: Vec, l2_bytes: Vec, @@ -132,7 +132,7 @@ impl KeyExchange for TripleDH { )) } - fn generate_ke3>( + fn generate_ke3( l2_component: Vec, ke2_message: Self::KE2Message, ke1_state: &Self::KE1State, diff --git a/src/opaque.rs b/src/opaque.rs index f65efb4..39b2922 100644 --- a/src/opaque.rs +++ b/src/opaque.rs @@ -141,7 +141,7 @@ where pub struct LoginFirstMessage { /// blinded password information alpha: CS::Group, - ke1_message: >::KE1Message, + ke1_message: >::KE1Message, } impl TryFrom<&[u8]> for LoginFirstMessage { @@ -163,9 +163,10 @@ impl TryFrom<&[u8]> for LoginFirstMessage { let arr = GenericArray::from_slice(&checked_slice[..elem_len]); let alpha = CS::Group::from_element_slice(arr)?; - let ke1_message = >::KE1Message::try_from( - checked_slice[elem_len..].to_vec(), - )?; + let ke1_message = + >::KE1Message::try_from( + checked_slice[elem_len..].to_vec(), + )?; Ok(Self { alpha, ke1_message }) } } @@ -179,28 +180,15 @@ impl LoginFirstMessage { /// The answer sent by the server to the user, upon reception of the /// login attempt. -pub struct LoginSecondMessage -where - KeyFormat: KeyPair, - KE: KeyExchange, - D: Hash, -{ - _key_format: PhantomData, - _key_exchange: PhantomData, +pub struct LoginSecondMessage { /// the server's oprf output - beta: Grp, + beta: CS::Group, /// the user's sealed information, - envelope: Envelope, - ke2_message: KE::KE2Message, + envelope: Envelope, + ke2_message: >::KE2Message, } -impl LoginSecondMessage -where - Grp: Group, - KeyFormat: KeyPair, - KE: KeyExchange, - D: Hash, -{ +impl LoginSecondMessage { /// byte representation for the login response pub fn to_bytes(&self) -> Vec { [ @@ -212,19 +200,13 @@ where } } -impl TryFrom<&[u8]> for LoginSecondMessage -where - Grp: Group, - KeyFormat: KeyPair, - KE: KeyExchange, - D: Hash, -{ +impl TryFrom<&[u8]> for LoginSecondMessage { type Error = ProtocolError; fn try_from(second_message_bytes: &[u8]) -> Result { - let key_len = ::Len::to_usize(); - let envelope_size = key_len + Envelope::::additional_size(); - let elem_len = Grp::ElemLen::to_usize(); - let ke2_message_size = KE::ke2_message_size(); + let key_len = <::Repr as SizedBytes>::Len::to_usize(); + let envelope_size = key_len + Envelope::::additional_size(); + let elem_len = ::ElemLen::to_usize(); + let ke2_message_size = CS::KeyExchange::ke2_message_size(); let checked_slice = check_slice_size( second_message_bytes, elem_len + envelope_size + ke2_message_size, @@ -235,17 +217,17 @@ where // correct subgroup let beta_bytes = &checked_slice[..elem_len]; let arr = GenericArray::from_slice(beta_bytes); - let beta = Grp::from_element_slice(arr)?; + let beta = CS::Group::from_element_slice(arr)?; let envelope = - Envelope::::from_bytes(&checked_slice[elem_len..elem_len + envelope_size])?; + Envelope::::from_bytes(&checked_slice[elem_len..elem_len + envelope_size])?; let ke2_message = - KE::KE2Message::try_from(checked_slice[elem_len + envelope_size..].to_vec())?; + >::KE2Message::try_from( + checked_slice[elem_len + envelope_size..].to_vec(), + )?; Ok(Self { - _key_format: PhantomData, - _key_exchange: PhantomData, beta, envelope, ke2_message, @@ -256,7 +238,7 @@ where /// The answer sent by the client to the server, upon reception of the /// sealed envelope pub struct LoginThirdMessage { - ke3_message: >::KE3Message, + ke3_message: >::KE3Message, } impl TryFrom<&[u8]> for LoginThirdMessage { @@ -264,7 +246,9 @@ impl TryFrom<&[u8]> for LoginThirdMessage { fn try_from(bytes: &[u8]) -> Result { let ke3_message = - >::KE3Message::try_from(bytes.to_vec())?; + >::KE3Message::try_from( + bytes.to_vec(), + )?; Ok(Self { ke3_message }) } } @@ -644,14 +628,15 @@ pub struct ClientLogin { blinding_factor: ::Scalar, /// The user's password password: Vec, - ke1_state: >::KE1State, + ke1_state: >::KE1State, } impl TryFrom<&[u8]> for ClientLogin { type Error = ProtocolError; fn try_from(bytes: &[u8]) -> Result { let scalar_len = ::ScalarLen::to_usize(); - let ke1_state_size = >::ke1_state_size(); + let ke1_state_size = + >::ke1_state_size(); let min_expected_len = scalar_len + ke1_state_size; let checked_slice = (if bytes.len() <= min_expected_len { @@ -666,9 +651,10 @@ impl TryFrom<&[u8]> for ClientLogin { let blinding_factor_bytes = GenericArray::from_slice(&checked_slice[..scalar_len]); let blinding_factor = CS::Group::from_scalar_slice(blinding_factor_bytes)?; - let ke1_state = >::KE1State::try_from( - checked_slice[scalar_len..scalar_len + ke1_state_size].to_vec(), - )?; + let ke1_state = + >::KE1State::try_from( + checked_slice[scalar_len..scalar_len + ke1_state_size].to_vec(), + )?; let password = bytes[scalar_len + ke1_state_size..].to_vec(); Ok(Self { blinding_factor, @@ -732,8 +718,7 @@ impl ClientLogin { blinding_factor, } = oprf::generate_oprf1::(&password, pepper, rng)?; - let (ke1_state, ke1_message) = - CS::KeyExchange::generate_ke1::<_, CS::KeyFormat>(alpha.to_arr().to_vec(), rng)?; + let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(alpha.to_arr().to_vec(), rng)?; let l1 = LoginFirstMessage { alpha, ke1_message }; @@ -784,7 +769,7 @@ impl ClientLogin { /// ``` pub fn finish( self, - l2: LoginSecondMessage, + l2: LoginSecondMessage, server_s_pk: &<::KeyFormat as KeyPair>::Repr, _client_e_sk_rng: &mut R, ) -> Result, ProtocolError> { @@ -804,7 +789,7 @@ impl ClientLogin { err => PakeError::from(err), })?; - let (shared_secret, ke3_message) = CS::KeyExchange::generate_ke3::( + let (shared_secret, ke3_message) = CS::KeyExchange::generate_ke3( l2_bytes, l2.ke2_message, &self.ke1_state, @@ -822,7 +807,7 @@ impl ClientLogin { /// The state elements the server holds to record a login pub struct ServerLogin { - ke2_state: >::KE2State, + ke2_state: >::KE2State, _cs: PhantomData, } @@ -831,22 +816,15 @@ impl TryFrom<&[u8]> for ServerLogin { fn try_from(bytes: &[u8]) -> Result { Ok(Self { _cs: PhantomData, - ke2_state: >::KE2State::try_from( - bytes.to_vec(), - )?, + ke2_state: + >::KE2State::try_from( + bytes.to_vec(), + )?, }) } } -type ServerLoginStartResult = ( - LoginSecondMessage< - ::Group, - ::KeyFormat, - ::KeyExchange, - ::Hash, - >, - ServerLogin, -); +type ServerLoginStartResult = (LoginSecondMessage, ServerLogin); impl ServerLogin { /// byte representation for the server's login state @@ -905,7 +883,7 @@ impl ServerLogin { let l2_component: Vec = [&beta.to_arr()[..], &envelope.to_bytes()].concat(); - let (ke2_state, ke2_message) = CS::KeyExchange::generate_ke2::<_, CS::KeyFormat>( + let (ke2_state, ke2_message) = CS::KeyExchange::generate_ke2( rng, l1_bytes.to_vec(), l2_component, @@ -915,8 +893,6 @@ impl ServerLogin { )?; let l2 = LoginSecondMessage { - _key_format: PhantomData, - _key_exchange: PhantomData, beta, envelope, ke2_message, @@ -969,13 +945,16 @@ impl ServerLogin { /// # Ok::<(), ProtocolError>(()) /// ``` pub fn finish(&self, message: LoginThirdMessage) -> Result, ProtocolError> { - >::finish_ke(message.ke3_message, &self.ke2_state) - .map_err(|e| match e { - ProtocolError::VerificationError(PakeError::KeyExchangeMacValidationError) => { - ProtocolError::VerificationError(PakeError::InvalidLoginError) - } - err => err, - }) + >::finish_ke( + message.ke3_message, + &self.ke2_state, + ) + .map_err(|e| match e { + ProtocolError::VerificationError(PakeError::KeyExchangeMacValidationError) => { + ProtocolError::VerificationError(PakeError::InvalidLoginError) + } + err => err, + }) } } diff --git a/src/tests/opaque_ke_test.rs b/src/tests/opaque_ke_test.rs index c13988d..15f66ba 100644 --- a/src/tests/opaque_ke_test.rs +++ b/src/tests/opaque_ke_test.rs @@ -471,19 +471,15 @@ fn test_l3() -> Result<(), PakeError> { let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); let mut client_e_sk_rng = CycleRng::new(parameters.client_e_sk.to_vec()); - let (l3, shared_secret, export_key_login) = ClientLogin::::try_from( - ¶meters.client_login_state[..], - ) - .unwrap() - .finish( - LoginSecondMessage::::try_from( - ¶meters.l2[..], - ) - .unwrap(), - &Key::try_from(¶meters.server_s_pk[..])?, - &mut client_e_sk_rng, - ) - .unwrap(); + let (l3, shared_secret, export_key_login) = + ClientLogin::::try_from(¶meters.client_login_state[..]) + .unwrap() + .finish( + LoginSecondMessage::::try_from(¶meters.l2[..]).unwrap(), + &Key::try_from(¶meters.server_s_pk[..])?, + &mut client_e_sk_rng, + ) + .unwrap(); assert_eq!( hex::encode(¶meters.shared_secret), diff --git a/src/tests/serialization.rs b/src/tests/serialization.rs index 47ead1e..9988901 100644 --- a/src/tests/serialization.rs +++ b/src/tests/serialization.rs @@ -167,8 +167,10 @@ fn login_first_message_roundtrip() { rng.fill_bytes(&mut client_nonce); let ke1m: Vec = [&client_nonce[..], &client_e_kp.public()].concat(); - let reg = - >::KE1Message::try_from(ke1m[..].to_vec()).unwrap(); + let reg = >::KE1Message::try_from( + ke1m[..].to_vec(), + ) + .unwrap(); let reg_bytes = reg.to_bytes(); assert_eq!(reg_bytes, ke1m); } @@ -197,7 +199,7 @@ proptest! { #[test] fn test_nocrash_login_second_message(bytes in vec(any::(), 0..500)) { - LoginSecondMessage::::try_from(&bytes[..]).map_or(true, |_| true); + LoginSecondMessage::::try_from(&bytes[..]).map_or(true, |_| true); } #[test] From 56d38dba2cc78a234bfbbd054d5ce9434f27e809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Sun, 20 Sep 2020 09:58:54 -0400 Subject: [PATCH 5/5] Reverse use of `Tryinto<[u8; 64]>` since it's not yet on stable --- src/group.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/group.rs b/src/group.rs index 732260f..7a4ed55 100644 --- a/src/group.rs +++ b/src/group.rs @@ -19,7 +19,6 @@ use generic_array::{ ArrayLength, GenericArray, }; use rand_core::{CryptoRng, RngCore}; -use std::convert::TryInto; use std::ops::Mul; use zeroize::Zeroize; @@ -98,10 +97,12 @@ impl Group for RistrettoPoint { type UniformBytesLen = U64; fn hash_to_curve(uniform_bytes: &GenericArray) -> Self { - let bits: [u8; 64] = (&uniform_bytes[..]) - .try_into() - .expect("GenericArray has a type-level length"); - + // https://caniuse.rs/features/array_gt_32_impls + let bits: [u8; 64] = { + let mut bytes = [0u8; 64]; + bytes.copy_from_slice(uniform_bytes); + bytes + }; RistrettoPoint::from_uniform_bytes(&bits) } } @@ -157,6 +158,7 @@ impl Group for EdwardsPoint { mod tests { use super::*; use anyhow::{anyhow, Result}; + use std::convert::TryInto; const EIGHT_TORSION: [[u8; 32]; 8] = [ [