// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is dual-licensed under either the MIT license found in the // LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory // of this source tree. You may select, at your option, one of the above-listed // licenses. use core::ops::Add; use derive_where::derive_where; use digest::core_api::BlockSizeUser; use digest::{Digest, Output, OutputSizeUser, Update}; use generic_array::sequence::Concat; use generic_array::typenum::{IsLess, Le, NonZero, Sum, U1, U2, U32, U256, Unsigned}; use generic_array::{ArrayLength, GenericArray}; use hkdf::{Hkdf, HkdfExtract}; use rand::{CryptoRng, RngCore}; use super::{ Deserialize, GenerateKe1Result, KeyExchange, Serialize, SerializedContext, SerializedCredentialRequest, SerializedCredentialResponse, SerializedIdentifiers, }; use crate::ciphersuite::{CipherSuite, KeGroup, KeHash}; use crate::errors::{InternalError, ProtocolError}; use crate::hash::{Hash, OutputSize, ProxyHash}; use crate::key_exchange::group::Group; use crate::keypair::{KeyPair, PrivateKey, PublicKey}; use crate::serialization::{SliceExt, UpdateExt, i2osp}; /////////////// // Constants // // ========= // /////////////// pub(crate) type NonceLen = U32; pub(super) static STR_CONTEXT: &[u8] = b"OPAQUEv1-"; static STR_CLIENT_MAC: &[u8] = b"ClientMAC"; static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret"; static STR_SERVER_MAC: &[u8] = b"ServerMAC"; static STR_SESSION_KEY: &[u8] = b"SessionKey"; static STR_OPAQUE: &[u8] = b"OPAQUE-"; //////////////////////////// // High-level API Structs // // ====================== // //////////////////////////// /// Trait required by [`Group::Sk`] to be compatible with /// [`TripleDh`](crate::TripleDh) and [`SigmaI`](crate::SigmaI). pub trait DiffieHellman { /// Diffie-Hellman key exchange. fn diffie_hellman(&self, pk: &G::Pk) -> GenericArray; } /// The client state produced after the first key exchange message #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound( deserialize = "G::Sk: serde::Deserialize<'de>", serialize = "G::Sk: serde::Serialize" )) )] #[derive_where(Clone, ZeroizeOnDrop)] #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Sk)] pub struct Ke1State { pub(super) client_e_sk: PrivateKey, pub(super) client_nonce: GenericArray, } /// The first key exchange message #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound( deserialize = "G::Pk: serde::Deserialize<'de>", serialize = "G::Pk: serde::Serialize" )) )] #[derive_where(Clone, ZeroizeOnDrop)] #[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)] pub struct Ke1Message { pub(super) client_nonce: GenericArray, #[derive_where(skip(Zeroize))] pub(super) client_e_pk: PublicKey, } ///////////////////////// // Convenience Structs // //==================== // ///////////////////////// // Consists of a session key, followed by two mac keys: (session_key, km2, km3) pub(super) struct DerivedKeys { pub(super) session_key: Output, pub(super) km2: Output, pub(super) km3: Output, #[cfg(test)] pub(super) handshake_secret: Output, } //////////////////////////////////////////////// // Helper functions and Trait Implementations // // ========================================== // //////////////////////////////////////////////// // Helper functions pub(super) fn generate_ke1< R: RngCore + CryptoRng, KE: KeyExchange, KE1Message = Ke1Message>, G: Group, >( rng: &mut R, ) -> Result, ProtocolError> { let client_e_kp = KeyPair::::derive_random(rng); let client_nonce = generate_nonce::(rng); let ke1_message = Ke1Message { client_nonce, client_e_pk: client_e_kp.public().clone(), }; Ok(GenerateKe1Result { state: Ke1State { client_e_sk: client_e_kp.private().clone(), client_nonce, }, message: ke1_message, }) } // Generate a random nonce up to NonceLen::USIZE bytes. pub(super) fn generate_nonce(rng: &mut R) -> GenericArray { let mut nonce_bytes = GenericArray::default(); rng.fill_bytes(&mut nonce_bytes); nonce_bytes } pub(super) fn transcript( context: &SerializedContext<'_>, identifiers: &SerializedIdentifiers<'_, KeGroup>, credential_request: &SerializedCredentialRequest, ke1_message: &Ke1MessageIter, credential_response: &SerializedCredentialResponse, server_nonce: GenericArray, server_e_pk: &GenericArray, ) -> KeHash { KeHash::::new() .chain_iter(context.iter()) .chain_iter(identifiers.client.iter()) .chain_iter(credential_request.iter()) .chain_iter(ke1_message.iter()) .chain_iter(identifiers.server.iter()) .chain_iter(credential_response.iter()) .chain(server_nonce) .chain(server_e_pk) } // Internal function which takes computed shared secrets, along with some // auxiliary metadata, to produce the session key and two MAC keys pub(super) fn derive_keys<'a, H: Hash>( ikms: impl Iterator, hashed_derivation_transcript: &[u8], ) -> Result, ProtocolError> where H::Core: ProxyHash, ::BlockSize: IsLess, Le<::BlockSize, U256>: NonZero, { let mut hkdf = HkdfExtract::::new(None); for ikm in ikms { hkdf.input_ikm(ikm); } let (_, extracted_ikm) = hkdf.finalize(); let handshake_secret = derive_secrets::( &extracted_ikm, STR_HANDSHAKE_SECRET, hashed_derivation_transcript, )?; let session_key = derive_secrets::( &extracted_ikm, STR_SESSION_KEY, hashed_derivation_transcript, )?; let km2 = hkdf_expand_label::(&handshake_secret, STR_SERVER_MAC, b"")?; let km3 = hkdf_expand_label::(&handshake_secret, STR_CLIENT_MAC, b"")?; Ok(DerivedKeys { session_key, km2, km3, #[cfg(test)] handshake_secret, }) } fn hkdf_expand_label( secret: &[u8], label: &[u8], context: &[u8], ) -> Result, ProtocolError> where H::Core: ProxyHash, ::BlockSize: IsLess, Le<::BlockSize, U256>: NonZero, { let h = Hkdf::::from_prk(secret).map_err(|_| InternalError::HkdfError)?; hkdf_expand_label_extracted(&h, label, context) } fn hkdf_expand_label_extracted( hkdf: &Hkdf, label: &[u8], context: &[u8], ) -> Result, ProtocolError> where H::Core: ProxyHash, ::BlockSize: IsLess, Le<::BlockSize, U256>: NonZero, { let mut okm = GenericArray::default(); let length = i2osp::(OutputSize::::USIZE)?; let label_length = i2osp::(STR_OPAQUE.len() + label.len())?; let context_len = i2osp::(context.len())?; let hkdf_label = [ length.as_slice(), &label_length, STR_OPAQUE, label, &context_len, context, ]; hkdf.expand_multi_info(&hkdf_label, &mut okm) .map_err(|_| InternalError::HkdfError)?; Ok(okm) } fn derive_secrets( hkdf: &Hkdf, label: &[u8], hashed_derivation_transcript: &[u8], ) -> Result, ProtocolError> where H::Core: ProxyHash, ::BlockSize: IsLess, Le<::BlockSize, U256>: NonZero, { hkdf_expand_label_extracted::(hkdf, label, hashed_derivation_transcript) } // Serialization and deserialization implementations impl Deserialize for Ke1State { fn deserialize_take(bytes: &mut &[u8]) -> Result { Ok(Self { client_e_sk: PrivateKey::deserialize_take(bytes)?, client_nonce: bytes.take_array("client nonce")?, }) } } impl Serialize for Ke1State where // Ke1State: KeSk + Nonce G::SkLen: Add, Sum: ArrayLength, { type Len = Sum; fn serialize(&self) -> GenericArray { self.client_e_sk.serialize().concat(self.client_nonce) } } impl Deserialize for Ke1Message { fn deserialize_take(input: &mut &[u8]) -> Result { Ok(Self { client_nonce: input.take_array("client nonce")?, client_e_pk: PublicKey::deserialize_take(input)?, }) } } impl Serialize for Ke1Message where // Ke1Message: Nonce + KePk NonceLen: Add, Sum: ArrayLength, { type Len = Sum; fn serialize(&self) -> GenericArray { self.client_nonce.concat(self.client_e_pk.serialize()) } } impl Ke1Message { pub(crate) fn to_iter(&self) -> Ke1MessageIter { Ke1MessageIter { client_nonce: self.client_nonce, client_e_pk: self.client_e_pk.serialize(), } } } #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = "") )] #[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)] pub(crate) struct Ke1MessageIter { client_nonce: GenericArray, client_e_pk: GenericArray, } pub(crate) type Ke1MessageIterLen = Sum; impl Ke1MessageIter { pub(crate) fn iter(&self) -> impl Clone + Iterator { [self.client_nonce.as_slice(), self.client_e_pk.as_slice()].into_iter() } pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result { Ok(Ke1MessageIter { client_nonce: input.take_array("client nonce")?, client_e_pk: input.take_array("client ephemeral public key")?, }) } } impl Ke1MessageIter where NonceLen: Add, Ke1MessageIterLen: ArrayLength, { pub(crate) fn serialize(&self) -> GenericArray> { self.client_nonce.concat(self.client_e_pk.clone()) } }