use crate::error::PdfError;
use super::der::{
maybe_read_context, read_context, read_expected, read_integer_bytes, read_integer_u64,
read_octet_string, read_oid, read_sequence, read_set, Class,
};
pub const OID_ENVELOPED_DATA: [u64; 7] = [1, 2, 840, 113549, 1, 7, 3];
pub const OID_SIGNED_DATA: [u64; 7] = [1, 2, 840, 113549, 1, 7, 2];
pub const OID_DATA: [u64; 7] = [1, 2, 840, 113549, 1, 7, 1];
pub const OID_RSA_ENCRYPTION: [u64; 7] = [1, 2, 840, 113549, 1, 1, 1];
pub const OID_RC4: [u64; 6] = [1, 2, 840, 113549, 3, 4];
pub const OID_AES128_CBC: [u64; 9] = [2, 16, 840, 1, 101, 3, 4, 1, 2];
pub const OID_AES256_CBC: [u64; 9] = [2, 16, 840, 1, 101, 3, 4, 1, 42];
pub const OID_RC2_CBC: [u64; 6] = [1, 2, 840, 113549, 3, 2];
pub const OID_DES_EDE3_CBC: [u64; 6] = [1, 2, 840, 113549, 3, 7];
#[derive(Debug, Clone)]
pub enum ContentEncryption {
Rc4,
Aes128Cbc { iv: [u8; 16] },
Aes256Cbc { iv: [u8; 16] },
Rc2Cbc {
effective_key_bits: u32,
iv: [u8; 8],
},
DesEde3Cbc {
iv: [u8; 8],
},
}
#[derive(Debug, Clone)]
pub struct IssuerAndSerial {
pub issuer_der: Vec<u8>,
pub serial: Vec<u8>,
}
#[derive(Debug, Clone)]
pub enum RecipientId {
IssuerAndSerial(IssuerAndSerial),
SubjectKeyIdentifier(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct KeyTransRecipientInfo {
pub rid: RecipientId,
pub key_encryption_oid: Vec<u64>,
pub encrypted_key: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct OriginatorPublicKey {
pub algorithm_oid: Vec<u64>,
pub algorithm_params: Vec<u8>,
pub public_key: Vec<u8>,
}
#[derive(Debug, Clone)]
pub enum OriginatorId {
IssuerAndSerial(IssuerAndSerial),
SubjectKeyIdentifier(Vec<u8>),
OriginatorKey(OriginatorPublicKey),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OtherKeyAttribute {
pub key_attr_id: Vec<u64>,
pub key_attr: Vec<u8>,
}
#[derive(Debug, Clone)]
pub enum KeyAgreeRecipientId {
IssuerAndSerial(IssuerAndSerial),
RecipientKeyIdentifier {
ski: Vec<u8>,
date: Option<Vec<u8>>,
other: Option<OtherKeyAttribute>,
},
}
#[derive(Debug, Clone)]
pub struct RecipientEncryptedKey {
pub rid: KeyAgreeRecipientId,
pub encrypted_key: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct KeyAgreeRecipientInfo {
pub originator: OriginatorId,
pub ukm: Vec<u8>,
pub key_encryption_oid: Vec<u64>,
pub key_encryption_params: Vec<u8>,
pub recipient_encrypted_keys: Vec<RecipientEncryptedKey>,
}
#[derive(Debug, Clone)]
pub enum RecipientInfoVariant {
KeyTrans(KeyTransRecipientInfo),
KeyAgree(KeyAgreeRecipientInfo),
}
#[derive(Debug, Clone, Default)]
pub struct OriginatorInfo {
pub certs: Vec<Vec<u8>>,
pub crls: Vec<Vec<u8>>,
}
impl OriginatorInfo {
pub fn is_empty(&self) -> bool {
self.certs.is_empty() && self.crls.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct EnvelopedData {
pub recipients: Vec<KeyTransRecipientInfo>,
pub all_recipients: Vec<RecipientInfoVariant>,
pub content_encryption: ContentEncryption,
pub encrypted_content: Vec<u8>,
pub originator_info: OriginatorInfo,
}
impl EnvelopedData {
pub fn originator_info(&self) -> Option<&OriginatorInfo> {
if self.originator_info.is_empty() {
None
} else {
Some(&self.originator_info)
}
}
}
pub fn parse_envelope(data: &[u8]) -> Result<EnvelopedData, PdfError> {
let (body, rest) = read_sequence(data)?;
if !rest.is_empty() {
return Err(PdfError::other(
"CMS: trailing bytes after ContentInfo SEQUENCE",
));
}
let (oid, rest) = read_oid(body)?;
if oid != OID_ENVELOPED_DATA {
return Err(PdfError::other(format!(
"CMS: ContentInfo contentType must be id-envelopedData (got {oid:?})"
)));
}
let (content, rest) = read_context(rest, 0)?;
if !rest.is_empty() {
return Err(PdfError::other(
"CMS: trailing bytes after [0] EXPLICIT content",
));
}
parse_enveloped_data(content)
}
pub fn parse_enveloped_data(data: &[u8]) -> Result<EnvelopedData, PdfError> {
let (body, rest) = read_sequence(data)?;
if !rest.is_empty() {
return Err(PdfError::other(
"CMS: trailing bytes after EnvelopedData SEQUENCE",
));
}
let (version, body) = read_integer_u64(body)?;
if version > 4 {
return Err(PdfError::other(format!(
"CMS: unsupported EnvelopedData version {version}"
)));
}
let (orig_opt, body) = maybe_read_context(body, 0)?;
let originator_info = match orig_opt {
Some(b) => parse_originator_info(b)?,
None => OriginatorInfo::default(),
};
let (ri_set, body) = read_set(body)?;
let mut recipients = Vec::new();
let mut all_recipients = Vec::new();
let mut cursor = ri_set;
while !cursor.is_empty() {
let (parsed, tail) = parse_recipient_info(cursor)?;
if let Some(p) = parsed {
if let RecipientInfoVariant::KeyTrans(ktri) = &p {
recipients.push(ktri.clone());
}
all_recipients.push(p);
}
cursor = tail;
}
if all_recipients.is_empty() {
return Err(PdfError::other(
"CMS: EnvelopedData has no recognised RecipientInfo entries",
));
}
let (eci, body) = read_sequence(body)?;
let (_ct_oid, eci_rest) = read_oid(eci)?;
let (alg_seq, eci_rest) = read_sequence(eci_rest)?;
let (alg_oid, alg_params) = read_oid(alg_seq)?;
let content_encryption = decode_content_alg(&alg_oid, alg_params)?;
let (enc_body, eci_rest) = read_expected(eci_rest, Class::ContextSpecific, 0)?;
if enc_body.constructed {
return Err(PdfError::other(
"CMS: encryptedContent constructed-form not supported",
));
}
if !eci_rest.is_empty() {
return Err(PdfError::other(
"CMS: trailing bytes after EncryptedContentInfo",
));
}
let encrypted_content = enc_body.body.to_vec();
let _ = maybe_read_context(body, 1)?;
Ok(EnvelopedData {
recipients,
all_recipients,
content_encryption,
encrypted_content,
originator_info,
})
}
fn parse_originator_info(body: &[u8]) -> Result<OriginatorInfo, PdfError> {
let mut cursor = body;
let mut info = OriginatorInfo::default();
if !cursor.is_empty() {
let (peek, _) = super::der::read_tlv(cursor)?;
if peek.class == Class::ContextSpecific && peek.tag_number == 0 {
let (set_body, after) = super::der::read_tlv(cursor)?;
info.certs = split_set_into_raw_entries(set_body.body)?;
cursor = after;
}
}
if !cursor.is_empty() {
let (peek, _) = super::der::read_tlv(cursor)?;
if peek.class == Class::ContextSpecific && peek.tag_number == 1 {
let (set_body, after) = super::der::read_tlv(cursor)?;
info.crls = split_set_into_raw_entries(set_body.body)?;
cursor = after;
}
}
if !cursor.is_empty() {
return Err(PdfError::other(
"CMS: trailing bytes after OriginatorInfo SEQUENCE body",
));
}
Ok(info)
}
fn split_set_into_raw_entries(set_body: &[u8]) -> Result<Vec<Vec<u8>>, PdfError> {
let mut out = Vec::new();
let mut cursor = set_body;
while !cursor.is_empty() {
let before_len = cursor.len();
let (_tlv, after) = super::der::read_tlv(cursor)?;
let consumed = before_len - after.len();
out.push(cursor[..consumed].to_vec());
cursor = after;
}
Ok(out)
}
fn parse_recipient_info(data: &[u8]) -> Result<(Option<RecipientInfoVariant>, &[u8]), PdfError> {
let (peek, peek_tail) = super::der::read_tlv(data)?;
if peek.class == Class::ContextSpecific {
match peek.tag_number {
1 => {
let kari = parse_kari(peek.body)?;
return Ok((Some(RecipientInfoVariant::KeyAgree(kari)), peek_tail));
}
_ => return Ok((None, peek_tail)),
}
}
let (ktri_body, tail) = read_sequence(data)?;
let (version, after_ver) = read_integer_u64(ktri_body)?;
if version > 2 {
return Err(PdfError::other(format!(
"CMS: unsupported KeyTransRecipientInfo version {version}"
)));
}
let (rid, after_rid) = if version == 0 {
let (ias_body, rest) = read_sequence(after_ver)?;
let (issuer_tlv, ias_after_issuer) = super::der::read_tlv(ias_body)?;
if issuer_tlv.class != Class::Universal
|| issuer_tlv.tag_number != super::der::tag::SEQUENCE
{
return Err(PdfError::other(
"CMS: IssuerAndSerialNumber.issuer must be a SEQUENCE",
));
}
let issuer_total = ias_body.len() - ias_after_issuer.len();
let issuer_der = ias_body[..issuer_total].to_vec();
let (serial_body, _) = read_integer_bytes(ias_after_issuer)?;
(
RecipientId::IssuerAndSerial(IssuerAndSerial {
issuer_der,
serial: serial_body.to_vec(),
}),
rest,
)
} else {
let (tlv, rest) = super::der::read_tlv(after_ver)?;
if tlv.class != Class::ContextSpecific || tlv.tag_number != 0 {
return Err(PdfError::other(format!(
"CMS: KeyTransRecipientInfo[v=2] expects [0] SubjectKeyIdentifier, got class={:?} tag={}",
tlv.class, tlv.tag_number
)));
}
if tlv.constructed {
return Err(PdfError::other(
"CMS: SubjectKeyIdentifier must be primitive [0] IMPLICIT OCTET STRING",
));
}
(RecipientId::SubjectKeyIdentifier(tlv.body.to_vec()), rest)
};
let (alg_seq, after_alg) = read_sequence(after_rid)?;
let (kea_oid, _alg_params) = read_oid(alg_seq)?;
if kea_oid != OID_RSA_ENCRYPTION {
return Err(PdfError::other(format!(
"CMS: unsupported KeyEncryptionAlgorithm {kea_oid:?} (only rsaEncryption)"
)));
}
let (enc_key, after_key) = read_octet_string(after_alg)?;
if !after_key.is_empty() {
return Err(PdfError::other(
"CMS: trailing bytes after KeyTransRecipientInfo.encryptedKey",
));
}
Ok((
Some(RecipientInfoVariant::KeyTrans(KeyTransRecipientInfo {
rid,
key_encryption_oid: kea_oid,
encrypted_key: enc_key.to_vec(),
})),
tail,
))
}
fn parse_kari(data: &[u8]) -> Result<KeyAgreeRecipientInfo, PdfError> {
let (version, body) = read_integer_u64(data)?;
if version != 3 {
return Err(PdfError::other(format!(
"CMS: KeyAgreeRecipientInfo version must be 3 (got {version})"
)));
}
let (orig_body, body) = read_context(body, 0)?;
let originator = parse_originator(orig_body)?;
let (ukm_opt, body) = maybe_read_context(body, 1)?;
let ukm = match ukm_opt {
Some(b) => {
let (ukm_bytes, rest) = read_octet_string(b)?;
if !rest.is_empty() {
return Err(PdfError::other(
"CMS: KARI ukm context wrapper has trailing bytes",
));
}
ukm_bytes.to_vec()
}
None => Vec::new(),
};
let (alg_body, body) = read_sequence(body)?;
let (kea_oid, alg_params) = read_oid(alg_body)?;
let (rek_body, body) = read_sequence(body)?;
if !body.is_empty() {
return Err(PdfError::other(
"CMS: KARI has trailing bytes after recipientEncryptedKeys",
));
}
let mut recipient_encrypted_keys = Vec::new();
let mut cursor = rek_body;
while !cursor.is_empty() {
let (rek, tail) = parse_recipient_encrypted_key(cursor)?;
recipient_encrypted_keys.push(rek);
cursor = tail;
}
if recipient_encrypted_keys.is_empty() {
return Err(PdfError::other("CMS: KARI recipientEncryptedKeys is empty"));
}
Ok(KeyAgreeRecipientInfo {
originator,
ukm,
key_encryption_oid: kea_oid,
key_encryption_params: alg_params.to_vec(),
recipient_encrypted_keys,
})
}
fn parse_originator(data: &[u8]) -> Result<OriginatorId, PdfError> {
let (peek, _) = super::der::read_tlv(data)?;
if peek.class == Class::ContextSpecific {
match peek.tag_number {
0 => {
if peek.constructed {
return Err(PdfError::other(
"CMS: KARI originator [0] SKI must be primitive",
));
}
Ok(OriginatorId::SubjectKeyIdentifier(peek.body.to_vec()))
}
1 => {
let opk = parse_originator_public_key(peek.body)?;
Ok(OriginatorId::OriginatorKey(opk))
}
other => Err(PdfError::other(format!(
"CMS: KARI originator unknown context-tag {other}"
))),
}
} else {
let (ias_body, rest) = read_sequence(data)?;
if !rest.is_empty() {
return Err(PdfError::other(
"CMS: KARI originator IAS has trailing bytes",
));
}
let (issuer_tlv, ias_after_issuer) = super::der::read_tlv(ias_body)?;
if issuer_tlv.class != Class::Universal
|| issuer_tlv.tag_number != super::der::tag::SEQUENCE
{
return Err(PdfError::other(
"CMS: KARI originator IAS issuer must be a SEQUENCE",
));
}
let issuer_total = ias_body.len() - ias_after_issuer.len();
let issuer_der = ias_body[..issuer_total].to_vec();
let (serial_body, _) = read_integer_bytes(ias_after_issuer)?;
Ok(OriginatorId::IssuerAndSerial(IssuerAndSerial {
issuer_der,
serial: serial_body.to_vec(),
}))
}
}
fn parse_originator_public_key(data: &[u8]) -> Result<OriginatorPublicKey, PdfError> {
let (alg_body, after_alg) = read_sequence(data)?;
let (alg_oid, alg_params) = read_oid(alg_body)?;
let (bs, rest) = super::der::read_tlv(after_alg)?;
if bs.class != Class::Universal || bs.tag_number != super::der::tag::BIT_STRING {
return Err(PdfError::other(
"CMS: KARI OriginatorPublicKey expects BIT STRING for publicKey",
));
}
if !rest.is_empty() {
return Err(PdfError::other(
"CMS: KARI OriginatorPublicKey has trailing bytes",
));
}
if bs.body.is_empty() {
return Err(PdfError::other(
"CMS: KARI OriginatorPublicKey BIT STRING empty",
));
}
Ok(OriginatorPublicKey {
algorithm_oid: alg_oid,
algorithm_params: alg_params.to_vec(),
public_key: bs.body[1..].to_vec(),
})
}
fn parse_recipient_encrypted_key(data: &[u8]) -> Result<(RecipientEncryptedKey, &[u8]), PdfError> {
let (rek_body, tail) = read_sequence(data)?;
let (peek, _) = super::der::read_tlv(rek_body)?;
let (rid, after_rid) = if peek.class == Class::ContextSpecific && peek.tag_number == 0 {
let (rkid_tlv, after) = super::der::read_tlv(rek_body)?;
let (ski, after_ski) = read_octet_string(rkid_tlv.body)?;
let mut cursor = after_ski;
let mut date: Option<Vec<u8>> = None;
const TAG_GENERALIZED_TIME: u32 = 24;
if !cursor.is_empty() {
let (peek_dt, _) = super::der::read_tlv(cursor)?;
if peek_dt.class == Class::Universal && peek_dt.tag_number == TAG_GENERALIZED_TIME {
let (dt_tlv, after_dt) = super::der::read_tlv(cursor)?;
date = Some(dt_tlv.body.to_vec());
cursor = after_dt;
}
}
let mut other: Option<OtherKeyAttribute> = None;
if !cursor.is_empty() {
let (peek_other, _) = super::der::read_tlv(cursor)?;
if peek_other.class == Class::Universal
&& peek_other.tag_number == super::der::tag::SEQUENCE
{
let (oka_body, after_oka) = read_sequence(cursor)?;
let (oid_arcs, after_oid) = read_oid(oka_body)?;
let key_attr = after_oid.to_vec();
other = Some(OtherKeyAttribute {
key_attr_id: oid_arcs,
key_attr,
});
cursor = after_oka;
} else {
return Err(PdfError::other(format!(
"CMS: RecipientKeyIdentifier trailing TLV class={:?} tag={} \
is neither GeneralizedTime nor OtherKeyAttribute SEQUENCE",
peek_other.class, peek_other.tag_number,
)));
}
}
if !cursor.is_empty() {
return Err(PdfError::other(
"CMS: RecipientKeyIdentifier has trailing bytes after \
(subjectKeyIdentifier, date?, other?)",
));
}
(
KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: ski.to_vec(),
date,
other,
},
after,
)
} else {
let (ias_body, after) = read_sequence(rek_body)?;
let (issuer_tlv, ias_after_issuer) = super::der::read_tlv(ias_body)?;
if issuer_tlv.class != Class::Universal
|| issuer_tlv.tag_number != super::der::tag::SEQUENCE
{
return Err(PdfError::other(
"CMS: KARI REK IAS issuer must be a SEQUENCE",
));
}
let issuer_total = ias_body.len() - ias_after_issuer.len();
let issuer_der = ias_body[..issuer_total].to_vec();
let (serial_body, _) = read_integer_bytes(ias_after_issuer)?;
(
KeyAgreeRecipientId::IssuerAndSerial(IssuerAndSerial {
issuer_der,
serial: serial_body.to_vec(),
}),
after,
)
};
let (enc_key, after_key) = read_octet_string(after_rid)?;
if !after_key.is_empty() {
return Err(PdfError::other(
"CMS: KARI RecipientEncryptedKey has trailing bytes",
));
}
Ok((
RecipientEncryptedKey {
rid,
encrypted_key: enc_key.to_vec(),
},
tail,
))
}
fn decode_content_alg(oid: &[u64], params: &[u8]) -> Result<ContentEncryption, PdfError> {
if oid == OID_RC4 {
Ok(ContentEncryption::Rc4)
} else if oid == OID_AES128_CBC || oid == OID_AES256_CBC {
let (iv, _) = read_octet_string(params)?;
if iv.len() != 16 {
return Err(PdfError::other(format!(
"CMS: AES-CBC IV must be 16 bytes (got {})",
iv.len()
)));
}
let mut iv_arr = [0u8; 16];
iv_arr.copy_from_slice(iv);
if oid == OID_AES128_CBC {
Ok(ContentEncryption::Aes128Cbc { iv: iv_arr })
} else {
Ok(ContentEncryption::Aes256Cbc { iv: iv_arr })
}
} else if oid == OID_RC2_CBC {
let (param_seq, after_seq) = match read_sequence(params) {
Ok(parts) => parts,
Err(_) => {
let (iv_bytes, _) = read_octet_string(params)?;
if iv_bytes.len() != 8 {
return Err(PdfError::other(format!(
"CMS: RC2-CBC bare-OCTET-STRING IV must be 8 bytes (got {})",
iv_bytes.len()
)));
}
let mut iv_arr = [0u8; 8];
iv_arr.copy_from_slice(iv_bytes);
return Ok(ContentEncryption::Rc2Cbc {
effective_key_bits: 32,
iv: iv_arr,
});
}
};
let _ = after_seq;
let mut cursor = param_seq;
let mut effective_key_bits = 32u32;
let (peek, _) = super::der::read_tlv(cursor)?;
if peek.class == super::der::Class::Universal && peek.tag_number == super::der::tag::INTEGER
{
let (vers_u64, after) = read_integer_u64(cursor)?;
effective_key_bits = match vers_u64 {
160 => 40,
120 => 64,
58 => 128,
v if v <= 255 => v as u32,
_ => {
return Err(PdfError::other(format!(
"CMS: RC2 rc2ParameterVersion {vers_u64} out of range (RFC 2268 §6)"
)))
}
};
cursor = after;
}
let (iv_bytes, rest) = read_octet_string(cursor)?;
if !rest.is_empty() {
return Err(PdfError::other(
"CMS: RC2-CBC parameters trailing bytes after IV",
));
}
if iv_bytes.len() != 8 {
return Err(PdfError::other(format!(
"CMS: RC2-CBC IV must be 8 bytes (got {})",
iv_bytes.len()
)));
}
let mut iv_arr = [0u8; 8];
iv_arr.copy_from_slice(iv_bytes);
Ok(ContentEncryption::Rc2Cbc {
effective_key_bits,
iv: iv_arr,
})
} else if oid == OID_DES_EDE3_CBC {
let (iv_bytes, _) = read_octet_string(params)?;
if iv_bytes.len() != 8 {
return Err(PdfError::other(format!(
"CMS: DES-EDE3-CBC IV must be 8 bytes (got {})",
iv_bytes.len()
)));
}
let mut iv_arr = [0u8; 8];
iv_arr.copy_from_slice(iv_bytes);
Ok(ContentEncryption::DesEde3Cbc { iv: iv_arr })
} else {
Err(PdfError::other(format!(
"CMS: unsupported contentEncryptionAlgorithm {oid:?}"
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pubsec::cms_build::{build_envelope_aes256, RecipientPlain};
#[test]
fn parse_handcrafted_aes256_envelope() {
let issuer_der = super::super::der::write_sequence(b"");
let recipient =
RecipientPlain::ias(issuer_der.clone(), vec![0x01, 0x02, 0x03], vec![0xAA; 256]);
let plaintext =
b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13";
let envelope = build_envelope_aes256(&[recipient], plaintext, &[0xBBu8; 32], &[0xCCu8; 16]);
let parsed = parse_envelope(&envelope).expect("parse envelope");
assert_eq!(parsed.recipients.len(), 1);
match &parsed.recipients[0].rid {
super::RecipientId::IssuerAndSerial(ias) => {
assert_eq!(ias.serial, vec![0x01, 0x02, 0x03]);
assert_eq!(ias.issuer_der, issuer_der);
}
other => panic!("unexpected rid: {other:?}"),
}
match parsed.content_encryption {
ContentEncryption::Aes256Cbc { iv } => assert_eq!(iv, [0xCC; 16]),
_ => panic!("expected AES256CBC"),
}
}
}