From 6eeeb0db280e457e7d331bf6dcaeaf026d1950d8 Mon Sep 17 00:00:00 2001 From: Kevin Lewi Date: Tue, 26 Jan 2021 09:27:30 -0800 Subject: [PATCH] Fixing test vectors and use of SHA512 (#120) --- src/envelope.rs | 277 ++++++----- src/key_exchange/traits.rs | 2 - src/key_exchange/tripledh.rs | 54 +-- src/keypair.rs | 1 - src/messages.rs | 69 ++- src/opaque.rs | 43 +- src/serialization/tests.rs | 30 +- src/tests/{opaque_ke_test.rs => full_test.rs} | 89 ++-- src/tests/mod.rs | 3 +- src/tests/opaque_test_vectors.rs | 430 ++++++++++++++++++ 10 files changed, 708 insertions(+), 290 deletions(-) rename src/tests/{opaque_ke_test.rs => full_test.rs} (80%) create mode 100644 src/tests/opaque_test_vectors.rs diff --git a/src/envelope.rs b/src/envelope.rs index f26a915..216b2d5 100644 --- a/src/envelope.rs +++ b/src/envelope.rs @@ -4,26 +4,22 @@ // LICENSE file in the root directory of this source tree. use crate::{ - errors::{InternalPakeError, PakeError, ProtocolError}, + errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError}, hash::Hash, serialization::{serialize, tokenize}, }; use digest::Digest; -use generic_array::{ - typenum::{Unsigned, U32}, - GenericArray, -}; +use generic_array::{typenum::Unsigned, GenericArray}; use hkdf::Hkdf; use hmac::{Hmac, Mac, NewMac}; use rand_core::{CryptoRng, RngCore}; use std::convert::TryFrom; // Constant string used as salt for HKDF computation -const STR_ENVU: &[u8] = b"EnvU"; - -/// The length of the "export key" output by the client registration -/// and login finish steps -pub(crate) type ExportKeySize = U32; +const STR_RWDU: &[u8] = b"rwdU"; +const STR_PAD: &[u8] = b"Pad"; +const STR_AUTH_KEY: &[u8] = b"AuthKey"; +const STR_EXPORT_KEY: &[u8] = b"ExportKey"; const NONCE_LEN: usize = 32; @@ -44,91 +40,18 @@ impl TryFrom for InnerEnvelopeMode { } } -/// This struct is an instantiation of the envelope as described in -/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4 -/// -/// Note that earlier versions of this specification described an -/// implementation of this envelope using an encryption scheme that -/// satisfied random-key robustness -/// (https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4). -/// The specification update has simplified this assumption by taking -/// an XOR-based approach without compromising on security, and to avoid -/// the confusion around the implementation of an RKR-secure encryption. -pub(crate) struct Envelope { +pub(crate) struct InnerEnvelope { mode: InnerEnvelopeMode, nonce: Vec, ciphertext: Vec, - auth_data: Vec, - hmac: GenericArray::OutputSize>, } -pub(crate) struct OpenedEnvelope { - pub(crate) client_s_sk: Vec, - pub(crate) export_key: GenericArray, -} - -pub(crate) struct OpenedInnerEnvelope { - pub(crate) plaintext: Vec, - pub(crate) export_key: GenericArray, -} - -impl Envelope { - /// The additional number of bytes added to the plaintext - pub(crate) fn additional_size() -> usize { - NONCE_LEN + ::OutputSize::to_usize() - } - - fn hmac_key_size() -> usize { - ::OutputSize::to_usize() - } - - fn export_key_size() -> usize { - ExportKeySize::to_usize() - } - - pub(crate) fn get_mode(&self) -> InnerEnvelopeMode { - self.mode - } - - pub(crate) fn new( - mode: InnerEnvelopeMode, - nonce: Vec, - ciphertext: Vec, - auth_data: Vec, - hmac: GenericArray::OutputSize>, - ) -> Self { - Self { - mode, - nonce, - ciphertext, - auth_data, - hmac, - } - } - - /// The format of the output is: - /// mode | nonce | ciphertext | hmac - /// u8 | nonce_size bytes | variable length | hmac_size bytes - pub(crate) fn from_bytes(bytes: &[u8]) -> Result { - let (result, remainder) = Self::deserialize(bytes) - .map_err(|_| InternalPakeError::InvalidEnvelopeStructureError)?; - if !remainder.is_empty() { - return Err(InternalPakeError::InvalidEnvelopeStructureError); - } - Ok(result) - } - - pub(crate) fn to_bytes(&self) -> Vec { - self.serialize() - } - +impl InnerEnvelope { pub(crate) fn serialize(&self) -> Vec { [ &[self.mode as u8], &self.nonce[..], &serialize(&self.ciphertext, 2)[..], - &serialize(&self.auth_data, 2)[..], - &serialize(&self.hmac, 2)[..], ] .concat() } @@ -150,19 +73,96 @@ impl Envelope { let nonce = &bytes[..NONCE_LEN]; let (ciphertext, remainder) = tokenize(&bytes[NONCE_LEN..], 2)?; - let (auth_data, remainder) = tokenize(&remainder, 2)?; - let (hmac, remainder) = tokenize(&remainder, 2)?; + Ok(( - Self::new( + Self { mode, - nonce.to_vec(), + nonce: nonce.to_vec(), ciphertext, - auth_data, - GenericArray::clone_from_slice(&hmac[..]), - ), + }, remainder, )) } +} + +/// This struct is an instantiation of the envelope as described in +/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4 +/// +/// Note that earlier versions of this specification described an +/// implementation of this envelope using an encryption scheme that +/// satisfied random-key robustness +/// (https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4). +/// The specification update has simplified this assumption by taking +/// an XOR-based approach without compromising on security, and to avoid +/// the confusion around the implementation of an RKR-secure encryption. +pub(crate) struct Envelope { + inner_envelope: InnerEnvelope, + hmac: GenericArray::OutputSize>, +} + +pub(crate) struct OpenedEnvelope { + pub(crate) client_s_sk: Vec, + pub(crate) export_key: GenericArray::OutputSize>, +} + +pub(crate) struct OpenedInnerEnvelope { + pub(crate) plaintext: Vec, + pub(crate) export_key: GenericArray::OutputSize>, +} + +impl Envelope { + /// The additional number of bytes added to the plaintext + pub(crate) fn additional_size() -> usize { + NONCE_LEN + ::OutputSize::to_usize() + } + + fn hmac_key_size() -> usize { + ::OutputSize::to_usize() + } + + fn export_key_size() -> usize { + ::OutputSize::to_usize() + } + + pub(crate) fn get_mode(&self) -> InnerEnvelopeMode { + self.inner_envelope.mode + } + + /// The format of the output is: + /// mode | nonce | ciphertext | hmac + /// u8 | nonce_size bytes | variable length | hmac_size bytes + pub(crate) fn from_bytes(bytes: &[u8]) -> Result { + let (result, remainder) = Self::deserialize(bytes) + .map_err(|_| InternalPakeError::InvalidEnvelopeStructureError)?; + if !remainder.is_empty() { + return Err(InternalPakeError::InvalidEnvelopeStructureError); + } + Ok(result) + } + + pub(crate) fn to_bytes(&self) -> Vec { + self.serialize() + } + + pub(crate) fn serialize(&self) -> Vec { + [&self.inner_envelope.serialize(), &self.hmac[..]].concat() + } + + pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec), ProtocolError> { + let (inner_envelope, remainder) = InnerEnvelope::deserialize(input)?; + + let hmac_key_size = Self::hmac_key_size(); + let hmac_and_remainder = + check_slice_size_atleast(&remainder, hmac_key_size, "hmac_key_size")?; + + Ok(( + Self { + inner_envelope, + hmac: GenericArray::clone_from_slice(&hmac_and_remainder[..hmac_key_size]), + }, + hmac_and_remainder[hmac_key_size..].to_vec(), + )) + } pub(crate) fn seal( rng: &mut R, @@ -170,7 +170,7 @@ impl Envelope { client_s_sk: &[u8], server_s_pk: &[u8], optional_ids: Option<(Vec, Vec)>, - ) -> Result<(Self, GenericArray), InternalPakeError> { + ) -> Result<(Self, GenericArray::OutputSize>), InternalPakeError> { let plaintext = serialize(&client_s_sk, 2); let aad = construct_aad(server_s_pk, &optional_ids); Self::seal_raw(rng, key, &plaintext, &aad, mode_from_ids(&optional_ids)) @@ -184,17 +184,27 @@ impl Envelope { plaintext: &[u8], aad: &[u8], mode: InnerEnvelopeMode, - ) -> Result<(Self, GenericArray), InternalPakeError> { + ) -> Result<(Self, GenericArray::OutputSize>), InternalPakeError> { let mut nonce = vec![0u8; NONCE_LEN]; rng.fill_bytes(&mut nonce); - let h = Hkdf::::new(Some(&nonce), &key); - let mut okm = vec![0u8; plaintext.len() + Self::hmac_key_size() + Self::export_key_size()]; - h.expand(STR_ENVU, &mut okm) + let h = Hkdf::::new(Some(STR_RWDU), &key); + let mut xor_key = vec![0u8; plaintext.len()]; + let mut hmac_key = vec![0u8; Self::hmac_key_size()]; + let mut export_key = vec![0u8; Self::export_key_size()]; + + h.expand(&[nonce.clone(), STR_PAD.to_vec()].concat(), &mut xor_key) .map_err(|_| InternalPakeError::HkdfError)?; - let xor_key = &okm[..plaintext.len()]; - let hmac_key = &okm[plaintext.len()..plaintext.len() + Self::hmac_key_size()]; - let export_key = &okm[plaintext.len() + Self::hmac_key_size()..]; + h.expand( + &[nonce.clone(), STR_AUTH_KEY.to_vec()].concat(), + &mut hmac_key, + ) + .map_err(|_| InternalPakeError::HkdfError)?; + h.expand( + &[nonce.clone(), STR_EXPORT_KEY.to_vec()].concat(), + &mut export_key, + ) + .map_err(|_| InternalPakeError::HkdfError)?; let ciphertext: Vec = xor_key .iter() @@ -202,21 +212,25 @@ impl Envelope { .map(|(&x1, &x2)| x1 ^ x2) .collect(); + let inner_envelope = InnerEnvelope { + mode, + nonce, + ciphertext, + }; + let mut hmac = Hmac::::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?; - hmac.update(&nonce); - hmac.update(&ciphertext); + hmac.update(&inner_envelope.serialize()); hmac.update(&aad); + let hmac_bytes = hmac.finalize().into_bytes(); + Ok(( - Self::new( - mode, - nonce, - ciphertext.to_vec(), - aad.to_vec(), - hmac.finalize().into_bytes(), - ), - *GenericArray::from_slice(&export_key), + Self { + inner_envelope, + hmac: hmac_bytes, + }, + GenericArray::clone_from_slice(&export_key), )) } @@ -225,9 +239,9 @@ impl Envelope { key: &[u8], server_s_pk: &[u8], optional_ids: &Option<(Vec, Vec)>, - ) -> Result { + ) -> Result, InternalPakeError> { // First, check that mode matches - if self.mode != mode_from_ids(optional_ids) { + if self.inner_envelope.mode != mode_from_ids(optional_ids) { return Err(InternalPakeError::IncompatibleEnvelopeModeError); } @@ -253,20 +267,31 @@ impl Envelope { &self, key: &[u8], aad: &[u8], - ) -> Result { - let h = Hkdf::::new(Some(&self.nonce), &key); - let mut okm = - vec![0u8; self.ciphertext.len() + Self::hmac_key_size() + Self::export_key_size()]; - h.expand(STR_ENVU, &mut okm) - .map_err(|_| InternalPakeError::HkdfError)?; - let xor_key = &okm[..self.ciphertext.len()]; - let hmac_key = &okm[self.ciphertext.len()..self.ciphertext.len() + Self::hmac_key_size()]; - let export_key = &okm[self.ciphertext.len() + Self::hmac_key_size()..]; + ) -> Result, InternalPakeError> { + let h = Hkdf::::new(Some(STR_RWDU), &key); + let mut xor_key = vec![0u8; self.inner_envelope.ciphertext.len()]; + let mut hmac_key = vec![0u8; Self::hmac_key_size()]; + let mut export_key = vec![0u8; Self::export_key_size()]; + + h.expand( + &[self.inner_envelope.nonce.clone(), STR_PAD.to_vec()].concat(), + &mut xor_key, + ) + .map_err(|_| InternalPakeError::HkdfError)?; + h.expand( + &[self.inner_envelope.nonce.clone(), STR_AUTH_KEY.to_vec()].concat(), + &mut hmac_key, + ) + .map_err(|_| InternalPakeError::HkdfError)?; + h.expand( + &[self.inner_envelope.nonce.clone(), STR_EXPORT_KEY.to_vec()].concat(), + &mut export_key, + ) + .map_err(|_| InternalPakeError::HkdfError)?; let mut hmac = Hmac::::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?; - hmac.update(&self.nonce); - hmac.update(&self.ciphertext); + hmac.update(&self.inner_envelope.serialize()); hmac.update(aad); if hmac.verify(&self.hmac).is_err() { return Err(InternalPakeError::SealOpenHmacError); @@ -274,12 +299,14 @@ impl Envelope { let plaintext: Vec = xor_key .iter() - .zip(self.ciphertext.iter()) + .zip(self.inner_envelope.ciphertext.iter()) .map(|(&x1, &x2)| x1 ^ x2) .collect(); Ok(OpenedInnerEnvelope { plaintext, - export_key: *GenericArray::from_slice(&export_key), + export_key: GenericArray::::OutputSize>::clone_from_slice( + &export_key, + ), }) } } diff --git a/src/key_exchange/traits.rs b/src/key_exchange/traits.rs index 26b0ac9..089b322 100644 --- a/src/key_exchange/traits.rs +++ b/src/key_exchange/traits.rs @@ -56,8 +56,6 @@ pub trait KeyExchange { ke2_state: &Self::KE2State, ) -> Result, ProtocolError>; - fn ke1_state_size() -> usize; - fn ke2_message_size() -> usize; } diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs index d434f1e..71e4ac1 100644 --- a/src/key_exchange/tripledh.rs +++ b/src/key_exchange/tripledh.rs @@ -44,14 +44,14 @@ static STR_OPAQUE: &[u8] = b"OPAQUE "; pub struct TripleDH; impl KeyExchange for TripleDH { - type KE1State = KE1State<::OutputSize>; + type KE1State = KE1State; type KE2State = KE2State<::OutputSize>; type KE1Message = KE1Message; type KE2Message = KE2Message<::OutputSize>; type KE3Message = KE3Message<::OutputSize>; fn generate_ke1( - l1_component: Vec, + alpha_bytes: Vec, info: Vec, rng: &mut R, ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> { @@ -68,16 +68,14 @@ impl KeyExchange for TripleDH { client_e_pk: client_e_kp.public().clone(), }; - let l1_data: Vec = [&l1_component[..], &ke1_message.to_bytes()].concat(); - let mut hasher = D::new(); - hasher.update(&l1_data); - let hashed_l1 = hasher.finalize(); + // TODO: must match the serialization of a credential request, could be done more cleanly + let serialized_credential_request = [alpha_bytes, ke1_message.to_bytes()].concat(); Ok(( KE1State { client_e_sk: client_e_kp.private().clone(), client_nonce, - hashed_l1, + serialized_credential_request, }, ke1_message, )) @@ -86,7 +84,7 @@ impl KeyExchange for TripleDH { #[allow(clippy::type_complexity)] fn generate_ke2( rng: &mut R, - l1_bytes: Vec, + serialized_credential_request: Vec, l2_bytes: Vec, ke1_message: Self::KE1Message, client_s_pk: Key, @@ -118,7 +116,7 @@ impl KeyExchange for TripleDH { )?; // Compute encryption of e_info - let h = Hkdf::::new(None, &ke2); + let h = Hkdf::::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?; let mut encryption_pad = vec![0u8; e_info.len()]; h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad) .map_err(|_| InternalPakeError::HkdfError)?; @@ -128,12 +126,8 @@ impl KeyExchange for TripleDH { .map(|(&x1, &x2)| x1 ^ x2) .collect(); - let mut hasher = D::new(); - hasher.update(&l1_bytes); - let hashed_l1 = hasher.finalize(); - let transcript2: Vec = [ - &hashed_l1[..], + &serialized_credential_request[..], &l2_bytes[..], &server_nonce[..], &server_e_kp.public().to_arr(), @@ -152,7 +146,6 @@ impl KeyExchange for TripleDH { let mut hasher3 = D::new(); hasher3.update(&transcript2); - hasher3.update(mac.clone()); let hashed_transcript = hasher3.finalize(); Ok(( @@ -197,7 +190,7 @@ impl KeyExchange for TripleDH { )?; let transcript: Vec = [ - &ke1_state.hashed_l1[..], + &ke1_state.serialized_credential_request[..], &l2_component[..], &ke2_message.to_bytes_without_mac(), ] @@ -219,7 +212,7 @@ impl KeyExchange for TripleDH { let mut hasher2 = D::new(); hasher2.update(transcript); - hasher2.update(ke2_message.mac.to_vec()); + // hasher2.update(ke2_message.mac.to_vec()); // FIXME, sync with @caw on including this let hashed_transcript = hasher2.finalize(); let mut client_mac = @@ -227,7 +220,7 @@ impl KeyExchange for TripleDH { client_mac.update(&hashed_transcript); // Compute decryption of e_info - let h = Hkdf::::new(None, &ke2); + let h = Hkdf::::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?; let mut encryption_pad = vec![0u8; ke2_message.e_info.len()]; h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad) .map_err(|_| InternalPakeError::HkdfError)?; @@ -264,10 +257,6 @@ impl KeyExchange for TripleDH { Ok(ke2_state.session_secret.to_vec()) } - fn ke1_state_size() -> usize { - NONCE_LEN + KEY_LEN + <::OutputSize as Unsigned>::to_usize() - } - fn ke2_message_size() -> usize { NONCE_LEN + KEY_LEN + <::OutputSize as Unsigned>::to_usize() } @@ -275,10 +264,10 @@ 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: GenericArray, - hashed_l1: GenericArray, + serialized_credential_request: Vec, } /// The first key exchange message @@ -289,32 +278,28 @@ pub struct KE1Message { pub(crate) client_e_pk: Key, } -impl> TryFrom<&[u8]> for KE1State { +impl TryFrom<&[u8]> for KE1State { type Error = PakeError; fn try_from(bytes: &[u8]) -> Result { - let checked_bytes = check_slice_size( - bytes, - KEY_LEN + NONCE_LEN + HashLen::to_usize(), - "ke1_state", - )?; + let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + NONCE_LEN, "ke1_state")?; Ok(Self { client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?, 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..]), + serialized_credential_request: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(), }) } } -impl> ToBytes for KE1State { +impl ToBytes for KE1State { fn to_bytes(&self) -> Vec { let output: Vec = [ &self.client_e_sk.to_arr(), &self.client_nonce[..], - &self.hashed_l1[..], + &self.serialized_credential_request[..], ] .concat(); output @@ -505,6 +490,7 @@ fn derive_3dh_keys( let extracted_ikm = Hkdf::::new(None, &ikm); let handshake_secret = derive_secrets::(&extracted_ikm, &STR_HANDSHAKE_SECRET, &info)?; let session_secret = derive_secrets::(&extracted_ikm, &STR_SESSION_SECRET, &info)?; + let km2 = hkdf_expand_label::( &handshake_secret, &STR_SERVER_MAC, @@ -538,7 +524,7 @@ fn hkdf_expand_label( context: &[u8], length: usize, ) -> Result, ProtocolError> { - let h = Hkdf::::new(None, secret); + let h = Hkdf::::from_prk(secret).map_err(|_| InternalPakeError::HkdfError)?; hkdf_expand_label_extracted(&h, label, context, length) } diff --git a/src/keypair.rs b/src/keypair.rs index 9c9b24c..25b7b2a 100644 --- a/src/keypair.rs +++ b/src/keypair.rs @@ -17,7 +17,6 @@ use rand::{rngs::StdRng, SeedableRng}; use rand_core::{CryptoRng, RngCore}; use std::fmt::Debug; use std::marker::PhantomData; - use std::ops::Deref; // Pub(crate) convenience extension trait of SizedBytes for our purposes diff --git a/src/messages.rs b/src/messages.rs index 109ce9f..531714d 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -54,22 +54,13 @@ impl RegistrationRequest { /// Serialization into bytes pub fn serialize(&self) -> Vec { - serialize(&self.alpha.to_arr(), 2) + self.alpha.to_arr().to_vec() } /// Deserialization from bytes pub fn deserialize(input: &[u8]) -> Result { - let (alpha_bytes, remainder) = tokenize(&input, 2)?; - - if !remainder.is_empty() { - return Err(PakeError::SerializationError.into()); - } - - let checked_slice = check_slice_size( - &alpha_bytes, - Grp::ElemLen::to_usize(), - "first_message_bytes", - )?; + let checked_slice = + check_slice_size(&input, Grp::ElemLen::to_usize(), "first_message_bytes")?; // Check that the message is actually containing an element of the // correct subgroup let arr = GenericArray::from_slice(checked_slice); @@ -122,29 +113,26 @@ where /// Serialization into bytes pub fn serialize(&self) -> Vec { let mut registration_response: Vec = Vec::new(); - registration_response.extend_from_slice(&serialize(&self.beta.to_arr(), 2)); + registration_response.extend_from_slice(&self.beta.to_arr()); registration_response.extend_from_slice(&serialize(&self.server_s_pk, 2)); registration_response } /// Deserialization from bytes pub fn deserialize(input: &[u8]) -> Result { - let (beta_bytes, remainder) = tokenize(&input, 2)?; - let (server_s_pk, remainder) = tokenize(&remainder, 2)?; + let checked_slice = + check_slice_size_atleast(&input, Grp::ElemLen::to_usize(), "second_message_bytes")?; + // Check that the message is actually containing an element of the + // correct subgroup + let arr = GenericArray::from_slice(&checked_slice[..Grp::ElemLen::to_usize()]); + let beta = Grp::from_element_slice(arr)?; + + let (server_s_pk, remainder) = tokenize(&checked_slice[Grp::ElemLen::to_usize()..], 2)?; if !remainder.is_empty() { return Err(PakeError::SerializationError.into()); } - let checked_slice = check_slice_size( - &beta_bytes, - Grp::ElemLen::to_usize(), - "second_message_bytes", - )?; - // Check that the message is actually containing an element of the - // correct subgroup - let arr = GenericArray::from_slice(&checked_slice); - let beta = Grp::from_element_slice(arr)?; Ok(Self { server_s_pk, beta }) } } @@ -186,15 +174,15 @@ impl RegistrationUpload { /// Serialization into bytes pub fn serialize(&self) -> Vec { let mut message: Vec = Vec::new(); - message.extend_from_slice(&self.envelope.serialize()); message.extend_from_slice(&serialize(&self.client_s_pk.to_arr(), 2)); + message.extend_from_slice(&self.envelope.serialize()); message } /// Deserialization from bytes pub fn deserialize(input: &[u8]) -> Result { - let (envelope, remainder) = Envelope::::deserialize(&input)?; - let (client_s_pk, remainder) = tokenize(&remainder, 2)?; + let (client_s_pk, remainder) = tokenize(&input, 2)?; + let (envelope, remainder) = Envelope::::deserialize(&remainder)?; if !remainder.is_empty() { return Err(PakeError::SerializationError.into()); @@ -231,22 +219,27 @@ impl CredentialRequest { /// Serialization into bytes pub fn serialize(&self) -> Vec { let mut credential_request: Vec = Vec::new(); - credential_request.extend_from_slice(&serialize(&self.alpha.to_arr(), 2)); + credential_request.extend_from_slice(&self.alpha.to_arr()); credential_request.extend_from_slice(&self.ke1_message.to_bytes()); credential_request } /// Deserialization from bytes pub fn deserialize(input: &[u8]) -> Result { - let (alpha_bytes, ke1m) = tokenize(&input, 2)?; - let elem_len = ::ElemLen::to_usize(); - let checked_slice = check_slice_size(&alpha_bytes, elem_len, "login_first_message_bytes")?; + + let checked_slice = + check_slice_size_atleast(&input, elem_len, "login_first_message_bytes")?; + + // Check that the message is actually containing an element of the + // correct subgroup let arr = GenericArray::from_slice(&checked_slice[..elem_len]); - let alpha = ::from_element_slice(arr)?; + let alpha = CS::Group::from_element_slice(arr)?; let ke1_message = - >::KE1Message::try_from(&ke1m[..])?; + >::KE1Message::try_from( + &checked_slice[elem_len..], + )?; Ok(Self { alpha, ke1_message }) } @@ -267,7 +260,7 @@ impl CredentialResponse { /// Serialization into bytes pub fn serialize(&self) -> Vec { let mut credential_response: Vec = Vec::new(); - credential_response.extend_from_slice(&serialize(&self.beta.to_arr(), 2)); + credential_response.extend_from_slice(&self.beta.to_arr()); credential_response.extend_from_slice(&serialize(&self.server_s_pk.to_arr().to_vec(), 2)); credential_response.extend_from_slice(&self.envelope.to_bytes()); credential_response.extend_from_slice(&self.ke2_message.to_bytes()); @@ -276,13 +269,7 @@ impl CredentialResponse { /// Deserialization from bytes pub fn deserialize(input: &[u8]) -> Result { - let (beta_bytes, server_s_pk_and_envelope_and_ke2m_bytes) = tokenize(&input, 2)?; - let concatenated = [ - &beta_bytes[..], - &server_s_pk_and_envelope_and_ke2m_bytes[..], - ] - .concat(); - Self::try_from(&concatenated[..]) + Self::try_from(input) } } diff --git a/src/opaque.rs b/src/opaque.rs index de34493..923f0ed 100644 --- a/src/opaque.rs +++ b/src/opaque.rs @@ -7,7 +7,7 @@ use crate::{ ciphersuite::CipherSuite, - envelope::{mode_from_ids, Envelope, ExportKeySize}, + envelope::{mode_from_ids, Envelope}, errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError}, group::Group, hash::Hash, @@ -15,18 +15,19 @@ use crate::{ keypair::{Key, KeyPair, SizedBytesExt}, map_to_curve::GroupWithMapToCurve, oprf, - serialization::serialize, + serialization::{serialize, tokenize}, slow_hash::SlowHash, CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, }; +use digest::Digest; use generic_array::{typenum::Unsigned, GenericArray}; use generic_bytes::SizedBytes; use rand_core::{CryptoRng, RngCore}; use std::{convert::TryFrom, marker::PhantomData}; use zeroize::Zeroize; -static STR_OPAQUE_VERSION: &[u8] = b"OPAQUE00"; +static STR_OPAQUE_VERSION: &[u8] = b"OPAQUE01"; // Registration // ============ @@ -142,7 +143,7 @@ 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, + pub export_key: GenericArray::OutputSize>, _g: PhantomData, } @@ -413,14 +414,10 @@ impl TryFrom<&[u8]> for ClientLogin { type Error = ProtocolError; fn try_from(input: &[u8]) -> Result { let scalar_len = ::ScalarLen::to_usize(); - let ke1_state_size = - >::ke1_state_size(); - - let min_expected_len = scalar_len + ke1_state_size; - let checked_slice = (if input.len() <= min_expected_len { + let checked_slice = (if input.len() <= scalar_len { Err(InternalPakeError::SizeError { name: "client_login_bytes", - len: min_expected_len, + len: scalar_len, actual_len: input.len(), }) } else { @@ -429,10 +426,12 @@ 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_bytes, password) = tokenize(&checked_slice[scalar_len..], 2)?; + let ke1_state = >::KE1State::try_from( - &checked_slice[scalar_len..scalar_len + ke1_state_size], + &ke1_state_bytes[..], )?; - let password = input[scalar_len + ke1_state_size..].to_vec(); Ok(Self { token: oprf::Token { data: password, @@ -448,7 +447,7 @@ impl ClientLogin { pub fn to_bytes(&self) -> Vec { let output: Vec = [ &CS::Group::scalar_as_bytes(&self.token.blind)[..], - &self.ke1_state.to_bytes(), + &serialize(&self.ke1_state.to_bytes(), 2), &self.token.data, ] .concat(); @@ -497,7 +496,7 @@ pub struct ClientLoginFinishResult { /// The shared session secret pub shared_secret: Vec, /// The client-side export key - pub export_key: GenericArray, + pub export_key: GenericArray::OutputSize>, /// The server's static public key pub server_s_pk: Key, /// The confidential info sent by the client @@ -597,7 +596,6 @@ impl ClientLogin { let password_derived_key = get_password_derived_key::(&self.token, l2.beta)?; - let opened_envelope = &l2 .envelope .open(&password_derived_key, &server_s_pk_bytes, &optional_ids) @@ -619,9 +617,9 @@ impl ClientLogin { }; let l2_bytes: Vec = [ - serialize(&l2_beta_bytes, 2), - serialize(&server_s_pk_bytes, 2), - l2.envelope.to_bytes(), + l2_beta_bytes, + &serialize(&server_s_pk_bytes, 2), + &l2.envelope.to_bytes(), ] .concat(); @@ -639,7 +637,7 @@ impl ClientLogin { confidential_info, message: CredentialFinalization { ke3_message }, shared_secret, - export_key: opened_envelope.export_key, + export_key: opened_envelope.export_key.clone(), server_s_pk: l2.server_s_pk, }) } @@ -777,10 +775,11 @@ impl ServerLogin { let server_s_pk = KeyPair::::public_from_private(&server_s_sk); + // TODO: must match serialization of credential response, could be done more cleanly let l2_component: Vec = [ - serialize(&beta.to_arr()[..], 2), - serialize(&server_s_pk.to_arr()[..], 2), - envelope.to_bytes(), + &beta.to_arr()[..], + &serialize(&server_s_pk.to_arr()[..], 2), + &envelope.to_bytes()[..], ] .concat(); diff --git a/src/serialization/tests.rs b/src/serialization/tests.rs index 83cb7b2..05b142f 100644 --- a/src/serialization/tests.rs +++ b/src/serialization/tests.rs @@ -21,7 +21,7 @@ use generic_bytes::SizedBytes; use proptest::{collection::vec, prelude::*}; use rand_core::{OsRng, RngCore}; -use sha2::{Digest, Sha512}; +use sha2::Digest; use std::convert::TryFrom; struct Default; @@ -81,10 +81,7 @@ fn server_registration_roundtrip() { mock_envelope_bytes.extend_from_slice(&[0; 1]); // mode = 0 mock_envelope_bytes.extend_from_slice(&[0; NONCE_LEN]); // empty nonce mock_envelope_bytes.extend_from_slice(&[0, 0]); // empty ciphertext - mock_envelope_bytes.extend_from_slice(&[0, 0]); // empty auth_data - // length-MAC_SIZE hmac - mock_envelope_bytes.extend_from_slice(&[0, MAC_SIZE as u8]); - mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); + mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); // length-MAC_SIZE hmac let mock_client_kp = Default::generate_random_keypair(&mut rng); // serialization order: oprf_key, public key, envelope @@ -102,10 +99,7 @@ fn register_first_message_roundtrip() { let pt = random_ristretto_point(); let pt_bytes = pt.to_arr().to_vec(); - let alpha_length: usize = 32; - let mut input = Vec::new(); - input.extend_from_slice(&alpha_length.to_be_bytes()[std::mem::size_of::() - 2..]); input.extend_from_slice(pt_bytes.as_slice()); let r1 = RegistrationRequest::::deserialize(input.as_slice()).unwrap(); @@ -121,11 +115,9 @@ fn register_second_message_roundtrip() { let skp = Default::generate_random_keypair(&mut rng); let pubkey_bytes = skp.public().to_arr(); - let beta_length: usize = beta_bytes.len(); let pubkey_length: usize = pubkey_bytes.len(); let mut input = Vec::new(); - input.extend_from_slice(&beta_length.to_be_bytes()[std::mem::size_of::() - 2..]); input.extend_from_slice(beta_bytes.as_slice()); input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::() - 2..]); input.extend_from_slice(&pubkey_bytes.as_slice()); @@ -160,9 +152,9 @@ fn register_third_message_roundtrip() { let pubkey_length: usize = pubkey_bytes.len(); let mut input = Vec::new(); - input.extend_from_slice(&envelope_bytes); input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::() - 2..]); input.extend_from_slice(&pubkey_bytes[..]); + input.extend_from_slice(&envelope_bytes); let r3 = RegistrationUpload::::deserialize(&input[..]).unwrap(); let r3_bytes = r3.serialize(); @@ -189,10 +181,7 @@ fn login_first_message_roundtrip() { ] .concat(); - let alpha_length = alpha_bytes.len(); - let mut input = Vec::new(); - input.extend_from_slice(&alpha_length.to_be_bytes()[std::mem::size_of::() - 2..]); input.extend_from_slice(&alpha_bytes); input.extend_from_slice(&ke1m[..]); @@ -244,7 +233,6 @@ fn login_second_message_roundtrip() { .concat(); let mut input = Vec::new(); - input.extend_from_slice(&pt_bytes.len().to_be_bytes()[std::mem::size_of::() - 2..]); input.extend_from_slice(pt_bytes.as_slice()); input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::() - 2..]); input.extend_from_slice(&pubkey_bytes.as_slice()); @@ -280,19 +268,9 @@ fn client_login_roundtrip() { rng.fill_bytes(&mut client_nonce); let l1_data = [&sc.to_bytes()[..], &client_nonce, client_e_kp.public()].concat(); - let mut hasher = Sha512::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 bytes: Vec = [&sc.as_bytes()[..], &serialize(&l1_data, 2), &pw[..]].concat(); let reg = ClientLogin::::try_from(&bytes[..]).unwrap(); let reg_bytes = reg.to_bytes(); assert_eq!(reg_bytes, bytes); diff --git a/src/tests/opaque_ke_test.rs b/src/tests/full_test.rs similarity index 80% rename from src/tests/opaque_ke_test.rs rename to src/tests/full_test.rs index d65e213..b7d9424 100644 --- a/src/tests/opaque_ke_test.rs +++ b/src/tests/full_test.rs @@ -55,7 +55,7 @@ pub struct TestVectorParameters { pub registration_upload: Vec, pub credential_request: Vec, pub credential_response: Vec, - pub key_exchange: Vec, + pub credential_finalization: Vec, client_registration_state: Vec, server_registration_state: Vec, client_login_state: Vec, @@ -67,37 +67,37 @@ pub struct TestVectorParameters { static TEST_VECTOR: &str = r#" { - "client_s_pk": "64b071d0b8eccc39673b16384e86df49f258a12bad24f21f5d5f4a9a8f45561c", - "client_s_sk": "98a23d0757ac5c7aba8b218c5643c3a3029fb815a656de24cfc05df877925809", - "client_e_pk": "3a575317d7a6269bc4e76074ec906d8886b4c135fb6b590972f6fdd6c1a55676", - "client_e_sk": "163b91328785a5e84e21579e941be1dd7bdbc44d4c959f603d7f79417ab6b201", - "server_s_pk": "fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d75", - "server_s_sk": "f7068582fdf2a57f7da63e6f5142abda805a7b508ce171c84f58a4e5e634a406", - "server_e_pk": "8e32a64f670b9d2115eba0006b00667972523bf2f8686933ef2a2819d6b5a13b", - "server_e_sk": "a3a06d81eeb60aeedaee96769149ae5f45184eec66e1b2bdeb04114d27019206", + "client_s_pk": "ba8c7e239ed64ea1e6068b52cbd5a34ba6f55d635d20a4593884dc271865ea63", + "client_s_sk": "edb5e886441ddcd4fdd80593d9ea7b7f82f284fcc1d565b802e99997457b960a", + "client_e_pk": "08973113b27d6f2291aaaeb92f34a8438d279a48b1b412bddbf2785be660e479", + "client_e_sk": "28fb3aadbc2716844d8de562d35e61914da734dcea0fa6ebd79603ef9eff4e01", + "server_s_pk": "b67d948b98eecfb516a626fab94170e490ccd90da05fce94a016c4d5d6b7136a", + "server_s_sk": "52350a684c353e39bbaadeb042b2de94226a493f6ae172b7fc45408bbfda6401", + "server_e_pk": "42103d44b03f3886fe3e419b1b96521fa5a706bb1a4e003907daa381960d664a", + "server_e_sk": "564e113d5b2784fcf3a55d5ca48512a2fd5c3c393753f6cfd157cb712b317c04", "id_u": "696455", "id_s": "696453", "password": "70617373776f7264", - "blinding_factor": "3497d8c6728a3dc0873e363dcd60acd95f7897d15d33cdf1d49c8b856f061900", - "oprf_key": "c728d1ea06fb94f577ef63d50d1645e9fea14575987971e4a64e05dda367c703", - "envelope_nonce": "d7d138a3a4cb796662814391f5221e05b66438a92e5f57cc9624e6227da34eab", - "client_nonce": "ad87ae4609f1f56aeb1de0f4760120fa75ceff39751dffc35d25ff3f2896bb60", - "server_nonce": "d7a8cd7cd2c4f0f7650390f6f2d4858ae99ac79f2769a9e15583d570b11c434c", + "blinding_factor": "a70b37951d84ed312a0a8e025b71eafdd362b4e7db872881762d506a271b6e07", + "oprf_key": "1eaf3cdd64da67e17e3364182f00ed0c323bfb44b20a1b7e0025a0334ab6d40d", + "envelope_nonce": "5dba53e994e82b6d76353341102791cdfb1c2b460e1f913c5741a9c8cf2017c9", + "client_nonce": "488244dbf75f86ebaa30307773d3e0eabccb91be349abef8a69c458dbf236e4c", + "server_nonce": "13cc5ceb7687d80293005e7f9452ce104d1bac6598dd2265f184cacd558d6b31", "info1": "696e666f31", "einfo2": "65696e666f32", - "registration_request": "002096738fbc9883a85a8067763abe33efadce0adb2bc64857d3cd0a6cc92e9ce325", - "registration_response": "0020a86baffac435f6239cfe5f61d823e7458ac211dce1d3c364375e9d1f40843c750020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d75", - "registration_upload": "01d7d138a3a4cb796662814391f5221e05b66438a92e5f57cc9624e6227da34eab002229979df679067f8baecbbe063ee97e2b5be88c2b99163d595b32a74d640138771d70002c0020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d750003696455000369645300400b6119d0de405d85e0e6b1748eb66194b1768ea71fd47c103f8e1fcbc837add2b282889469921798d626a02b6dd763ee9cd028f333386a19c13a3d4d4c6a3312002064b071d0b8eccc39673b16384e86df49f258a12bad24f21f5d5f4a9a8f45561c", - "credential_request": "002096738fbc9883a85a8067763abe33efadce0adb2bc64857d3cd0a6cc92e9ce325ad87ae4609f1f56aeb1de0f4760120fa75ceff39751dffc35d25ff3f2896bb600005696e666f313a575317d7a6269bc4e76074ec906d8886b4c135fb6b590972f6fdd6c1a55676", - "credential_response": "0020a86baffac435f6239cfe5f61d823e7458ac211dce1d3c364375e9d1f40843c750020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d7501d7d138a3a4cb796662814391f5221e05b66438a92e5f57cc9624e6227da34eab002229979df679067f8baecbbe063ee97e2b5be88c2b99163d595b32a74d640138771d70002c0020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d750003696455000369645300400b6119d0de405d85e0e6b1748eb66194b1768ea71fd47c103f8e1fcbc837add2b282889469921798d626a02b6dd763ee9cd028f333386a19c13a3d4d4c6a3312a3a06d81eeb60aeedaee96769149ae5f45184eec66e1b2bdeb04114d270192068e32a64f670b9d2115eba0006b00667972523bf2f8686933ef2a2819d6b5a13b00066c7c22ba8dbae814beb02d1b89659379ca61697cd3212169d387b61f5659b77f6993833a1542a692cb5ad259244a1d53a51243fb5366a5bd8e4ed7eb2f50caa9a568fc8267e9", - "key_exchange": "3f9d3754920514328529bf4e78d4c2d0f411dfb871a4ef83c87d3462119b836381bdc651096cb62ca96de5f4b5a9acaa87b180d2acefebd2f3feb5785adcd038", - "client_registration_state": "3497d8c6728a3dc0873e363dcd60acd95f7897d15d33cdf1d49c8b856f06190070617373776f7264", - "client_login_state": "3497d8c6728a3dc0873e363dcd60acd95f7897d15d33cdf1d49c8b856f061900163b91328785a5e84e21579e941be1dd7bdbc44d4c959f603d7f79417ab6b201ad87ae4609f1f56aeb1de0f4760120fa75ceff39751dffc35d25ff3f2896bb607962b421c96efcfdb465365eea8fe9914e6ac5c0b216e2b07c7fb3b17fa0d21ee8c37373fee8dfec6a5eae559f21c8c4e1505ed974ce8baff3529304cb4d90b570617373776f7264", - "server_registration_state": "c728d1ea06fb94f577ef63d50d1645e9fea14575987971e4a64e05dda367c703", - "server_login_state": "644fb6d36a91e0197f5d82d17f04354f9e24794988923160534d99c80970b66fe54a3d8b36d5a4d21eaf0fa7bb4717ed1741be3f7d24b96d27aeb7f4b59a72bf80d0d969a2398ea5a5232d3257c60516dcc0de65b66fc5a07b8e2f58523eb0079ac09798a23cee0e31454594d25e40f34b10b92195fea6f90e197c0fbc8658bb3435bb6179370029f03d2dbd5efa2d0f8b8f4f69ca42ef70245686f70c2d7f326d0a8bc596145099e6a668d23ce1a93f9a3f83bfdddde15c78f467a639f124dd", - "password_file": "c728d1ea06fb94f577ef63d50d1645e9fea14575987971e4a64e05dda367c70364b071d0b8eccc39673b16384e86df49f258a12bad24f21f5d5f4a9a8f45561c01d7d138a3a4cb796662814391f5221e05b66438a92e5f57cc9624e6227da34eab002229979df679067f8baecbbe063ee97e2b5be88c2b99163d595b32a74d640138771d70002c0020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d750003696455000369645300400b6119d0de405d85e0e6b1748eb66194b1768ea71fd47c103f8e1fcbc837add2b282889469921798d626a02b6dd763ee9cd028f333386a19c13a3d4d4c6a3312", - "export_key": "005ba69bed794d3e24ac750ed575165a0235e103d1f8d7ca1b9aa0cfc2c3d9b6", - "shared_secret": "3435bb6179370029f03d2dbd5efa2d0f8b8f4f69ca42ef70245686f70c2d7f326d0a8bc596145099e6a668d23ce1a93f9a3f83bfdddde15c78f467a639f124dd" + "registration_request": "ecf819ef83351e4c75e016ef845b62a12c14f397328960574935a42e04e0fa42", + "registration_response": "2e9bf80e5c101ee2d391ff2aec06af97af17f443fc93a5cc98189681cb943d700020b67d948b98eecfb516a626fab94170e490ccd90da05fce94a016c4d5d6b7136a", + "registration_upload": "0020ba8c7e239ed64ea1e6068b52cbd5a34ba6f55d635d20a4593884dc271865ea63015dba53e994e82b6d76353341102791cdfb1c2b460e1f913c5741a9c8cf2017c90022f11b89b207eb0735f2bdb2a9968bc9d7c1c962fd0274b4fc1fff2466c769d3710586a3a21ac8ecf13aafe2dc5460eb2f52d8f760396a17beeaf1ef3d1e4e50dd906555832fc51fc474554cb9ff063f6685f9043efc08fd6816666833d68d3b0eb6d9", + "credential_request": "ecf819ef83351e4c75e016ef845b62a12c14f397328960574935a42e04e0fa42488244dbf75f86ebaa30307773d3e0eabccb91be349abef8a69c458dbf236e4c0005696e666f3108973113b27d6f2291aaaeb92f34a8438d279a48b1b412bddbf2785be660e479", + "credential_response": "2e9bf80e5c101ee2d391ff2aec06af97af17f443fc93a5cc98189681cb943d700020b67d948b98eecfb516a626fab94170e490ccd90da05fce94a016c4d5d6b7136a015dba53e994e82b6d76353341102791cdfb1c2b460e1f913c5741a9c8cf2017c90022f11b89b207eb0735f2bdb2a9968bc9d7c1c962fd0274b4fc1fff2466c769d3710586a3a21ac8ecf13aafe2dc5460eb2f52d8f760396a17beeaf1ef3d1e4e50dd906555832fc51fc474554cb9ff063f6685f9043efc08fd6816666833d68d3b0eb6d913cc5ceb7687d80293005e7f9452ce104d1bac6598dd2265f184cacd558d6b3142103d44b03f3886fe3e419b1b96521fa5a706bb1a4e003907daa381960d664a0006a0f4de671e13c37b24e29138ff9d3df3d55c3b822fedee8dcefd643a1deab7cda5aa1c20f31a0c2a1823c8de3e793f50b257833788b75609fb6c5c4a9cad225f3a981c4ff7b2", + "credential_finalization": "0640114ec21fbad6da11c61dfa77a48026646a2a1bd091ef5c9dc46b45f9e53c72f5d714e026d808b6de8911947eef437e9f6a6e969e1ea13f2f4880adc7c879", + "client_registration_state": "a70b37951d84ed312a0a8e025b71eafdd362b4e7db872881762d506a271b6e0770617373776f7264", + "client_login_state": "a70b37951d84ed312a0a8e025b71eafdd362b4e7db872881762d506a271b6e0700a728fb3aadbc2716844d8de562d35e61914da734dcea0fa6ebd79603ef9eff4e01488244dbf75f86ebaa30307773d3e0eabccb91be349abef8a69c458dbf236e4cecf819ef83351e4c75e016ef845b62a12c14f397328960574935a42e04e0fa42488244dbf75f86ebaa30307773d3e0eabccb91be349abef8a69c458dbf236e4c0005696e666f3108973113b27d6f2291aaaeb92f34a8438d279a48b1b412bddbf2785be660e47970617373776f7264", + "server_registration_state": "1eaf3cdd64da67e17e3364182f00ed0c323bfb44b20a1b7e0025a0334ab6d40d", + "server_login_state": "4952acf61167e6d0e799572827724fdf8c80bda8c62ac28deb68d125661dade2cc1136e634d5fb93f1de4d170ae3e2f5885e333ac3b72d630ab56618c2247fa4d5968eefd69cd270ed14fbcb39627fea9a93cd18175e1b5cfceb06f705bbcb5ecddcd0602aacd7cb358f3273b1099895210bf8baa37d2fab5ed8c092fe9e5739bc6c50cf08ca7d4f4107d48057507480e0c9534b4f04d77cd5156cbd112565d8a2d5beaa473003dcf8dd82d4046648a701c72afeed0f627fcb782627dfaed3ca", + "password_file": "1eaf3cdd64da67e17e3364182f00ed0c323bfb44b20a1b7e0025a0334ab6d40dba8c7e239ed64ea1e6068b52cbd5a34ba6f55d635d20a4593884dc271865ea63015dba53e994e82b6d76353341102791cdfb1c2b460e1f913c5741a9c8cf2017c90022f11b89b207eb0735f2bdb2a9968bc9d7c1c962fd0274b4fc1fff2466c769d3710586a3a21ac8ecf13aafe2dc5460eb2f52d8f760396a17beeaf1ef3d1e4e50dd906555832fc51fc474554cb9ff063f6685f9043efc08fd6816666833d68d3b0eb6d9", + "export_key": "2b86667de895e4d2c15b93bc70f0aa2df67b83987fa53deac9473697e2fade045e0fd0e4af478ccff52165c1467272993da34dcb3baa45aa59cf9d817e6c6bcb", + "shared_secret": "bc6c50cf08ca7d4f4107d48057507480e0c9534b4f04d77cd5156cbd112565d8a2d5beaa473003dcf8dd82d4046648a701c72afeed0f627fcb782627dfaed3ca" } "#; @@ -132,7 +132,7 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters { registration_upload: decode(&values, "registration_upload").unwrap(), credential_request: decode(&values, "credential_request").unwrap(), credential_response: decode(&values, "credential_response").unwrap(), - key_exchange: decode(&values, "key_exchange").unwrap(), + credential_finalization: decode(&values, "credential_finalization").unwrap(), client_registration_state: decode(&values, "client_registration_state").unwrap(), client_login_state: decode(&values, "client_login_state").unwrap(), server_registration_state: decode(&values, "server_registration_state").unwrap(), @@ -211,7 +211,13 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String { ) .as_str(), ); - s.push_str(format!("\"key_exchange\": \"{}\",\n", hex::encode(&p.key_exchange)).as_str()); + s.push_str( + format!( + "\"credential_finalization\": \"{}\",\n", + hex::encode(&p.credential_finalization) + ) + .as_str(), + ); s.push_str( format!( "\"client_registration_state\": \"{}\",\n", @@ -349,9 +355,15 @@ fn generate_parameters() -> TestVectorParameters { let credential_request_bytes = client_login_start_result.message.serialize().to_vec(); let client_login_state = client_login_start_result.state.to_bytes().to_vec(); - let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_arr().to_vec()); + let mut server_e_sk_and_nonce_rng = CycleRng::new( + [ + server_e_kp.private().to_arr().to_vec(), + server_nonce.to_vec(), + ] + .concat(), + ); let server_login_start_result = ServerLogin::::start( - &mut server_e_sk_rng, + &mut server_e_sk_and_nonce_rng, password_file, server_s_kp.private(), client_login_start_result.message, @@ -372,7 +384,7 @@ fn generate_parameters() -> TestVectorParameters { ClientLoginFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()), ) .unwrap(); - let key_exchange_bytes = client_login_finish_result.message.to_bytes().to_vec(); + let credential_finalization_bytes = client_login_finish_result.message.to_bytes().to_vec(); TestVectorParameters { client_s_pk: client_s_kp.public().to_arr().to_vec(), @@ -398,7 +410,7 @@ fn generate_parameters() -> TestVectorParameters { registration_upload: registration_upload_bytes, credential_request: credential_request_bytes, credential_response: credential_response_bytes, - key_exchange: key_exchange_bytes, + credential_finalization: credential_finalization_bytes, password_file: password_file_bytes, client_registration_state, server_registration_state, @@ -529,9 +541,10 @@ fn test_credential_request() -> Result<(), ProtocolError> { fn test_credential_response() -> Result<(), ProtocolError> { let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); - let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk); + let mut server_e_sk_and_nonce_rng = + CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat()); let server_login_start_result = ServerLogin::::start( - &mut server_e_sk_rng, + &mut server_e_sk_and_nonce_rng, ServerRegistration::try_from(¶meters.password_file[..]).unwrap(), &Key::try_from(¶meters.server_s_sk[..]).unwrap(), CredentialRequest::::deserialize( @@ -560,7 +573,7 @@ fn test_credential_response() -> Result<(), ProtocolError> { } #[test] -fn test_key_exchange() -> Result<(), ProtocolError> { +fn test_credential_finalization() -> Result<(), ProtocolError> { let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); let client_login_finish_result = @@ -586,7 +599,7 @@ fn test_key_exchange() -> Result<(), ProtocolError> { hex::encode(&client_login_finish_result.shared_secret) ); assert_eq!( - hex::encode(¶meters.key_exchange), + hex::encode(¶meters.credential_finalization), hex::encode(client_login_finish_result.message.to_bytes()) ); assert_eq!( @@ -604,7 +617,7 @@ fn test_server_login_finish() -> Result<(), ProtocolError> { let server_login_result = ServerLogin::::try_from(¶meters.server_login_state[..])? .finish(CredentialFinalization::try_from( - ¶meters.key_exchange[..], + ¶meters.credential_finalization[..], )?)?; assert_eq!( diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 8c642ba..e3fceee 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -3,6 +3,7 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. +mod full_test; pub mod mock_rng; -mod opaque_ke_test; +mod opaque_test_vectors; mod voprf_test_vectors; diff --git a/src/tests/opaque_test_vectors.rs b/src/tests/opaque_test_vectors.rs new file mode 100644 index 0000000..c5454f0 --- /dev/null +++ b/src/tests/opaque_test_vectors.rs @@ -0,0 +1,430 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use crate::{ + ciphersuite::CipherSuite, errors::*, key_exchange::tripledh::TripleDH, keypair::Key, opaque::*, + slow_hash::NoOpHash, tests::mock_rng::CycleRng, *, +}; +use curve25519_dalek::ristretto::RistrettoPoint; +use rand_core::OsRng; +use serde_json::Value; +use std::convert::TryFrom; + +// Tests +// ===== + +struct Ristretto255Sha512NoSlowHash; +impl CipherSuite for Ristretto255Sha512NoSlowHash { + type Group = RistrettoPoint; + type KeyExchange = TripleDH; + type Hash = sha2::Sha512; + type SlowHash = NoOpHash; +} + +pub struct TestVectorParameters { + pub client_s_pk: Vec, + pub client_s_sk: Vec, + pub client_e_pk: Vec, + pub client_e_sk: Vec, + pub server_s_pk: Vec, + pub server_s_sk: Vec, + pub server_e_pk: Vec, + pub server_e_sk: Vec, + pub client_identifier: Vec, + pub server_identifier: Vec, + pub password: Vec, + pub blinding_factor_registration: Vec, + pub oprf_key: Vec, + pub envelope_nonce: Vec, + pub client_nonce: Vec, + pub server_nonce: Vec, + pub info1: Vec, + pub info2: Vec, + pub registration_request: Vec, + pub registration_response: Vec, + pub registration_upload: Vec, + pub credential_request: Vec, + pub blinding_factor_login: Vec, + pub credential_response: Vec, + pub credential_finalization: Vec, + pub password_file: Vec, + pub export_key: Vec, + pub shared_secret: Vec, +} + +static TEST_VECTOR: &str = r#" +{ + "auth_key": "5da3dcc6721e638fa4eef769f7af5bd30f7132308d41fff00f0cf9745e9090235f75697a0dc58fe8692204c4702a20db4a6ee59119fe6ab4173322f06b896309", + "blinding_factor_login": "ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63e3263503", + "blinding_factor_registration": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03", + "client_e_pk": "645a5d8c715454a2ba77918d44234d9cbbd723d03113b68c9ea076ab4e776067", + "client_e_sk": "9be30157b01e572d9ee296defd7bd2f98fed15fbbf3cbed847547d6759c73c08", + "client_identifier": "0aa0f9643e2ecaf3075201e9e762371d1b5cdfac674494b2d5ed0b0908c46e5b", + "client_nonce": "a9f275415d9bee6b86c2c390b7577c7684d70479c7e23bfbc01652a7464fb1cc", + "client_s_pk": "0aa0f9643e2ecaf3075201e9e762371d1b5cdfac674494b2d5ed0b0908c46e5b", + "client_s_sk": "8772da7da9af3fcdae2a23cf8a34a49954a56b48ae2f866b71113132664f0e09", + "credential_finalization": "b8de4c989e5caefb57f5a506fd469c4e329aa3076d17a8c8648266c2c2db6f4c33be2161c11c63b7039459de0f9f7f9c5b6a429e6002ac03516ba3979fc970f7", + "credential_request": "b68e0e356f8490fa9c3bed952e16cc02db21eda686b3c484f3d9d912caa41f76a9f275415d9bee6b86c2c390b7577c7684d70479c7e23bfbc01652a7464fb1cc000968656c6c6f20626f62645a5d8c715454a2ba77918d44234d9cbbd723d03113b68c9ea076ab4e776067", + "credential_response": "e83812f06568d57b8cdfdcc90fe91454e21bd25dd2a1c32dd1599a2e4a4b6c35002008a499182bb67b86190e2d8315ca1ae4a6cdd503db50bed3d90cc7670dcb9f2b0090f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d60022147e90c0c40a3652bdef905ab2f61386a4119a121f3411c1da6463ac5d0c633c6e6eaf0eb0d45ab8afb746ccd00a1e96cbb2eca09cdc61520772b32978470dcdb0f8aaec7016681d431f194a447bea2c0be84c7191837e362f0f69b7f29676e15d0351c529e997a215d410ef6f387a2f1e708729d2316bf27b75138fb440020c1fcdbcae720a4acd4a44602024a9d6f2d536baf230f8301c9f9ff5113e229549fd05000f3b629da91fc6405e82b16c18ae4be1ff00020b37dfa8dbd7eeea4bef1c678693b2f1e2da0370e6792f5ab860fad7a098f0d5d00d4d4205aae4d70887fb28edc16f5f1268aa021dba8810909cb2f9e6", + "envU": "0090f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d60022147e90c0c40a3652bdef905ab2f61386a4119a121f3411c1da6463ac5d0c633c6e6eaf0eb0d45ab8afb746ccd00a1e96cbb2eca09cdc61520772b32978470dcdb0f8aaec7016681d431f194a447bea2c0be84c7191837e362f0f69b7f29676e15d03", + "envelope_nonce": "90f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d6", + "export_key": "453f61eae9b840519eb025d2297eb31ad13c960deba40c2799d2cd4b5034161e1c13695877b460643386ed5e41d5ec9bf2bc845592469fab50abf77d4812fcf0", + "handshake_secret": "67b75ff7291d68e20bdbc6a15209aba0c61b7b690d69f293db3761ad62f6014476daeb8ce1235e2d29404bdba3b843dbd544250751e51af747010bb25c310fd5", + "info1": "68656c6c6f20626f62", + "info2": "6772656574696e677320616c696365", + "ke2": "44feeac8078ca91e22ac0133fe33890207c3d927ace293fffa66296c74e82e09ffb1ac00f76198e0ebe74eab722ac49173bdee616f32b3a7416ddee1d8075d54", + "km2": "0d5b3bd8e1f6b937570b501d5647cb061276bba36cfc3213205c36d2240e8fe55f2338108f52947fd589ce344ce0a677ddb178c881deb4d075874ade133b99c7", + "km3": "8ed3a633ad925fedcf19f9f4acca15f8bfbd147ba11e0950a2ae6c3efb6e41aef75faa20ae0f9104801bdb8a4edbd23a5cc19fe626a00c50e4718d8dedb180fd", + "oprf_key": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b", + "password": "436f7272656374486f72736542617474657279537461706c65", + "pseudorandom_pad": "145e17b21e779ffd82223e70913999b20088ceb7747cbfee5c0f12bd6c3e05736067", + "registration_request": "241b621c417c0705b5ea7a8b7cdd5039fd61e6b63effe2a44418164c4d49003e", + "registration_response": "1867301bcc67bdf8e640b7d6edcbe2a65488446417b50d30cdba66ccb379e572002008a499182bb67b86190e2d8315ca1ae4a6cdd503db50bed3d90cc7670dcb9f2b", + "registration_upload": "00200aa0f9643e2ecaf3075201e9e762371d1b5cdfac674494b2d5ed0b0908c46e5b0090f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d60022147e90c0c40a3652bdef905ab2f61386a4119a121f3411c1da6463ac5d0c633c6e6eaf0eb0d45ab8afb746ccd00a1e96cbb2eca09cdc61520772b32978470dcdb0f8aaec7016681d431f194a447bea2c0be84c7191837e362f0f69b7f29676e15d03", + "rwdU": "17eacc90e168aafd6cf5cbf4aba71e6c08d16800e4880a3fcc5252a1b5072ab7253942f3b4da8a2132863730970ca7a737b5bec96e038a8fa2915b4eeb678180", + "server_e_pk": "bcae720a4acd4a44602024a9d6f2d536baf230f8301c9f9ff5113e229549fd05", + "server_e_sk": "c02a638f90e8a9fd25b69252ec7465b7dd33aa905ba947c013b6e6be6edf3909", + "server_identifier": "08a499182bb67b86190e2d8315ca1ae4a6cdd503db50bed3d90cc7670dcb9f2b", + "server_nonce": "51c529e997a215d410ef6f387a2f1e708729d2316bf27b75138fb440020c1fcd", + "server_s_pk": "08a499182bb67b86190e2d8315ca1ae4a6cdd503db50bed3d90cc7670dcb9f2b", + "server_s_sk": "852b5300434b5f39f7885ee812255747793e04846b910cf9ae80a5a904bd3a03", + "shared_secret": "d0336451e8de1df9a648772c86f97a1b9ed860297f0fafa13faa4e23bb44b9355ec196a25ba39e7db5b9102a06fa09ca778b10aee5acb11f4eb5826b1370e98c", + "password_file": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b0aa0f9643e2ecaf3075201e9e762371d1b5cdfac674494b2d5ed0b0908c46e5b0090f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d60022147e90c0c40a3652bdef905ab2f61386a4119a121f3411c1da6463ac5d0c633c6e6eaf0eb0d45ab8afb746ccd00a1e96cbb2eca09cdc61520772b32978470dcdb0f8aaec7016681d431f194a447bea2c0be84c7191837e362f0f69b7f29676e15d03" +} +"#; + +fn decode(values: &Value, key: &str) -> Option> { + values[key] + .as_str() + .and_then(|s| hex::decode(&s.to_string()).ok()) +} + +macro_rules! parse { + ( $v:ident, $s:expr ) => { + match decode(&$v, $s) { + Some(x) => x, + None => vec![], + } + }; +} + +fn populate_test_vectors(values: &Value) -> TestVectorParameters { + TestVectorParameters { + client_s_pk: parse!(values, "client_s_pk"), + client_s_sk: parse!(values, "client_s_sk"), + client_e_pk: parse!(values, "client_e_pk"), + client_e_sk: parse!(values, "client_e_sk"), + server_s_pk: parse!(values, "server_s_pk"), + server_s_sk: parse!(values, "server_s_sk"), + server_e_pk: parse!(values, "server_e_pk"), + server_e_sk: parse!(values, "server_e_sk"), + client_identifier: parse!(values, "client_identifier"), + server_identifier: parse!(values, "server_identifier"), + password: parse!(values, "password"), + blinding_factor_registration: parse!(values, "blinding_factor_registration"), + oprf_key: parse!(values, "oprf_key"), + envelope_nonce: parse!(values, "envelope_nonce"), + client_nonce: parse!(values, "client_nonce"), + server_nonce: parse!(values, "server_nonce"), + info1: parse!(values, "info1"), + info2: parse!(values, "info2"), + registration_request: parse!(values, "registration_request"), + registration_response: parse!(values, "registration_response"), + registration_upload: parse!(values, "registration_upload"), + credential_request: parse!(values, "credential_request"), + credential_response: parse!(values, "credential_response"), + credential_finalization: parse!(values, "credential_finalization"), + blinding_factor_login: parse!(values, "blinding_factor_login"), + password_file: parse!(values, "password_file"), + export_key: parse!(values, "export_key"), + shared_secret: parse!(values, "shared_secret"), + } +} + +#[test] +fn test_registration_request() -> Result<(), ProtocolError> { + let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); + let mut rng = CycleRng::new(parameters.blinding_factor_registration.to_vec()); + let client_registration_start_result = + ClientRegistration::::start(&mut rng, ¶meters.password)?; + assert_eq!( + hex::encode(¶meters.registration_request), + hex::encode(client_registration_start_result.message.serialize()) + ); + Ok(()) +} + +#[test] +fn test_registration_response() -> Result<(), ProtocolError> { + let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); + let mut oprf_key_rng = CycleRng::new(parameters.oprf_key); + let server_registration_start_result = + ServerRegistration::::start( + &mut oprf_key_rng, + RegistrationRequest::deserialize(¶meters.registration_request[..]).unwrap(), + &Key::try_from(¶meters.server_s_pk[..]).unwrap(), + )?; + assert_eq!( + hex::encode(parameters.registration_response), + hex::encode(server_registration_start_result.message.serialize()) + ); + Ok(()) +} + +#[test] +fn test_registration_upload() -> Result<(), ProtocolError> { + let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); + + let mut rng = CycleRng::new(parameters.blinding_factor_registration.to_vec()); + let client_registration_start_result = + ClientRegistration::::start(&mut rng, ¶meters.password)?; + + let sk_u_and_nonce: Vec = [parameters.client_s_sk, parameters.envelope_nonce].concat(); + let mut finish_registration_rng = CycleRng::new(sk_u_and_nonce); + let result = client_registration_start_result.state.finish( + &mut finish_registration_rng, + RegistrationResponse::deserialize(¶meters.registration_response[..]).unwrap(), + ClientRegistrationFinishParameters::default(), + )?; + + assert_eq!( + hex::encode(parameters.registration_upload), + hex::encode(result.message.serialize()) + ); + assert_eq!( + hex::encode(parameters.export_key), + hex::encode(result.export_key.to_vec()) + ); + + Ok(()) +} + +#[test] +fn test_password_file() -> Result<(), ProtocolError> { + let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); + + let mut oprf_key_rng = CycleRng::new(parameters.oprf_key); + let server_registration_start_result = + ServerRegistration::::start( + &mut oprf_key_rng, + RegistrationRequest::deserialize(¶meters.registration_request[..]).unwrap(), + &Key::try_from(¶meters.server_s_pk[..]).unwrap(), + )?; + + let password_file = server_registration_start_result + .state + .finish(RegistrationUpload::deserialize(¶meters.registration_upload[..]).unwrap())?; + assert_eq!( + hex::encode(parameters.password_file), + hex::encode(password_file.to_bytes()) + ); + Ok(()) +} + +#[test] +fn test_credential_request() -> Result<(), ProtocolError> { + let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); + + let client_login_start = [ + parameters.blinding_factor_login, + parameters.client_e_sk, + parameters.client_nonce, + ] + .concat(); + let mut client_login_start_rng = CycleRng::new(client_login_start); + let client_login_start_result = ClientLogin::::start( + &mut client_login_start_rng, + ¶meters.password, + ClientLoginStartParameters::WithInfo(parameters.info1), + )?; + assert_eq!( + hex::encode(¶meters.credential_request), + hex::encode(client_login_start_result.message.serialize()) + ); + Ok(()) +} + +#[test] +fn test_credential_response() -> Result<(), ProtocolError> { + let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); + + let mut server_e_sk_and_nonce_rng = + CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat()); + let server_login_start_result = ServerLogin::::start( + &mut server_e_sk_and_nonce_rng, + ServerRegistration::try_from(¶meters.password_file[..]).unwrap(), + &Key::try_from(¶meters.server_s_sk[..]).unwrap(), + CredentialRequest::::deserialize( + ¶meters.credential_request[..], + ) + .unwrap(), + ServerLoginStartParameters::WithInfo(parameters.info2.to_vec()), + )?; + assert_eq!( + hex::encode(¶meters.info1), + hex::encode(server_login_start_result.plain_info), + ); + assert_eq!( + hex::encode(¶meters.credential_response), + hex::encode(server_login_start_result.message.serialize()) + ); + Ok(()) +} + +#[test] +fn test_credential_finalization() -> Result<(), ProtocolError> { + let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); + + let client_login_start = [ + parameters.blinding_factor_login, + parameters.client_e_sk, + parameters.client_nonce, + ] + .concat(); + let mut client_login_start_rng = CycleRng::new(client_login_start); + let client_login_start_result = ClientLogin::::start( + &mut client_login_start_rng, + ¶meters.password, + ClientLoginStartParameters::WithInfo(parameters.info1), + )?; + + let client_login_finish_result = client_login_start_result.state.finish( + CredentialResponse::::deserialize( + ¶meters.credential_response[..], + )?, + ClientLoginFinishParameters::default(), + )?; + + assert_eq!( + hex::encode(¶meters.info2), + hex::encode(&client_login_finish_result.confidential_info) + ); + assert_eq!( + hex::encode(¶meters.shared_secret), + hex::encode(&client_login_finish_result.shared_secret) + ); + assert_eq!( + hex::encode(¶meters.credential_finalization), + hex::encode(client_login_finish_result.message.to_bytes()) + ); + assert_eq!( + hex::encode(¶meters.export_key), + hex::encode(client_login_finish_result.export_key) + ); + Ok(()) +} + +#[test] +fn test_server_login_finish() -> Result<(), ProtocolError> { + let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); + + let mut server_e_sk_and_nonce_rng = + CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat()); + let server_login_start_result = ServerLogin::::start( + &mut server_e_sk_and_nonce_rng, + ServerRegistration::try_from(¶meters.password_file[..]).unwrap(), + &Key::try_from(¶meters.server_s_sk[..]).unwrap(), + CredentialRequest::::deserialize( + ¶meters.credential_request[..], + ) + .unwrap(), + ServerLoginStartParameters::WithInfo(parameters.info2.to_vec()), + )?; + + let server_login_result = + server_login_start_result + .state + .finish(CredentialFinalization::try_from( + ¶meters.credential_finalization[..], + )?)?; + + assert_eq!( + hex::encode(parameters.shared_secret), + hex::encode(server_login_result.shared_secret) + ); + + Ok(()) +} + +fn test_complete_flow( + registration_password: &[u8], + login_password: &[u8], +) -> Result<(), ProtocolError> { + let mut client_rng = OsRng; + let mut server_rng = OsRng; + let server_kp = Ristretto255Sha512NoSlowHash::generate_random_keypair(&mut server_rng); + let client_registration_start_result = + ClientRegistration::::start( + &mut client_rng, + registration_password, + )?; + let server_registration_start_result = + ServerRegistration::::start( + &mut server_rng, + client_registration_start_result.message, + server_kp.public(), + )?; + let client_registration_finish_result = client_registration_start_result.state.finish( + &mut client_rng, + server_registration_start_result.message, + ClientRegistrationFinishParameters::default(), + )?; + let p_file = server_registration_start_result + .state + .finish(client_registration_finish_result.message)?; + let client_login_start_result = ClientLogin::::start( + &mut client_rng, + login_password, + ClientLoginStartParameters::default(), + )?; + let server_login_start_result = ServerLogin::::start( + &mut server_rng, + p_file, + &server_kp.private(), + client_login_start_result.message, + ServerLoginStartParameters::default(), + )?; + + let client_login_result = client_login_start_result.state.finish( + server_login_start_result.message, + ClientLoginFinishParameters::default(), + ); + + if hex::encode(registration_password) == hex::encode(login_password) { + let client_login_finish_result = client_login_result?; + let server_login_finish_result = server_login_start_result + .state + .finish(client_login_finish_result.message)?; + + assert_eq!( + hex::encode(server_login_finish_result.shared_secret), + hex::encode(client_login_finish_result.shared_secret) + ); + assert_eq!( + hex::encode(client_registration_finish_result.export_key), + hex::encode(client_login_finish_result.export_key) + ); + } else { + let res = matches!( + client_login_result, + Err(ProtocolError::VerificationError( + PakeError::InvalidLoginError + )) + ); + assert!(res); + } + + Ok(()) +} + +#[test] +fn test_complete_flow_success() -> Result<(), ProtocolError> { + test_complete_flow(b"good password", b"good password") +} + +#[test] +fn test_complete_flow_fail() -> Result<(), ProtocolError> { + test_complete_flow(b"good password", b"bad password") +}