use std::{error::Error, fmt, marker::PhantomData};
use crate::{
error::CryptError,
hpke::{HpkeSuite, Mode},
sign::SignAlgorithm,
};
pub const SIGNED_HPKE_V1_LABEL: &[u8] = b"crypt_guard:signed-hpke";
pub const SIGNED_HPKE_V1: u16 = 1;
#[derive(Clone, Copy)]
pub struct SignedHpkeBinding<'a> {
pub suite: HpkeSuite,
pub mode: Mode,
pub recipient_key_id: &'a [u8],
pub info: &'a [u8],
pub encapsulation: &'a [u8],
pub aad: &'a [u8],
pub ciphertext: &'a [u8],
}
pub struct SignedHpkeEnvelopeParts<S> {
pub version: u16,
pub suite: HpkeSuite,
pub mode: Mode,
pub recipient_key_id: Vec<u8>,
pub info: Vec<u8>,
pub encapsulation: Vec<u8>,
pub aad: Vec<u8>,
pub ciphertext: Vec<u8>,
pub signature: S,
}
#[derive(Debug)]
pub enum SignedHpkeError {
UnsupportedVersion { version: u16 },
FieldTooLong { field: &'static str, length: usize },
Signing(CryptError),
SignatureVerification(CryptError),
}
impl fmt::Display for SignedHpkeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedVersion { version } => {
write!(f, "unsupported Signed-HPKE envelope version {version}")
}
Self::FieldTooLong { field, length } => write!(
f,
"Signed-HPKE field {field} is {length} bytes and exceeds the version-1 u32 limit"
),
Self::Signing(error) => write!(f, "Signed-HPKE signing failed: {error}"),
Self::SignatureVerification(error) => {
write!(f, "Signed-HPKE signature verification failed: {error}")
}
}
}
}
impl Error for SignedHpkeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Signing(error) | Self::SignatureVerification(error) => Some(error),
Self::UnsupportedVersion { .. } | Self::FieldTooLong { .. } => None,
}
}
}
pub struct SignedHpkeEnvelope<A: SignAlgorithm> {
version: u16,
suite: HpkeSuite,
mode: Mode,
recipient_key_id: Vec<u8>,
info: Vec<u8>,
encapsulation: Vec<u8>,
aad: Vec<u8>,
ciphertext: Vec<u8>,
signature: A::Sig,
}
impl<A: SignAlgorithm> SignedHpkeEnvelope<A> {
pub fn sign(
signing_key: &A::SigningKey,
binding: SignedHpkeBinding<'_>,
) -> Result<Self, SignedHpkeError> {
let transcript = canonical_transcript(SIGNED_HPKE_V1, binding)?;
let signature = A::sign(signing_key, &transcript).map_err(SignedHpkeError::Signing)?;
Ok(Self {
version: SIGNED_HPKE_V1,
suite: binding.suite,
mode: binding.mode,
recipient_key_id: binding.recipient_key_id.to_vec(),
info: binding.info.to_vec(),
encapsulation: binding.encapsulation.to_vec(),
aad: binding.aad.to_vec(),
ciphertext: binding.ciphertext.to_vec(),
signature,
})
}
pub fn from_parts(parts: SignedHpkeEnvelopeParts<A::Sig>) -> Self {
Self {
version: parts.version,
suite: parts.suite,
mode: parts.mode,
recipient_key_id: parts.recipient_key_id,
info: parts.info,
encapsulation: parts.encapsulation,
aad: parts.aad,
ciphertext: parts.ciphertext,
signature: parts.signature,
}
}
pub fn signature(&self) -> &A::Sig {
&self.signature
}
pub fn verify(
&self,
verifying_key: &A::VerifyingKey,
) -> Result<VerifiedSignedHpkeEnvelope<'_, A>, SignedHpkeError> {
if self.version != SIGNED_HPKE_V1 {
return Err(SignedHpkeError::UnsupportedVersion {
version: self.version,
});
}
let transcript = self.canonical_transcript()?;
A::verify(verifying_key, &transcript, &self.signature)
.map_err(SignedHpkeError::SignatureVerification)?;
Ok(VerifiedSignedHpkeEnvelope {
envelope: self,
algorithm: PhantomData,
})
}
fn canonical_transcript(&self) -> Result<Vec<u8>, SignedHpkeError> {
canonical_transcript(
self.version,
SignedHpkeBinding {
suite: self.suite,
mode: self.mode,
recipient_key_id: &self.recipient_key_id,
info: &self.info,
encapsulation: &self.encapsulation,
aad: &self.aad,
ciphertext: &self.ciphertext,
},
)
}
}
pub struct VerifiedSignedHpkeEnvelope<'a, A: SignAlgorithm> {
envelope: &'a SignedHpkeEnvelope<A>,
algorithm: PhantomData<A>,
}
impl<'a, A: SignAlgorithm> VerifiedSignedHpkeEnvelope<'a, A> {
pub const fn version(&self) -> u16 {
self.envelope.version
}
pub const fn suite(&self) -> HpkeSuite {
self.envelope.suite
}
pub const fn mode(&self) -> Mode {
self.envelope.mode
}
pub fn recipient_key_id(&self) -> &[u8] {
&self.envelope.recipient_key_id
}
pub fn info(&self) -> &[u8] {
&self.envelope.info
}
pub fn encapsulation(&self) -> &[u8] {
&self.envelope.encapsulation
}
pub fn aad(&self) -> &[u8] {
&self.envelope.aad
}
pub fn ciphertext(&self) -> &[u8] {
&self.envelope.ciphertext
}
}
fn canonical_transcript(
version: u16,
binding: SignedHpkeBinding<'_>,
) -> Result<Vec<u8>, SignedHpkeError> {
let mut transcript = Vec::with_capacity(
SIGNED_HPKE_V1_LABEL.len()
+ 2
+ 10
+ 1
+ (4 * 5)
+ binding.recipient_key_id.len()
+ binding.info.len()
+ binding.encapsulation.len()
+ binding.aad.len()
+ binding.ciphertext.len(),
);
transcript.extend_from_slice(SIGNED_HPKE_V1_LABEL);
transcript.extend_from_slice(&version.to_be_bytes());
transcript.extend_from_slice(&binding.suite.suite_id());
transcript.push(binding.mode.as_u8());
append_field(
&mut transcript,
"recipient_key_id",
binding.recipient_key_id,
)?;
append_field(&mut transcript, "info", binding.info)?;
append_field(&mut transcript, "encapsulation", binding.encapsulation)?;
append_field(&mut transcript, "aad", binding.aad)?;
append_field(&mut transcript, "ciphertext", binding.ciphertext)?;
Ok(transcript)
}
fn append_field(
transcript: &mut Vec<u8>,
field: &'static str,
value: &[u8],
) -> Result<(), SignedHpkeError> {
let length = u32::try_from(value.len()).map_err(|_| SignedHpkeError::FieldTooLong {
field,
length: value.len(),
})?;
transcript.extend_from_slice(&length.to_be_bytes());
transcript.extend_from_slice(value);
Ok(())
}