Make tripleDH manipulate GenericArray<u8, N> over Vec<u8>

upgrade some of the structure contents of the KE types
This commit is contained in:
François Garillot
2020-09-19 19:16:28 -04:00
parent 84cc0e9b72
commit 3c555e82ae
5 changed files with 76 additions and 114 deletions
+2 -4
View File
@@ -86,8 +86,7 @@ impl Group for RistrettoPoint {
element_bits: &GenericArray<u8, Self::ElemLen>, element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError> { ) -> Result<Self, InternalPakeError> {
CompressedRistretto::from_slice(element_bits) CompressedRistretto::from_slice(element_bits)
.decompress() .decompress().ok_or(InternalPakeError::PointError)
.ok_or_else(|| InternalPakeError::PointError)
} }
// serialization of a group element // serialization of a group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> { fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
@@ -131,8 +130,7 @@ impl Group for EdwardsPoint {
element_bits: &GenericArray<u8, Self::ElemLen>, element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError> { ) -> Result<Self, InternalPakeError> {
let point = CompressedEdwardsY::from_slice(element_bits) let point = CompressedEdwardsY::from_slice(element_bits)
.decompress() .decompress().ok_or(InternalPakeError::PointError)?;
.ok_or_else(|| InternalPakeError::PointError)?;
if point.is_small_order() { if point.is_small_order() {
return Err(InternalPakeError::SubGroupError); return Err(InternalPakeError::SubGroupError);
+2 -11
View File
@@ -4,19 +4,10 @@
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
use digest::{BlockInput, FixedOutput, Reset, Update}; use digest::{BlockInput, FixedOutput, Reset, Update};
use generic_array::ArrayLength;
/// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC /// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC
// Associated types could be simplified when they are made as defaults: // Associated types could be simplified when they are made as defaults:
// https://github.com/rust-lang/rust/issues/29661 // https://github.com/rust-lang/rust/issues/29661
pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone { pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {}
/// The block size for the hash function
type BlockSize: ArrayLength<u8>;
/// The output size of the hash function
type OutputSize: ArrayLength<u8>;
}
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T { impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
type BlockSize = T::BlockSize;
type OutputSize = T::OutputSize;
}
+71 -66
View File
@@ -9,13 +9,9 @@ use crate::{
hash::Hash, hash::Hash,
key_exchange::traits::{KeyExchange, ToBytes}, key_exchange::traits::{KeyExchange, ToBytes},
keypair::{Key, KeyPair, SizedBytes}, 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 hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac}; use hmac::{Hmac, Mac, NewMac};
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
@@ -24,6 +20,7 @@ use std::convert::TryFrom;
const KEY_LEN: usize = 32; const KEY_LEN: usize = 32;
pub(crate) const NONCE_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 KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_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; pub struct TripleDH;
impl<D: Hash> KeyExchange<D> for TripleDH { impl<D: Hash> KeyExchange<D> for TripleDH {
type KE1State = KE1State; type KE1State = KE1State<<D as FixedOutput>::OutputSize>;
type KE2State = KE2State; type KE2State = KE2State<<D as FixedOutput>::OutputSize>;
type KE1Message = KE1Message; type KE1Message = KE1Message;
type KE2Message = KE2Message; type KE2Message = KE2Message<<D as FixedOutput>::OutputSize>;
type KE3Message = KE3Message; type KE3Message = KE3Message<<D as FixedOutput>::OutputSize>;
fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>( fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
l1_component: Vec<u8>, l1_component: Vec<u8>,
rng: &mut R, rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> { ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
let client_e_kp = KeyFormat::generate_random(rng)?; let client_e_kp = KeyFormat::generate_random(rng)?;
let mut client_nonce = [0u8; NONCE_LEN]; let client_nonce: GenericArray<u8, NonceLen> = {
rng.fill_bytes(&mut client_nonce); let mut client_nonce_bytes = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce_bytes);
client_nonce_bytes.into()
};
let ke1_message = KE1Message { let ke1_message = KE1Message {
client_nonce: client_nonce.to_vec(), client_nonce,
client_e_pk: client_e_kp.public().clone(), client_e_pk: client_e_kp.public().clone(),
}; };
@@ -60,8 +60,8 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
Ok(( Ok((
KE1State { KE1State {
client_e_sk: client_e_kp.private().clone(), client_e_sk: client_e_kp.private().clone(),
client_nonce: client_nonce.to_vec(), client_nonce,
hashed_l1: hashed_l1.to_vec(), hashed_l1,
}, },
ke1_message, ke1_message,
)) ))
@@ -76,8 +76,11 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
server_s_sk: KeyFormat::Repr, server_s_sk: KeyFormat::Repr,
) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> { ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> {
let server_e_kp = KeyFormat::generate_random(rng)?; let server_e_kp = KeyFormat::generate_random(rng)?;
let mut server_nonce = [0u8; NONCE_LEN]; let server_nonce: GenericArray<u8, NonceLen> = {
rng.fill_bytes(&mut server_nonce); 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::<KeyFormat, D>( let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
TripleDHComponents { TripleDHComponents {
@@ -115,14 +118,14 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
Ok(( Ok((
KE2State { KE2State {
km3: km3.to_vec(), km3,
hashed_transcript: hashed_transcript.to_vec(), hashed_transcript,
shared_secret: shared_secret.to_vec(), shared_secret,
}, },
KE2Message { KE2Message {
server_nonce: server_nonce.to_vec(), server_nonce,
server_e_pk: server_e_kp.public().clone(), 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<D: Hash> KeyExchange<D> for TripleDH {
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?; Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.update(&hashed_transcript); 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( return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError, PakeError::KeyExchangeMacValidationError,
)); ));
@@ -178,7 +181,7 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
Ok(( Ok((
shared_secret.to_vec(), shared_secret.to_vec(),
KE3Message { KE3Message {
mac: client_mac.finalize().into_bytes().to_vec(), mac: client_mac.finalize().into_bytes(),
}, },
)) ))
} }
@@ -191,7 +194,7 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
Hmac::<D>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?; Hmac::<D>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&ke2_state.hashed_transcript); 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( return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError, PakeError::KeyExchangeMacValidationError,
)); ));
@@ -211,34 +214,40 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
/// The client state produced after the first key exchange message /// The client state produced after the first key exchange message
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
pub struct KE1State { pub struct KE1State<HashLen: ArrayLength<u8>> {
client_e_sk: Key, client_e_sk: Key,
client_nonce: Vec<u8>, client_nonce: GenericArray<u8, NonceLen>,
hashed_l1: Vec<u8>, hashed_l1: GenericArray<u8, HashLen>,
} }
/// The first key exchange message /// The first key exchange message
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
pub struct KE1Message { pub struct KE1Message {
pub(crate) client_nonce: Vec<u8>, pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) client_e_pk: Key, pub(crate) client_e_pk: Key,
} }
impl TryFrom<Vec<u8>> for KE1State { impl<HashLen: ArrayLength<u8>> TryFrom<Vec<u8>> for KE1State<HashLen> {
type Error = InternalPakeError; type Error = InternalPakeError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
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 { Ok(Self {
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?, client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN].to_vec(), client_nonce: GenericArray::clone_from_slice(
hashed_l1: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(), &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<HashLen: ArrayLength<u8>> ToBytes for KE1State<HashLen> {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [ let output: Vec<u8> = [
&self.client_e_sk.to_arr(), &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 { impl ToBytes for KE1Message {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat() [&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
@@ -266,29 +273,27 @@ impl TryFrom<Vec<u8>> for KE1Message {
check_slice_size(&ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?; check_slice_size(&ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?;
Ok(Self { 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..])?, 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 /// The server state produced after the second key exchange message
pub struct KE2State { pub struct KE2State<HashLen: ArrayLength<u8>> {
km3: Vec<u8>, km3: GenericArray<u8, HashLen>,
hashed_transcript: Vec<u8>, hashed_transcript: GenericArray<u8, HashLen>,
shared_secret: Vec<u8>, shared_secret: GenericArray<u8, HashLen>,
} }
/// The second key exchange message /// The second key exchange message
pub struct KE2Message { pub struct KE2Message<HashLen: ArrayLength<u8>> {
server_nonce: Vec<u8>, server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: Key, server_e_pk: Key,
mac: Vec<u8>, mac: GenericArray<u8, HashLen>,
} }
impl ToBytes for KE2State { impl<HashLen: ArrayLength<u8>> ToBytes for KE2State<HashLen> {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [ let output: Vec<u8> = [
&self.km3[..], &self.km3[..],
@@ -300,21 +305,21 @@ impl ToBytes for KE2State {
} }
} }
impl TryFrom<Vec<u8>> for KE2State { impl<HashLen: ArrayLength<u8>> TryFrom<Vec<u8>> for KE2State<HashLen> {
type Error = ProtocolError; type Error = ProtocolError;
fn try_from(ke1_message_bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(ke1_message_bytes: Vec<u8>) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(&ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?; let checked_bytes = check_slice_size(&ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?;
Ok(Self { Ok(Self {
km3: checked_bytes[..KEY_LEN].to_vec(), km3: GenericArray::clone_from_slice(&checked_bytes[..KEY_LEN]),
hashed_transcript: checked_bytes[KEY_LEN..2 * KEY_LEN].to_vec(), hashed_transcript: GenericArray::clone_from_slice(&checked_bytes[KEY_LEN..2 * KEY_LEN]),
shared_secret: checked_bytes[2 * KEY_LEN..].to_vec(), shared_secret: GenericArray::clone_from_slice(&checked_bytes[2 * KEY_LEN..]),
}) })
} }
} }
impl ToBytes for KE2Message { impl<HashLen: ArrayLength<u8>> ToBytes for KE2Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [ let output: Vec<u8> = [
&self.server_nonce[..], &self.server_nonce[..],
@@ -326,16 +331,16 @@ impl ToBytes for KE2Message {
} }
} }
impl TryFrom<Vec<u8>> for KE2Message { impl<HashLen: ArrayLength<u8>> TryFrom<Vec<u8>> for KE2Message<HashLen> {
type Error = ProtocolError; type Error = ProtocolError;
fn try_from(ke2_message_bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(ke2_message_bytes: Vec<u8>) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(&ke2_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?; let checked_bytes = check_slice_size(&ke2_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?;
Ok(Self { 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])?, 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 // Consists of a shared secret, followed by two mac keys
type TripleDHDerivationResult<D> = ( type TripleDHDerivationResult<D> = (
GenericArray<u8, <D as Hash>::OutputSize>, GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as Hash>::OutputSize>, GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as Hash>::OutputSize>, GenericArray<u8, <D as FixedOutput>::OutputSize>,
); );
// Internal function which takes the public and private components of the client and server keypairs, along // 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 // with some auxiliary metadata, to produce the shared secret and two MAC keys
fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>, D: Hash>( fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>, D: Hash>(
dh: TripleDHComponents, dh: TripleDHComponents,
client_nonce: &[u8], client_nonce: &GenericArray<u8, NonceLen>,
server_nonce: &[u8], server_nonce: &GenericArray<u8, NonceLen>,
client_s_pk: KeyFormat::Repr, client_s_pk: KeyFormat::Repr,
server_s_pk: KeyFormat::Repr, server_s_pk: KeyFormat::Repr,
) -> Result<TripleDHDerivationResult<D>, ProtocolError> { ) -> Result<TripleDHDerivationResult<D>, ProtocolError> {
@@ -395,24 +400,24 @@ fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>, D: Hash>(
} }
/// The third key exchange message /// The third key exchange message
pub struct KE3Message { pub struct KE3Message<HashLen: ArrayLength<u8>> {
mac: Vec<u8>, mac: GenericArray<u8, HashLen>,
} }
impl ToBytes for KE3Message { impl<HashLen: ArrayLength<u8>> ToBytes for KE3Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
self.mac.clone() self.mac.to_vec()
} }
} }
impl TryFrom<Vec<u8>> for KE3Message { impl<HashLen: ArrayLength<u8>> TryFrom<Vec<u8>> for KE3Message<HashLen> {
type Error = ProtocolError; type Error = ProtocolError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(&bytes, KEY_LEN, "ke3_message")?; let checked_bytes = check_slice_size(&bytes, KEY_LEN, "ke3_message")?;
Ok(Self { Ok(Self {
mac: checked_bytes.to_vec(), mac: GenericArray::clone_from_slice(&checked_bytes),
}) })
} }
} }
-29
View File
@@ -91,35 +91,6 @@ trait KeyPairExt: KeyPair + Debug {
#[cfg(test)] #[cfg(test)]
impl<KP> KeyPairExt for KP where KP: KeyPair + Debug {} impl<KP> 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<u8>` 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<u8, Self::Len> {
generic_array::GenericArray::clone_from_slice(&self.to_bytes())
}
fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
let checked_bytes = check_slice_size(
bytes,
<Self::Len as generic_array::typenum::Unsigned>::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: /// 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 `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T`
/// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block /// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block
+1 -4
View File
@@ -558,10 +558,7 @@ fn test_complete_flow(
hex::encode(login_export_key) hex::encode(login_export_key)
); );
} else { } else {
let res = match client_login_result { let res = matches!(client_login_result, Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)));
Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true,
_ => false,
};
assert!(res); assert!(res);
} }