// 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::convert::TryFrom; use derive_where::derive_where; use digest::Output; use generic_array::sequence::Concat; use generic_array::typenum::{Sum, U32}; use generic_array::GenericArray; use hkdf::Hkdf; use hmac::{Hmac, Mac}; use rand::{CryptoRng, RngCore}; use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::ciphersuite::{CipherSuite, KeGroup, OprfHash}; use crate::errors::{InternalError, ProtocolError}; use crate::hash::OutputSize; use crate::key_exchange::group::Group; use crate::key_exchange::traits::SerializedIdentifiers; use crate::keypair::{KeyPair, PrivateKey, PublicKey}; use crate::opaque::Identifiers; use crate::serialization::{GenericArrayExt, SliceExt, UpdateExt}; // Constant string used as salt for HKDF computation const STR_AUTH_KEY: [u8; 7] = *b"AuthKey"; const STR_EXPORT_KEY: [u8; 9] = *b"ExportKey"; const STR_PRIVATE_KEY: [u8; 10] = *b"PrivateKey"; pub(crate) type NonceLen = U32; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)] pub(crate) enum InnerEnvelopeMode { Zero = 0, Internal = 1, } impl Zeroize for InnerEnvelopeMode { fn zeroize(&mut self) { *self = Self::Zero } } impl TryFrom for InnerEnvelopeMode { type Error = ProtocolError; fn try_from(x: u8) -> Result { match x { 1 => Ok(InnerEnvelopeMode::Internal), _ => Err(ProtocolError::SerializationError), } } } /// This struct is an instantiation of the envelope. /// /// Note that earlier versions of this specification described an implementation /// of this envelope using an encryption scheme that satisfied random-key /// robustness. /// The specification update has simplified this assumption by taking an /// XOR-based approach without compromising on security, and to avoid the /// confusion around the implementation of an RKR-secure encryption. #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = "") )] #[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)] pub(crate) struct Envelope { pub(crate) mode: InnerEnvelopeMode, nonce: GenericArray, hmac: Output>, } // Note that this struct represents an envelope that has been "opened" with the // asssociated key. This key is also used to derive the export_key parameter, // which is technically unrelated to the envelope's encrypted and authenticated // contents. pub(crate) struct OpenedEnvelope<'a, CS: CipherSuite> { pub(crate) client_static_keypair: KeyPair>, pub(crate) export_key: Output>, pub(crate) identifiers: SerializedIdentifiers<'a, KeGroup>, } pub(crate) struct OpenedInnerEnvelope { pub(crate) export_key: Output>, } #[cfg(not(test))] type SealRawResult = (Envelope, Output>); #[cfg(test)] type SealRawResult = (Envelope, Output>, Output>); #[cfg(not(test))] type SealResult = (Envelope, PublicKey>, Output>); #[cfg(test)] type SealResult = ( Envelope, PublicKey>, Output>, Output>, ); pub(crate) type EnvelopeLen = Sum>, NonceLen>; impl Envelope { #[allow(clippy::type_complexity)] pub(crate) fn seal( rng: &mut R, randomized_pwd_hasher: Hkdf>, server_s_pk: &PublicKey>, ids: Identifiers, ) -> Result, ProtocolError> { let mut nonce = GenericArray::default(); rng.fill_bytes(&mut nonce); let (mode, client_s_pk) = ( InnerEnvelopeMode::Internal, build_inner_envelope_internal::(randomized_pwd_hasher.clone(), nonce)?, ); let server_s_pk_bytes = server_s_pk.serialize(); let identifiers = SerializedIdentifiers::>::from_identifiers( ids, client_s_pk.serialize(), server_s_pk_bytes.clone(), )?; let aad = construct_aad( identifiers.client.iter(), identifiers.server.iter(), &server_s_pk_bytes, ); let result = Self::seal_raw(randomized_pwd_hasher, nonce, aad, mode)?; Ok(( result.0, client_s_pk, result.1, #[cfg(test)] result.2, )) } /// Uses a key to convert the plaintext into an envelope, authenticated by /// the aad field. Note that a new nonce is sampled for each call to seal. #[allow(clippy::type_complexity)] pub(crate) fn seal_raw<'a>( randomized_pwd_hasher: Hkdf>, nonce: GenericArray, aad: impl Iterator, mode: InnerEnvelopeMode, ) -> Result, InternalError> { let mut hmac_key = Output::>::default(); let mut export_key = Output::>::default(); randomized_pwd_hasher .expand_multi_info(&[&nonce, &STR_AUTH_KEY], &mut hmac_key) .map_err(|_| InternalError::HkdfError)?; randomized_pwd_hasher .expand_multi_info(&[&nonce, &STR_EXPORT_KEY], &mut export_key) .map_err(|_| InternalError::HkdfError)?; let mut hmac = Hmac::>::new_from_slice(&hmac_key) .map_err(|_| InternalError::HmacError)?; hmac.update(&nonce); hmac.update_iter(aad); let hmac_bytes = hmac.finalize().into_bytes(); Ok(( Self { mode, nonce, hmac: hmac_bytes, }, export_key, #[cfg(test)] hmac_key, )) } pub(crate) fn open<'a>( &self, randomized_pwd_hasher: Hkdf>, server_s_pk: PublicKey>, optional_ids: Identifiers<'a>, ) -> Result, ProtocolError> { let client_static_keypair = match self.mode { InnerEnvelopeMode::Zero => { return Err(InternalError::IncompatibleEnvelopeModeError.into()) } InnerEnvelopeMode::Internal => { recover_keys_internal::(randomized_pwd_hasher.clone(), self.nonce)? } }; let server_s_pk_bytes = server_s_pk.serialize(); let identifiers = SerializedIdentifiers::>::from_identifiers( optional_ids, client_static_keypair.public().serialize(), server_s_pk_bytes.clone(), )?; let aad = construct_aad( identifiers.client.iter(), identifiers.server.iter(), &server_s_pk_bytes, ); let opened = self.open_raw(randomized_pwd_hasher, aad)?; Ok(OpenedEnvelope { client_static_keypair, export_key: opened.export_key, identifiers, }) } /// Attempts to decrypt the envelope using a key, which is successful only /// if the key and aad used to construct the envelope are the same. pub(crate) fn open_raw<'a>( &self, randomized_pwd_hasher: Hkdf>, aad: impl Iterator, ) -> Result, InternalError> { let mut hmac_key = Output::>::default(); let mut export_key = Output::>::default(); randomized_pwd_hasher .expand(&self.nonce.concat(STR_AUTH_KEY.into()), &mut hmac_key) .map_err(|_| InternalError::HkdfError)?; randomized_pwd_hasher .expand(&self.nonce.concat(STR_EXPORT_KEY.into()), &mut export_key) .map_err(|_| InternalError::HkdfError)?; let mut hmac = Hmac::>::new_from_slice(&hmac_key) .map_err(|_| InternalError::HmacError)?; hmac.update(&self.nonce); hmac.update_iter(aad); hmac.verify(&self.hmac) .map_err(|_| InternalError::SealOpenHmacError)?; Ok(OpenedInnerEnvelope { export_key }) } // Creates a dummy envelope object that serializes to the all-zeros byte string pub(crate) fn dummy() -> Self { Self { mode: InnerEnvelopeMode::Zero, nonce: GenericArray::default(), hmac: GenericArray::default(), } } #[cfg(test)] pub(crate) fn len() -> usize { use generic_array::typenum::Unsigned; OutputSize::>::USIZE + NonceLen::USIZE } pub(crate) fn serialize(&self) -> GenericArray> { self.nonce.concat_ext(&self.hmac) } pub(crate) fn deserialize_take(bytes: &mut &[u8]) -> Result { Ok(Self { mode: InnerEnvelopeMode::Internal, nonce: bytes.take_array("nonce")?, hmac: bytes.take_array("hmac")?, }) } } // Helper functions fn build_inner_envelope_internal( randomized_pwd_hasher: Hkdf>, nonce: GenericArray, ) -> Result>, ProtocolError> { let mut keypair_seed = GenericArray::<_, as Group>::SkLen>::default(); randomized_pwd_hasher .expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed) .map_err(|_| InternalError::HkdfError)?; let client_s_sk = PrivateKey::new(KeGroup::::derive_scalar(keypair_seed)?); Ok(client_s_sk.public_key()) } fn recover_keys_internal( randomized_pwd_hasher: Hkdf>, nonce: GenericArray, ) -> Result>, ProtocolError> { let mut keypair_seed = GenericArray::<_, as Group>::SkLen>::default(); randomized_pwd_hasher .expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed) .map_err(|_| InternalError::HkdfError)?; let client_s_sk = PrivateKey::new(KeGroup::::derive_scalar(keypair_seed)?); let client_s_pk = client_s_sk.public_key(); Ok(KeyPair::new(client_s_sk, client_s_pk)) } fn construct_aad<'a>( id_u: impl Iterator, id_s: impl Iterator, server_s_pk: &'a [u8], ) -> impl Iterator { [server_s_pk].into_iter().chain(id_s).chain(id_u) } ////////////////////////// // Test Implementations // //===================== // ////////////////////////// #[cfg(test)] use crate::serialization::AssertZeroized; #[cfg(test)] impl AssertZeroized for Envelope { fn assert_zeroized(&self) { let Self { mode, nonce, hmac } = self; assert_eq!(mode, &InnerEnvelopeMode::Zero); for byte in nonce.iter().chain(hmac) { assert_eq!(byte, &0); } } }