use core::convert::TryInto;
use std::{error::Error as StdError, fmt};
pub use crate::hpke_pq::draft_ietf_hpke_pq_05_full::{
generate_recipient_key_pair, setup_base_receiver as setup_base_receiver_inner,
setup_base_sender as setup_base_sender_inner, setup_psk_receiver as setup_psk_receiver_inner,
setup_psk_sender as setup_psk_sender_inner, Aead, Capability, Encapsulation, Error, Kdf, Kem,
RecipientContext, RecipientKeyPair, RecipientPrivateKey, RecipientPublicKey, SenderContext,
Suite,
};
pub const DRAFT_NAME: &str = crate::hpke_pq::draft_ietf_hpke_pq_05_full::DRAFT_NAME;
pub const DEFAULT_SUITE: Suite =
Suite::new(Kem::MlKem1024P384, Kdf::Shake256, Aead::ChaCha20Poly1305);
pub const ENVELOPE_MAGIC: [u8; 4] = *b"CGH3";
pub const ENVELOPE_VERSION: u16 = 1;
const FIXED_HEADER_LEN: usize = 20;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EnvelopeError {
InvalidMagic,
UnsupportedVersion { actual: u16 },
UnsupportedSuite { kem: u16, kdf: u16, aead: u16 },
InvalidEncoding,
}
impl fmt::Display for EnvelopeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidMagic => f.write_str("not a crypt_guard v3 PQ HPKE envelope"),
Self::UnsupportedVersion { actual } => {
write!(
f,
"unsupported crypt_guard PQ HPKE envelope version {actual}"
)
}
Self::UnsupportedSuite { kem, kdf, aead } => write!(
f,
"unsupported crypt_guard PQ HPKE suite ({kem:#06x}, {kdf:#06x}, {aead:#06x})"
),
Self::InvalidEncoding => f.write_str("invalid crypt_guard PQ HPKE envelope encoding"),
}
}
}
impl StdError for EnvelopeError {}
pub fn setup_base_sender(
suite: Suite,
recipient: &RecipientPublicKey,
info: &[u8],
) -> Result<(Encapsulation, SenderContext), Error> {
require_standard_aead(suite)?;
setup_base_sender_inner(suite, recipient, info)
}
pub fn setup_base_receiver(
suite: Suite,
recipient: &RecipientPrivateKey,
encapsulation: &Encapsulation,
info: &[u8],
) -> Result<RecipientContext, Error> {
require_standard_aead(suite)?;
setup_base_receiver_inner(suite, recipient, encapsulation, info)
}
pub fn setup_psk_sender(
suite: Suite,
recipient: &RecipientPublicKey,
info: &[u8],
psk: &[u8],
psk_id: &[u8],
) -> Result<(Encapsulation, SenderContext), Error> {
require_standard_aead(suite)?;
setup_psk_sender_inner(suite, recipient, info, psk, psk_id)
}
pub fn setup_psk_receiver(
suite: Suite,
recipient: &RecipientPrivateKey,
encapsulation: &Encapsulation,
info: &[u8],
psk: &[u8],
psk_id: &[u8],
) -> Result<RecipientContext, Error> {
require_standard_aead(suite)?;
setup_psk_receiver_inner(suite, recipient, encapsulation, info, psk, psk_id)
}
fn require_standard_aead(suite: Suite) -> Result<(), Error> {
if suite.aead().is_private_extension() {
return Err(Error::UnavailableCapability {
suite,
reason: "crypt_guard private AEAD extensions require HpkeEnvelope transport",
});
}
Ok(())
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HpkeEnvelope {
suite: Suite,
encapsulation: Vec<u8>,
ciphertext: Vec<u8>,
}
impl HpkeEnvelope {
pub fn new(suite: Suite, encapsulation: &Encapsulation, ciphertext: Vec<u8>) -> Self {
Self {
suite,
encapsulation: encapsulation.as_bytes().to_vec(),
ciphertext,
}
}
pub const fn suite(&self) -> Suite {
self.suite
}
pub fn encapsulation(&self) -> &[u8] {
&self.encapsulation
}
pub fn ciphertext(&self) -> &[u8] {
&self.ciphertext
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut encoded =
Vec::with_capacity(FIXED_HEADER_LEN + self.encapsulation.len() + self.ciphertext.len());
encoded.extend_from_slice(&ENVELOPE_MAGIC);
encoded.extend_from_slice(&ENVELOPE_VERSION.to_be_bytes());
encoded.extend_from_slice(&self.suite.kem().id().to_be_bytes());
encoded.extend_from_slice(&self.suite.kdf().id().to_be_bytes());
encoded.extend_from_slice(&self.suite.aead().id().to_be_bytes());
encoded.extend_from_slice(&(self.encapsulation.len() as u32).to_be_bytes());
encoded.extend_from_slice(&(self.ciphertext.len() as u32).to_be_bytes());
encoded.extend_from_slice(&self.encapsulation);
encoded.extend_from_slice(&self.ciphertext);
encoded
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EnvelopeError> {
if bytes.len() < FIXED_HEADER_LEN {
return Err(EnvelopeError::InvalidEncoding);
}
if bytes[..4] != ENVELOPE_MAGIC {
return Err(EnvelopeError::InvalidMagic);
}
let version = u16::from_be_bytes(
bytes[4..6]
.try_into()
.map_err(|_| EnvelopeError::InvalidEncoding)?,
);
if version != ENVELOPE_VERSION {
return Err(EnvelopeError::UnsupportedVersion { actual: version });
}
let kem = u16::from_be_bytes(
bytes[6..8]
.try_into()
.map_err(|_| EnvelopeError::InvalidEncoding)?,
);
let kdf = u16::from_be_bytes(
bytes[8..10]
.try_into()
.map_err(|_| EnvelopeError::InvalidEncoding)?,
);
let aead = u16::from_be_bytes(
bytes[10..12]
.try_into()
.map_err(|_| EnvelopeError::InvalidEncoding)?,
);
let enc_len = u32::from_be_bytes(
bytes[12..16]
.try_into()
.map_err(|_| EnvelopeError::InvalidEncoding)?,
) as usize;
let ct_len = u32::from_be_bytes(
bytes[16..20]
.try_into()
.map_err(|_| EnvelopeError::InvalidEncoding)?,
) as usize;
let end = FIXED_HEADER_LEN
.checked_add(enc_len)
.and_then(|n| n.checked_add(ct_len))
.ok_or(EnvelopeError::InvalidEncoding)?;
if end != bytes.len() {
return Err(EnvelopeError::InvalidEncoding);
}
let suite = suite_from_ids(kem, kdf, aead)?;
Ok(Self {
suite,
encapsulation: bytes[FIXED_HEADER_LEN..FIXED_HEADER_LEN + enc_len].to_vec(),
ciphertext: bytes[FIXED_HEADER_LEN + enc_len..].to_vec(),
})
}
pub fn seal(
suite: Suite,
recipient: &RecipientPublicKey,
info: &[u8],
aad: &[u8],
plaintext: &[u8],
) -> Result<Self, Error> {
let (encapsulation, mut sender) = setup_base_sender_inner(suite, recipient, info)?;
let ciphertext = sender.seal(aad, plaintext)?;
Ok(Self::new(suite, &encapsulation, ciphertext))
}
pub fn open(
&self,
recipient: &RecipientPrivateKey,
info: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, Error> {
let encapsulation = Encapsulation::from_bytes(self.suite.kem(), &self.encapsulation)?;
let mut receiver = setup_base_receiver_inner(self.suite, recipient, &encapsulation, info)?;
receiver.open(aad, &self.ciphertext)
}
}
fn suite_from_ids(kem: u16, kdf: u16, aead: u16) -> Result<Suite, EnvelopeError> {
let kem = match kem {
0x0040 => Kem::MlKem512,
0x0041 => Kem::MlKem768,
0x0042 => Kem::MlKem1024,
0x0050 => Kem::MlKem768P256,
0x0051 => Kem::MlKem1024P384,
0x647a => Kem::MlKem768X25519,
_ => return Err(EnvelopeError::UnsupportedSuite { kem, kdf, aead }),
};
let kdf = match kdf {
0x0001 => Kdf::HkdfSha256,
0x0002 => Kdf::HkdfSha384,
0x0003 => Kdf::HkdfSha512,
0x0010 => Kdf::Shake128,
0x0011 => Kdf::Shake256,
0x0012 => Kdf::TurboShake128,
0x0013 => Kdf::TurboShake256,
_ => {
return Err(EnvelopeError::UnsupportedSuite {
kem: kem.id(),
kdf,
aead,
})
}
};
let aead = match aead {
0x0001 => Aead::Aes128Gcm,
0x0002 => Aead::Aes256Gcm,
0x0003 => Aead::ChaCha20Poly1305,
0xff01 => Aead::Aes256GcmSiv,
0xff02 => Aead::XChaCha20Poly1305,
0xffff => Aead::ExportOnly,
_ => {
return Err(EnvelopeError::UnsupportedSuite {
kem: kem.id(),
kdf: kdf.id(),
aead,
})
}
};
Ok(Suite::new(kem, kdf, aead))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_profile_round_trip_binds_info_and_aad() {
let keys = generate_recipient_key_pair(DEFAULT_SUITE.kem()).unwrap();
let envelope =
HpkeEnvelope::seal(DEFAULT_SUITE, keys.public_key(), b"info", b"aad", b"").unwrap();
assert_eq!(
envelope.open(keys.private_key(), b"info", b"aad").unwrap(),
b""
);
assert_eq!(
envelope.open(keys.private_key(), b"different", b"aad"),
Err(Error::AuthenticationFailed)
);
}
#[test]
fn envelope_is_not_cgv2_and_round_trips() {
let keys = generate_recipient_key_pair(DEFAULT_SUITE.kem()).unwrap();
let envelope =
HpkeEnvelope::seal(DEFAULT_SUITE, keys.public_key(), b"i", b"a", b"payload").unwrap();
let encoded = envelope.to_bytes();
assert_ne!(&encoded[..4], b"CGv2");
let parsed = HpkeEnvelope::from_bytes(&encoded).unwrap();
assert_eq!(
parsed.open(keys.private_key(), b"i", b"a").unwrap(),
b"payload"
);
}
}