Skip to main content

opaque_vx/
opaque.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) VexaHub and contributors.
3// Copyright (c) Meta Platforms, Inc. and affiliates.
4
5//! Provides the main OPAQUE API
6
7use core::ops::Add;
8use derive_where::derive_where;
9use digest::Output;
10use generic_array::typenum::{Sum, Unsigned};
11use generic_array::{ArrayLength, GenericArray};
12use hkdf::Hkdf;
13use hkdf::SimpleHkdfExtract as HkdfExtract;
14use rand::{CryptoRng, Rng};
15use subtle::{Choice, ConstantTimeEq, CtOption};
16use voprf::{BlindedElement, Group as _, OprfClient, OprfClientLen};
17use zeroize::Zeroizing;
18
19use crate::ciphersuite::{CipherSuite, KeGroup, KeHash, OprfGroup, OprfHash};
20use crate::envelope::{Envelope, EnvelopeLen};
21use crate::errors::{InternalError, ProtocolError};
22use crate::hash::OutputSize;
23use crate::key_exchange::group::Group;
24use crate::key_exchange::shared::NonceLen;
25use crate::key_exchange::{
26    Deserialize, Ke1MessageLen, Ke1StateLen, Ke2StateLen, KeyExchange, Serialize,
27    SerializedContext, SerializedCredentialResponse, SerializedIdentifiers,
28};
29use crate::keypair::{
30    KeyPair, OprfSeed, OprfSeedSerialization, PrivateKey, PrivateKeySerialization, PublicKey,
31};
32use crate::ksf::Ksf;
33use crate::messages::{CredentialRequestLen, RegistrationUploadLen};
34use crate::serialization::{ConcatExt, GenericArrayExt, SliceExt};
35use crate::{
36    CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
37    RegistrationResponse, RegistrationUpload, ServerLoginBuilder,
38};
39
40///////////////
41// Constants //
42// ========= //
43///////////////
44
45const STR_CREDENTIAL_RESPONSE_PAD: &[u8; 21] = b"CredentialResponsePad";
46const STR_MASKING_KEY: &[u8; 10] = b"MaskingKey";
47const STR_OPRF_KEY: &[u8; 7] = b"OprfKey";
48const STR_OPAQUE_DERIVE_KEY_PAIR: &[u8; 20] = b"OPAQUE-DeriveKeyPair";
49
50////////////////////////////
51// High-level API Structs //
52// ====================== //
53////////////////////////////
54
55/// The state elements the server holds upon setup
56#[cfg_attr(
57    feature = "serde",
58    derive(serde::Deserialize, serde::Serialize),
59    serde(bound(
60        deserialize = "<KeGroup<CS> as Group>::Pk: serde::Deserialize<'de>, <KeGroup<CS> as \
61                       Group>::Sk: serde::Deserialize<'de>, SK: serde::Deserialize<'de>, OS: \
62                       serde::Deserialize<'de>",
63        serialize = "<KeGroup<CS> as Group>::Pk: serde::Serialize, <KeGroup<CS> as Group>::Sk: \
64                     serde::Serialize, SK: serde::Serialize, OS: serde::Serialize"
65    ))
66)]
67#[derive_where(Clone)]
68#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <KeGroup<CS> as Group>::Pk, <KeGroup<CS> as Group>::Sk, SK, OS
69)]
70pub struct ServerSetup<
71    CS: CipherSuite,
72    SK: Clone = PrivateKey<KeGroup<CS>>,
73    OS: Clone = OprfSeed<OprfHash<CS>>,
74> {
75    oprf_seed: OS,
76    keypair: KeyPair<KeGroup<CS>, SK>,
77    pub(crate) dummy_pk: PublicKey<KeGroup<CS>>,
78}
79
80/// The state elements the client holds to register itself
81#[cfg_attr(
82    feature = "serde",
83    derive(serde::Deserialize, serde::Serialize),
84    serde(bound = "")
85)]
86#[derive_where(Clone, ZeroizeOnDrop)]
87#[derive_where(
88    Debug, Eq, Hash, PartialEq;
89    voprf::OprfClient<CS::OprfCs>,
90    voprf::BlindedElement<CS::OprfCs>,
91)]
92pub struct ClientRegistration<CS: CipherSuite> {
93    pub(crate) oprf_client: OprfClient<CS::OprfCs>,
94    pub(crate) blinded_element: BlindedElement<CS::OprfCs>,
95}
96
97/// The state elements the server holds to record a registration
98#[cfg_attr(
99    feature = "serde",
100    derive(serde::Deserialize, serde::Serialize),
101    serde(bound(
102        deserialize = "<KeGroup<CS> as Group>::Pk: serde::Deserialize<'de>",
103        serialize = "<KeGroup<CS> as Group>::Pk: serde::Serialize"
104    ))
105)]
106#[derive_where(Clone, ZeroizeOnDrop)]
107#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <KeGroup<CS> as Group>::Pk)]
108pub struct ServerRegistration<CS: CipherSuite>(pub(crate) RegistrationUpload<CS>);
109
110/// The state elements the client holds to perform a login
111#[cfg_attr(
112    feature = "serde",
113    derive(serde::Deserialize, serde::Serialize),
114    serde(bound(
115        deserialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Deserialize<'de>, \
116                       <CS::KeyExchange as KeyExchange>::KE1State: serde::Deserialize<'de>",
117        serialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Serialize, \
118                     <CS::KeyExchange as KeyExchange>::KE1State: serde::Serialize"
119    ))
120)]
121#[derive_where(Clone, ZeroizeOnDrop)]
122#[derive_where(
123    Debug, Eq, Hash, PartialEq;
124    voprf::OprfClient<CS::OprfCs>,
125    <CS::KeyExchange as KeyExchange>::KE1State,
126    CredentialRequest<CS>,
127)]
128pub struct ClientLogin<CS: CipherSuite> {
129    pub(crate) oprf_client: OprfClient<CS::OprfCs>,
130    pub(crate) ke1_state: <CS::KeyExchange as KeyExchange>::KE1State,
131    pub(crate) credential_request: CredentialRequest<CS>,
132}
133
134/// The state elements the server holds to record a login
135#[cfg_attr(
136    feature = "serde",
137    derive(serde::Deserialize, serde::Serialize),
138    serde(bound(
139        deserialize = "<CS::KeyExchange as KeyExchange>::KE2State<CS>: serde::Deserialize<'de>",
140        serialize = "<CS::KeyExchange as KeyExchange>::KE2State<CS>: serde::Serialize"
141    ))
142)]
143#[derive_where(Clone, ZeroizeOnDrop)]
144#[derive_where(Debug, Eq, Hash, PartialEq; <CS::KeyExchange as KeyExchange>::KE2State<CS>)]
145pub struct ServerLogin<CS: CipherSuite> {
146    ke2_state: <CS::KeyExchange as KeyExchange>::KE2State<CS>,
147}
148
149////////////////////////////////
150// High-level Implementations //
151// ========================== //
152////////////////////////////////
153
154// Server Setup
155// ============
156
157impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<KeGroup<CS>>> {
158    /// Generate a new instance of server setup
159    pub fn new<R: CryptoRng + Rng>(rng: &mut R) -> Self {
160        let keypair = KeyPair::random(rng);
161        Self::new_with_key_pair(rng, keypair)
162    }
163}
164
165/// Length of [`ServerSetup`] in bytes for serialization.
166pub type ServerSetupLen<
167    CS: CipherSuite,
168    SK: PrivateKeySerialization<KeGroup<CS>>,
169    OS: OprfSeedSerialization<OprfHash<CS>, SK::Error>,
170> = Sum<Sum<OS::Len, SK::Len>, <KeGroup<CS> as Group>::PkLen>;
171
172impl<CS: CipherSuite, SK: Clone, OS: Clone> ServerSetup<CS, SK, OS> {
173    /// Create [`ServerSetup`] with the given keypair and OPRF seed.
174    ///
175    /// This function should not be used to restore a previously-existing
176    /// instance of [`ServerSetup`]. Instead, use [`ServerSetup::serialize`] and
177    /// [`ServerSetup::deserialize`] for this purpose.
178    pub fn new_with_key_pair_and_seed<R: CryptoRng + Rng>(
179        rng: &mut R,
180        keypair: KeyPair<KeGroup<CS>, SK>,
181        oprf_seed: OS,
182    ) -> Self {
183        Self {
184            oprf_seed,
185            keypair,
186            dummy_pk: KeyPair::<KeGroup<CS>>::random(rng).public().clone(),
187        }
188    }
189
190    /// The information required to generate the key material for
191    /// [`ServerRegistration::start_with_key_material()`] and
192    /// [`ServerLogin::builder_with_key_material()`].
193    pub fn key_material_info<'ci>(
194        &self,
195        credential_identifier: &'ci [u8],
196    ) -> KeyMaterialInfo<'ci, OS> {
197        KeyMaterialInfo {
198            ikm: self.oprf_seed.clone(),
199            info: [credential_identifier, STR_OPRF_KEY],
200        }
201    }
202
203    /// Serialization into bytes
204    pub fn serialize(&self) -> GenericArray<u8, ServerSetupLen<CS, SK, OS>>
205    where
206        SK: PrivateKeySerialization<KeGroup<CS>>,
207        OS: OprfSeedSerialization<OprfHash<CS>, SK::Error>,
208        // ServerSetup: Hash + KeSk + KePk
209        OS::Len: Add<SK::Len>,
210        Sum<OS::Len, SK::Len>: ArrayLength + Add<<KeGroup<CS> as Group>::PkLen>,
211        ServerSetupLen<CS, SK, OS>: ArrayLength,
212    {
213        self.oprf_seed
214            .serialize()
215            .cat(SK::serialize_key_pair(&self.keypair))
216            .cat(self.dummy_pk.serialize())
217    }
218
219    /// Deserialization from bytes
220    pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError<SK::Error>>
221    where
222        SK: PrivateKeySerialization<KeGroup<CS>>,
223        OS: OprfSeedSerialization<OprfHash<CS>, SK::Error>,
224    {
225        Ok(Self {
226            oprf_seed: OS::deserialize_take(&mut input)?,
227            keypair: SK::deserialize_take_key_pair(&mut input)?,
228            dummy_pk: PublicKey::deserialize_take(&mut input)
229                .map_err(ProtocolError::into_custom)?,
230        })
231    }
232
233    /// Returns the keypair
234    pub fn keypair(&self) -> &KeyPair<KeGroup<CS>, SK> {
235        &self.keypair
236    }
237}
238
239impl<CS: CipherSuite, SK: Clone> ServerSetup<CS, SK> {
240    /// Create [`ServerSetup`] with the given keypair
241    ///
242    /// This function should not be used to restore a previously-existing
243    /// instance of [`ServerSetup`]. Instead, use [`ServerSetup::serialize`] and
244    /// [`ServerSetup::deserialize`] for this purpose.
245    pub fn new_with_key_pair<R: CryptoRng + Rng>(
246        rng: &mut R,
247        keypair: KeyPair<KeGroup<CS>, SK>,
248    ) -> Self {
249        let mut oprf_seed = Output::<OprfHash<CS>>::default();
250        rng.fill_bytes(&mut oprf_seed);
251
252        Self::new_with_key_pair_and_seed(rng, keypair, OprfSeed(oprf_seed))
253    }
254}
255
256/// The information required to generate the key material for
257/// [`ServerRegistration::start_with_key_material()`] and
258/// [`ServerLogin::builder_with_key_material()`].
259///
260/// Use a HKDF, with the input key material [`ikm`](Self::ikm), expand operation
261/// with [`info`](Self::info) with an output length
262/// of [`CS::OprfCs::ScalarLen`](voprf::Group::ScalarLen).
263pub struct KeyMaterialInfo<'ci, OS: Clone> {
264    /// Input key material for the HKDF.
265    pub ikm: OS,
266    /// Info for the HKDF expand operation.
267    pub info: [&'ci [u8]; 2],
268}
269
270// Registration
271// ============
272
273pub(crate) type ClientRegistrationLen<CS: CipherSuite> =
274    Sum<<OprfGroup<CS> as voprf::Group>::ScalarLen, <OprfGroup<CS> as voprf::Group>::ElemLen>;
275
276impl<CS: CipherSuite> ClientRegistration<CS> {
277    /// Serialization into bytes
278    pub fn serialize(&self) -> GenericArray<u8, ClientRegistrationLen<CS>>
279    where
280        // ClientRegistration: KgSk + KgPk
281        <OprfGroup<CS> as voprf::Group>::ScalarLen:
282            Add<<OprfGroup<CS> as voprf::Group>::ElemLen> + ArrayLength,
283        <OprfGroup<CS> as voprf::Group>::ElemLen: ArrayLength,
284        ClientRegistrationLen<CS>: ArrayLength,
285    {
286        GenericArray::from_ha0_4(self.oprf_client.serialize())
287            .cat(GenericArray::from_ha0_4(self.blinded_element.serialize()))
288    }
289
290    /// Deserialization from bytes
291    pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
292        let client_len = OprfClientLen::<CS::OprfCs>::USIZE;
293        if input.len() < client_len {
294            return Err(ProtocolError::SerializationError);
295        }
296        let oprf_client = OprfClient::deserialize(&input[..client_len])?;
297        input = &input[client_len..];
298
299        let blinded_element = BlindedElement::deserialize(input)?;
300
301        Ok(Self {
302            oprf_client,
303            blinded_element,
304        })
305    }
306
307    /// Returns an initial "blinded" request to send to the server, as well as a
308    /// [`ClientRegistration`]
309    pub fn start<R: Rng + CryptoRng>(
310        blinding_factor_rng: &mut R,
311        password: &[u8],
312    ) -> Result<ClientRegistrationStartResult<CS>, ProtocolError> {
313        let blind_result = blind::<CS, _>(blinding_factor_rng, password)?;
314
315        Ok(ClientRegistrationStartResult {
316            message: RegistrationRequest {
317                blinded_element: blind_result.message.clone(),
318            },
319            state: Self {
320                oprf_client: blind_result.state,
321                blinded_element: blind_result.message,
322            },
323        })
324    }
325
326    /// "Unblinds" the server's answer and returns a final message containing
327    /// cryptographic identifiers, to be sent to the server on setup
328    /// finalization
329    pub fn finish<R: CryptoRng + Rng>(
330        self,
331        rng: &mut R,
332        password: &[u8],
333        registration_response: RegistrationResponse<CS>,
334        params: ClientRegistrationFinishParameters<CS>,
335    ) -> Result<ClientRegistrationFinishResult<CS>, ProtocolError> {
336        // Check for reflected value from server and halt if detected
337        if self
338            .blinded_element
339            .value()
340            .ct_eq(&registration_response.evaluation_element.value())
341            .into()
342        {
343            return Err(ProtocolError::ReflectedValueError);
344        }
345
346        #[cfg_attr(not(test), allow(unused_variables))]
347        let (randomized_pwd, randomized_pwd_hasher) = get_password_derived_key::<CS>(
348            password,
349            self.oprf_client.clone(),
350            registration_response.evaluation_element,
351            params.ksf,
352        )?;
353
354        let mut masking_key = Output::<OprfHash<CS>>::default();
355        randomized_pwd_hasher
356            .expand(STR_MASKING_KEY, &mut masking_key)
357            .map_err(|_| InternalError::HkdfError)?;
358
359        let result = Envelope::<CS>::seal(
360            rng,
361            &randomized_pwd_hasher,
362            &registration_response.server_s_pk,
363            params.identifiers,
364        )?;
365
366        Ok(ClientRegistrationFinishResult {
367            message: RegistrationUpload {
368                envelope: result.0,
369                masking_key,
370                client_s_pk: result.1,
371            },
372            export_key: result.2,
373            server_s_pk: registration_response.server_s_pk,
374            #[cfg(test)]
375            state: self,
376            #[cfg(test)]
377            auth_key: result.3,
378            #[cfg(test)]
379            randomized_pwd,
380        })
381    }
382}
383
384/// Length of [`ServerRegistration`] in bytes for serialization.
385pub type ServerRegistrationLen<CS> = RegistrationUploadLen<CS>;
386
387impl<CS: CipherSuite> ServerRegistration<CS> {
388    /// Serialization into bytes
389    pub fn serialize(&self) -> GenericArray<u8, ServerRegistrationLen<CS>>
390    where
391        // RegistrationUpload: (KePk + Hash) + Envelope
392        <KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
393        Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
394            ArrayLength + Add<EnvelopeLen<CS>>,
395        RegistrationUploadLen<CS>: ArrayLength,
396        // ServerRegistration = RegistrationUpload
397    {
398        self.0.serialize()
399    }
400
401    /// Deserialization from bytes
402    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
403        Ok(Self(RegistrationUpload::deserialize(input)?))
404    }
405
406    /// Create a [`RegistrationResponse`] with a remote OPRF seed. To generate
407    /// the `key_material` see [`ServerSetup::key_material_info()`].
408    ///
409    /// See [`ServerRegistration::start()`] for the regular path.
410    pub fn start_with_key_material<SK: Clone, OS: Clone>(
411        server_setup: &ServerSetup<CS, SK, OS>,
412        key_material: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
413        message: RegistrationRequest<CS>,
414    ) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
415        let oprf_key = oprf_key_from_key_material::<CS>(key_material)?;
416
417        let server = voprf::OprfServer::new_with_key(&oprf_key)?;
418        let evaluation_element = server.blind_evaluate(&message.blinded_element);
419
420        Ok(ServerRegistrationStartResult {
421            message: RegistrationResponse {
422                evaluation_element,
423                server_s_pk: server_setup.keypair().public().clone(),
424            },
425            #[cfg(test)]
426            oprf_key,
427        })
428    }
429
430    /// From the client's "blinded" password, returns a response to be sent back
431    /// to the client, as well as a [`ServerRegistration`]
432    pub fn start<SK: Clone>(
433        server_setup: &ServerSetup<CS, SK>,
434        message: RegistrationRequest<CS>,
435        credential_identifier: &[u8],
436    ) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
437        let KeyMaterialInfo {
438            ikm: oprf_seed,
439            info,
440        } = server_setup.key_material_info(credential_identifier);
441        let key_material = oprf_key_material::<CS>(&oprf_seed.0, &info)?;
442
443        Self::start_with_key_material(server_setup, key_material, message)
444    }
445
446    /// From the client's cryptographic identifiers, fully populates and returns
447    /// a [`ServerRegistration`]
448    pub fn finish(message: RegistrationUpload<CS>) -> Self {
449        Self(message)
450    }
451
452    // Creates a dummy instance used for faking a [CredentialResponse]
453    pub(crate) fn dummy<R: Rng + CryptoRng, SK: Clone, S: Clone>(
454        rng: &mut R,
455        server_setup: &ServerSetup<CS, SK, S>,
456    ) -> Self {
457        Self(RegistrationUpload::dummy(rng, server_setup))
458    }
459}
460
461// Login
462// =====
463
464pub(crate) type ClientLoginLen<CS: CipherSuite> =
465    Sum<Sum<<OprfGroup<CS> as voprf::Group>::ScalarLen, CredentialRequestLen<CS>>, Ke1StateLen<CS>>;
466
467impl<CS: CipherSuite> ClientLogin<CS> {
468    /// Serialization into bytes
469    pub fn serialize(&self) -> GenericArray<u8, ClientLoginLen<CS>>
470    where
471        // CredentialRequest: KgPk + Ke1Message
472        <CS::KeyExchange as KeyExchange>::KE1Message: Serialize,
473        <OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>>,
474        CredentialRequestLen<CS>: ArrayLength,
475        // ClientLogin: KgSk + CredentialRequest + Ke1State
476        <OprfGroup<CS> as voprf::Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
477        <CS::KeyExchange as KeyExchange>::KE1State: Serialize,
478        Sum<<OprfGroup<CS> as voprf::Group>::ScalarLen, CredentialRequestLen<CS>>:
479            ArrayLength + Add<Ke1StateLen<CS>>,
480        ClientLoginLen<CS>: ArrayLength,
481    {
482        GenericArray::from_ha0_4(self.oprf_client.serialize())
483            .cat(self.credential_request.serialize())
484            .cat(self.ke1_state.serialize())
485    }
486
487    /// Deserialization from bytes
488    pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
489    where
490        <CS::KeyExchange as KeyExchange>::KE1Message: Deserialize + Serialize,
491        <CS::KeyExchange as KeyExchange>::KE1State: Deserialize + Serialize,
492    {
493        let client_len = OprfClientLen::<CS::OprfCs>::USIZE;
494        if input.len() < client_len {
495            return Err(ProtocolError::SerializationError);
496        }
497        let oprf_client = OprfClient::deserialize(&input[..client_len])?;
498        input = &input[client_len..];
499
500        Ok(Self {
501            oprf_client,
502            credential_request: CredentialRequest::deserialize_take(&mut input)?,
503            ke1_state: <CS::KeyExchange as KeyExchange>::KE1State::deserialize_take(&mut input)?,
504        })
505    }
506}
507
508impl<CS: CipherSuite> ClientLogin<CS> {
509    /// Returns an initial "blinded" password request to send to the server, as
510    /// well as a [`ClientLogin`]
511    pub fn start<R: Rng + CryptoRng>(
512        rng: &mut R,
513        password: &[u8],
514    ) -> Result<ClientLoginStartResult<CS>, ProtocolError> {
515        let blind_result = blind::<CS, _>(rng, password)?;
516        let ke1_result = CS::KeyExchange::generate_ke1(rng)?;
517
518        let credential_request = CredentialRequest {
519            blinded_element: blind_result.message,
520            ke1_message: ke1_result.message,
521        };
522
523        Ok(ClientLoginStartResult {
524            message: credential_request.clone(),
525            state: Self {
526                oprf_client: blind_result.state,
527                ke1_state: ke1_result.state,
528                credential_request,
529            },
530        })
531    }
532
533    /// "Unblinds" the server's answer and returns the opened assets from the
534    /// server
535    pub fn finish<R: CryptoRng + Rng>(
536        self,
537        rng: &mut R,
538        password: &[u8],
539        credential_response: CredentialResponse<CS>,
540        params: ClientLoginFinishParameters<CS>,
541    ) -> Result<ClientLoginFinishResult<CS>, ProtocolError> {
542        // Check if beta value from server is equal to alpha value from client
543        if self
544            .credential_request
545            .blinded_element
546            .value()
547            .ct_eq(&credential_response.evaluation_element.value())
548            .into()
549        {
550            return Err(ProtocolError::ReflectedValueError);
551        }
552
553        let (_, randomized_pwd_hasher) = get_password_derived_key::<CS>(
554            password,
555            self.oprf_client.clone(),
556            credential_response.evaluation_element.clone(),
557            params.ksf,
558        )?;
559
560        let mut masking_key = Output::<OprfHash<CS>>::default();
561        randomized_pwd_hasher
562            .expand(STR_MASKING_KEY, &mut masking_key)
563            .map_err(|_| InternalError::HkdfError)?;
564
565        let (server_s_pk, envelope) = unmask_response::<CS>(
566            &masking_key,
567            &credential_response.masking_nonce,
568            &credential_response.masked_response,
569        )
570        .map_err(|e| match e {
571            ProtocolError::SerializationError => ProtocolError::InvalidLoginError,
572            err => err,
573        })?;
574
575        let opened_envelope = envelope
576            .open(
577                &randomized_pwd_hasher,
578                server_s_pk.clone(),
579                params.identifiers,
580            )
581            .map_err(|e| match e {
582                ProtocolError::LibraryError(InternalError::SealOpenHmacError) => {
583                    ProtocolError::InvalidLoginError
584                }
585                err => err,
586            })?;
587
588        let context = SerializedContext::from(params.context)?;
589
590        let result = CS::KeyExchange::generate_ke3(
591            rng,
592            self.credential_request.to_parts(),
593            self.credential_request.ke1_message.clone(),
594            credential_response.to_parts(),
595            &self.ke1_state,
596            credential_response.ke2_message,
597            server_s_pk.clone(),
598            opened_envelope.client_static_keypair.private().clone(),
599            opened_envelope.identifiers,
600            context,
601        )?;
602
603        Ok(ClientLoginFinishResult {
604            message: CredentialFinalization {
605                ke3_message: result.message,
606            },
607            session_key: result.session_key,
608            export_key: opened_envelope.export_key,
609            server_s_pk,
610            #[cfg(test)]
611            state: self,
612            #[cfg(test)]
613            handshake_secret: result.handshake_secret,
614            #[cfg(test)]
615            client_mac_key: result.km3,
616        })
617    }
618}
619
620impl<CS: CipherSuite> ServerLogin<CS> {
621    /// Serialization into bytes
622    pub fn serialize(&self) -> GenericArray<u8, Ke2StateLen<CS>>
623    where
624        <CS::KeyExchange as KeyExchange>::KE2State<CS>: Serialize,
625    {
626        self.ke2_state.serialize()
627    }
628
629    /// Deserialization from bytes
630    pub fn deserialize(mut bytes: &[u8]) -> Result<Self, ProtocolError>
631    where
632        <CS::KeyExchange as KeyExchange>::KE2State<CS>: Deserialize,
633    {
634        Ok(Self {
635            ke2_state:
636                <<CS::KeyExchange as KeyExchange>::KE2State<CS> as Deserialize>::deserialize_take(
637                    &mut bytes,
638                )?,
639        })
640    }
641
642    /// Create a [`ServerLoginBuilder`] with a remote OPRF seed and private key.
643    /// To generate the `key_material` see
644    /// [`ServerSetup::key_material_info()`].
645    ///
646    /// See [`ServerLogin::start()`] for the regular path. Or
647    /// [`ServerLogin::builder()`] with just a remote private key.
648    pub fn builder_with_key_material<'a, R: Rng + CryptoRng, SK: Clone, OS: Clone>(
649        rng: &mut R,
650        server_setup: &ServerSetup<CS, SK, OS>,
651        key_material: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
652        password_file: Option<ServerRegistration<CS>>,
653        credential_request: CredentialRequest<CS>,
654        ServerLoginParameters {
655            context,
656            identifiers,
657        }: ServerLoginParameters<'a, 'a>,
658    ) -> Result<ServerLoginBuilder<'a, CS, SK>, ProtocolError> {
659        let record = CtOption::new(
660            ServerRegistration::dummy(rng, server_setup),
661            Choice::from(password_file.is_none() as u8),
662        )
663        .into_option()
664        .unwrap_or_else(|| password_file.unwrap());
665
666        let client_s_pk = record.0.client_s_pk.clone();
667        let context = SerializedContext::from(context)?;
668        let server_s_pk = server_setup.keypair.public();
669
670        let mut masking_nonce = GenericArray::<_, NonceLen>::default();
671        rng.fill_bytes(&mut masking_nonce);
672
673        let masked_response = mask_response(
674            &record.0.masking_key,
675            &masking_nonce,
676            server_s_pk,
677            &record.0.envelope,
678        )?;
679
680        let serialized_client_s_pk = client_s_pk.serialize();
681        let serialized_server_s_pk = server_s_pk.serialize();
682        let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
683            identifiers,
684            serialized_client_s_pk.clone(),
685            serialized_server_s_pk.clone(),
686        )?;
687
688        let oprf_key = oprf_key_from_key_material::<CS>(key_material)?;
689        let server = voprf::OprfServer::new_with_key(&oprf_key).map_err(ProtocolError::from)?;
690        let evaluation_element = server.blind_evaluate(&credential_request.blinded_element);
691
692        let credential_response = SerializedCredentialResponse::new(
693            &evaluation_element,
694            masking_nonce,
695            masked_response.clone(),
696        );
697
698        let ke2_builder = CS::KeyExchange::ke2_builder(
699            rng,
700            credential_request.to_parts(),
701            credential_request.ke1_message.clone(),
702            credential_response,
703            client_s_pk,
704            identifiers,
705            context,
706        )?;
707
708        Ok(ServerLoginBuilder {
709            server_s_sk: server_setup.keypair().private().clone(),
710            evaluation_element,
711            masking_nonce: Zeroizing::new(masking_nonce),
712            masked_response,
713            #[cfg(test)]
714            oprf_key: Zeroizing::new(oprf_key),
715            ke2_builder,
716        })
717    }
718
719    /// Create a [`ServerLoginBuilder`] to use with a remote private key.
720    ///
721    /// See [`ServerLogin::start()`] for the regular path.
722    pub fn builder<'a, R: Rng + CryptoRng, SK: Clone>(
723        rng: &mut R,
724        server_setup: &ServerSetup<CS, SK>,
725        password_file: Option<ServerRegistration<CS>>,
726        credential_request: CredentialRequest<CS>,
727        credential_identifier: &[u8],
728        params: ServerLoginParameters<'a, 'a>,
729    ) -> Result<ServerLoginBuilder<'a, CS, SK>, ProtocolError> {
730        let KeyMaterialInfo {
731            ikm: oprf_seed,
732            info,
733        } = server_setup.key_material_info(credential_identifier);
734        let key_material = oprf_key_material::<CS>(&oprf_seed.0, &info)?;
735
736        Self::builder_with_key_material(
737            rng,
738            server_setup,
739            key_material,
740            password_file,
741            credential_request,
742            params,
743        )
744    }
745
746    pub(crate) fn build<SK: Clone>(
747        builder: ServerLoginBuilder<CS, SK>,
748        input: <CS::KeyExchange as KeyExchange>::KE2BuilderInput<CS>,
749    ) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
750        let result = CS::KeyExchange::build_ke2(builder.ke2_builder.clone(), input)?;
751
752        let credential_response = CredentialResponse {
753            evaluation_element: builder.evaluation_element.clone(),
754            masking_nonce: *builder.masking_nonce,
755            masked_response: builder.masked_response.clone(),
756            ke2_message: result.message,
757        };
758
759        Ok(ServerLoginStartResult {
760            message: credential_response,
761            state: Self {
762                ke2_state: result.state,
763            },
764            #[cfg(test)]
765            handshake_secret: result.handshake_secret,
766            #[cfg(test)]
767            server_mac_key: result.km2,
768            #[cfg(test)]
769            oprf_key: (*builder.oprf_key).clone(),
770        })
771    }
772
773    /// From the client's "blinded" password, returns a challenge to be sent
774    /// back to the client, as well as a [`ServerLogin`]
775    pub fn start<R: Rng + CryptoRng>(
776        rng: &mut R,
777        server_setup: &ServerSetup<CS>,
778        password_file: Option<ServerRegistration<CS>>,
779        credential_request: CredentialRequest<CS>,
780        credential_identifier: &[u8],
781        parameters: ServerLoginParameters,
782    ) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
783        let builder = Self::builder(
784            rng,
785            server_setup,
786            password_file,
787            credential_request,
788            credential_identifier,
789            parameters,
790        )?;
791        let input = CS::KeyExchange::generate_ke2_input(
792            &builder.ke2_builder,
793            rng,
794            server_setup.keypair.private(),
795        );
796
797        Self::build(builder, input)
798    }
799
800    /// From the client's second and final message, check the client's
801    /// authentication and produce a message transport
802    pub fn finish(
803        self,
804        message: CredentialFinalization<CS>,
805        parameters: ServerLoginParameters,
806    ) -> Result<ServerLoginFinishResult<CS>, ProtocolError> {
807        let context = SerializedContext::from(parameters.context)?;
808
809        let session_key = <CS::KeyExchange as KeyExchange>::finish_ke(
810            &self.ke2_state,
811            message.ke3_message,
812            parameters.identifiers,
813            context,
814        )?;
815
816        Ok(ServerLoginFinishResult {
817            session_key,
818            #[cfg(test)]
819            state: self,
820        })
821    }
822}
823
824/////////////////////////
825// Convenience Structs //
826//==================== //
827/////////////////////////
828
829/// Options for specifying custom identifiers
830#[derive(Clone, Copy, Debug, Default)]
831pub struct Identifiers<'a> {
832    /// Client identifier
833    pub client: Option<&'a [u8]>,
834    /// Server identifier
835    pub server: Option<&'a [u8]>,
836}
837
838/// Optional parameters for client registration finish
839#[derive_where(Clone, Default)]
840pub struct ClientRegistrationFinishParameters<'i, 'h, CS: CipherSuite> {
841    /// Specifying the identifiers idU and idS
842    pub identifiers: Identifiers<'i>,
843    /// Specifying a configuration for the key stretching function
844    pub ksf: Option<&'h CS::Ksf>,
845}
846
847impl<'i, 'h, CS: CipherSuite> ClientRegistrationFinishParameters<'i, 'h, CS> {
848    /// Create a new [`ClientRegistrationFinishParameters`]
849    pub fn new(identifiers: Identifiers<'i>, ksf: Option<&'h CS::Ksf>) -> Self {
850        Self { identifiers, ksf }
851    }
852}
853
854/// Contains the fields that are returned by a client registration start
855#[derive_where(Clone)]
856pub struct ClientRegistrationStartResult<CS: CipherSuite> {
857    /// The registration request message to be sent to the server
858    pub message: RegistrationRequest<CS>,
859    /// The client state that must be persisted in order to complete
860    /// registration
861    pub state: ClientRegistration<CS>,
862}
863
864/// Contains the fields that are returned by a client registration finish
865#[derive_where(Clone)]
866pub struct ClientRegistrationFinishResult<CS: CipherSuite> {
867    /// The registration upload message to be sent to the server
868    pub message: RegistrationUpload<CS>,
869    /// The export key output by client registration
870    pub export_key: Output<OprfHash<CS>>,
871    /// The server's static public key
872    pub server_s_pk: PublicKey<KeGroup<CS>>,
873    /// Instance of the `ClientRegistration`, only used in tests for checking
874    /// zeroize
875    #[cfg(test)]
876    pub state: ClientRegistration<CS>,
877    /// `AuthKey`, only used in tests
878    #[cfg(test)]
879    pub auth_key: Output<OprfHash<CS>>,
880    /// Password derived key, only used in tests
881    #[cfg(test)]
882    pub randomized_pwd: Output<OprfHash<CS>>,
883}
884
885/// Contains the fields that are returned by a server registration start. Note
886/// that there is no state output in this step
887#[derive_where(Clone)]
888pub struct ServerRegistrationStartResult<CS: CipherSuite> {
889    /// The registration resposne message to send to the client
890    pub message: RegistrationResponse<CS>,
891    /// OPRF key, only used in tests
892    #[cfg(test)]
893    pub oprf_key: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
894}
895
896/// Contains the fields that are returned by a client login start
897#[derive_where(Clone)]
898pub struct ClientLoginStartResult<CS: CipherSuite> {
899    /// The message to send to the server to begin the login protocol
900    pub message: CredentialRequest<CS>,
901    /// The state that the client must keep in order to complete the protocol
902    pub state: ClientLogin<CS>,
903}
904
905/// Optional parameters for client login finish
906#[derive_where(Clone, Default)]
907pub struct ClientLoginFinishParameters<'c, 'i, 'h, CS: CipherSuite> {
908    /// Specifying a context field that the server must agree on
909    pub context: Option<&'c [u8]>,
910    /// Specifying a user identifier and server identifier that will be matched
911    /// against the server
912    pub identifiers: Identifiers<'i>,
913    /// Specifying a configuration for the key stretching hash
914    pub ksf: Option<&'h CS::Ksf>,
915}
916
917impl<'c, 'i, 'h, CS: CipherSuite> ClientLoginFinishParameters<'c, 'i, 'h, CS> {
918    /// Create a new [`ClientLoginFinishParameters`]
919    pub fn new(
920        context: Option<&'c [u8]>,
921        identifiers: Identifiers<'i>,
922        ksf: Option<&'h CS::Ksf>,
923    ) -> Self {
924        Self {
925            context,
926            identifiers,
927            ksf,
928        }
929    }
930}
931
932/// Contains the fields that are returned by a client login finish
933#[derive_where(Clone)]
934pub struct ClientLoginFinishResult<CS: CipherSuite> {
935    /// The message to send to the server to complete the protocol
936    pub message: CredentialFinalization<CS>,
937    /// The session key
938    pub session_key: Output<KeHash<CS>>,
939    /// The client-side export key
940    pub export_key: Output<OprfHash<CS>>,
941    /// The server's static public key
942    pub server_s_pk: PublicKey<KeGroup<CS>>,
943    /// Instance of the `ClientLogin`, only used in tests for checking zeroize
944    #[cfg(test)]
945    pub state: ClientLogin<CS>,
946    /// Handshake secret, only used in tests
947    #[cfg(test)]
948    pub handshake_secret: Output<KeHash<CS>>,
949    /// Client MAC key, only used in tests
950    #[cfg(test)]
951    pub client_mac_key: Output<KeHash<CS>>,
952}
953
954/// Contains the fields that are returned by a server login finish
955#[derive_where(Clone)]
956#[cfg_attr(not(test), derive_where(Debug))]
957#[cfg_attr(test, derive_where(Debug; ServerLogin<CS>))]
958pub struct ServerLoginFinishResult<CS: CipherSuite> {
959    /// The session key between client and server
960    pub session_key: Output<KeHash<CS>>,
961    /// Instance of the `ClientRegistration`, only used in tests for checking
962    /// zeroize
963    #[cfg(test)]
964    pub state: ServerLogin<CS>,
965}
966
967/// Optional parameters for server login start and finish
968#[derive(Clone, Debug, Default)]
969pub struct ServerLoginParameters<'c, 'i> {
970    /// Specifying a context field that the client must agree on
971    pub context: Option<&'c [u8]>,
972    /// Specifying a user identifier and server identifier that will be matched
973    /// against the client
974    pub identifiers: Identifiers<'i>,
975}
976
977/// Contains the fields that are returned by a server login start
978#[derive_where(Clone)]
979#[derive_where(
980    Debug;
981    <KeGroup<CS> as Group>::Pk,
982    voprf::EvaluationElement<CS::OprfCs>,
983    <CS::KeyExchange as KeyExchange>::KE2Message,
984    <CS::KeyExchange as KeyExchange>::KE2State<CS>,
985)]
986pub struct ServerLoginStartResult<CS: CipherSuite> {
987    /// The message to send back to the client
988    pub message: CredentialResponse<CS>,
989    /// The state that the server must keep in order to finish the protocl
990    pub state: ServerLogin<CS>,
991    /// Handshake secret, only used in tests
992    #[cfg(test)]
993    pub handshake_secret: Output<KeHash<CS>>,
994    /// Server MAC key, only used in tests
995    #[cfg(test)]
996    pub server_mac_key: Output<KeHash<CS>>,
997    /// OPRF key, only used in tests
998    #[cfg(test)]
999    pub oprf_key: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
1000}
1001
1002////////////////////////////////////////////////
1003// Helper functions and Trait Implementations //
1004// ========================================== //
1005////////////////////////////////////////////////
1006
1007// Helper functions
1008#[allow(clippy::type_complexity)]
1009fn get_password_derived_key<CS: CipherSuite>(
1010    input: &[u8],
1011    oprf_client: OprfClient<CS::OprfCs>,
1012    evaluation_element: voprf::EvaluationElement<CS::OprfCs>,
1013    ksf: Option<&CS::Ksf>,
1014) -> Result<(Output<OprfHash<CS>>, hkdf::SimpleHkdf<OprfHash<CS>>), ProtocolError> {
1015    let oprf_output = oprf_client.finalize(input, &evaluation_element)?;
1016    let oprf_ga = GenericArray::from_ha0_4(oprf_output.clone());
1017
1018    let hardened_output = if let Some(ksf) = ksf {
1019        ksf.hash(oprf_ga.clone())
1020    } else {
1021        CS::Ksf::default().hash(oprf_ga.clone())
1022    }
1023    .map_err(ProtocolError::from)?;
1024
1025    let mut hkdf = HkdfExtract::<OprfHash<CS>>::new(None);
1026    hkdf.input_ikm(&oprf_ga);
1027    hkdf.input_ikm(&hardened_output);
1028    Ok(hkdf.finalize())
1029}
1030
1031fn oprf_key_material<CS: CipherSuite>(
1032    oprf_seed: &Output<OprfHash<CS>>,
1033    info: &[&[u8]],
1034) -> Result<GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>, InternalError> {
1035    let mut ikm = GenericArray::<_, <OprfGroup<CS> as voprf::Group>::ScalarLen>::default();
1036    Hkdf::<OprfHash<CS>>::from_prk(oprf_seed)
1037        .ok()
1038        .and_then(|hkdf| hkdf.expand_multi_info(info, &mut ikm).ok())
1039        .ok_or(InternalError::HkdfError)?;
1040
1041    Ok(ikm)
1042}
1043
1044fn oprf_key_from_key_material<CS: CipherSuite>(
1045    input: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
1046) -> Result<GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>, InternalError> {
1047    Ok(GenericArray::from_ha0_4(OprfGroup::<CS>::serialize_scalar(
1048        voprf::derive_key::<CS::OprfCs>(&input, STR_OPAQUE_DERIVE_KEY_PAIR, voprf::Mode::Oprf)?,
1049    )))
1050}
1051
1052#[cfg_attr(
1053    feature = "serde",
1054    derive(serde::Deserialize, serde::Serialize),
1055    serde(bound = "")
1056)]
1057#[derive_where(Clone, Zeroize)]
1058#[derive_where(Debug, Eq, Hash, PartialEq)]
1059pub(crate) struct MaskedResponse<CS: CipherSuite> {
1060    pub(crate) nonce: GenericArray<u8, NonceLen>,
1061    pub(crate) hash: Output<OprfHash<CS>>,
1062    pub(crate) pk: GenericArray<u8, <KeGroup<CS> as Group>::PkLen>,
1063}
1064
1065pub(crate) type MaskedResponseLen<CS: CipherSuite> =
1066    Sum<Sum<OutputSize<OprfHash<CS>>, NonceLen>, <KeGroup<CS> as Group>::PkLen>;
1067
1068impl<CS: CipherSuite> MaskedResponse<CS> {
1069    pub(crate) fn serialize(&self) -> GenericArray<u8, MaskedResponseLen<CS>> {
1070        let hash_ga: &GenericArray<u8, OutputSize<OprfHash<CS>>> =
1071            GenericArray::from_slice(self.hash.as_slice());
1072
1073        self.nonce.concat_ext(hash_ga).cat(self.pk.clone())
1074    }
1075    pub(crate) fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
1076        Ok(Self {
1077            nonce: bytes.take_array("masked nonce")?,
1078            hash: bytes
1079                .take_array::<OutputSize<OprfHash<CS>>>("masked hash")?
1080                .into_ha0_4(),
1081            pk: bytes.take_array("masked public key")?,
1082        })
1083    }
1084
1085    pub(crate) fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
1086        [
1087            self.nonce.as_slice(),
1088            self.hash.as_slice(),
1089            self.pk.as_slice(),
1090        ]
1091        .into_iter()
1092    }
1093}
1094
1095fn mask_response<CS: CipherSuite>(
1096    masking_key: &[u8],
1097    masking_nonce: &[u8],
1098    server_s_pk: &PublicKey<KeGroup<CS>>,
1099    envelope: &Envelope<CS>,
1100) -> Result<MaskedResponse<CS>, ProtocolError> {
1101    let mut xor_pad = GenericArray::<_, MaskedResponseLen<CS>>::default();
1102
1103    Hkdf::<OprfHash<CS>>::from_prk(masking_key)
1104        .map_err(|_| InternalError::HkdfError)?
1105        .expand_multi_info(&[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD], &mut xor_pad)
1106        .map_err(|_| InternalError::HkdfError)?;
1107
1108    for (x1, x2) in xor_pad.iter_mut().zip(
1109        server_s_pk
1110            .serialize()
1111            .as_slice()
1112            .iter()
1113            .chain(envelope.serialize().iter()),
1114    ) {
1115        *x1 ^= x2
1116    }
1117
1118    let mut slice: &[u8] = &xor_pad;
1119
1120    MaskedResponse::deserialize_take(&mut (slice))
1121}
1122
1123fn unmask_response<CS: CipherSuite>(
1124    masking_key: &[u8],
1125    masking_nonce: &[u8],
1126    masked_response: &MaskedResponse<CS>,
1127) -> Result<(PublicKey<KeGroup<CS>>, Envelope<CS>), ProtocolError> {
1128    let mut xor_pad = GenericArray::<_, MaskedResponseLen<CS>>::default();
1129
1130    Hkdf::<OprfHash<CS>>::from_prk(masking_key)
1131        .map_err(|_| InternalError::HkdfError)?
1132        .expand_multi_info(&[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD], &mut xor_pad)
1133        .map_err(|_| InternalError::HkdfError)?;
1134
1135    for (x1, x2) in xor_pad.iter_mut().zip(masked_response.iter().flatten()) {
1136        *x1 ^= x2
1137    }
1138
1139    let mut xor_pad: &[u8] = xor_pad.as_ref();
1140    let server_s_pk =
1141        PublicKey::deserialize_take(&mut xor_pad).map_err(|_| ProtocolError::SerializationError)?;
1142    let envelope = Envelope::deserialize_take(&mut xor_pad)?;
1143
1144    Ok((server_s_pk, envelope))
1145}
1146
1147/// Internal function for computing the blind result by calling the voprf
1148/// library. Note that for tests, we use the deterministic blinding in order to
1149/// be able to set the blinding factor directly from the passed-in rng.
1150fn blind<CS: CipherSuite, R: Rng + CryptoRng>(
1151    rng: &mut R,
1152    password: &[u8],
1153) -> Result<voprf::OprfClientBlindResult<CS::OprfCs>, voprf::Error> {
1154    #[cfg(not(test))]
1155    let result = OprfClient::blind(password, rng)?;
1156
1157    #[cfg(test)]
1158    let result = {
1159        let mut blind_bytes =
1160            GenericArray::<_, <OprfGroup<CS> as voprf::Group>::ScalarLen>::default();
1161        let blind = loop {
1162            rng.fill_bytes(&mut blind_bytes);
1163            if let Ok(scalar) = <OprfGroup<CS> as voprf::Group>::deserialize_scalar(&blind_bytes) {
1164                break scalar;
1165            }
1166        };
1167        OprfClient::deterministic_blind_unchecked(password, blind)?
1168    };
1169
1170    Ok(result)
1171}