#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code, unused_must_use, unstable_features)]
#![deny(
trivial_casts,
trivial_numeric_casts,
missing_docs,
unused_import_braces,
unused_qualifications
)]
#[cfg(not(feature = "alloc"))]
compile_error!(
"lib-q-hpke requires the 'alloc' feature to be enabled. This crate cannot function without alloc support."
);
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{
boxed::Box,
sync::Arc,
vec::Vec,
};
pub use error::*;
pub use types::*;
pub mod error;
pub mod hpke_core;
pub mod hpke_session;
pub mod interop;
pub mod kdf;
pub mod providers;
pub mod types;
pub mod aead;
pub mod benchmarking;
pub mod kem;
pub mod protocol;
pub mod security;
pub use hpke_session::{
HpkeReceiverContext,
HpkeSenderContext,
};
#[cfg(feature = "wasm")]
mod wasm;
#[cfg(test)]
mod security_tests;
#[allow(unused_imports)]
use lib_q_core::{
KemContext,
KemPublicKey,
KemSecretKey,
Result,
};
#[allow(unused_imports)]
use providers::{
post_quantum::PostQuantumProvider,
traits::HpkeCryptoProvider,
};
use crate::security::{
CryptoRng,
EntropyCryptoRng,
};
pub struct HpkeContext {
kem_ctx: KemContext,
cipher_suite: HpkeCipherSuite,
psk_wire_format: HpkePskWireFormat,
hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync>,
rng: Box<dyn CryptoRng + Send>,
}
fn create_kem_context() -> KemContext {
KemContext::new()
}
impl HpkeContext {
pub fn new() -> Self {
let hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync> =
Arc::new(PostQuantumProvider::new());
Self {
kem_ctx: create_kem_context(),
cipher_suite: HpkeCipherSuite::new(
HpkeKem::MlKem512,
HpkeKdf::HkdfShake256,
HpkeAead::Saturnin256,
),
psk_wire_format: HpkePskWireFormat::default(),
hpke_crypto,
rng: Box::new(EntropyCryptoRng),
}
}
pub fn with_hpke_crypto(hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync>) -> Self {
Self {
kem_ctx: create_kem_context(),
cipher_suite: HpkeCipherSuite::new(
HpkeKem::MlKem512,
HpkeKdf::HkdfShake256,
HpkeAead::Saturnin256,
),
psk_wire_format: HpkePskWireFormat::default(),
hpke_crypto,
rng: Box::new(EntropyCryptoRng),
}
}
pub fn with_provider(provider: Box<dyn lib_q_core::CryptoProvider>) -> Self {
let hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync> =
Arc::new(PostQuantumProvider::new());
Self {
kem_ctx: KemContext::with_provider(provider),
cipher_suite: HpkeCipherSuite::new(
HpkeKem::MlKem512,
HpkeKdf::HkdfShake256,
HpkeAead::Saturnin256,
),
psk_wire_format: HpkePskWireFormat::default(),
hpke_crypto,
rng: Box::new(EntropyCryptoRng),
}
}
pub fn with_kem_and_hpke_crypto(
kem_provider: Box<dyn lib_q_core::CryptoProvider>,
hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync>,
) -> Self {
Self {
kem_ctx: KemContext::with_provider(kem_provider),
cipher_suite: HpkeCipherSuite::new(
HpkeKem::MlKem512,
HpkeKdf::HkdfShake256,
HpkeAead::Saturnin256,
),
psk_wire_format: HpkePskWireFormat::default(),
hpke_crypto,
rng: Box::new(EntropyCryptoRng),
}
}
pub fn set_hpke_crypto(&mut self, hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync>) {
self.hpke_crypto = hpke_crypto;
}
pub fn hpke_crypto(&self) -> &(dyn HpkeCryptoProvider + Send + Sync) {
self.hpke_crypto.as_ref()
}
pub fn set_rng(&mut self, rng: Box<dyn CryptoRng + Send>) {
self.rng = rng;
}
}
impl Default for HpkeContext {
fn default() -> Self {
Self::new()
}
}
impl HpkeContext {
pub fn cipher_suite(&self) -> &HpkeCipherSuite {
&self.cipher_suite
}
pub fn set_cipher_suite(&mut self, cipher_suite: HpkeCipherSuite) {
self.cipher_suite = cipher_suite;
}
#[must_use]
pub fn psk_wire_format(&self) -> HpkePskWireFormat {
self.psk_wire_format
}
pub fn set_psk_wire_format(&mut self, format: HpkePskWireFormat) {
self.psk_wire_format = format;
}
pub fn setup_sender(
&mut self,
recipient_pk: &KemPublicKey,
info: &[u8],
) -> Result<HpkeSenderContext> {
hpke_core::setup_sender(
&mut self.kem_ctx,
recipient_pk,
info,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
self.rng.as_mut(),
self.hpke_crypto.clone(),
)
.map_err(|e| e.into())
}
pub fn setup_receiver(
&mut self,
encapsulated_key: &[u8],
recipient_sk: &KemSecretKey,
info: &[u8],
) -> Result<HpkeReceiverContext> {
hpke_core::setup_receiver(
&mut self.kem_ctx,
encapsulated_key,
recipient_sk,
info,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
self.hpke_crypto.clone(),
)
.map_err(|e| e.into())
}
pub fn setup_sender_psk(
&mut self,
recipient_pk: &KemPublicKey,
info: &[u8],
psk: &[u8],
psk_id: &[u8],
) -> Result<HpkeSenderContext> {
hpke_core::setup_sender_with_mode(
&mut self.kem_ctx,
recipient_pk,
info,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
self.rng.as_mut(),
HpkeMode::Psk,
Some(psk),
Some(psk_id),
None,
None,
self.psk_wire_format,
self.hpke_crypto.clone(),
)
.map_err(|e| e.into())
}
pub fn setup_sender_auth(
&mut self,
recipient_pk: &KemPublicKey,
info: &[u8],
sender_sk: &KemSecretKey,
sender_pk: &KemPublicKey,
) -> Result<HpkeSenderContext> {
hpke_core::setup_sender_with_mode(
&mut self.kem_ctx,
recipient_pk,
info,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
self.rng.as_mut(),
HpkeMode::Auth,
None,
None,
Some(sender_sk),
Some(sender_pk),
self.psk_wire_format,
self.hpke_crypto.clone(),
)
.map_err(|e| e.into())
}
pub fn setup_sender_auth_psk(
&mut self,
recipient_pk: &KemPublicKey,
info: &[u8],
psk: &[u8],
psk_id: &[u8],
sender_sk: &KemSecretKey,
sender_pk: &KemPublicKey,
) -> Result<HpkeSenderContext> {
hpke_core::setup_sender_with_mode(
&mut self.kem_ctx,
recipient_pk,
info,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
self.rng.as_mut(),
HpkeMode::AuthPsk,
Some(psk),
Some(psk_id),
Some(sender_sk),
Some(sender_pk),
self.psk_wire_format,
self.hpke_crypto.clone(),
)
.map_err(|e| e.into())
}
pub fn setup_receiver_psk(
&mut self,
encapsulated_key: &[u8],
recipient_sk: &KemSecretKey,
info: &[u8],
psk: &[u8],
psk_id: &[u8],
) -> Result<HpkeReceiverContext> {
hpke_core::setup_receiver_with_mode(
&mut self.kem_ctx,
encapsulated_key,
recipient_sk,
info,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
HpkeMode::Psk,
Some(psk),
Some(psk_id),
None,
self.psk_wire_format,
self.hpke_crypto.clone(),
)
.map_err(|e| e.into())
}
pub fn setup_receiver_auth(
&mut self,
encapsulated_key: &[u8],
recipient_sk: &KemSecretKey,
info: &[u8],
sender_pk: &KemPublicKey,
) -> Result<HpkeReceiverContext> {
hpke_core::setup_receiver_with_mode(
&mut self.kem_ctx,
encapsulated_key,
recipient_sk,
info,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
HpkeMode::Auth,
None,
None,
Some(sender_pk),
self.psk_wire_format,
self.hpke_crypto.clone(),
)
.map_err(|e| e.into())
}
pub fn setup_receiver_auth_psk(
&mut self,
encapsulated_key: &[u8],
recipient_sk: &KemSecretKey,
info: &[u8],
psk: &[u8],
psk_id: &[u8],
sender_pk: &KemPublicKey,
) -> Result<HpkeReceiverContext> {
hpke_core::setup_receiver_with_mode(
&mut self.kem_ctx,
encapsulated_key,
recipient_sk,
info,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
HpkeMode::AuthPsk,
Some(psk),
Some(psk_id),
Some(sender_pk),
self.psk_wire_format,
self.hpke_crypto.clone(),
)
.map_err(|e| e.into())
}
pub fn seal(
&mut self,
recipient_pk: &KemPublicKey,
info: &[u8],
aad: &[u8],
plaintext: &[u8],
) -> Result<(Vec<u8>, Vec<u8>)> {
hpke_core::seal(
&mut self.kem_ctx,
recipient_pk,
info,
aad,
plaintext,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
self.rng.as_mut(),
)
.map_err(|e| e.into())
}
pub fn open(
&mut self,
encapsulated_key: &[u8],
recipient_sk: &KemSecretKey,
info: &[u8],
aad: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>> {
hpke_core::open(
&mut self.kem_ctx,
encapsulated_key,
recipient_sk,
info,
aad,
ciphertext,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
self.hpke_crypto.clone(),
)
.map_err(|e| e.into())
}
}
impl HpkeSenderContext {
pub fn seal(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>> {
if !self.can_encrypt() {
if self.sequence_number >= self.max_sequence_number {
self.state = HpkeContextState::NeedsRekey;
}
return Err(lib_q_core::Error::InternalError {
operation: "Context validation".into(),
details: "Context cannot be used for encryption".into(),
});
}
let ciphertext = hpke_core::seal_message(
self.aead,
self.key.as_slice(),
self.nonce.as_slice(),
self.sequence_number,
aad,
plaintext,
self.hpke_crypto.as_ref(),
)
.map_err(lib_q_core::Error::from)?;
self.increment_sequence().map_err(lib_q_core::Error::from)?;
Ok(ciphertext)
}
pub fn export(&self, exporter_context: &[u8], length: usize) -> Result<Vec<u8>> {
hpke_core::export(
self.exporter_secret.as_slice(),
exporter_context,
length,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
)
.map_err(|e| e.into())
}
}
impl HpkeReceiverContext {
pub fn open(&mut self, aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
if !self.can_decrypt() {
return Err(lib_q_core::Error::InternalError {
operation: "Context validation".into(),
details: "Context cannot be used for decryption".into(),
});
}
let plaintext = hpke_core::open_message(
self.aead,
self.key.as_slice(),
self.nonce.as_slice(),
self.sequence_number,
aad,
ciphertext,
self.hpke_crypto.as_ref(),
)
.map_err(lib_q_core::Error::from)?;
self.increment_sequence().map_err(lib_q_core::Error::from)?;
Ok(plaintext)
}
pub fn export(&self, exporter_context: &[u8], length: usize) -> Result<Vec<u8>> {
hpke_core::export(
self.exporter_secret.as_slice(),
exporter_context,
length,
&self.cipher_suite,
self.hpke_crypto.as_ref(),
)
.map_err(|e| e.into())
}
}
pub fn create_hpke_context() -> HpkeContext {
HpkeContext::new()
}
pub fn create_hpke_context_with_provider(
provider: Box<dyn lib_q_core::CryptoProvider>,
) -> HpkeContext {
HpkeContext::with_provider(provider)
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec;
use super::*;
fn dummy_ml_kem_512_keys() -> (KemPublicKey, KemSecretKey) {
(
KemPublicKey::new(vec![1u8; HpkeKem::MlKem512.public_key_len()]),
KemSecretKey::new(vec![2u8; HpkeKem::MlKem512.secret_key_len()]),
)
}
#[test]
fn context_constructors_and_cipher_suite_accessors() {
let mut ctx = HpkeContext::new();
assert_eq!(ctx.cipher_suite().kem, HpkeKem::MlKem512);
assert_eq!(ctx.cipher_suite().kdf, HpkeKdf::HkdfShake256);
assert_eq!(ctx.cipher_suite().aead, HpkeAead::Saturnin256);
let suite =
HpkeCipherSuite::new(HpkeKem::MlKem768, HpkeKdf::HkdfSha3_256, HpkeAead::Shake256);
ctx.set_cipher_suite(suite);
assert_eq!(ctx.cipher_suite().kem, suite.kem);
assert_eq!(ctx.cipher_suite().kdf, suite.kdf);
assert_eq!(ctx.cipher_suite().aead, suite.aead);
let _default_ctx = HpkeContext::default();
let _created_ctx = create_hpke_context();
}
#[test]
fn hpke_setup_and_single_shot_paths_are_exercised() {
let (recipient_pk, recipient_sk) = dummy_ml_kem_512_keys();
let (sender_pk, sender_sk) = dummy_ml_kem_512_keys();
let mut ctx = HpkeContext::new();
let info = b"test-info";
let psk = b"test-psk";
let psk_id = b"test-psk-id";
let aad = b"aad";
let plaintext = b"plaintext";
let fake_enc = vec![0u8; HpkeKem::MlKem512.enc_len()];
let _ = ctx.setup_sender(&recipient_pk, info);
let _ = ctx.setup_receiver(&fake_enc, &recipient_sk, info);
let _ = ctx.setup_sender_psk(&recipient_pk, info, psk, psk_id);
let _ = ctx.setup_receiver_psk(&fake_enc, &recipient_sk, info, psk, psk_id);
let _ = ctx.setup_sender_auth(&recipient_pk, info, &sender_sk, &sender_pk);
let _ = ctx.setup_receiver_auth(&fake_enc, &recipient_sk, info, &sender_pk);
let _ = ctx.setup_sender_auth_psk(&recipient_pk, info, psk, psk_id, &sender_sk, &sender_pk);
let _ =
ctx.setup_receiver_auth_psk(&fake_enc, &recipient_sk, info, psk, psk_id, &sender_pk);
let _ = ctx.seal(&recipient_pk, info, aad, plaintext);
let _ = ctx.open(&fake_enc, &recipient_sk, info, aad, b"ciphertext");
}
#[test]
fn sender_and_receiver_context_guards_are_exercised() {
let hpke_crypto: Arc<dyn HpkeCryptoProvider + Send + Sync> =
Arc::new(PostQuantumProvider::new());
let mut sender = HpkeSenderContext::new(
vec![3u8; 32].into(),
vec![4u8; 32].into(),
vec![5u8; 32].into(),
vec![6u8; 16].into(),
vec![7u8; HpkeKem::MlKem512.enc_len()],
HpkeCipherSuite::new(
HpkeKem::MlKem512,
HpkeKdf::HkdfShake256,
HpkeAead::Saturnin256,
),
HpkeAead::Saturnin256,
hpke_crypto.clone(),
);
sender.state = HpkeContextState::Closed;
let sender_err = sender.seal(b"aad", b"msg").unwrap_err().to_string();
assert!(sender_err.contains("Context cannot be used for encryption"));
let mut receiver = HpkeReceiverContext::new(
vec![8u8; 32].into(),
vec![9u8; 32].into(),
vec![10u8; 32].into(),
vec![11u8; 16].into(),
HpkeCipherSuite::new(
HpkeKem::MlKem512,
HpkeKdf::HkdfShake256,
HpkeAead::Saturnin256,
),
HpkeAead::Saturnin256,
hpke_crypto,
);
receiver.state = HpkeContextState::Closed;
let receiver_err = receiver.open(b"aad", b"ct").unwrap_err().to_string();
assert!(receiver_err.contains("Context cannot be used for decryption"));
}
}