use serde::{
de::{Error as _, IgnoredAny, SeqAccess, Visitor},
ser::{Error as _, SerializeSeq},
Deserialize, Deserializer, Serialize, Serializer,
};
use crate::{
header::{decode_protected, encode_protected, validate_header_buckets},
iana, Error, Header, Label,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RecipientAlgorithmClass {
Direct,
KeyWrap,
KeyTransport,
DirectKeyAgreement,
KeyAgreementWithKeyWrap,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Recipient {
pub protected: Header,
pub unprotected: Header,
pub ciphertext: Option<Vec<u8>>,
pub recipients: Vec<Recipient>,
}
impl Recipient {
pub fn new() -> Self {
Recipient::default()
}
pub fn from_slice(data: &[u8]) -> Result<Self, Error> {
Ok(cbor2::from_slice(data)?)
}
pub fn to_vec(&self) -> Result<Vec<u8>, Error> {
self.validate()?;
Ok(cbor2::to_canonical_vec(self)?)
}
pub fn alg(&self) -> Result<Option<Label>, Error> {
match self.protected.alg()? {
Some(alg) => Ok(Some(alg)),
None => self.unprotected.alg(),
}
}
pub fn algorithm_class(&self) -> Result<Option<RecipientAlgorithmClass>, Error> {
Ok(self.alg()?.as_ref().and_then(classify_recipient_algorithm))
}
pub fn validate(&self) -> Result<(), Error> {
validate_header_buckets(&self.protected, &self.unprotected)?;
let alg = self
.alg()?
.ok_or_else(|| Error::Custom("COSE_recipient is missing alg".into()))?;
match classify_recipient_algorithm(&alg) {
Some(RecipientAlgorithmClass::Direct) => {
if !matches!(self.ciphertext.as_deref(), Some([])) {
return Err(Error::Custom(
"direct COSE_recipient requires zero-length ciphertext".into(),
));
}
if !self.recipients.is_empty() {
return Err(Error::Custom(
"direct COSE_recipient must not contain nested recipients".into(),
));
}
}
Some(RecipientAlgorithmClass::KeyWrap) => {
if !self.protected.is_empty() {
return Err(Error::Custom(
"key-wrap COSE_recipient requires empty protected headers".into(),
));
}
if self.unprotected.alg()?.is_none() {
return Err(Error::Custom(
"key-wrap COSE_recipient requires alg in unprotected headers".into(),
));
}
if self.ciphertext.is_none() {
return Err(Error::Custom(
"key-wrap COSE_recipient requires encrypted key ciphertext".into(),
));
}
}
Some(RecipientAlgorithmClass::KeyTransport) => {
if !self.protected.is_empty() {
return Err(Error::Custom(
"key-transport COSE_recipient requires empty protected headers".into(),
));
}
if self.unprotected.alg()?.is_none() {
return Err(Error::Custom(
"key-transport COSE_recipient requires alg in unprotected headers".into(),
));
}
if self.ciphertext.is_none() {
return Err(Error::Custom(
"key-transport COSE_recipient requires encrypted key ciphertext".into(),
));
}
if !self.recipients.is_empty() {
return Err(Error::Custom(
"key-transport COSE_recipient must not contain nested recipients".into(),
));
}
}
Some(RecipientAlgorithmClass::DirectKeyAgreement) => {
if !matches!(self.ciphertext.as_deref(), Some([])) {
return Err(Error::Custom(
"direct key-agreement COSE_recipient requires zero-length ciphertext"
.into(),
));
}
if !self.recipients.is_empty() {
return Err(Error::Custom(
"direct key-agreement COSE_recipient must not contain nested recipients"
.into(),
));
}
}
Some(RecipientAlgorithmClass::KeyAgreementWithKeyWrap) if self.ciphertext.is_none() => {
return Err(Error::Custom(
"key-agreement-with-key-wrap COSE_recipient requires encrypted key ciphertext"
.into(),
));
}
Some(RecipientAlgorithmClass::KeyAgreementWithKeyWrap) => {}
None => {}
}
validate_recipient_list(&self.recipients)?;
Ok(())
}
}
pub(crate) fn validate_recipient_list(recipients: &[Recipient]) -> Result<(), Error> {
for recipient in recipients {
recipient.validate()?;
}
if recipients.len() <= 1 {
return Ok(());
}
for recipient in recipients {
match recipient.algorithm_class()? {
Some(RecipientAlgorithmClass::Direct) => {
return Err(Error::Custom(
"direct COSE_recipient must be the only recipient in its layer".into(),
));
}
Some(RecipientAlgorithmClass::DirectKeyAgreement) => {
return Err(Error::Custom(
"direct key-agreement COSE_recipient must be the only recipient in its layer"
.into(),
));
}
Some(_) | None => {}
}
}
Ok(())
}
impl Serialize for Recipient {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.validate().map_err(S::Error::custom)?;
let protected_raw = encode_protected(&self.protected).map_err(S::Error::custom)?;
let len = if self.recipients.is_empty() { 3 } else { 4 };
let mut seq = serializer.serialize_seq(Some(len))?;
seq.serialize_element(serde_bytes::Bytes::new(&protected_raw))?;
seq.serialize_element(&self.unprotected)?;
match &self.ciphertext {
Some(c) => seq.serialize_element(&Some(serde_bytes::Bytes::new(c)))?,
None => seq.serialize_element(&Option::<&serde_bytes::Bytes>::None)?,
}
if !self.recipients.is_empty() {
seq.serialize_element(&self.recipients)?;
}
seq.end()
}
}
impl<'de> Deserialize<'de> for Recipient {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct RecipientVisitor;
impl<'de> Visitor<'de> for RecipientVisitor {
type Value = Recipient;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("a COSE_recipient array of 3 or 4 elements")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Recipient, A::Error>
where
A: SeqAccess<'de>,
{
let protected_raw: serde_bytes::ByteBuf = seq
.next_element()?
.ok_or_else(|| A::Error::custom("missing protected header"))?;
let unprotected: Header = seq
.next_element()?
.ok_or_else(|| A::Error::custom("missing unprotected header"))?;
let ciphertext: Option<serde_bytes::ByteBuf> = seq
.next_element()?
.ok_or_else(|| A::Error::custom("missing ciphertext"))?;
let recipients = seq.next_element::<Vec<Recipient>>()?.unwrap_or_default();
if seq.next_element::<IgnoredAny>()?.is_some() {
return Err(A::Error::invalid_length(5, &self));
}
let protected = decode_protected(&protected_raw).map_err(A::Error::custom)?;
validate_header_buckets(&protected, &unprotected).map_err(A::Error::custom)?;
let recipient = Recipient {
protected,
unprotected,
ciphertext: ciphertext.map(|c| c.into_vec()),
recipients,
};
recipient.validate().map_err(A::Error::custom)?;
Ok(recipient)
}
}
deserializer.deserialize_seq(RecipientVisitor)
}
}
fn classify_recipient_algorithm(alg: &Label) -> Option<RecipientAlgorithmClass> {
let Label::Int(alg) = alg else {
return None;
};
match *alg {
iana::AlgorithmDirect
| iana::AlgorithmDirect_HKDF_SHA_256
| iana::AlgorithmDirect_HKDF_SHA_512
| iana::AlgorithmDirect_HKDF_AES_128
| iana::AlgorithmDirect_HKDF_AES_256 => Some(RecipientAlgorithmClass::Direct),
iana::AlgorithmA128KW | iana::AlgorithmA192KW | iana::AlgorithmA256KW => {
Some(RecipientAlgorithmClass::KeyWrap)
}
iana::AlgorithmRSAES_OAEP_RFC_8017_default
| iana::AlgorithmRSAES_OAEP_SHA_256
| iana::AlgorithmRSAES_OAEP_SHA_512 => Some(RecipientAlgorithmClass::KeyTransport),
iana::AlgorithmECDH_ES_HKDF_256
| iana::AlgorithmECDH_ES_HKDF_512
| iana::AlgorithmECDH_SS_HKDF_256
| iana::AlgorithmECDH_SS_HKDF_512 => Some(RecipientAlgorithmClass::DirectKeyAgreement),
iana::AlgorithmECDH_ES_A128KW
| iana::AlgorithmECDH_ES_A192KW
| iana::AlgorithmECDH_ES_A256KW
| iana::AlgorithmECDH_SS_A128KW
| iana::AlgorithmECDH_SS_A192KW
| iana::AlgorithmECDH_SS_A256KW => Some(RecipientAlgorithmClass::KeyAgreementWithKeyWrap),
_ => None,
}
}