From c9d467e368cf4495e9cd2e06fe0476baf9920290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Fri, 19 Jun 2020 11:42:16 -0400 Subject: [PATCH] Add (normal) macros showing how to generate SizedBytes to/from TryFrom + to_bytes This is useful for getting serialization of the KEXState, KEXMessage formats without too much boilerplate Add client_login, login_first_message roundtrip serialization tests --- src/key_exchange.rs | 16 ++++++++-- src/keypair.rs | 64 ++++++++++++++++++++++++++++++++----- src/tests/opaque_ke_test.rs | 6 ++-- src/tests/serialization.rs | 46 +++++++++++++++++++++++++- 4 files changed, 117 insertions(+), 15 deletions(-) diff --git a/src/key_exchange.rs b/src/key_exchange.rs index c0edb0b..39ca876 100644 --- a/src/key_exchange.rs +++ b/src/key_exchange.rs @@ -6,8 +6,12 @@ use crate::{ errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError}, keypair::{Key, KeyPair, SizedBytes}, + sized_bytes_using_constant_and_try_from, +}; +use generic_array::{ + typenum::{U64, U96}, + GenericArray, }; -use generic_array::GenericArray; use hkdf::Hkdf; use hmac::{Hmac, Mac, NewMac}; use rand_core::{CryptoRng, RngCore}; @@ -28,19 +32,21 @@ pub(crate) const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN; static STR_3DH: &[u8] = b"3DH keys"; +#[derive(PartialEq, Eq)] pub(crate) struct KE1State { client_e_sk: Key, client_nonce: Vec, hashed_l1: Vec, } +#[derive(PartialEq, Eq)] pub(crate) struct KE1Message { pub(crate) client_nonce: Vec, pub(crate) client_e_pk: Key, } impl TryFrom<&[u8]> for KE1State { - type Error = ProtocolError; + type Error = InternalPakeError; fn try_from(bytes: &[u8]) -> Result { let checked_bytes = check_slice_size(bytes, KE1_STATE_LEN, "ke1_state")?; @@ -65,6 +71,8 @@ impl KE1State { } } +sized_bytes_using_constant_and_try_from!(KE1State, U96); + impl KE1Message { pub fn to_bytes(&self) -> Vec { [&self.client_nonce[..], &self.client_e_pk.to_arr()].concat() @@ -72,7 +80,7 @@ impl KE1Message { } impl TryFrom<&[u8]> for KE1Message { - type Error = ProtocolError; + type Error = InternalPakeError; fn try_from(ke1_message_bytes: &[u8]) -> Result { let checked_bytes = @@ -85,6 +93,8 @@ impl TryFrom<&[u8]> for KE1Message { } } +sized_bytes_using_constant_and_try_from!(KE1Message, U64); + pub(crate) fn generate_ke1>( l1_component: Vec, rng: &mut R, diff --git a/src/keypair.rs b/src/keypair.rs index 03611f6..85625a2 100644 --- a/src/keypair.rs +++ b/src/keypair.rs @@ -91,6 +91,60 @@ 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) + } + } + }; +} + +/// 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 +/// +/// Because SizedBytes has a strong notion of size, and TryFrom/to_bytes does +/// not, it's better to use this macro than the one above, where possible. +macro_rules! try_from_and_to_bytes_using_sized_bytes { + ($sized_type: ident) => { + impl TryFrom<&[u8]> for $sized_type { + type Error = InternalPakeError; + + fn try_from(bytes: &[u8]) -> Result { + <$sized_type as SizedBytes>::from_bytes(bytes) + } + } + + #[allow(dead_code)] + impl $sized_type { + fn to_bytes(&self) -> Vec { + self.to_arr().to_vec() + } + } + }; +} + /// This is a blanket implementation of SizedBytes for any instance of KeyPair /// with any length of keys. This encodes that we serialize the public key /// first, followed by the private key in binary formats (and expect it in this @@ -133,14 +187,6 @@ impl Deref for Key { } } -impl TryFrom> for Key { - type Error = InternalPakeError; - - fn try_from(key_bytes: Vec) -> Result { - Key::from_bytes(&key_bytes[..]) - } -} - impl SizedBytes for Key { type Len = U32; @@ -155,6 +201,8 @@ impl SizedBytes for Key { } } +try_from_and_to_bytes_using_sized_bytes!(Key); + /// A representation of an X25519 keypair according to RFC7748 #[derive(Debug, PartialEq, Eq)] pub struct X25519KeyPair { diff --git a/src/tests/opaque_ke_test.rs b/src/tests/opaque_ke_test.rs index 2696a83..f7ac540 100644 --- a/src/tests/opaque_ke_test.rs +++ b/src/tests/opaque_ke_test.rs @@ -391,7 +391,7 @@ fn test_r3() -> Result<(), PakeError> { .unwrap() .finish( RegisterSecondMessage::try_from(¶meters.r2[..]).unwrap(), - &Key::try_from(parameters.server_s_pk).unwrap(), + &Key::try_from(¶meters.server_s_pk[..]).unwrap(), &mut finish_registration_rng, ) .unwrap(); @@ -456,7 +456,7 @@ fn test_l2() -> Result<(), PakeError> { let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk); let (l2, server_login) = ServerLogin::start::( ServerRegistration::try_from(¶meters.password_file[..]).unwrap(), - &Key::try_from(parameters.server_s_sk).unwrap(), + &Key::try_from(¶meters.server_s_sk[..]).unwrap(), LoginFirstMessage::::try_from(¶meters.l1[..]).unwrap(), &mut server_e_sk_rng, ) @@ -481,7 +481,7 @@ fn test_l3() -> Result<(), PakeError> { .finish( LoginSecondMessage::::try_from(¶meters.l2[..]) .unwrap(), - &Key::try_from(parameters.server_s_pk)?, + &Key::try_from(¶meters.server_s_pk[..])?, &mut client_e_sk_rng, ) .unwrap(); diff --git a/src/tests/serialization.rs b/src/tests/serialization.rs index 995e673..1e6b43b 100644 --- a/src/tests/serialization.rs +++ b/src/tests/serialization.rs @@ -6,6 +6,7 @@ use crate::{ ciphersuite::CipherSuite, group::Group, + key_exchange::{KE1Message, NONCE_LEN}, keypair::{KeyPair, SizedBytes, X25519KeyPair}, opaque::*, rkr_encryption::{RKRCipher as _, RKRCiphertext}, @@ -16,7 +17,7 @@ use curve25519_dalek::ristretto::RistrettoPoint; use chacha20poly1305::ChaCha20Poly1305; use rand_core::{OsRng, RngCore}; -use sha2::Digest; +use sha2::{Digest, Sha256}; use std::convert::TryFrom; struct Default; @@ -130,3 +131,46 @@ fn register_third_message_roundtrip() { let r3_bytes = r3.to_bytes(); assert_eq!(message, r3_bytes); } + +#[test] +fn client_login_roundtrip() { + let pw = b"hunter2"; + let mut rng = OsRng; + let sc = ::random_scalar(&mut rng); + + let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap(); + let mut client_nonce = [0u8; NONCE_LEN]; + rng.fill_bytes(&mut client_nonce); + + let l1_data = [&sc.to_bytes()[..], &client_nonce, client_e_kp.public()].concat(); + let mut hasher = Sha256::new(); + hasher.update(l1_data); + let hashed_l1 = hasher.finalize(); + + // serialization order: scalar, password, ke1_state + let bytes: Vec = [ + &sc.as_bytes()[..], + &pw[..], + client_e_kp.public(), + &client_nonce, + hashed_l1.as_slice(), + ] + .concat(); + let reg = ClientLogin::::try_from(&bytes[..]).unwrap(); + let reg_bytes = reg.to_bytes(); + assert_eq!(reg_bytes, bytes); +} + +#[test] +fn login_first_message_roundtrip() { + let mut rng = OsRng; + + let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap(); + let mut client_nonce = [0u8; NONCE_LEN]; + rng.fill_bytes(&mut client_nonce); + + let ke1m: Vec = [&client_nonce[..], &client_e_kp.public()].concat(); + let reg = KE1Message::try_from(&ke1m[..]).unwrap(); + let reg_bytes = reg.to_bytes(); + assert_eq!(reg_bytes, ke1m); +}