#[cfg(feature = "alloc")]
use alloc::sync::Arc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::fmt;
use crate::providers::traits::HpkeCryptoProvider;
use crate::types::{
HpkeAead,
HpkeCipherSuite,
HpkeContextState,
SecretBytes,
};
pub struct HpkeSenderContext {
pub shared_secret: SecretBytes,
pub exporter_secret: SecretBytes,
pub key: SecretBytes,
pub nonce: SecretBytes,
pub cipher_suite: HpkeCipherSuite,
pub aead: HpkeAead,
pub encapsulated_key: Vec<u8>,
pub sequence_number: u64,
pub max_sequence_number: u64,
pub state: HpkeContextState,
pub(crate) hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync>,
}
impl fmt::Debug for HpkeSenderContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HpkeSenderContext")
.field("shared_secret", &"<redacted>")
.field("exporter_secret", &"<redacted>")
.field("key", &"<redacted>")
.field("nonce", &"<redacted>")
.field("cipher_suite", &self.cipher_suite)
.field("aead", &self.aead)
.field("encapsulated_key_len", &self.encapsulated_key.len())
.field("sequence_number", &self.sequence_number)
.field("max_sequence_number", &self.max_sequence_number)
.field("state", &self.state)
.field("hpke_crypto", &self.hpke_crypto.name())
.finish()
}
}
impl HpkeSenderContext {
#[allow(clippy::too_many_arguments)] pub fn new(
shared_secret: SecretBytes,
exporter_secret: SecretBytes,
key: SecretBytes,
nonce: SecretBytes,
encapsulated_key: Vec<u8>,
cipher_suite: HpkeCipherSuite,
aead: HpkeAead,
hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync>,
) -> Self {
Self {
shared_secret,
exporter_secret,
key,
nonce,
cipher_suite,
aead,
encapsulated_key,
sequence_number: 0,
max_sequence_number: u64::MAX - 1,
state: HpkeContextState::Active,
hpke_crypto,
}
}
pub fn hpke_crypto(&self) -> &(dyn HpkeCryptoProvider + Send + Sync) {
self.hpke_crypto.as_ref()
}
pub fn can_encrypt(&self) -> bool {
self.aead != HpkeAead::Export &&
self.state == HpkeContextState::Active &&
self.sequence_number < self.max_sequence_number
}
pub fn increment_sequence(&mut self) -> Result<(), crate::error::HpkeError> {
if self.sequence_number >= self.max_sequence_number {
self.state = HpkeContextState::NeedsRekey;
return Err(crate::error::HpkeError::CryptoError(
"Sequence number overflow: context needs rekeying".into(),
));
}
self.sequence_number = self.sequence_number.wrapping_add(1);
Ok(())
}
pub fn close(&mut self) {
self.state = HpkeContextState::Closed;
}
pub fn encapsulated_key(&self) -> &[u8] {
&self.encapsulated_key
}
}
pub struct HpkeReceiverContext {
pub shared_secret: SecretBytes,
pub exporter_secret: SecretBytes,
pub key: SecretBytes,
pub nonce: SecretBytes,
pub cipher_suite: HpkeCipherSuite,
pub aead: HpkeAead,
pub sequence_number: u64,
pub max_sequence_number: u64,
pub state: HpkeContextState,
pub(crate) hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync>,
}
impl fmt::Debug for HpkeReceiverContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HpkeReceiverContext")
.field("shared_secret", &"<redacted>")
.field("exporter_secret", &"<redacted>")
.field("key", &"<redacted>")
.field("nonce", &"<redacted>")
.field("cipher_suite", &self.cipher_suite)
.field("aead", &self.aead)
.field("sequence_number", &self.sequence_number)
.field("max_sequence_number", &self.max_sequence_number)
.field("state", &self.state)
.field("hpke_crypto", &self.hpke_crypto.name())
.finish()
}
}
impl HpkeReceiverContext {
pub fn new(
shared_secret: SecretBytes,
exporter_secret: SecretBytes,
key: SecretBytes,
nonce: SecretBytes,
cipher_suite: HpkeCipherSuite,
aead: HpkeAead,
hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync>,
) -> Self {
Self {
shared_secret,
exporter_secret,
key,
nonce,
cipher_suite,
aead,
sequence_number: 0,
max_sequence_number: u64::MAX - 1,
state: HpkeContextState::Active,
hpke_crypto,
}
}
pub fn hpke_crypto(&self) -> &(dyn HpkeCryptoProvider + Send + Sync) {
self.hpke_crypto.as_ref()
}
pub fn can_decrypt(&self) -> bool {
self.aead != HpkeAead::Export &&
self.state == HpkeContextState::Active &&
self.sequence_number < self.max_sequence_number
}
pub fn increment_sequence(&mut self) -> Result<(), crate::error::HpkeError> {
if self.sequence_number >= self.max_sequence_number {
self.state = HpkeContextState::NeedsRekey;
return Err(crate::error::HpkeError::CryptoError(
"Sequence number overflow: context needs rekeying".into(),
));
}
self.sequence_number = self.sequence_number.wrapping_add(1);
Ok(())
}
pub fn close(&mut self) {
self.state = HpkeContextState::Closed;
}
}
#[cfg(test)]
mod tests {
use alloc::sync::Arc;
use alloc::vec;
use zeroize::Zeroizing;
use super::{
HpkeReceiverContext,
HpkeSenderContext,
};
use crate::error::HpkeError;
use crate::providers::post_quantum::PostQuantumProvider;
use crate::providers::traits::HpkeCryptoProvider;
use crate::types::{
HpkeAead,
HpkeCipherSuite,
HpkeContextState,
HpkeKdf,
HpkeKem,
};
#[test]
fn export_only_context_disallows_payload_ops() {
let export_suite =
HpkeCipherSuite::new(HpkeKem::MlKem512, HpkeKdf::HkdfShake256, HpkeAead::Export);
let hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync> =
Arc::new(PostQuantumProvider::new());
let sender = HpkeSenderContext::new(
Zeroizing::new(vec![1u8; 32]),
Zeroizing::new(vec![2u8; 32]),
Zeroizing::new(vec![]),
Zeroizing::new(vec![]),
vec![5u8; 768],
export_suite,
HpkeAead::Export,
hpke_crypto.clone(),
);
assert!(!sender.can_encrypt());
let receiver = HpkeReceiverContext::new(
Zeroizing::new(vec![1u8; 32]),
Zeroizing::new(vec![2u8; 32]),
Zeroizing::new(vec![]),
Zeroizing::new(vec![]),
export_suite,
HpkeAead::Export,
hpke_crypto,
);
assert!(!receiver.can_decrypt());
}
#[test]
fn sender_context_state_transitions() {
let suite = HpkeCipherSuite::new(
HpkeKem::MlKem512,
HpkeKdf::HkdfShake256,
HpkeAead::Saturnin256,
);
let hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync> =
Arc::new(PostQuantumProvider::new());
let mut sender = HpkeSenderContext::new(
Zeroizing::new(vec![1; 32]),
Zeroizing::new(vec![2; 32]),
Zeroizing::new(vec![3; 32]),
Zeroizing::new(vec![4; 16]),
vec![5; 768],
suite,
HpkeAead::Saturnin256,
hpke_crypto,
);
assert!(sender.can_encrypt());
assert_eq!(sender.encapsulated_key(), &[5; 768]);
assert!(sender.increment_sequence().is_ok());
assert_eq!(sender.sequence_number, 1);
sender.max_sequence_number = 1;
let overflow = sender.increment_sequence();
assert!(matches!(overflow, Err(HpkeError::CryptoError(_))));
assert_eq!(sender.state, HpkeContextState::NeedsRekey);
assert!(!sender.can_encrypt());
sender.close();
assert_eq!(sender.state, HpkeContextState::Closed);
assert!(!sender.can_encrypt());
}
#[test]
fn receiver_context_state_transitions() {
let suite =
HpkeCipherSuite::new(HpkeKem::MlKem512, HpkeKdf::HkdfShake256, HpkeAead::Shake256);
let hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync> =
Arc::new(PostQuantumProvider::new());
let mut receiver = HpkeReceiverContext::new(
Zeroizing::new(vec![1; 32]),
Zeroizing::new(vec![2; 32]),
Zeroizing::new(vec![3; 32]),
Zeroizing::new(vec![4; 16]),
suite,
HpkeAead::Shake256,
hpke_crypto,
);
assert!(receiver.can_decrypt());
assert!(receiver.increment_sequence().is_ok());
assert_eq!(receiver.sequence_number, 1);
receiver.max_sequence_number = 1;
let overflow = receiver.increment_sequence();
assert!(matches!(overflow, Err(HpkeError::CryptoError(_))));
assert_eq!(receiver.state, HpkeContextState::NeedsRekey);
assert!(!receiver.can_decrypt());
receiver.close();
assert_eq!(receiver.state, HpkeContextState::Closed);
assert!(!receiver.can_decrypt());
}
}