use crate::der;
use crate::types::{Oid, RecipientInfoKind, WrapDescription};
const ENVELOPED_DATA: &str = "1.2.840.113549.1.7.3";
const AUTH_ENVELOPED_DATA: &str = "1.2.840.113549.1.9.16.1.23";
const ORI_KEM: &str = "1.2.840.113549.1.9.16.13.3";
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("CMS artifact is not readable as a wrap: {0}")]
pub struct CmsReadError(String);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CmsFacts {
pub description: WrapDescription,
pub recipient_key_identifier: Option<Vec<u8>>,
}
pub fn describe(der: &[u8]) -> Result<CmsFacts, CmsReadError> {
let (content_info, rest) = read_tlv(der)?;
if !rest.is_empty() {
return Err(err("trailing bytes after ContentInfo"));
}
let body = content_info.expect_tag(TAG_SEQUENCE, "ContentInfo")?;
let (content_type, after_type) = read_tlv(body)?;
let content_type = oid_of(&content_type, "ContentInfo.contentType")?;
match content_type.as_str() {
ENVELOPED_DATA | AUTH_ENVELOPED_DATA => {}
other => {
return Err(err(format!(
"content type {other} is not an enveloped structure"
)));
}
}
let (explicit_content, _) = read_tlv(after_type)?;
let content = explicit_content.expect_tag(TAG_CONTEXT_0_CONSTRUCTED, "ContentInfo.content")?;
let (enveloped, _) = read_tlv(content)?;
let enveloped = enveloped.expect_tag(TAG_SEQUENCE, "EnvelopedData")?;
let (_version, after_version) = read_tlv(enveloped)?;
let (next, after_next) = read_tlv(after_version)?;
let (recipients, after_recipients) = if next.tag == TAG_CONTEXT_0_CONSTRUCTED {
read_tlv(after_next)?
} else {
(next, after_next)
};
let recipients = recipients.expect_tag(TAG_SET, "recipientInfos")?;
let (first_recipient, _) = read_tlv(recipients)?;
let recipient = read_recipient(&first_recipient)?;
let (encrypted_content_info, _) = read_tlv(after_recipients)?;
let content_encryption = content_encryption_oid(&encrypted_content_info)?;
Ok(CmsFacts {
description: recipient
.description
.with_content_encryption(content_encryption),
recipient_key_identifier: recipient.key_identifier,
})
}
struct Recipient {
description: WrapDescription,
key_identifier: Option<Vec<u8>>,
}
fn read_recipient(info: &Tlv<'_>) -> Result<Recipient, CmsReadError> {
match info.tag {
TAG_SEQUENCE => read_ktri(info.value),
TAG_CONTEXT_1_CONSTRUCTED => read_kari(info.value),
TAG_CONTEXT_2_CONSTRUCTED => {
let kekri = read_kekri(info.value)?;
Ok(Recipient {
description: WrapDescription::new(RecipientInfoKind::Kekri, kekri.algorithm),
key_identifier: Some(kekri.key_identifier.to_vec()),
})
}
TAG_CONTEXT_3_CONSTRUCTED => Err(err("PasswordRecipientInfo is not a key wrap")),
TAG_CONTEXT_4_CONSTRUCTED => read_ori(info.value),
other => Err(err(format!("unknown RecipientInfo tag 0x{other:02x}"))),
}
}
fn read_ktri(body: &[u8]) -> Result<Recipient, CmsReadError> {
let (_version, after_version) = read_tlv(body)?;
let (rid, after_rid) = read_tlv(after_version)?;
let (algorithm, _) = read_tlv(after_rid)?;
let key_identifier = (rid.tag == TAG_CONTEXT_0_PRIMITIVE).then(|| rid.value.to_vec());
Ok(Recipient {
description: WrapDescription::new(
RecipientInfoKind::Ktri,
algorithm_oid(&algorithm, "keyEncryptionAlgorithm")?,
),
key_identifier,
})
}
struct Kekri<'a> {
key_identifier: &'a [u8],
algorithm: Oid,
encrypted_key: &'a [u8],
}
fn read_kekri(body: &[u8]) -> Result<Kekri<'_>, CmsReadError> {
let (_version, after_version) = read_tlv(body)?;
let (kekid, after_kekid) = read_tlv(after_version)?;
let (algorithm, after_algorithm) = read_tlv(after_kekid)?;
let (encrypted_key, _) = read_tlv(after_algorithm)?;
let (key_identifier, _) = read_tlv(kekid.expect_tag(TAG_SEQUENCE, "KEKIdentifier")?)?;
Ok(Kekri {
key_identifier: key_identifier.expect_tag(TAG_OCTET_STRING, "kekid.keyIdentifier")?,
algorithm: algorithm_oid(&algorithm, "keyEncryptionAlgorithm")?,
encrypted_key: encrypted_key.expect_tag(TAG_OCTET_STRING, "encryptedKey")?,
})
}
fn read_kari(body: &[u8]) -> Result<Recipient, CmsReadError> {
let (_version, after_version) = read_tlv(body)?;
let (originator, after_originator) = read_tlv(after_version)?;
if originator.tag != TAG_CONTEXT_0_CONSTRUCTED {
return Err(err("KeyAgreeRecipientInfo without an originator"));
}
let (next, after_next) = read_tlv(after_originator)?;
let (algorithm, after_algorithm) = if next.tag == TAG_CONTEXT_1_CONSTRUCTED {
read_tlv(after_next)?
} else {
(next, after_next)
};
let (scheme, parameters) = algorithm_parts(&algorithm, "keyEncryptionAlgorithm")?;
let mut description = WrapDescription::new(RecipientInfoKind::Kari, scheme);
if let Some(parameters) = parameters {
description = description.with_kek_wrap(algorithm_oid(¶meters, "keyWrapAlgorithm")?);
}
let (recipient_keys, _) = read_tlv(after_algorithm)?;
Ok(Recipient {
description,
key_identifier: agreed_key_identifier(recipient_keys.value)?,
})
}
fn agreed_key_identifier(body: &[u8]) -> Result<Option<Vec<u8>>, CmsReadError> {
let (first, _) = read_tlv(body)?;
let (rid, _) = read_tlv(first.expect_tag(TAG_SEQUENCE, "RecipientEncryptedKey")?)?;
if rid.tag != TAG_CONTEXT_0_CONSTRUCTED {
return Ok(None);
}
let (key_id, _) = read_tlv(rid.value)?;
Ok(Some(
key_id
.expect_tag(TAG_OCTET_STRING, "subjectKeyIdentifier")?
.to_vec(),
))
}
fn read_ori(body: &[u8]) -> Result<Recipient, CmsReadError> {
let (oid, after_oid) = read_tlv(body)?;
let oid = oid_of(&oid, "OtherRecipientInfo.oriType")?;
if oid.as_str() != ORI_KEM {
return Ok(Recipient {
description: WrapDescription::new(RecipientInfoKind::Ori, oid),
key_identifier: None,
});
}
let (kemri, _) = read_tlv(after_oid)?;
read_kemri(kemri.expect_tag(TAG_SEQUENCE, "KEMRecipientInfo")?)
}
fn read_kemri(body: &[u8]) -> Result<Recipient, CmsReadError> {
let (_version, after_version) = read_tlv(body)?;
let (rid, after_rid) = read_tlv(after_version)?;
let (kem, after_kem) = read_tlv(after_rid)?;
let (_kemct, after_kemct) = read_tlv(after_kem)?;
let (kdf, after_kdf) = read_tlv(after_kemct)?;
let (_kek_length, after_kek_length) = read_tlv(after_kdf)?;
let (next, after_next) = read_tlv(after_kek_length)?;
let (wrap, _) = if next.tag == TAG_CONTEXT_0_CONSTRUCTED {
read_tlv(after_next)?
} else {
(next, after_next)
};
let description = WrapDescription::new(
RecipientInfoKind::Kemri,
algorithm_oid(&kem, "KEMRecipientInfo.kem")?,
)
.with_kdf(algorithm_oid(&kdf, "KEMRecipientInfo.kdf")?)
.with_kek_wrap(algorithm_oid(&wrap, "KEMRecipientInfo.wrap")?);
Ok(Recipient {
description,
key_identifier: (rid.tag == TAG_CONTEXT_0_PRIMITIVE).then(|| rid.value.to_vec()),
})
}
fn content_encryption_oid(info: &Tlv<'_>) -> Result<Oid, CmsReadError> {
let body = info.expect_tag(TAG_SEQUENCE, "encryptedContentInfo")?;
let (_content_type, after_type) = read_tlv(body)?;
let (algorithm, _) = read_tlv(after_type)?;
algorithm_oid(&algorithm, "contentEncryptionAlgorithm")
}
fn algorithm_oid(algorithm: &Tlv<'_>, field: &str) -> Result<Oid, CmsReadError> {
Ok(algorithm_parts(algorithm, field)?.0)
}
fn algorithm_parts<'a>(
algorithm: &Tlv<'a>,
field: &str,
) -> Result<(Oid, Option<Tlv<'a>>), CmsReadError> {
let body = algorithm.expect_tag(TAG_SEQUENCE, field)?;
let (oid, rest) = read_tlv(body)?;
let oid = oid_of(&oid, field)?;
let parameters = if rest.is_empty() {
None
} else {
Some(read_tlv(rest)?.0)
};
Ok((oid, parameters))
}
const TAG_INTEGER: u8 = 0x02;
const TAG_OCTET_STRING: u8 = 0x04;
const TAG_OID: u8 = 0x06;
const TAG_SEQUENCE: u8 = 0x30;
const TAG_SET: u8 = 0x31;
const TAG_CONTEXT_0_PRIMITIVE: u8 = 0x80;
const TAG_CONTEXT_0_CONSTRUCTED: u8 = 0xa0;
const TAG_CONTEXT_1_CONSTRUCTED: u8 = 0xa1;
const TAG_CONTEXT_2_CONSTRUCTED: u8 = 0xa2;
const TAG_CONTEXT_3_CONSTRUCTED: u8 = 0xa3;
const TAG_CONTEXT_4_CONSTRUCTED: u8 = 0xa4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Tlv<'a> {
tag: u8,
value: &'a [u8],
}
impl<'a> Tlv<'a> {
fn expect_tag(&self, tag: u8, field: &str) -> Result<&'a [u8], CmsReadError> {
if self.tag == tag {
Ok(self.value)
} else {
Err(err(format!(
"{field}: expected tag 0x{tag:02x}, found 0x{:02x}",
self.tag
)))
}
}
}
fn read_tlv(input: &[u8]) -> Result<(Tlv<'_>, &[u8]), CmsReadError> {
let (&tag, after_tag) = input.split_first().ok_or_else(|| err("truncated tag"))?;
let (&first_length, after_first) = after_tag
.split_first()
.ok_or_else(|| err("truncated length"))?;
let (length, after_length) = match first_length {
short if short < 0x80 => (usize::from(short), after_first),
0x80 => return Err(err("indefinite length is BER, not DER")),
long => {
let count = usize::from(long & 0x7f);
if count > std::mem::size_of::<usize>() {
return Err(err("length does not fit in a usize"));
}
let (bytes, rest) = after_first
.split_at_checked(count)
.ok_or_else(|| err("truncated long-form length"))?;
let length = bytes
.iter()
.fold(0usize, |acc, &b| (acc << 8) | usize::from(b));
(length, rest)
}
};
let (value, rest) = after_length
.split_at_checked(length)
.ok_or_else(|| err("value runs past the end of the input"))?;
Ok((Tlv { tag, value }, rest))
}
fn oid_of(element: &Tlv<'_>, field: &str) -> Result<Oid, CmsReadError> {
let bytes = element.expect_tag(TAG_OID, field)?;
let mut subidentifiers = Vec::new();
let mut value: u64 = 0;
let mut pending = false;
for &byte in bytes {
value = value
.checked_mul(128)
.and_then(|shifted| shifted.checked_add(u64::from(byte & 0x7f)))
.ok_or_else(|| err(format!("{field}: OID arc overflows")))?;
pending = byte & 0x80 != 0;
if !pending {
subidentifiers.push(value);
value = 0;
}
}
if pending {
return Err(err(format!("{field}: OID ends mid-arc")));
}
let (&packed, rest) = subidentifiers
.split_first()
.ok_or_else(|| err(format!("{field}: empty OID")))?;
let (first, second) = match packed.checked_sub(80) {
Some(above) => (2, above),
None => (packed / 40, packed % 40),
};
let mut arcs = vec![first.to_string(), second.to_string()];
arcs.extend(rest.iter().map(u64::to_string));
Oid::new(&arcs.join(".")).map_err(|e| err(format!("{field}: {e}")))
}
fn err(message: impl Into<String>) -> CmsReadError {
CmsReadError(message.into())
}
const DATA: &str = "1.2.840.113549.1.7.1";
const GCM_ICV_LENGTHS: std::ops::RangeInclusive<usize> = 12..=16;
pub const GCM_ICV_LEN: usize = 16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KekEnvelope {
pub key_identifier: Vec<u8>,
pub wrapped_cek: Vec<u8>,
pub nonce: Vec<u8>,
pub ciphertext: Vec<u8>,
pub tag: Vec<u8>,
}
pub fn write_kek_enveloped(envelope: &KekEnvelope) -> Result<Vec<u8>, CmsReadError> {
if !GCM_ICV_LENGTHS.contains(&envelope.tag.len()) {
return Err(err(format!(
"a {}-byte authentication tag is not one RFC 5084 admits",
envelope.tag.len()
)));
}
let oid = |dotted: &str| {
crate::der::object_identifier(dotted)
.ok_or_else(|| err(format!("{dotted} is not an object identifier")))
};
let kekri = der::tlv(
TAG_CONTEXT_2_CONSTRUCTED,
&[
der::small_integer(4),
der::sequence(&[der::octet_string(&envelope.key_identifier)]),
der::sequence(&[oid(crate::types::oid::AES_256_WRAP)?]),
der::octet_string(&envelope.wrapped_cek),
]
.concat(),
);
let content = der::sequence(&[
oid(DATA)?,
der::sequence(&[
oid(crate::types::oid::AES_256_GCM)?,
der::sequence(&[
der::octet_string(&envelope.nonce),
der::small_integer(u8::try_from(envelope.tag.len()).unwrap_or(0)),
]),
]),
der::tlv(TAG_CONTEXT_0_PRIMITIVE, &envelope.ciphertext),
]);
let auth_enveloped = der::sequence(&[
der::small_integer(0),
der::set_of(&[kekri]),
content,
der::octet_string(&envelope.tag),
]);
Ok(der::sequence(&[
oid(AUTH_ENVELOPED_DATA)?,
der::tlv(TAG_CONTEXT_0_CONSTRUCTED, &auth_enveloped),
]))
}
pub fn read_kek_enveloped(der_bytes: &[u8]) -> Result<KekEnvelope, CmsReadError> {
let (content_info, rest) = read_tlv(der_bytes)?;
if !rest.is_empty() {
return Err(err("trailing bytes after ContentInfo"));
}
let body = content_info.expect_tag(TAG_SEQUENCE, "ContentInfo")?;
let (content_type, after_type) = read_tlv(body)?;
if oid_of(&content_type, "ContentInfo.contentType")?.as_str() != AUTH_ENVELOPED_DATA {
return Err(err("not an AuthEnvelopedData"));
}
let (explicit, _) = read_tlv(after_type)?;
let (auth_enveloped, _) =
read_tlv(explicit.expect_tag(TAG_CONTEXT_0_CONSTRUCTED, "ContentInfo.content")?)?;
let auth_enveloped = auth_enveloped.expect_tag(TAG_SEQUENCE, "AuthEnvelopedData")?;
let (_version, after_version) = read_tlv(auth_enveloped)?;
let (next, after_next) = read_tlv(after_version)?;
let (recipients, after_recipients) = if next.tag == TAG_CONTEXT_0_CONSTRUCTED {
read_tlv(after_next)?
} else {
(next, after_next)
};
let recipients = recipients.expect_tag(TAG_SET, "recipientInfos")?;
let (recipient, more) = read_tlv(recipients)?;
if !more.is_empty() {
return Err(err("more than one recipient"));
}
let kekri = read_kekri(recipient.expect_tag(TAG_CONTEXT_2_CONSTRUCTED, "KEKRecipientInfo")?)?;
if kekri.algorithm.as_str() != crate::types::oid::AES_256_WRAP {
return Err(err(format!(
"key encryption algorithm {} is not id-aes256-wrap",
kekri.algorithm
)));
}
let (content_info, after_content) = read_tlv(after_recipients)?;
let (nonce, icv_len, ciphertext) =
read_gcm_content(content_info.expect_tag(TAG_SEQUENCE, "authEncryptedContentInfo")?)?;
let (mac, _) = read_tlv(after_content)?;
if mac.tag == TAG_CONTEXT_1_CONSTRUCTED {
return Err(err(
"this AuthEnvelopedData carries authAttrs, which are part of what its \
tag authenticates and which this reader does not cover",
));
}
let tag = mac.expect_tag(TAG_OCTET_STRING, "mac")?;
if tag.len() != icv_len {
return Err(err(format!(
"GCMParameters names a {icv_len}-byte tag and the mac is {} bytes",
tag.len()
)));
}
Ok(KekEnvelope {
key_identifier: kekri.key_identifier.to_vec(),
wrapped_cek: kekri.encrypted_key.to_vec(),
nonce,
ciphertext,
tag: tag.to_vec(),
})
}
fn read_gcm_content(body: &[u8]) -> Result<(Vec<u8>, usize, Vec<u8>), CmsReadError> {
let (_content_type, after_type) = read_tlv(body)?;
let (algorithm, after_algorithm) = read_tlv(after_type)?;
let (algorithm_body, parameters) = algorithm_parts(&algorithm, "contentEncryptionAlgorithm")?;
if algorithm_body.as_str() != crate::types::oid::AES_256_GCM {
return Err(err(format!(
"content encryption algorithm {algorithm_body} is not id-aes256-GCM"
)));
}
let parameters = parameters.ok_or_else(|| err("AES-GCM content carries no GCMParameters"))?;
let (nonce, after_nonce) = read_tlv(parameters.expect_tag(TAG_SEQUENCE, "GCMParameters")?)?;
let nonce = nonce.expect_tag(TAG_OCTET_STRING, "GCMParameters.aes-nonce")?;
let icv_len = match read_tlv(after_nonce) {
Ok((field, _)) => usize::from(
*field
.expect_tag(TAG_INTEGER, "GCMParameters.aes-ICVlen")?
.first()
.ok_or_else(|| err("GCMParameters.aes-ICVlen is empty"))?,
),
Err(_) => 12,
};
if !GCM_ICV_LENGTHS.contains(&icv_len) {
return Err(err(format!(
"GCMParameters names a {icv_len}-byte tag, which RFC 5084 does not admit"
)));
}
let (content, _) = read_tlv(after_algorithm)?;
Ok((
nonce.to_vec(),
icv_len,
content
.expect_tag(TAG_CONTEXT_0_PRIMITIVE, "encryptedContent")?
.to_vec(),
))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
mod tests {
use super::*;
use base64ct::Encoding as _;
#[test]
fn reads_a_dotted_oid_from_its_der_encoding() {
let der = [
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2e,
];
let (element, rest) = read_tlv(&der).unwrap();
assert!(rest.is_empty());
assert_eq!(
oid_of(&element, "test").unwrap().as_str(),
"2.16.840.1.101.3.4.1.46"
);
}
#[test]
fn reads_a_first_arc_that_spans_two_bytes() {
let der = [0x06, 0x03, 0x81, 0x34, 0x03];
let (element, _) = read_tlv(&der).unwrap();
assert_eq!(oid_of(&element, "test").unwrap().as_str(), "2.100.3");
}
#[test]
fn reads_a_long_form_length() {
let mut der = vec![0x04, 0x81, 0x80];
der.extend(std::iter::repeat_n(0xaa, 0x80));
let (element, rest) = read_tlv(&der).unwrap();
assert_eq!(element.value.len(), 0x80);
assert!(rest.is_empty());
}
#[test]
fn refuses_indefinite_length_and_truncation() {
assert!(read_tlv(&[0x30, 0x80, 0x00, 0x00]).is_err());
assert!(read_tlv(&[0x30, 0x05, 0x01]).is_err());
assert!(read_tlv(&[0x30]).is_err());
assert!(read_tlv(&[]).is_err());
}
const RFC4134_ENVELOPED_DATA: &str = concat!(
"MIIBHgYJKoZIhvcNAQcDoIIBDzCCAQsCAQAxgcAwgb0CAQAwJjASMRAwDgYDVQQDEwdDYXJ",
"sUlNBAhBGNGvHgABWvBHTbi7NXXHQMA0GCSqGSIb3DQEBAQUABIGAC3EN5nGIiJi2lsGPcP",
"2iJ97a4e8kbKQz36zg6Z2i0yx6zYC4mZ7mX7FBs3IWg+f6KgCLx3M1eCbWx8+MDFbbpXadC",
"DgO8/nUkUNYeNxJtuzubGgzoyEd8Ch4H/dd9gdzTd+taTEgS0ipdSJuNnkVY4/M652jKKHR",
"LFf02hosdR8wQwYJKoZIhvcNAQcBMBQGCCqGSIb3DQMHBAgtaMXpRwZRNYAgDsiSf8Z9P43",
"LrY4OxUk660cu1lXeCSFOSOpOJ7FuVyU=",
);
fn sample_envelope() -> KekEnvelope {
KekEnvelope {
key_identifier: vec![0x76, 0x3c, 0xbc],
wrapped_cek: vec![0x11; 40],
nonce: vec![0x22; 12],
ciphertext: vec![0x33; 64],
tag: vec![0x44; 16],
}
}
#[test]
fn a_kek_envelope_round_trips() {
let envelope = sample_envelope();
let der = write_kek_enveloped(&envelope).unwrap();
assert_eq!(read_kek_enveloped(&der).unwrap(), envelope);
}
#[test]
fn a_kek_envelope_describes_itself() {
let der = write_kek_enveloped(&sample_envelope()).unwrap();
let facts = describe(&der).unwrap();
assert_eq!(
facts.description.recipient_info,
Some(RecipientInfoKind::Kekri)
);
assert_eq!(
facts.description.key_encryption_oid.as_str(),
crate::types::oid::AES_256_WRAP
);
assert_eq!(
facts
.description
.content_encryption_oid
.as_ref()
.map(Oid::as_str),
Some(crate::types::oid::AES_256_GCM)
);
assert_eq!(
facts.recipient_key_identifier,
Some(vec![0x76, 0x3c, 0xbc]),
"the recipient is named by the key's check value"
);
}
#[test]
fn a_kek_envelope_carries_bytes_under_the_generic_content_type() {
let der = write_kek_enveloped(&sample_envelope()).unwrap();
let data_oid = crate::der::object_identifier(DATA).unwrap();
assert!(
der.windows(data_oid.len()).any(|w| w == data_oid),
"the encrypted content is id-data, not a key-specific type"
);
}
#[test]
fn a_kek_envelope_round_trips_across_the_length_boundary() {
for size in [0, 1, 127, 128, 129, 255, 256, 1024, 65_536] {
let envelope = KekEnvelope {
ciphertext: vec![0x5a; size],
..sample_envelope()
};
let der = write_kek_enveloped(&envelope).unwrap();
assert_eq!(
read_kek_enveloped(&der).unwrap(),
envelope,
"a {size}-byte content did not survive the round trip"
);
}
}
#[test]
fn a_kek_envelope_carries_the_tag_length_it_declares() {
for length in [12usize, 13, 14, 15, 16] {
let envelope = KekEnvelope {
tag: vec![0x44; length],
..sample_envelope()
};
let der = write_kek_enveloped(&envelope).unwrap();
assert_eq!(read_kek_enveloped(&der).unwrap().tag.len(), length);
}
for length in [0usize, 8, 11, 17] {
let envelope = KekEnvelope {
tag: vec![0x44; length],
..sample_envelope()
};
assert!(
write_kek_enveloped(&envelope).is_err(),
"a {length}-byte tag is not one RFC 5084 admits"
);
}
}
#[test]
fn reading_refuses_what_is_not_this_structure() {
let der = write_kek_enveloped(&sample_envelope()).unwrap();
assert!(read_kek_enveloped(&[]).is_err(), "empty input");
assert!(
read_kek_enveloped(&der[..der.len() - 1]).is_err(),
"truncated input"
);
let mut trailing = der.clone();
trailing.push(0x00);
assert!(read_kek_enveloped(&trailing).is_err(), "trailing bytes");
let other = base64ct::Base64::decode_vec(RFC4134_ENVELOPED_DATA).unwrap();
assert!(
read_kek_enveloped(&other).is_err(),
"an EnvelopedData with a key transport recipient is not this"
);
}
#[test]
fn reads_the_rfc_4134_enveloped_data_example() {
let der = base64ct::Base64::decode_vec(RFC4134_ENVELOPED_DATA).unwrap();
assert_eq!(der.len(), 290, "RFC 4134 section 5.1 is 290 bytes");
let facts = describe(&der).unwrap();
assert_eq!(
facts.description.recipient_info,
Some(RecipientInfoKind::Ktri)
);
assert_eq!(
facts.description.key_encryption_oid.as_str(),
crate::types::oid::RSA_ENCRYPTION
);
assert_eq!(
facts
.description
.content_encryption_oid
.as_ref()
.map(Oid::as_str),
Some("1.2.840.113549.3.7"),
"des-EDE3-CBC, as the RFC's dump names it"
);
assert_eq!(
facts.description.kek_wrap_oid, None,
"no KEK in key transport"
);
assert_eq!(
facts.recipient_key_identifier, None,
"this recipient is named by issuer and serial, which identifies a \
certificate rather than a key"
);
assert!(!facts.description.content_is_authenticated());
assert!(!crate::types::WrapScheme::CmsAes256Gcm.permits(&facts.description));
}
#[test]
fn reads_a_recorded_ml_kem_recipient() {
let der = include_bytes!("../tests/fixtures/ml_kem_768_authenveloped.der");
let facts = describe(der).unwrap();
let description = &facts.description;
assert_eq!(description.recipient_info, Some(RecipientInfoKind::Kemri));
assert_eq!(
description.key_encryption_oid.as_str(),
"2.16.840.1.101.3.4.4.2"
);
assert_eq!(
description.kdf_oid.as_ref().map(Oid::as_str),
Some("1.2.840.113549.1.9.16.3.28")
);
assert_eq!(
description.kek_wrap_oid.as_ref().map(Oid::as_str),
Some(crate::types::oid::AES_256_WRAP)
);
assert!(description.content_is_authenticated());
assert!(
facts.recipient_key_identifier.is_some(),
"the blob names its recipient by key id"
);
}
#[test]
fn refuses_an_auth_enveloped_data_carrying_auth_attrs() {
let der = base64ct::Base64::decode_vec(concat!(
"MIHGBgsqhkiG9w0BCRABF6CBtjCBswIBADFEokICAQQwBgQEAQIDBDALBglg",
"hkgBZQMEAS0EKHQahB32AHvkD3mve3AQWdpTNQ/d825fM9NJOwFCxtF9DZJ4",
"PXsjRgcwOgYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwDAwMDAwMDAwMD",
"AwMCARCADbWcgkV4nh1cv9wwVfGhGjAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN",
"AQcBBBCiz7sY6w+LFX3mIoJbWngX",
))
.unwrap();
let error = read_kek_enveloped(&der).unwrap_err();
assert!(
error.to_string().contains("authAttrs"),
"the refusal should name the field: {error}"
);
let facts = describe(&der).expect("the recipient is readable either way");
assert_eq!(
facts.description.recipient_info,
Some(RecipientInfoKind::Kekri)
);
}
#[test]
fn refuses_bytes_that_are_not_an_enveloped_structure() {
let der = [
0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02,
];
let error = describe(&der).unwrap_err();
assert!(
error.to_string().contains("1.2.840.113549.1.7.2"),
"the error should name what was found: {error}"
);
}
}