// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is dual-licensed under either the MIT license found in the // LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory // of this source tree. You may select, at your option, one of the above-listed // licenses. //! Provides the main OPAQUE API use core::ops::Add; use derive_where::derive_where; use digest::Output; use generic_array::typenum::{Sum, Unsigned}; use generic_array::{ArrayLength, GenericArray}; use hkdf::Hkdf; use hkdf::SimpleHkdfExtract as HkdfExtract; use rand::{CryptoRng, Rng}; use subtle::{Choice, ConstantTimeEq, CtOption}; use voprf::{BlindedElement, Group as _, OprfClient, OprfClientLen}; use zeroize::Zeroizing; use crate::ciphersuite::{CipherSuite, KeGroup, KeHash, OprfGroup, OprfHash}; use crate::envelope::{Envelope, EnvelopeLen}; use crate::errors::{InternalError, ProtocolError}; use crate::hash::OutputSize; use crate::key_exchange::group::Group; use crate::key_exchange::shared::NonceLen; use crate::key_exchange::{ Deserialize, Ke1MessageLen, Ke1StateLen, Ke2StateLen, KeyExchange, Serialize, SerializedContext, SerializedCredentialResponse, SerializedIdentifiers, }; use crate::keypair::{ KeyPair, OprfSeed, OprfSeedSerialization, PrivateKey, PrivateKeySerialization, PublicKey, }; use crate::ksf::Ksf; use crate::messages::{CredentialRequestLen, RegistrationUploadLen}; use crate::serialization::{ConcatExt, GenericArrayExt, SliceExt}; use crate::{ CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLoginBuilder, }; /////////////// // Constants // // ========= // /////////////// const STR_CREDENTIAL_RESPONSE_PAD: &[u8; 21] = b"CredentialResponsePad"; const STR_MASKING_KEY: &[u8; 10] = b"MaskingKey"; const STR_OPRF_KEY: &[u8; 7] = b"OprfKey"; const STR_OPAQUE_DERIVE_KEY_PAIR: &[u8; 20] = b"OPAQUE-DeriveKeyPair"; //////////////////////////// // High-level API Structs // // ====================== // //////////////////////////// /// The state elements the server holds upon setup #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound( deserialize = " as Group>::Pk: serde::Deserialize<'de>, as \ Group>::Sk: serde::Deserialize<'de>, SK: serde::Deserialize<'de>, OS: \ serde::Deserialize<'de>", serialize = " as Group>::Pk: serde::Serialize, as Group>::Sk: \ serde::Serialize, SK: serde::Serialize, OS: serde::Serialize" )) )] #[derive_where(Clone)] #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; as Group>::Pk, as Group>::Sk, SK, OS )] pub struct ServerSetup< CS: CipherSuite, SK: Clone = PrivateKey>, OS: Clone = OprfSeed>, > { oprf_seed: OS, keypair: KeyPair, SK>, pub(crate) dummy_pk: PublicKey>, } /// The state elements the client holds to register itself #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = "") )] #[derive_where(Clone, ZeroizeOnDrop)] #[derive_where( Debug, Eq, Hash, PartialEq; voprf::OprfClient, voprf::BlindedElement, )] pub struct ClientRegistration { pub(crate) oprf_client: OprfClient, pub(crate) blinded_element: BlindedElement, } /// The state elements the server holds to record a registration #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound( deserialize = " as Group>::Pk: serde::Deserialize<'de>", serialize = " as Group>::Pk: serde::Serialize" )) )] #[derive_where(Clone, ZeroizeOnDrop)] #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; as Group>::Pk)] pub struct ServerRegistration(pub(crate) RegistrationUpload); /// The state elements the client holds to perform a login #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound( deserialize = "::KE1Message: serde::Deserialize<'de>, \ ::KE1State: serde::Deserialize<'de>", serialize = "::KE1Message: serde::Serialize, \ ::KE1State: serde::Serialize" )) )] #[derive_where(Clone, ZeroizeOnDrop)] #[derive_where( Debug, Eq, Hash, PartialEq; voprf::OprfClient, ::KE1State, CredentialRequest, )] pub struct ClientLogin { pub(crate) oprf_client: OprfClient, pub(crate) ke1_state: ::KE1State, pub(crate) credential_request: CredentialRequest, } /// The state elements the server holds to record a login #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound( deserialize = "::KE2State: serde::Deserialize<'de>", serialize = "::KE2State: serde::Serialize" )) )] #[derive_where(Clone, ZeroizeOnDrop)] #[derive_where(Debug, Eq, Hash, PartialEq; ::KE2State)] pub struct ServerLogin { ke2_state: ::KE2State, } //////////////////////////////// // High-level Implementations // // ========================== // //////////////////////////////// // Server Setup // ============ impl ServerSetup>> { /// Generate a new instance of server setup pub fn new(rng: &mut R) -> Self { let keypair = KeyPair::random(rng); Self::new_with_key_pair(rng, keypair) } } /// Length of [`ServerSetup`] in bytes for serialization. pub type ServerSetupLen< CS: CipherSuite, SK: PrivateKeySerialization>, OS: OprfSeedSerialization, SK::Error>, > = Sum, as Group>::PkLen>; impl ServerSetup { /// Create [`ServerSetup`] with the given keypair and OPRF seed. /// /// This function should not be used to restore a previously-existing /// instance of [`ServerSetup`]. Instead, use [`ServerSetup::serialize`] and /// [`ServerSetup::deserialize`] for this purpose. pub fn new_with_key_pair_and_seed( rng: &mut R, keypair: KeyPair, SK>, oprf_seed: OS, ) -> Self { Self { oprf_seed, keypair, dummy_pk: KeyPair::>::random(rng).public().clone(), } } /// The information required to generate the key material for /// [`ServerRegistration::start_with_key_material()`] and /// [`ServerLogin::builder_with_key_material()`]. pub fn key_material_info<'ci>( &self, credential_identifier: &'ci [u8], ) -> KeyMaterialInfo<'ci, OS> { KeyMaterialInfo { ikm: self.oprf_seed.clone(), info: [credential_identifier, STR_OPRF_KEY], } } /// Serialization into bytes pub fn serialize(&self) -> GenericArray> where SK: PrivateKeySerialization>, OS: OprfSeedSerialization, SK::Error>, // ServerSetup: Hash + KeSk + KePk OS::Len: Add, Sum: ArrayLength + Add< as Group>::PkLen>, ServerSetupLen: ArrayLength, { self.oprf_seed .serialize() .cat(SK::serialize_key_pair(&self.keypair)) .cat(self.dummy_pk.serialize()) } /// Deserialization from bytes pub fn deserialize(mut input: &[u8]) -> Result> where SK: PrivateKeySerialization>, OS: OprfSeedSerialization, SK::Error>, { Ok(Self { oprf_seed: OS::deserialize_take(&mut input)?, keypair: SK::deserialize_take_key_pair(&mut input)?, dummy_pk: PublicKey::deserialize_take(&mut input) .map_err(ProtocolError::into_custom)?, }) } /// Returns the keypair pub fn keypair(&self) -> &KeyPair, SK> { &self.keypair } } impl ServerSetup { /// Create [`ServerSetup`] with the given keypair /// /// This function should not be used to restore a previously-existing /// instance of [`ServerSetup`]. Instead, use [`ServerSetup::serialize`] and /// [`ServerSetup::deserialize`] for this purpose. pub fn new_with_key_pair( rng: &mut R, keypair: KeyPair, SK>, ) -> Self { let mut oprf_seed = Output::>::default(); rng.fill_bytes(&mut oprf_seed); Self::new_with_key_pair_and_seed(rng, keypair, OprfSeed(oprf_seed)) } } /// The information required to generate the key material for /// [`ServerRegistration::start_with_key_material()`] and /// [`ServerLogin::builder_with_key_material()`]. /// /// Use a HKDF, with the input key material [`ikm`](Self::ikm), expand operation /// with [`info`](Self::info) with an output length /// of [`CS::OprfCs::ScalarLen`](voprf::Group::ScalarLen). pub struct KeyMaterialInfo<'ci, OS: Clone> { /// Input key material for the HKDF. pub ikm: OS, /// Info for the HKDF expand operation. pub info: [&'ci [u8]; 2], } // Registration // ============ pub(crate) type ClientRegistrationLen = Sum< as voprf::Group>::ScalarLen, as voprf::Group>::ElemLen>; impl ClientRegistration { /// Serialization into bytes pub fn serialize(&self) -> GenericArray> where // ClientRegistration: KgSk + KgPk as voprf::Group>::ScalarLen: Add< as voprf::Group>::ElemLen> + ArrayLength, as voprf::Group>::ElemLen: ArrayLength, ClientRegistrationLen: ArrayLength, { GenericArray::from_ha0_4(self.oprf_client.serialize()) .cat(GenericArray::from_ha0_4(self.blinded_element.serialize())) } /// Deserialization from bytes pub fn deserialize(mut input: &[u8]) -> Result { let oprf_client = OprfClient::deserialize(input)?; input = &input[OprfClientLen::::USIZE..]; let blinded_element = BlindedElement::deserialize(input)?; Ok(Self { oprf_client, blinded_element, }) } /// Returns an initial "blinded" request to send to the server, as well as a /// [`ClientRegistration`] pub fn start( blinding_factor_rng: &mut R, password: &[u8], ) -> Result, ProtocolError> { let blind_result = blind::(blinding_factor_rng, password)?; Ok(ClientRegistrationStartResult { message: RegistrationRequest { blinded_element: blind_result.message.clone(), }, state: Self { oprf_client: blind_result.state, blinded_element: blind_result.message, }, }) } /// "Unblinds" the server's answer and returns a final message containing /// cryptographic identifiers, to be sent to the server on setup /// finalization pub fn finish( self, rng: &mut R, password: &[u8], registration_response: RegistrationResponse, params: ClientRegistrationFinishParameters, ) -> Result, ProtocolError> { // Check for reflected value from server and halt if detected if self .blinded_element .value() .ct_eq(®istration_response.evaluation_element.value()) .into() { return Err(ProtocolError::ReflectedValueError); } #[cfg_attr(not(test), allow(unused_variables))] let (randomized_pwd, randomized_pwd_hasher) = get_password_derived_key::( password, self.oprf_client.clone(), registration_response.evaluation_element, params.ksf, )?; let mut masking_key = Output::>::default(); randomized_pwd_hasher .expand(STR_MASKING_KEY, &mut masking_key) .map_err(|_| InternalError::HkdfError)?; let result = Envelope::::seal( rng, &randomized_pwd_hasher, ®istration_response.server_s_pk, params.identifiers, )?; Ok(ClientRegistrationFinishResult { message: RegistrationUpload { envelope: result.0, masking_key, client_s_pk: result.1, }, export_key: result.2, server_s_pk: registration_response.server_s_pk, #[cfg(test)] state: self, #[cfg(test)] auth_key: result.3, #[cfg(test)] randomized_pwd, }) } } /// Length of [`ServerRegistration`] in bytes for serialization. pub type ServerRegistrationLen = RegistrationUploadLen; impl ServerRegistration { /// Serialization into bytes pub fn serialize(&self) -> GenericArray> where // RegistrationUpload: (KePk + Hash) + Envelope as Group>::PkLen: Add>>, Sum< as Group>::PkLen, OutputSize>>: ArrayLength + Add>, RegistrationUploadLen: ArrayLength, // ServerRegistration = RegistrationUpload { self.0.serialize() } /// Deserialization from bytes pub fn deserialize(input: &[u8]) -> Result { Ok(Self(RegistrationUpload::deserialize(input)?)) } /// Create a [`RegistrationResponse`] with a remote OPRF seed. To generate /// the `key_material` see [`ServerSetup::key_material_info()`]. /// /// See [`ServerRegistration::start()`] for the regular path. pub fn start_with_key_material( server_setup: &ServerSetup, key_material: GenericArray as voprf::Group>::ScalarLen>, message: RegistrationRequest, ) -> Result, ProtocolError> { let oprf_key = oprf_key_from_key_material::(key_material)?; let server = voprf::OprfServer::new_with_key(&oprf_key)?; let evaluation_element = server.blind_evaluate(&message.blinded_element); Ok(ServerRegistrationStartResult { message: RegistrationResponse { evaluation_element, server_s_pk: server_setup.keypair().public().clone(), }, #[cfg(test)] oprf_key, }) } /// From the client's "blinded" password, returns a response to be sent back /// to the client, as well as a [`ServerRegistration`] pub fn start( server_setup: &ServerSetup, message: RegistrationRequest, credential_identifier: &[u8], ) -> Result, ProtocolError> { let KeyMaterialInfo { ikm: oprf_seed, info, } = server_setup.key_material_info(credential_identifier); let key_material = oprf_key_material::(&oprf_seed.0, &info)?; Self::start_with_key_material(server_setup, key_material, message) } /// From the client's cryptographic identifiers, fully populates and returns /// a [`ServerRegistration`] pub fn finish(message: RegistrationUpload) -> Self { Self(message) } // Creates a dummy instance used for faking a [CredentialResponse] pub(crate) fn dummy( rng: &mut R, server_setup: &ServerSetup, ) -> Self { Self(RegistrationUpload::dummy(rng, server_setup)) } } // Login // ===== pub(crate) type ClientLoginLen = Sum as voprf::Group>::ScalarLen, CredentialRequestLen>, Ke1StateLen>; impl ClientLogin { /// Serialization into bytes pub fn serialize(&self) -> GenericArray> where // CredentialRequest: KgPk + Ke1Message ::KE1Message: Serialize, as voprf::Group>::ElemLen: Add>, CredentialRequestLen: ArrayLength, // ClientLogin: KgSk + CredentialRequest + Ke1State as voprf::Group>::ScalarLen: Add>, ::KE1State: Serialize, Sum< as voprf::Group>::ScalarLen, CredentialRequestLen>: ArrayLength + Add>, ClientLoginLen: ArrayLength, { GenericArray::from_ha0_4(self.oprf_client.serialize()) .cat(self.credential_request.serialize()) .cat(self.ke1_state.serialize()) } /// Deserialization from bytes pub fn deserialize(mut input: &[u8]) -> Result where ::KE1Message: Deserialize + Serialize, ::KE1State: Deserialize + Serialize, { let oprf_client = OprfClient::deserialize(input)?; input = &input[OprfClientLen::::USIZE..]; Ok(Self { oprf_client, credential_request: CredentialRequest::deserialize_take(&mut input)?, ke1_state: ::KE1State::deserialize_take(&mut input)?, }) } } impl ClientLogin { /// Returns an initial "blinded" password request to send to the server, as /// well as a [`ClientLogin`] pub fn start( rng: &mut R, password: &[u8], ) -> Result, ProtocolError> { let blind_result = blind::(rng, password)?; let ke1_result = CS::KeyExchange::generate_ke1(rng)?; let credential_request = CredentialRequest { blinded_element: blind_result.message, ke1_message: ke1_result.message, }; Ok(ClientLoginStartResult { message: credential_request.clone(), state: Self { oprf_client: blind_result.state, ke1_state: ke1_result.state, credential_request, }, }) } /// "Unblinds" the server's answer and returns the opened assets from the /// server pub fn finish( self, rng: &mut R, password: &[u8], credential_response: CredentialResponse, params: ClientLoginFinishParameters, ) -> Result, ProtocolError> { // Check if beta value from server is equal to alpha value from client if self .credential_request .blinded_element .value() .ct_eq(&credential_response.evaluation_element.value()) .into() { return Err(ProtocolError::ReflectedValueError); } let (_, randomized_pwd_hasher) = get_password_derived_key::( password, self.oprf_client.clone(), credential_response.evaluation_element.clone(), params.ksf, )?; let mut masking_key = Output::>::default(); randomized_pwd_hasher .expand(STR_MASKING_KEY, &mut masking_key) .map_err(|_| InternalError::HkdfError)?; let (server_s_pk, envelope) = unmask_response::( &masking_key, &credential_response.masking_nonce, &credential_response.masked_response, ) .map_err(|e| match e { ProtocolError::SerializationError => ProtocolError::InvalidLoginError, err => err, })?; let opened_envelope = envelope .open( &randomized_pwd_hasher, server_s_pk.clone(), params.identifiers, ) .map_err(|e| match e { ProtocolError::LibraryError(InternalError::SealOpenHmacError) => { ProtocolError::InvalidLoginError } err => err, })?; let context = SerializedContext::from(params.context)?; let result = CS::KeyExchange::generate_ke3( rng, self.credential_request.to_parts(), self.credential_request.ke1_message.clone(), credential_response.to_parts(), &self.ke1_state, credential_response.ke2_message, server_s_pk.clone(), opened_envelope.client_static_keypair.private().clone(), opened_envelope.identifiers, context, )?; Ok(ClientLoginFinishResult { message: CredentialFinalization { ke3_message: result.message, }, session_key: result.session_key, export_key: opened_envelope.export_key, server_s_pk, #[cfg(test)] state: self, #[cfg(test)] handshake_secret: result.handshake_secret, #[cfg(test)] client_mac_key: result.km3, }) } } impl ServerLogin { /// Serialization into bytes pub fn serialize(&self) -> GenericArray> where ::KE2State: Serialize, { self.ke2_state.serialize() } /// Deserialization from bytes pub fn deserialize(mut bytes: &[u8]) -> Result where ::KE2State: Deserialize, { Ok(Self { ke2_state: <::KE2State as Deserialize>::deserialize_take( &mut bytes, )?, }) } /// Create a [`ServerLoginBuilder`] with a remote OPRF seed and private key. /// To generate the `key_material` see /// [`ServerSetup::key_material_info()`]. /// /// See [`ServerLogin::start()`] for the regular path. Or /// [`ServerLogin::builder()`] with just a remote private key. pub fn builder_with_key_material<'a, R: Rng + CryptoRng, SK: Clone, OS: Clone>( rng: &mut R, server_setup: &ServerSetup, key_material: GenericArray as voprf::Group>::ScalarLen>, password_file: Option>, credential_request: CredentialRequest, ServerLoginParameters { context, identifiers, }: ServerLoginParameters<'a, 'a>, ) -> Result, ProtocolError> { let record = CtOption::new( ServerRegistration::dummy(rng, server_setup), Choice::from(password_file.is_none() as u8), ) .into_option() .unwrap_or_else(|| password_file.unwrap()); let client_s_pk = record.0.client_s_pk.clone(); let context = SerializedContext::from(context)?; let server_s_pk = server_setup.keypair.public(); let mut masking_nonce = GenericArray::<_, NonceLen>::default(); rng.fill_bytes(&mut masking_nonce); let masked_response = mask_response( &record.0.masking_key, &masking_nonce, server_s_pk, &record.0.envelope, )?; let serialized_client_s_pk = client_s_pk.serialize(); let serialized_server_s_pk = server_s_pk.serialize(); let identifiers = SerializedIdentifiers::>::from_identifiers( identifiers, serialized_client_s_pk.clone(), serialized_server_s_pk.clone(), )?; let oprf_key = oprf_key_from_key_material::(key_material)?; let server = voprf::OprfServer::new_with_key(&oprf_key).map_err(ProtocolError::from)?; let evaluation_element = server.blind_evaluate(&credential_request.blinded_element); let credential_response = SerializedCredentialResponse::new( &evaluation_element, masking_nonce, masked_response.clone(), ); let ke2_builder = CS::KeyExchange::ke2_builder( rng, credential_request.to_parts(), credential_request.ke1_message.clone(), credential_response, client_s_pk, identifiers, context, )?; Ok(ServerLoginBuilder { server_s_sk: server_setup.keypair().private().clone(), evaluation_element, masking_nonce: Zeroizing::new(masking_nonce), masked_response, #[cfg(test)] oprf_key: Zeroizing::new(oprf_key), ke2_builder, }) } /// Create a [`ServerLoginBuilder`] to use with a remote private key. /// /// See [`ServerLogin::start()`] for the regular path. pub fn builder<'a, R: Rng + CryptoRng, SK: Clone>( rng: &mut R, server_setup: &ServerSetup, password_file: Option>, credential_request: CredentialRequest, credential_identifier: &[u8], params: ServerLoginParameters<'a, 'a>, ) -> Result, ProtocolError> { let KeyMaterialInfo { ikm: oprf_seed, info, } = server_setup.key_material_info(credential_identifier); let key_material = oprf_key_material::(&oprf_seed.0, &info)?; Self::builder_with_key_material( rng, server_setup, key_material, password_file, credential_request, params, ) } pub(crate) fn build( builder: ServerLoginBuilder, input: ::KE2BuilderInput, ) -> Result, ProtocolError> { let result = CS::KeyExchange::build_ke2(builder.ke2_builder.clone(), input)?; let credential_response = CredentialResponse { evaluation_element: builder.evaluation_element.clone(), masking_nonce: *builder.masking_nonce, masked_response: builder.masked_response.clone(), ke2_message: result.message, }; Ok(ServerLoginStartResult { message: credential_response, state: Self { ke2_state: result.state, }, #[cfg(test)] handshake_secret: result.handshake_secret, #[cfg(test)] server_mac_key: result.km2, #[cfg(test)] oprf_key: (*builder.oprf_key).clone(), }) } /// From the client's "blinded" password, returns a challenge to be sent /// back to the client, as well as a [`ServerLogin`] pub fn start( rng: &mut R, server_setup: &ServerSetup, password_file: Option>, credential_request: CredentialRequest, credential_identifier: &[u8], parameters: ServerLoginParameters, ) -> Result, ProtocolError> { let builder = Self::builder( rng, server_setup, password_file, credential_request, credential_identifier, parameters, )?; let input = CS::KeyExchange::generate_ke2_input( &builder.ke2_builder, rng, server_setup.keypair.private(), ); Self::build(builder, input) } /// From the client's second and final message, check the client's /// authentication and produce a message transport pub fn finish( self, message: CredentialFinalization, parameters: ServerLoginParameters, ) -> Result, ProtocolError> { let context = SerializedContext::from(parameters.context)?; let session_key = ::finish_ke( &self.ke2_state, message.ke3_message, parameters.identifiers, context, )?; Ok(ServerLoginFinishResult { session_key, #[cfg(test)] state: self, }) } } ///////////////////////// // Convenience Structs // //==================== // ///////////////////////// /// Options for specifying custom identifiers #[derive(Clone, Copy, Debug, Default)] pub struct Identifiers<'a> { /// Client identifier pub client: Option<&'a [u8]>, /// Server identifier pub server: Option<&'a [u8]>, } /// Optional parameters for client registration finish #[derive_where(Clone, Default)] pub struct ClientRegistrationFinishParameters<'i, 'h, CS: CipherSuite> { /// Specifying the identifiers idU and idS pub identifiers: Identifiers<'i>, /// Specifying a configuration for the key stretching function pub ksf: Option<&'h CS::Ksf>, } impl<'i, 'h, CS: CipherSuite> ClientRegistrationFinishParameters<'i, 'h, CS> { /// Create a new [`ClientRegistrationFinishParameters`] pub fn new(identifiers: Identifiers<'i>, ksf: Option<&'h CS::Ksf>) -> Self { Self { identifiers, ksf } } } /// Contains the fields that are returned by a client registration start #[derive_where(Clone)] pub struct ClientRegistrationStartResult { /// The registration request message to be sent to the server pub message: RegistrationRequest, /// The client state that must be persisted in order to complete /// registration pub state: ClientRegistration, } /// Contains the fields that are returned by a client registration finish #[derive_where(Clone)] 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: Output>, /// The server's static public key pub server_s_pk: PublicKey>, /// Instance of the `ClientRegistration`, only used in tests for checking /// zeroize #[cfg(test)] pub state: ClientRegistration, /// `AuthKey`, only used in tests #[cfg(test)] pub auth_key: Output>, /// Password derived key, only used in tests #[cfg(test)] pub randomized_pwd: Output>, } /// Contains the fields that are returned by a server registration start. Note /// that there is no state output in this step #[derive_where(Clone)] pub struct ServerRegistrationStartResult { /// The registration resposne message to send to the client pub message: RegistrationResponse, /// OPRF key, only used in tests #[cfg(test)] pub oprf_key: GenericArray as voprf::Group>::ScalarLen>, } /// Contains the fields that are returned by a client login start #[derive_where(Clone)] pub struct ClientLoginStartResult { /// The message to send to the server to begin the login protocol pub message: CredentialRequest, /// The state that the client must keep in order to complete the protocol pub state: ClientLogin, } /// Optional parameters for client login finish #[derive_where(Clone, Default)] pub struct ClientLoginFinishParameters<'c, 'i, 'h, CS: CipherSuite> { /// Specifying a context field that the server must agree on pub context: Option<&'c [u8]>, /// Specifying a user identifier and server identifier that will be matched /// against the server pub identifiers: Identifiers<'i>, /// Specifying a configuration for the key stretching hash pub ksf: Option<&'h CS::Ksf>, } impl<'c, 'i, 'h, CS: CipherSuite> ClientLoginFinishParameters<'c, 'i, 'h, CS> { /// Create a new [`ClientLoginFinishParameters`] pub fn new( context: Option<&'c [u8]>, identifiers: Identifiers<'i>, ksf: Option<&'h CS::Ksf>, ) -> Self { Self { context, identifiers, ksf, } } } /// Contains the fields that are returned by a client login finish #[derive_where(Clone)] pub struct ClientLoginFinishResult { /// The message to send to the server to complete the protocol pub message: CredentialFinalization, /// The session key pub session_key: Output>, /// The client-side export key pub export_key: Output>, /// The server's static public key pub server_s_pk: PublicKey>, /// Instance of the `ClientLogin`, only used in tests for checking zeroize #[cfg(test)] pub state: ClientLogin, /// Handshake secret, only used in tests #[cfg(test)] pub handshake_secret: Output>, /// Client MAC key, only used in tests #[cfg(test)] pub client_mac_key: Output>, } /// Contains the fields that are returned by a server login finish #[derive_where(Clone)] #[cfg_attr(not(test), derive_where(Debug))] #[cfg_attr(test, derive_where(Debug; ServerLogin))] pub struct ServerLoginFinishResult { /// The session key between client and server pub session_key: Output>, /// Instance of the `ClientRegistration`, only used in tests for checking /// zeroize #[cfg(test)] pub state: ServerLogin, } /// Optional parameters for server login start and finish #[derive(Clone, Debug, Default)] pub struct ServerLoginParameters<'c, 'i> { /// Specifying a context field that the client must agree on pub context: Option<&'c [u8]>, /// Specifying a user identifier and server identifier that will be matched /// against the client pub identifiers: Identifiers<'i>, } /// Contains the fields that are returned by a server login start #[derive_where(Clone)] #[derive_where( Debug; as Group>::Pk, voprf::EvaluationElement, ::KE2Message, ::KE2State, )] pub struct ServerLoginStartResult { /// The message to send back to the client pub message: CredentialResponse, /// The state that the server must keep in order to finish the protocl pub state: ServerLogin, /// Handshake secret, only used in tests #[cfg(test)] pub handshake_secret: Output>, /// Server MAC key, only used in tests #[cfg(test)] pub server_mac_key: Output>, /// OPRF key, only used in tests #[cfg(test)] pub oprf_key: GenericArray as voprf::Group>::ScalarLen>, } //////////////////////////////////////////////// // Helper functions and Trait Implementations // // ========================================== // //////////////////////////////////////////////// // Helper functions #[allow(clippy::type_complexity)] fn get_password_derived_key( input: &[u8], oprf_client: OprfClient, evaluation_element: voprf::EvaluationElement, ksf: Option<&CS::Ksf>, ) -> Result<(Output>, hkdf::SimpleHkdf>), ProtocolError> { let oprf_output = oprf_client.finalize(input, &evaluation_element)?; let oprf_ga = GenericArray::from_ha0_4(oprf_output.clone()); let hardened_output = if let Some(ksf) = ksf { ksf.hash(oprf_ga.clone()) } else { CS::Ksf::default().hash(oprf_ga.clone()) } .map_err(ProtocolError::from)?; let mut hkdf = HkdfExtract::>::new(None); hkdf.input_ikm(&oprf_ga); hkdf.input_ikm(&hardened_output); Ok(hkdf.finalize()) } fn oprf_key_material( oprf_seed: &Output>, info: &[&[u8]], ) -> Result as voprf::Group>::ScalarLen>, InternalError> { let mut ikm = GenericArray::<_, as voprf::Group>::ScalarLen>::default(); Hkdf::>::from_prk(oprf_seed) .ok() .and_then(|hkdf| hkdf.expand_multi_info(info, &mut ikm).ok()) .ok_or(InternalError::HkdfError)?; Ok(ikm) } fn oprf_key_from_key_material( input: GenericArray as voprf::Group>::ScalarLen>, ) -> Result as voprf::Group>::ScalarLen>, InternalError> { Ok(GenericArray::from_ha0_4(OprfGroup::::serialize_scalar( voprf::derive_key::(&input, STR_OPAQUE_DERIVE_KEY_PAIR, voprf::Mode::Oprf)?, ))) } #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = "") )] #[derive_where(Clone, Zeroize)] #[derive_where(Debug, Eq, Hash, PartialEq)] pub(crate) struct MaskedResponse { pub(crate) nonce: GenericArray, pub(crate) hash: Output>, pub(crate) pk: GenericArray as Group>::PkLen>, } pub(crate) type MaskedResponseLen = Sum>, NonceLen>, as Group>::PkLen>; impl MaskedResponse { pub(crate) fn serialize(&self) -> GenericArray> { let hash_ga: &GenericArray>> = GenericArray::from_slice(self.hash.as_slice()); self.nonce.concat_ext(hash_ga).cat(self.pk.clone()) } pub(crate) fn deserialize_take(bytes: &mut &[u8]) -> Result { Ok(Self { nonce: bytes.take_array("masked nonce")?, hash: bytes .take_array::>>("masked hash")? .into_ha0_4(), pk: bytes.take_array("masked public key")?, }) } pub(crate) fn iter(&self) -> impl Clone + Iterator { [ self.nonce.as_slice(), self.hash.as_slice(), self.pk.as_slice(), ] .into_iter() } } fn mask_response( masking_key: &[u8], masking_nonce: &[u8], server_s_pk: &PublicKey>, envelope: &Envelope, ) -> Result, ProtocolError> { let mut xor_pad = GenericArray::<_, MaskedResponseLen>::default(); Hkdf::>::from_prk(masking_key) .map_err(|_| InternalError::HkdfError)? .expand_multi_info(&[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD], &mut xor_pad) .map_err(|_| InternalError::HkdfError)?; for (x1, x2) in xor_pad.iter_mut().zip( server_s_pk .serialize() .as_slice() .iter() .chain(envelope.serialize().iter()), ) { *x1 ^= x2 } let mut slice: &[u8] = &xor_pad; MaskedResponse::deserialize_take(&mut (slice)) } fn unmask_response( masking_key: &[u8], masking_nonce: &[u8], masked_response: &MaskedResponse, ) -> Result<(PublicKey>, Envelope), ProtocolError> { let mut xor_pad = GenericArray::<_, MaskedResponseLen>::default(); Hkdf::>::from_prk(masking_key) .map_err(|_| InternalError::HkdfError)? .expand_multi_info(&[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD], &mut xor_pad) .map_err(|_| InternalError::HkdfError)?; for (x1, x2) in xor_pad.iter_mut().zip(masked_response.iter().flatten()) { *x1 ^= x2 } let mut xor_pad: &[u8] = xor_pad.as_ref(); let server_s_pk = PublicKey::deserialize_take(&mut xor_pad).map_err(|_| ProtocolError::SerializationError)?; let envelope = Envelope::deserialize_take(&mut xor_pad)?; Ok((server_s_pk, envelope)) } /// Internal function for computing the blind result by calling the voprf /// library. Note that for tests, we use the deterministic blinding in order to /// be able to set the blinding factor directly from the passed-in rng. fn blind( rng: &mut R, password: &[u8], ) -> Result, voprf::Error> { #[cfg(not(test))] let result = OprfClient::blind(password, rng)?; #[cfg(test)] let result = { let mut blind_bytes = GenericArray::<_, as voprf::Group>::ScalarLen>::default(); let blind = loop { rng.fill_bytes(&mut blind_bytes); if let Ok(scalar) = as voprf::Group>::deserialize_scalar(&blind_bytes) { break scalar; } }; OprfClient::deterministic_blind_unchecked(password, blind)? }; Ok(result) }