Files
opaque-vx/src/envelope.rs
T

334 lines
11 KiB
Rust
Raw Normal View History

2023-05-22 23:04:26 -07:00
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
2023-05-22 23:04:26 -07:00
// 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
2021-12-03 14:38:11 -08:00
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
2023-05-22 23:04:26 -07:00
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
2021-08-12 06:25:07 +02:00
use core::convert::TryFrom;
2022-01-06 06:19:02 +01:00
2022-02-25 07:13:22 +01:00
use derive_where::derive_where;
2025-04-15 22:31:37 +02:00
use digest::Output;
2022-01-06 06:19:02 +01:00
use generic_array::sequence::Concat;
2025-05-19 22:56:25 +02:00
use generic_array::typenum::{Sum, U32};
use generic_array::GenericArray;
use hkdf::Hkdf;
2022-01-06 00:10:57 +01:00
use hmac::{Hmac, Mac};
2021-02-11 18:10:48 -08:00
use rand::{CryptoRng, RngCore};
2022-02-25 07:13:22 +01:00
use zeroize::{Zeroize, ZeroizeOnDrop};
2025-05-19 22:56:25 +02:00
use crate::ciphersuite::{CipherSuite, KeGroup, OprfHash};
2022-01-06 06:19:02 +01:00
use crate::errors::{InternalError, ProtocolError};
2025-04-15 22:31:37 +02:00
use crate::hash::OutputSize;
2025-05-19 22:56:25 +02:00
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};
2022-01-06 06:19:02 +01:00
// Constant string used as salt for HKDF computation
2022-01-04 00:50:40 +01:00
const STR_AUTH_KEY: [u8; 7] = *b"AuthKey";
const STR_EXPORT_KEY: [u8; 9] = *b"ExportKey";
const STR_PRIVATE_KEY: [u8; 10] = *b"PrivateKey";
2025-04-15 22:31:37 +02:00
pub(crate) type NonceLen = U32;
2023-02-04 22:25:41 +01:00
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
2022-02-25 07:13:22 +01:00
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
pub(crate) enum InnerEnvelopeMode {
Zero = 0,
Internal = 1,
}
2022-02-25 07:13:22 +01:00
impl Zeroize for InnerEnvelopeMode {
fn zeroize(&mut self) {
*self = Self::Zero
}
}
impl TryFrom<u8> for InnerEnvelopeMode {
2021-08-22 12:28:19 -07:00
type Error = ProtocolError;
fn try_from(x: u8) -> Result<Self, Self::Error> {
match x {
1 => Ok(InnerEnvelopeMode::Internal),
2021-08-22 12:28:19 -07:00
_ => Err(ProtocolError::SerializationError),
}
}
}
2022-03-15 19:51:40 -07:00
/// This struct is an instantiation of the envelope.
///
2022-01-06 06:19:02 +01:00
/// Note that earlier versions of this specification described an implementation
/// of this envelope using an encryption scheme that satisfied random-key
2022-03-15 19:51:40 -07:00
/// robustness.
2022-01-06 06:19:02 +01:00
/// 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.
2022-04-02 01:10:00 +02:00
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
2023-02-04 22:25:41 +01:00
serde(bound = "")
2022-04-02 01:10:00 +02:00
)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
2025-04-15 22:31:37 +02:00
pub(crate) struct Envelope<CS: CipherSuite> {
2022-02-25 07:13:22 +01:00
pub(crate) mode: InnerEnvelopeMode,
2022-01-04 00:50:40 +01:00
nonce: GenericArray<u8, NonceLen>,
2022-02-25 07:13:22 +01:00
hmac: Output<OprfHash<CS>>,
}
2022-01-06 06:19:02 +01:00
// 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.
2025-04-15 22:31:37 +02:00
pub(crate) struct OpenedEnvelope<'a, CS: CipherSuite> {
2025-05-19 22:56:25 +02:00
pub(crate) client_static_keypair: KeyPair<KeGroup<CS>>,
2022-02-25 07:13:22 +01:00
pub(crate) export_key: Output<OprfHash<CS>>,
2025-05-19 22:56:25 +02:00
pub(crate) identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
}
2025-04-15 22:31:37 +02:00
pub(crate) struct OpenedInnerEnvelope<CS: CipherSuite> {
pub(crate) export_key: Output<OprfHash<CS>>,
2020-07-27 15:25:04 -07:00
}
2021-07-30 12:06:46 +02:00
#[cfg(not(test))]
2022-02-25 07:13:22 +01:00
type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>);
2021-07-30 12:06:46 +02:00
#[cfg(test)]
2022-02-25 07:13:22 +01:00
type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>, Output<OprfHash<CS>>);
2021-07-30 12:06:46 +02:00
#[cfg(not(test))]
2025-05-19 22:56:25 +02:00
type SealResult<CS: CipherSuite> = (Envelope<CS>, PublicKey<KeGroup<CS>>, Output<OprfHash<CS>>);
2021-07-30 12:06:46 +02:00
#[cfg(test)]
2022-01-06 00:10:57 +01:00
type SealResult<CS: CipherSuite> = (
2021-07-30 12:06:46 +02:00
Envelope<CS>,
2025-05-19 22:56:25 +02:00
PublicKey<KeGroup<CS>>,
2022-02-25 07:13:22 +01:00
Output<OprfHash<CS>>,
Output<OprfHash<CS>>,
2021-07-30 12:06:46 +02:00
);
2025-05-19 22:56:25 +02:00
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<OutputSize<OprfHash<CS>>, NonceLen>;
2022-01-04 00:50:40 +01:00
2025-04-15 22:31:37 +02:00
impl<CS: CipherSuite> Envelope<CS> {
#[allow(clippy::type_complexity)]
pub(crate) fn seal<R: RngCore + CryptoRng>(
rng: &mut R,
2022-02-25 07:13:22 +01:00
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
2025-05-19 22:56:25 +02:00
server_s_pk: &PublicKey<KeGroup<CS>>,
2022-01-04 00:50:40 +01:00
ids: Identifiers,
2021-07-30 13:51:15 +02:00
) -> Result<SealResult<CS>, ProtocolError> {
2022-01-04 00:50:40 +01:00
let mut nonce = GenericArray::default();
rng.fill_bytes(&mut nonce);
let (mode, client_s_pk) = (
InnerEnvelopeMode::Internal,
2022-01-04 00:50:40 +01:00
build_inner_envelope_internal::<CS>(randomized_pwd_hasher.clone(), nonce)?,
);
2022-04-02 01:10:00 +02:00
let server_s_pk_bytes = server_s_pk.serialize();
2025-05-19 22:56:25 +02:00
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
2022-01-04 00:50:40 +01:00
ids,
2022-04-02 01:10:00 +02:00
client_s_pk.serialize(),
2022-02-25 07:13:22 +01:00
server_s_pk_bytes.clone(),
2022-01-04 00:50:40 +01:00
)?;
2025-05-19 22:56:25 +02:00
let aad = construct_aad(
identifiers.client.iter(),
identifiers.server.iter(),
&server_s_pk_bytes,
);
2022-01-04 00:50:40 +01:00
let result = Self::seal_raw(randomized_pwd_hasher, nonce, aad, mode)?;
2021-07-30 12:06:46 +02:00
Ok((
result.0,
client_s_pk,
result.1,
#[cfg(test)]
result.2,
))
}
2022-01-06 06:19:02 +01:00
/// 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)]
2022-01-04 00:50:40 +01:00
pub(crate) fn seal_raw<'a>(
2022-02-25 07:13:22 +01:00
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
2022-01-04 00:50:40 +01:00
nonce: GenericArray<u8, NonceLen>,
aad: impl Iterator<Item = &'a [u8]>,
mode: InnerEnvelopeMode,
2021-08-22 12:28:19 -07:00
) -> Result<SealRawResult<CS>, InternalError> {
2022-02-25 07:13:22 +01:00
let mut hmac_key = Output::<OprfHash<CS>>::default();
let mut export_key = Output::<OprfHash<CS>>::default();
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher
2022-01-04 00:50:40 +01:00
.expand_multi_info(&[&nonce, &STR_AUTH_KEY], &mut hmac_key)
2021-08-22 12:28:19 -07:00
.map_err(|_| InternalError::HkdfError)?;
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher
2022-01-04 00:50:40 +01:00
.expand_multi_info(&[&nonce, &STR_EXPORT_KEY], &mut export_key)
2021-08-22 12:28:19 -07:00
.map_err(|_| InternalError::HkdfError)?;
2022-02-25 07:13:22 +01:00
let mut hmac = Hmac::<OprfHash<CS>>::new_from_slice(&hmac_key)
.map_err(|_| InternalError::HmacError)?;
2022-01-04 00:50:40 +01:00
hmac.update(&nonce);
hmac.update_iter(aad);
let hmac_bytes = hmac.finalize().into_bytes();
Ok((
Self {
mode,
2022-01-04 00:50:40 +01:00
nonce,
hmac: hmac_bytes,
},
2022-01-04 00:50:40 +01:00
export_key,
2021-07-30 12:06:46 +02:00
#[cfg(test)]
hmac_key,
))
}
2022-01-04 00:50:40 +01:00
pub(crate) fn open<'a>(
&self,
2022-02-25 07:13:22 +01:00
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
2025-05-19 22:56:25 +02:00
server_s_pk: PublicKey<KeGroup<CS>>,
2022-01-04 00:50:40 +01:00
optional_ids: Identifiers<'a>,
) -> Result<OpenedEnvelope<'a, CS>, ProtocolError> {
let client_static_keypair = match self.mode {
InnerEnvelopeMode::Zero => {
2021-08-22 12:28:19 -07:00
return Err(InternalError::IncompatibleEnvelopeModeError.into())
}
2021-10-25 02:54:32 -07:00
InnerEnvelopeMode::Internal => {
2022-01-04 00:50:40 +01:00
recover_keys_internal::<CS>(randomized_pwd_hasher.clone(), self.nonce)?
2021-10-25 02:54:32 -07:00
}
};
2022-04-02 01:10:00 +02:00
let server_s_pk_bytes = server_s_pk.serialize();
2025-05-19 22:56:25 +02:00
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
optional_ids,
2022-04-02 01:10:00 +02:00
client_static_keypair.public().serialize(),
2022-02-25 07:13:22 +01:00
server_s_pk_bytes.clone(),
2021-07-08 11:04:32 -07:00
)?;
2025-05-19 22:56:25 +02:00
let aad = construct_aad(
identifiers.client.iter(),
identifiers.server.iter(),
&server_s_pk_bytes,
);
2022-01-04 00:50:40 +01:00
let opened = self.open_raw(randomized_pwd_hasher, aad)?;
Ok(OpenedEnvelope {
client_static_keypair,
export_key: opened.export_key,
2025-05-19 22:56:25 +02:00
identifiers,
})
}
2022-01-06 06:19:02 +01:00
/// 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.
2022-01-04 00:50:40 +01:00
pub(crate) fn open_raw<'a>(
&self,
2022-02-25 07:13:22 +01:00
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
2022-01-04 00:50:40 +01:00
aad: impl Iterator<Item = &'a [u8]>,
2025-04-15 22:31:37 +02:00
) -> Result<OpenedInnerEnvelope<CS>, InternalError> {
2022-02-25 07:13:22 +01:00
let mut hmac_key = Output::<OprfHash<CS>>::default();
let mut export_key = Output::<OprfHash<CS>>::default();
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher
2022-01-04 00:50:40 +01:00
.expand(&self.nonce.concat(STR_AUTH_KEY.into()), &mut hmac_key)
2021-08-22 12:28:19 -07:00
.map_err(|_| InternalError::HkdfError)?;
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher
2022-01-04 00:50:40 +01:00
.expand(&self.nonce.concat(STR_EXPORT_KEY.into()), &mut export_key)
2021-08-22 12:28:19 -07:00
.map_err(|_| InternalError::HkdfError)?;
2022-02-25 07:13:22 +01:00
let mut hmac = Hmac::<OprfHash<CS>>::new_from_slice(&hmac_key)
.map_err(|_| InternalError::HmacError)?;
hmac.update(&self.nonce);
2022-01-04 00:50:40 +01:00
hmac.update_iter(aad);
hmac.verify(&self.hmac)
.map_err(|_| InternalError::SealOpenHmacError)?;
2022-01-04 00:50:40 +01:00
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,
2022-01-04 00:50:40 +01:00
nonce: GenericArray::default(),
hmac: GenericArray::default(),
}
}
2025-05-19 22:56:25 +02:00
#[cfg(test)]
pub(crate) fn len() -> usize {
2025-05-19 22:56:25 +02:00
use generic_array::typenum::Unsigned;
2022-02-25 07:13:22 +01:00
OutputSize::<OprfHash<CS>>::USIZE + NonceLen::USIZE
}
2025-05-19 22:56:25 +02:00
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>> {
self.nonce.concat_ext(&self.hmac)
}
2022-01-04 00:50:40 +01:00
2025-05-19 22:56:25 +02:00
pub(crate) fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
2025-05-19 22:56:25 +02:00
mode: InnerEnvelopeMode::Internal,
nonce: bytes.take_array("nonce")?,
hmac: bytes.take_array("hmac")?,
})
}
}
// Helper functions
fn build_inner_envelope_internal<CS: CipherSuite>(
2022-02-25 07:13:22 +01:00
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
2022-01-04 00:50:40 +01:00
nonce: GenericArray<u8, NonceLen>,
2025-05-19 22:56:25 +02:00
) -> Result<PublicKey<KeGroup<CS>>, ProtocolError> {
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher
2022-01-04 00:50:40 +01:00
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
2025-05-19 22:56:25 +02:00
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
2025-05-19 22:56:25 +02:00
Ok(client_s_sk.public_key())
}
fn recover_keys_internal<CS: CipherSuite>(
2022-02-25 07:13:22 +01:00
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
2022-01-04 00:50:40 +01:00
nonce: GenericArray<u8, NonceLen>,
2025-05-19 22:56:25 +02:00
) -> Result<KeyPair<KeGroup<CS>>, ProtocolError> {
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher
2022-01-04 00:50:40 +01:00
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
2025-05-19 22:56:25 +02:00
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
let client_s_pk = client_s_sk.public_key();
2025-05-19 22:56:25 +02:00
Ok(KeyPair::new(client_s_sk, client_s_pk))
}
2022-01-04 00:50:40 +01:00
fn construct_aad<'a>(
id_u: impl Iterator<Item = &'a [u8]>,
id_s: impl Iterator<Item = &'a [u8]>,
server_s_pk: &'a [u8],
) -> impl Iterator<Item = &'a [u8]> {
2022-01-06 00:10:57 +01:00
[server_s_pk].into_iter().chain(id_s).chain(id_u)
}
2025-05-19 22:56:25 +02:00
//////////////////////////
// Test Implementations //
//===================== //
//////////////////////////
#[cfg(test)]
use crate::serialization::AssertZeroized;
#[cfg(test)]
impl<CS: CipherSuite> AssertZeroized for Envelope<CS> {
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);
}
}
}