opaque_ke/
opaque.rs

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