Skip to main content

opaque_vx/
messages.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//! Contains the messages used for OPAQUE
6
7use core::ops::Add;
8
9use derive_where::derive_where;
10use digest::Output;
11use generic_array::typenum::{Sum, Unsigned};
12use generic_array::{ArrayLength, GenericArray};
13use hybrid_array::Array;
14use rand::{CryptoRng, Rng};
15use voprf::{BlindedElement, BlindedElementLen, EvaluationElement, EvaluationElementLen};
16use zeroize::Zeroizing;
17
18use crate::ciphersuite::{CipherSuite, KeGroup, OprfGroup, OprfHash};
19use crate::envelope::{Envelope, EnvelopeLen};
20use crate::errors::ProtocolError;
21use crate::hash::OutputSize;
22use crate::key_exchange::group::Group;
23use crate::key_exchange::shared::NonceLen;
24use crate::key_exchange::{
25    Deserialize, Ke1MessageLen, Ke2MessageLen, Ke3MessageLen, KeyExchange, Serialize,
26    SerializedCredentialRequest, SerializedCredentialResponse,
27};
28use crate::keypair::PublicKey;
29use crate::opaque::{
30    MaskedResponse, MaskedResponseLen, ServerLogin, ServerLoginStartResult, ServerSetup,
31};
32use crate::serialization::{ConcatExt, SliceExt};
33
34////////////////////////////
35// High-level API Structs //
36// ====================== //
37////////////////////////////
38
39/// The message sent by the client to the server, to initiate registration
40#[cfg_attr(
41    feature = "serde",
42    derive(serde::Deserialize, serde::Serialize),
43    serde(bound = "")
44)]
45#[derive_where(Clone)]
46#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::BlindedElement<CS::OprfCs>)]
47pub struct RegistrationRequest<CS: CipherSuite> {
48    /// blinded password information
49    pub(crate) blinded_element: BlindedElement<CS::OprfCs>,
50}
51
52/// The answer sent by the server to the user, upon reception of the
53/// registration attempt
54#[cfg_attr(
55    feature = "serde",
56    derive(serde::Deserialize, serde::Serialize),
57    serde(bound(
58        deserialize = "<KeGroup<CS> as Group>::Pk: serde::Deserialize<'de>",
59        serialize = "<KeGroup<CS> as Group>::Pk: serde::Serialize"
60    ))
61)]
62#[derive_where(Clone)]
63#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::EvaluationElement<CS::OprfCs>, <KeGroup<CS> as Group>::Pk
64)]
65pub struct RegistrationResponse<CS: CipherSuite> {
66    /// The server's oprf output
67    pub(crate) evaluation_element: EvaluationElement<CS::OprfCs>,
68    /// Server's static public key
69    pub(crate) server_s_pk: PublicKey<KeGroup<CS>>,
70}
71
72/// The final message from the client, containing sealed cryptographic
73/// identifiers
74#[cfg_attr(
75    feature = "serde",
76    derive(serde::Deserialize, serde::Serialize),
77    serde(bound(
78        deserialize = "<KeGroup<CS> as Group>::Pk: serde::Deserialize<'de>",
79        serialize = "<KeGroup<CS> as Group>::Pk: serde::Serialize"
80    ))
81)]
82#[derive_where(Clone, ZeroizeOnDrop)]
83#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <KeGroup<CS> as Group>::Pk)]
84pub struct RegistrationUpload<CS: CipherSuite> {
85    /// The "envelope" generated by the user, containing sealed cryptographic
86    /// identifiers
87    pub(crate) envelope: Envelope<CS>,
88    /// The masking key used to mask the envelope
89    pub(crate) masking_key: Output<OprfHash<CS>>,
90    /// The user's public key
91    #[derive_where(skip(Zeroize))]
92    pub(crate) client_s_pk: PublicKey<KeGroup<CS>>,
93}
94
95/// The message sent by the user to the server, to initiate registration
96#[cfg_attr(
97    feature = "serde",
98    derive(serde::Deserialize, serde::Serialize),
99    serde(bound(
100        deserialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Deserialize<'de>",
101        serialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Serialize"
102    ))
103)]
104#[derive_where(Clone, ZeroizeOnDrop)]
105#[derive_where(
106    Debug, Eq, Hash, PartialEq;
107    voprf::BlindedElement<CS::OprfCs>,
108    <CS::KeyExchange as KeyExchange>::KE1Message,
109)]
110pub struct CredentialRequest<CS: CipherSuite> {
111    pub(crate) blinded_element: BlindedElement<CS::OprfCs>,
112    pub(crate) ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message,
113}
114
115/// Builder for [`ServerLogin`] when using remote keys.
116#[cfg_attr(
117    feature = "serde",
118    derive(serde::Deserialize, serde::Serialize),
119    serde(bound(
120        deserialize = "SK: serde::Deserialize<'de>, <CS::KeyExchange as \
121                       KeyExchange>::KE2Builder<'a, CS>: serde::Deserialize<'de>",
122        serialize = "SK: serde::Serialize, <CS::KeyExchange as KeyExchange>::KE2Builder<'a, CS>: \
123                     serde::Serialize"
124    ))
125)]
126#[derive_where(Clone)]
127#[derive_where(
128    Debug, Eq, PartialEq;
129    <KeGroup<CS> as Group>::Pk,
130    SK,
131    voprf::EvaluationElement<CS::OprfCs>,
132    <CS::KeyExchange as KeyExchange>::KE2Builder<'a, CS>,
133)]
134pub struct ServerLoginBuilder<'a, CS: CipherSuite, SK: Clone> {
135    pub(crate) server_s_sk: SK,
136    pub(crate) evaluation_element: EvaluationElement<CS::OprfCs>,
137    pub(crate) masking_nonce: Zeroizing<GenericArray<u8, NonceLen>>,
138    pub(crate) masked_response: MaskedResponse<CS>,
139    #[cfg(test)]
140    pub(crate) oprf_key: Zeroizing<GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>>,
141    pub(crate) ke2_builder: <CS::KeyExchange as KeyExchange>::KE2Builder<'a, CS>,
142}
143
144impl<CS: CipherSuite, SK: Clone> ServerLoginBuilder<'_, CS, SK> {
145    /// The returned data here has to be processed and the result given as an
146    /// input to [`ServerLoginBuilder::build()`]. To understand what kind of
147    /// output is expected here and how to process it, refer to the
148    /// documentation of your chosen [`CipherSuite::KeyExchange`].
149    pub fn data(&self) -> <CS::KeyExchange as KeyExchange>::KE2BuilderData<'_, CS> {
150        CS::KeyExchange::ke2_builder_data(&self.ke2_builder)
151    }
152
153    /// The handle to the corresponding [`ServerSetup`]s private key.
154    pub fn private_key(&self) -> &SK {
155        &self.server_s_sk
156    }
157
158    /// Build [`ServerLogin`] after attaining the input for the key exchange. To
159    /// understand what kind of input is expected here, refer to the
160    /// documentation of your chosen [`CipherSuite::KeyExchange`].
161    ///
162    /// See [`ServerLogin::start()`] for the regular path.
163    pub fn build(
164        self,
165        input: <CS::KeyExchange as KeyExchange>::KE2BuilderInput<CS>,
166    ) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
167        ServerLogin::build(self, input)
168    }
169}
170
171/// The answer sent by the server to the user, upon reception of the login
172/// attempt
173#[cfg_attr(
174    feature = "serde",
175    derive(serde::Deserialize, serde::Serialize),
176    serde(bound(
177        deserialize = "<CS::KeyExchange as KeyExchange>::KE2Message: serde::Deserialize<'de>",
178        serialize = "<CS::KeyExchange as KeyExchange>::KE2Message: serde::Serialize"
179    ))
180)]
181#[derive_where(Clone)]
182#[derive_where(
183    Debug, Eq, Hash, PartialEq;
184    EvaluationElement<CS::OprfCs>,
185    <CS::KeyExchange as KeyExchange>::KE2Message,
186)]
187pub struct CredentialResponse<CS: CipherSuite> {
188    /// the server's oprf output
189    pub(crate) evaluation_element: EvaluationElement<CS::OprfCs>,
190    pub(crate) masking_nonce: GenericArray<u8, NonceLen>,
191    pub(crate) masked_response: MaskedResponse<CS>,
192    pub(crate) ke2_message: <CS::KeyExchange as KeyExchange>::KE2Message,
193}
194
195/// The answer sent by the client to the server, upon reception of the sealed
196/// envelope
197#[cfg_attr(
198    feature = "serde",
199    derive(serde::Deserialize, serde::Serialize),
200    serde(bound(
201        deserialize = "<CS::KeyExchange as KeyExchange>::KE3Message: serde::Deserialize<'de>",
202        serialize = "<CS::KeyExchange as KeyExchange>::KE3Message: serde::Serialize"
203    ))
204)]
205#[derive_where(Clone)]
206#[derive_where(
207    Debug, Eq, Hash, PartialEq;
208    <CS::KeyExchange as KeyExchange>::KE3Message,
209)]
210pub struct CredentialFinalization<CS: CipherSuite> {
211    pub(crate) ke3_message: <CS::KeyExchange as KeyExchange>::KE3Message,
212}
213
214////////////////////////////////
215// High-level Implementations //
216// ========================== //
217////////////////////////////////
218
219/// Length of [`RegistrationRequest`] in bytes for serialization.
220pub type RegistrationRequestLen<CS: CipherSuite> = <OprfGroup<CS> as voprf::Group>::ElemLen;
221
222impl<CS: CipherSuite> RegistrationRequest<CS> {
223    /// Only used for testing purposes
224    #[cfg(test)]
225    pub(crate) fn get_blinded_element_for_testing(&self) -> BlindedElement<CS::OprfCs> {
226        self.blinded_element.clone()
227    }
228
229    /// Serialization into bytes
230    pub fn serialize(&self) -> Array<u8, RegistrationRequestLen<CS>> {
231        <OprfGroup<CS> as voprf::Group>::serialize_elem(self.blinded_element.value())
232    }
233
234    /// Deserialization from bytes
235    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
236        let elem_len = <OprfGroup<CS> as voprf::Group>::ElemLen::USIZE;
237        if input.len() < elem_len {
238            return Err(ProtocolError::SerializationError);
239        }
240        Ok(Self {
241            blinded_element: BlindedElement::deserialize(input)?,
242        })
243    }
244}
245
246/// Length of [`RegistrationResponse`] in bytes for serialization.
247pub type RegistrationResponseLen<CS: CipherSuite> =
248    Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, <KeGroup<CS> as Group>::PkLen>;
249
250impl<CS: CipherSuite> RegistrationResponse<CS> {
251    /// Serialization into bytes
252    pub fn serialize(&self) -> GenericArray<u8, RegistrationResponseLen<CS>>
253    where
254        // RegistrationResponse: KgPk + KePk
255        <OprfGroup<CS> as voprf::Group>::ElemLen: Add<<KeGroup<CS> as Group>::PkLen> + ArrayLength,
256        RegistrationResponseLen<CS>: ArrayLength,
257    {
258        let elem = GenericArray::from_ha0_4(<OprfGroup<CS> as voprf::Group>::serialize_elem(
259            self.evaluation_element.value(),
260        ));
261
262        elem.cat(self.server_s_pk.serialize())
263    }
264
265    /// Deserialization from bytes
266    pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
267        let elem_len = EvaluationElementLen::<CS::OprfCs>::USIZE;
268        if input.len() < elem_len {
269            return Err(ProtocolError::SerializationError);
270        }
271        let evaluation_element = EvaluationElement::deserialize(&input[..elem_len])?;
272        input = &input[elem_len..];
273
274        Ok(Self {
275            evaluation_element,
276            server_s_pk: PublicKey::deserialize_take(&mut input)?,
277        })
278    }
279
280    #[cfg(test)]
281    /// Only used for tests, where we can set the beta value to test for the
282    /// reflection error case
283    pub(crate) fn set_evaluation_element_for_testing(
284        &self,
285        beta: <OprfGroup<CS> as voprf::Group>::Elem,
286    ) -> Self {
287        Self {
288            evaluation_element: EvaluationElement::from_value_unchecked(beta),
289            server_s_pk: self.server_s_pk.clone(),
290        }
291    }
292}
293
294/// Length of [`RegistrationUpload`] in bytes for serialization.
295pub type RegistrationUploadLen<CS: CipherSuite> =
296    Sum<Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>, EnvelopeLen<CS>>;
297
298impl<CS: CipherSuite> RegistrationUpload<CS> {
299    /// Serialization into bytes
300    pub fn serialize(&self) -> GenericArray<u8, RegistrationUploadLen<CS>>
301    where
302        // RegistrationUpload: (KePk + Hash) + Envelope
303        <KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
304        Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
305            ArrayLength + Add<EnvelopeLen<CS>>,
306        RegistrationUploadLen<CS>: ArrayLength,
307    {
308        self.client_s_pk
309            .serialize()
310            .cat(GenericArray::from_slice(self.masking_key.as_slice()).clone())
311            .cat(self.envelope.serialize())
312    }
313
314    /// Deserialization from bytes
315    pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
316        Ok(Self {
317            client_s_pk: PublicKey::deserialize_take(&mut input)?,
318            masking_key: input.take_array("masking key")?.into_ha0_4(),
319            envelope: Envelope::deserialize_take(&mut input)?,
320        })
321    }
322
323    // Creates a dummy instance used for faking a [CredentialResponse]
324    pub(crate) fn dummy<R: Rng + CryptoRng, SK: Clone, OS: Clone>(
325        rng: &mut R,
326        server_setup: &ServerSetup<CS, SK, OS>,
327    ) -> Self {
328        let mut masking_key = Output::<OprfHash<CS>>::default();
329        rng.fill_bytes(&mut masking_key);
330
331        Self {
332            envelope: Envelope::<CS>::dummy(),
333            masking_key,
334            client_s_pk: server_setup.dummy_pk.clone(),
335        }
336    }
337}
338
339/// Length of [`CredentialRequest`] in bytes for serialization.
340pub type CredentialRequestLen<CS: CipherSuite> =
341    Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, Ke1MessageLen<CS>>;
342
343impl<CS: CipherSuite> CredentialRequest<CS> {
344    /// Serialization into bytes
345    pub fn serialize(&self) -> GenericArray<u8, CredentialRequestLen<CS>>
346    where
347        <CS::KeyExchange as KeyExchange>::KE1Message: Serialize,
348        // CredentialRequest: KgPk + Ke1Message
349        <OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>> + ArrayLength,
350        CredentialRequestLen<CS>: ArrayLength,
351    {
352        let elem = GenericArray::from_ha0_4(<OprfGroup<CS> as voprf::Group>::serialize_elem(
353            self.blinded_element.value(),
354        ));
355
356        elem.cat(self.ke1_message.serialize())
357    }
358
359    /// Deserialization from bytes
360    pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
361    where
362        <CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
363    {
364        Self::deserialize_take(&mut input)
365    }
366
367    pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError>
368    where
369        <CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
370    {
371        let elem_len = BlindedElementLen::<CS::OprfCs>::USIZE;
372        if input.len() < elem_len {
373            return Err(ProtocolError::SerializationError);
374        }
375        let blinded_element = BlindedElement::deserialize(&input[..elem_len])?;
376        *input = &input[elem_len..];
377
378        Ok(Self {
379            blinded_element,
380            ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message::deserialize_take(input)?,
381        })
382    }
383
384    pub(crate) fn to_parts(&self) -> SerializedCredentialRequest<CS> {
385        SerializedCredentialRequest::new(&self.blinded_element)
386    }
387
388    /// Only used for testing purposes
389    #[cfg(test)]
390    pub(crate) fn get_blinded_element_for_testing(&self) -> BlindedElement<CS::OprfCs> {
391        self.blinded_element.clone()
392    }
393}
394
395/// Length of [`CredentialResponse`] in bytes for serialization.
396pub type CredentialResponseLen<CS: CipherSuite> =
397    Sum<CredentialResponseWithoutKeLen<CS>, Ke2MessageLen<CS>>;
398
399pub(crate) type CredentialResponseWithoutKeLen<CS: CipherSuite> =
400    Sum<Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
401
402impl<CS: CipherSuite> CredentialResponse<CS> {
403    /// Serialization into bytes
404    pub fn serialize(&self) -> GenericArray<u8, CredentialResponseLen<CS>>
405    where
406        <CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
407        // CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
408        <OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen> + ArrayLength,
409        Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
410            ArrayLength + Add<MaskedResponseLen<CS>>,
411        CredentialResponseWithoutKeLen<CS>: ArrayLength,
412        // CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
413        CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
414        CredentialResponseLen<CS>: ArrayLength,
415    {
416        let elem = GenericArray::from_ha0_4(<OprfGroup<CS> as voprf::Group>::serialize_elem(
417            self.evaluation_element.value(),
418        ));
419
420        elem.cat(self.masking_nonce)
421            .cat(self.masked_response.serialize())
422            .cat(self.ke2_message.serialize())
423    }
424
425    /// Deserialization from bytes
426    pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
427    where
428        <CS::KeyExchange as KeyExchange>::KE2Message: Deserialize,
429    {
430        let elem_len = EvaluationElementLen::<CS::OprfCs>::USIZE;
431        if input.len() < elem_len {
432            return Err(ProtocolError::SerializationError);
433        }
434        let evaluation_element = EvaluationElement::deserialize(&input[..elem_len])?;
435        input = &input[elem_len..];
436
437        Ok(Self {
438            evaluation_element,
439            masking_nonce: input.take_array("masking nonce")?,
440            masked_response: MaskedResponse::deserialize_take(&mut input)?,
441            ke2_message: <CS::KeyExchange as KeyExchange>::KE2Message::deserialize_take(
442                &mut input,
443            )?,
444        })
445    }
446
447    pub(crate) fn to_parts(&self) -> SerializedCredentialResponse<CS> {
448        SerializedCredentialResponse::new(
449            &self.evaluation_element,
450            self.masking_nonce,
451            self.masked_response.clone(),
452        )
453    }
454
455    #[cfg(test)]
456    /// Only used for tests, where we can set the beta value to test for the
457    /// reflection error case
458    pub(crate) fn set_evaluation_element_for_testing(
459        &self,
460        beta: <OprfGroup<CS> as voprf::Group>::Elem,
461    ) -> Self {
462        Self {
463            evaluation_element: EvaluationElement::from_value_unchecked(beta),
464            masking_nonce: self.masking_nonce,
465            masked_response: self.masked_response.clone(),
466            ke2_message: self.ke2_message.clone(),
467        }
468    }
469}
470
471/// Length of [`CredentialFinalization`] in bytes for serialization.
472pub type CredentialFinalizationLen<CS: CipherSuite> = Ke3MessageLen<CS>;
473
474impl<CS: CipherSuite> CredentialFinalization<CS> {
475    /// Serialization into bytes
476    pub fn serialize(&self) -> GenericArray<u8, CredentialFinalizationLen<CS>>
477    where
478        <CS::KeyExchange as KeyExchange>::KE3Message: Serialize,
479    {
480        self.ke3_message.serialize()
481    }
482
483    /// Deserialization from bytes
484    pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
485    where
486        <CS::KeyExchange as KeyExchange>::KE3Message: Deserialize,
487    {
488        Ok(Self {
489            ke3_message: <CS::KeyExchange as KeyExchange>::KE3Message::deserialize_take(
490                &mut input,
491            )?,
492        })
493    }
494}