Files
opaque-vx/src/envelope.rs
T

343 lines
12 KiB
Rust
Raw Normal View History

// Copyright (c) Facebook, Inc. and its affiliates.
//
2021-12-03 14:38:11 -08:00
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
use crate::{
ciphersuite::CipherSuite,
2021-08-22 12:28:19 -07:00
errors::{utils::check_slice_size, InternalError, ProtocolError},
hash::Hash,
2021-10-25 02:54:32 -07:00
key_exchange::group::KeGroup,
2021-08-17 05:11:53 +02:00
keypair::{KeyPair, PublicKey},
opaque::{bytestrings_from_identifiers, Identifiers},
2022-01-04 00:50:40 +01:00
serialization::{MacExt, Serialize},
};
2021-08-12 06:25:07 +02:00
use core::convert::TryFrom;
2022-01-04 00:50:40 +01:00
use core::ops::Add;
use derive_where::DeriveWhere;
use digest::{Digest, FixedOutput};
use generic_array::{
sequence::Concat,
typenum::{Sum, Unsigned, U2, U32},
ArrayLength, GenericArray,
};
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
2021-02-11 18:10:48 -08:00
use rand::{CryptoRng, RngCore};
2021-10-25 02:54:32 -07:00
use voprf::group::Group;
use zeroize::Zeroize;
// 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";
const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: [u8; 24] = *b"OPAQUE-DeriveAuthKeyPair";
type NonceLen = U32;
2022-01-04 00:50:40 +01:00
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
#[zeroize(drop)]
pub(crate) enum InnerEnvelopeMode {
Zero = 0,
Internal = 1,
}
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),
}
}
}
/// This struct is an instantiation of the envelope as described in
2022-01-04 00:50:40 +01:00
/// <https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4>
///
/// Note that earlier versions of this specification described an
/// implementation of this envelope using an encryption scheme that
/// satisfied random-key robustness
2022-01-04 00:50:40 +01:00
/// (<https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4>).
/// 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-01-04 00:50:40 +01:00
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
pub(crate) struct Envelope<CS: CipherSuite> {
mode: InnerEnvelopeMode,
2022-01-04 00:50:40 +01:00
nonce: GenericArray<u8, NonceLen>,
hmac: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
}
// 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.
2022-01-04 00:50:40 +01:00
pub(crate) struct OpenedEnvelope<'a, CS: CipherSuite> {
2021-08-04 21:24:46 +02:00
pub(crate) client_static_keypair: KeyPair<CS::KeGroup>,
pub(crate) export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
2022-01-04 00:50:40 +01:00
pub(crate) id_u: Serialize<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
pub(crate) id_s: Serialize<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
}
pub(crate) struct OpenedInnerEnvelope<D: Hash> {
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
2020-07-27 15:25:04 -07:00
}
2021-07-30 12:06:46 +02:00
#[cfg(not(test))]
2021-07-30 13:51:15 +02:00
type SealRawResult<CS> = (
2021-07-30 12:06:46 +02:00
Envelope<CS>,
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
);
#[cfg(test)]
2021-07-30 13:51:15 +02:00
type SealRawResult<CS> = (
2021-07-30 12:06:46 +02:00
Envelope<CS>,
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
2022-01-04 00:50:40 +01:00
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
2021-07-30 12:06:46 +02:00
);
#[cfg(not(test))]
2021-07-30 13:51:15 +02:00
type SealResult<CS> = (
2021-07-30 12:06:46 +02:00
Envelope<CS>,
2021-08-04 21:24:46 +02:00
PublicKey<<CS as CipherSuite>::KeGroup>,
2021-07-30 12:06:46 +02:00
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
);
#[cfg(test)]
2021-07-30 13:51:15 +02:00
type SealResult<CS> = (
2021-07-30 12:06:46 +02:00
Envelope<CS>,
2021-08-04 21:24:46 +02:00
PublicKey<<CS as CipherSuite>::KeGroup>,
2021-07-30 12:06:46 +02:00
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
2022-01-04 00:50:40 +01:00
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
2021-07-30 12:06:46 +02:00
);
2022-01-04 00:50:40 +01:00
#[allow(type_alias_bounds)]
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>;
impl<CS: CipherSuite> Envelope<CS> {
#[allow(clippy::type_complexity)]
pub(crate) fn seal<R: RngCore + CryptoRng>(
rng: &mut R,
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher: Hkdf<CS::Hash>,
2022-01-04 00:50:40 +01:00
server_s_pk: &PublicKey<CS::KeGroup>,
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-01-04 00:50:40 +01:00
let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
ids,
client_s_pk.to_arr(),
server_s_pk.to_arr(),
)?;
let aad = construct_aad(id_u.iter(), id_s.iter(), server_s_pk);
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,
))
}
/// 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>(
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher: Hkdf<CS::Hash>,
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-01-04 00:50:40 +01:00
let mut hmac_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
let mut export_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::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)?;
2021-08-22 12:28:19 -07:00
let mut hmac =
Hmac::<CS::Hash>::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,
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher: Hkdf<CS::Hash>,
2022-01-04 00:50:40 +01:00
server_s_pk: PublicKey<CS::KeGroup>,
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-01-04 00:50:40 +01:00
let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
optional_ids,
2022-01-04 00:50:40 +01:00
client_static_keypair.public().to_arr(),
server_s_pk.to_arr(),
2021-07-08 11:04:32 -07:00
)?;
2022-01-04 00:50:40 +01:00
let aad = construct_aad(id_u.iter(), id_s.iter(), &server_s_pk);
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,
id_u,
id_s,
})
}
/// 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,
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher: Hkdf<CS::Hash>,
2022-01-04 00:50:40 +01:00
aad: impl Iterator<Item = &'a [u8]>,
2021-08-22 12:28:19 -07:00
) -> Result<OpenedInnerEnvelope<CS::Hash>, InternalError> {
2022-01-04 00:50:40 +01:00
let mut hmac_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
let mut export_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::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)?;
2021-08-22 12:28:19 -07:00
let mut hmac =
Hmac::<CS::Hash>::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(),
}
}
fn hmac_key_size() -> usize {
<CS::Hash as Digest>::OutputSize::USIZE
}
pub(crate) fn len() -> usize {
2022-01-04 00:50:40 +01:00
<CS::Hash as Digest>::OutputSize::USIZE + NonceLen::USIZE
}
2022-01-04 00:50:40 +01:00
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>>
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
{
self.nonce.concat(self.hmac.clone())
}
2022-01-04 00:50:40 +01:00
pub(crate) fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this?
2022-01-04 00:50:40 +01:00
if bytes.len() < NonceLen::USIZE {
return Err(ProtocolError::SerializationError);
}
2022-01-04 00:50:40 +01:00
let nonce = GenericArray::clone_from_slice(&bytes[..NonceLen::USIZE]);
let remainder = match mode {
InnerEnvelopeMode::Zero => {
return Err(InternalError::IncompatibleEnvelopeModeError.into())
}
2022-01-04 00:50:40 +01:00
InnerEnvelopeMode::Internal => &bytes[NonceLen::USIZE..],
};
let hmac_key_size = Self::hmac_key_size();
2022-01-04 00:50:40 +01:00
let hmac = check_slice_size(remainder, hmac_key_size, "hmac_key_size")?;
Ok(Self {
mode,
nonce,
hmac: GenericArray::clone_from_slice(hmac),
})
}
}
// Helper functions
fn build_inner_envelope_internal<CS: CipherSuite>(
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher: Hkdf<CS::Hash>,
2022-01-04 00:50:40 +01:00
nonce: GenericArray<u8, NonceLen>,
) -> Result<PublicKey<CS::KeGroup>, ProtocolError> {
2022-01-04 00:50:40 +01:00
let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::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)?;
let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
2021-10-25 02:54:32 -07:00
&CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash, _, _>(
2022-01-04 00:50:40 +01:00
Some(keypair_seed.as_slice()),
GenericArray::from(STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
)?),
)?;
Ok(client_static_keypair.public().clone())
}
fn recover_keys_internal<CS: CipherSuite>(
2021-10-25 02:54:32 -07:00
randomized_pwd_hasher: Hkdf<CS::Hash>,
2022-01-04 00:50:40 +01:00
nonce: GenericArray<u8, NonceLen>,
) -> Result<KeyPair<CS::KeGroup>, ProtocolError> {
2022-01-04 00:50:40 +01:00
let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::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)?;
let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
2021-10-25 02:54:32 -07:00
&CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash, _, _>(
2022-01-04 00:50:40 +01:00
Some(keypair_seed.as_slice()),
GenericArray::from(STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
)?),
)?;
Ok(client_static_keypair)
}
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]> {
chain!(Some(server_s_pk).into_iter(), id_s, id_u)
}