#[cfg(any(
feature = "aead-aes128-gcm",
feature = "aead-aes256-gcm",
feature = "aead-chacha20-poly1305"
))]
use hpke::aead::{Aead as HpkeAead, AeadCtxR};
use hpke::kdf::Kdf as HpkeKdf;
use hpke::Kem as HpkeKem;
#[cfg(any(
feature = "aead-aes128-gcm",
feature = "aead-aes256-gcm",
feature = "aead-chacha20-poly1305"
))]
use hpke::{Deserializable, OpModeR, PskBundle};
use zeroize::{ZeroizeOnDrop, Zeroizing};
use crate::error::HpkeError;
use crate::identifiers::HpkeSuite;
use crate::types::{HpkeOpenOutput, HpkePskIdRef, HpkePskRef};
use crate::validation::{
require_sealing_suite, validate_encapsulated_key, validate_key_schedule_inputs,
validate_private_key, validate_psk,
};
pub struct HpkePskReceiverSetupRequest<'a> {
pub suite: HpkeSuite,
pub encapsulated_key: &'a [u8],
pub recipient_private_key: &'a [u8],
pub info: &'a [u8],
pub psk: HpkePskRef<'a>,
pub psk_id: HpkePskIdRef<'a>,
}
trait ReceiverContextBackend {
fn open(&mut self, aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, hpke::HpkeError>;
}
#[cfg(any(
feature = "aead-aes128-gcm",
feature = "aead-aes256-gcm",
feature = "aead-chacha20-poly1305"
))]
struct TypedReceiverContext<Aead, Kdf, Kem>
where
Aead: HpkeAead,
Kdf: HpkeKdf,
Kem: HpkeKem,
{
inner: AeadCtxR<Aead, Kdf, Kem>,
}
#[cfg(any(
feature = "aead-aes128-gcm",
feature = "aead-aes256-gcm",
feature = "aead-chacha20-poly1305"
))]
impl<Aead, Kdf, Kem> ReceiverContextBackend for TypedReceiverContext<Aead, Kdf, Kem>
where
Aead: HpkeAead,
Kdf: HpkeKdf,
Kem: HpkeKem,
{
fn open(&mut self, aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, hpke::HpkeError> {
self.inner.open(ciphertext, aad)
}
}
pub struct HpkeReceiverContext {
backend: Box<dyn ReceiverContextBackend>,
authentication_tag_len: usize,
}
impl ZeroizeOnDrop for HpkeReceiverContext {}
impl HpkeReceiverContext {
pub fn open(&mut self, aad: &[u8], ciphertext: &[u8]) -> Result<HpkeOpenOutput, HpkeError> {
let expected_plaintext_length = ciphertext
.len()
.checked_sub(self.authentication_tag_len)
.ok_or(HpkeError::InvalidCiphertext)?;
let plaintext = Zeroizing::new(
self.backend
.open(aad, ciphertext)
.map_err(map_context_open_error)?,
);
if plaintext.len() != expected_plaintext_length {
return Err(HpkeError::OpenFailed);
}
Ok(HpkeOpenOutput { plaintext })
}
}
pub fn setup_receiver_psk(
request: &HpkePskReceiverSetupRequest<'_>,
) -> Result<HpkeReceiverContext, HpkeError> {
require_sealing_suite(request.suite)?;
validate_encapsulated_key(request.suite, request.encapsulated_key)?;
validate_private_key(request.suite, request.recipient_private_key)?;
validate_psk(
request.suite.kdf,
request.psk.as_slice(),
request.psk_id.as_slice(),
)?;
validate_key_schedule_inputs(request.suite.kdf, request.info, request.psk_id.as_slice())?;
dispatch_kem!(request.suite.kem, setup_for_kem, request)
}
fn setup_for_kem<Kem>(
request: &HpkePskReceiverSetupRequest<'_>,
) -> Result<HpkeReceiverContext, HpkeError>
where
Kem: HpkeKem + 'static,
{
dispatch_kdf!(request.suite.kdf, setup_for_kdf, Kem, request)
}
fn setup_for_kdf<Kem, Kdf>(
request: &HpkePskReceiverSetupRequest<'_>,
) -> Result<HpkeReceiverContext, HpkeError>
where
Kem: HpkeKem + 'static,
Kdf: HpkeKdf + 'static,
{
dispatch_sealing_aead!(request.suite.aead, setup_for, Kdf, Kem, request)
}
#[cfg(any(
feature = "aead-aes128-gcm",
feature = "aead-aes256-gcm",
feature = "aead-chacha20-poly1305"
))]
fn setup_for<Aead, Kdf, Kem>(
request: &HpkePskReceiverSetupRequest<'_>,
) -> Result<HpkeReceiverContext, HpkeError>
where
Aead: HpkeAead + 'static,
Kdf: HpkeKdf + 'static,
Kem: HpkeKem + 'static,
{
let recipient_private_key =
<Kem::PrivateKey as Deserializable>::from_bytes(request.recipient_private_key)
.map_err(|_| HpkeError::InvalidPrivateKey)?;
let encapsulated_key =
<Kem::EncappedKey as Deserializable>::from_bytes(request.encapsulated_key)
.map_err(|_| HpkeError::InvalidEncapsulatedKey)?;
let psk_bundle = PskBundle::new(request.psk.as_slice(), request.psk_id.as_slice())
.map_err(|_| HpkeError::InvalidPsk)?;
let context = hpke::setup_receiver::<Aead, Kdf, Kem>(
&OpModeR::Psk(psk_bundle),
&recipient_private_key,
&encapsulated_key,
request.info,
)
.map_err(map_setup_error)?;
Ok(HpkeReceiverContext {
backend: Box::new(TypedReceiverContext { inner: context }),
authentication_tag_len: request.suite.tag_len(),
})
}
#[cfg(any(
feature = "aead-aes128-gcm",
feature = "aead-aes256-gcm",
feature = "aead-chacha20-poly1305"
))]
fn map_setup_error(error: hpke::HpkeError) -> HpkeError {
match error {
hpke::HpkeError::ValidationError
| hpke::HpkeError::IncorrectInputLength(_, _)
| hpke::HpkeError::DecapError => HpkeError::InvalidEncapsulatedKey,
hpke::HpkeError::InvalidPskBundle => HpkeError::InvalidPsk,
hpke::HpkeError::EncapError
| hpke::HpkeError::KdfOutputTooLong
| hpke::HpkeError::MessageLimitReached
| hpke::HpkeError::OpenError
| hpke::HpkeError::SealError => HpkeError::OpenFailed,
}
}
fn map_context_open_error(error: hpke::HpkeError) -> HpkeError {
match error {
hpke::HpkeError::InvalidPskBundle
| hpke::HpkeError::ValidationError
| hpke::HpkeError::IncorrectInputLength(_, _)
| hpke::HpkeError::EncapError
| hpke::HpkeError::DecapError
| hpke::HpkeError::KdfOutputTooLong
| hpke::HpkeError::MessageLimitReached
| hpke::HpkeError::OpenError
| hpke::HpkeError::SealError => HpkeError::OpenFailed,
}
}
#[cfg(test)]
#[path = "setup_receiver_tests.rs"]
mod tests;