use crate::error::PdfError;
use super::cms::{IssuerAndSerial, OID_SIGNED_DATA};
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, read_tlv, tag, Class,
};
#[derive(Debug, Clone)]
pub enum SignerIdentifier {
IssuerAndSerial(IssuerAndSerial),
SubjectKeyIdentifier(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct Attribute {
pub oid: Vec<u64>,
pub values: Vec<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct SignerInfo {
pub version: u64,
pub sid: SignerIdentifier,
pub digest_algorithm_oid: Vec<u64>,
pub digest_algorithm_params: Vec<u8>,
pub signed_attrs: Vec<Attribute>,
pub signed_attrs_der: Option<Vec<u8>>,
pub signature_algorithm_oid: Vec<u64>,
pub signature_algorithm_params: Vec<u8>,
pub signature: Vec<u8>,
pub unsigned_attrs: Vec<Attribute>,
}
#[derive(Debug, Clone)]
pub struct SignedData {
pub version: u64,
pub digest_algorithms: Vec<(Vec<u64>, Vec<u8>)>,
pub encap_content_type: Vec<u64>,
pub encap_content_octets: Option<Vec<u8>>,
pub certs: Vec<Vec<u8>>,
pub crls: Vec<Vec<u8>>,
pub signer_infos: Vec<SignerInfo>,
}
pub fn parse_signed_data(data: &[u8]) -> Result<SignedData, PdfError> {
let (body, rest) = read_sequence(data)?;
if !rest.is_empty() {
return Err(PdfError::other(
"CMS SignedData: trailing bytes after ContentInfo SEQUENCE",
));
}
let (oid, rest) = read_oid(body)?;
if oid != OID_SIGNED_DATA {
return Err(PdfError::other(format!(
"CMS SignedData: ContentInfo contentType must be id-signedData (got {oid:?})"
)));
}
let (content, rest) = read_context(rest, 0)?;
if !rest.is_empty() {
return Err(PdfError::other(
"CMS SignedData: trailing bytes after [0] EXPLICIT content",
));
}
parse_signed_data_inner(content)
}
pub fn parse_signed_data_inner(data: &[u8]) -> Result<SignedData, PdfError> {
let (body, rest) = read_sequence(data)?;
if !rest.is_empty() {
return Err(PdfError::other(
"CMS SignedData: trailing bytes after SignedData SEQUENCE",
));
}
let (version, body) = read_integer_u64(body)?;
if version > 5 {
return Err(PdfError::other(format!(
"CMS SignedData: unsupported version {version}"
)));
}
let (da_set, body) = read_set(body)?;
let mut digest_algorithms: Vec<(Vec<u64>, Vec<u8>)> = Vec::new();
let mut cursor = da_set;
while !cursor.is_empty() {
let (alg_seq, after) = read_sequence(cursor)?;
let (alg_oid, alg_params) = read_oid(alg_seq)?;
digest_algorithms.push((alg_oid, alg_params.to_vec()));
cursor = after;
}
let (eci, body) = read_sequence(body)?;
let (eci_oid, eci_rest) = read_oid(eci)?;
let (eci_econtent_opt, eci_rest) = maybe_read_context(eci_rest, 0)?;
if !eci_rest.is_empty() {
return Err(PdfError::other(
"CMS SignedData: trailing bytes after EncapsulatedContentInfo",
));
}
let encap_content_octets = match eci_econtent_opt {
Some(b) => {
if let Ok((tlv, rest_inner)) = read_tlv(b) {
if rest_inner.is_empty()
&& tlv.class == Class::Universal
&& tlv.tag_number == tag::OCTET_STRING
{
Some(tlv.body.to_vec())
} else {
Some(b.to_vec())
}
} else {
Some(b.to_vec())
}
}
None => None,
};
let mut cursor = body;
let mut certs: Vec<Vec<u8>> = Vec::new();
if !cursor.is_empty() {
let (peek, _) = read_tlv(cursor)?;
if peek.class == Class::ContextSpecific && peek.tag_number == 0 {
let (set_body, after) = read_tlv(cursor)?;
certs = split_set_into_raw_entries(set_body.body)?;
cursor = after;
}
}
let mut crls: Vec<Vec<u8>> = Vec::new();
if !cursor.is_empty() {
let (peek, _) = read_tlv(cursor)?;
if peek.class == Class::ContextSpecific && peek.tag_number == 1 {
let (set_body, after) = read_tlv(cursor)?;
crls = split_set_into_raw_entries(set_body.body)?;
cursor = after;
}
}
let (si_set, after_si) = read_set(cursor)?;
if !after_si.is_empty() {
return Err(PdfError::other(
"CMS SignedData: trailing bytes after signerInfos SET",
));
}
let mut signer_infos: Vec<SignerInfo> = Vec::new();
let mut si_cursor = si_set;
while !si_cursor.is_empty() {
let (info, tail) = parse_signer_info(si_cursor)?;
signer_infos.push(info);
si_cursor = tail;
}
if signer_infos.is_empty() {
return Err(PdfError::other(
"CMS SignedData: signerInfos SET must contain at least one SignerInfo",
));
}
Ok(SignedData {
version,
digest_algorithms,
encap_content_type: eci_oid,
encap_content_octets,
certs,
crls,
signer_infos,
})
}
fn parse_signer_info(data: &[u8]) -> Result<(SignerInfo, &[u8]), PdfError> {
let (body, tail) = read_sequence(data)?;
let (version, body) = read_integer_u64(body)?;
if version != 1 && version != 3 {
return Err(PdfError::other(format!(
"CMS SignedData: unsupported SignerInfo version {version} (expected 1 or 3)"
)));
}
let (sid, body) = if version == 1 {
let (ias_body, rest) = read_sequence(body)?;
let (issuer_tlv, ias_after_issuer) = read_tlv(ias_body)?;
if issuer_tlv.class != Class::Universal || issuer_tlv.tag_number != tag::SEQUENCE {
return Err(PdfError::other(
"CMS SignedData: SignerInfo 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)?;
(
SignerIdentifier::IssuerAndSerial(IssuerAndSerial {
issuer_der,
serial: serial_body.to_vec(),
}),
rest,
)
} else {
let (tlv, rest) = read_tlv(body)?;
if tlv.class != Class::ContextSpecific || tlv.tag_number != 0 {
return Err(PdfError::other(format!(
"CMS SignedData: SignerInfo[v=3] expects [0] SubjectKeyIdentifier (got class={:?} tag={})",
tlv.class, tlv.tag_number
)));
}
if tlv.constructed {
return Err(PdfError::other(
"CMS SignedData: SignerInfo SKI must be primitive [0] IMPLICIT OCTET STRING",
));
}
(
SignerIdentifier::SubjectKeyIdentifier(tlv.body.to_vec()),
rest,
)
};
let (da_seq, body) = read_sequence(body)?;
let (da_oid, da_params) = read_oid(da_seq)?;
let digest_algorithm_oid = da_oid;
let digest_algorithm_params = da_params.to_vec();
let mut cursor = body;
let mut signed_attrs: Vec<Attribute> = Vec::new();
let mut signed_attrs_der: Option<Vec<u8>> = None;
if !cursor.is_empty() {
let (peek, _) = read_tlv(cursor)?;
if peek.class == Class::ContextSpecific && peek.tag_number == 0 {
let (sa_tlv, after) = read_tlv(cursor)?;
signed_attrs_der = Some(sa_tlv.body.to_vec());
signed_attrs = split_attributes(sa_tlv.body)?;
cursor = after;
}
}
let (sa_seq, body) = read_sequence(cursor)?;
let (sa_oid, sa_params) = read_oid(sa_seq)?;
let signature_algorithm_oid = sa_oid;
let signature_algorithm_params = sa_params.to_vec();
let (sig_bytes, body) = read_octet_string(body)?;
let signature = sig_bytes.to_vec();
let mut cursor = body;
let mut unsigned_attrs: Vec<Attribute> = Vec::new();
if !cursor.is_empty() {
let (peek, _) = read_tlv(cursor)?;
if peek.class == Class::ContextSpecific && peek.tag_number == 1 {
let (ua_tlv, after) = read_tlv(cursor)?;
unsigned_attrs = split_attributes(ua_tlv.body)?;
cursor = after;
}
}
if !cursor.is_empty() {
return Err(PdfError::other(
"CMS SignedData: trailing bytes after SignerInfo body",
));
}
Ok((
SignerInfo {
version,
sid,
digest_algorithm_oid,
digest_algorithm_params,
signed_attrs,
signed_attrs_der,
signature_algorithm_oid,
signature_algorithm_params,
signature,
unsigned_attrs,
},
tail,
))
}
fn split_attributes(body: &[u8]) -> Result<Vec<Attribute>, PdfError> {
let mut out = Vec::new();
let mut cursor = body;
while !cursor.is_empty() {
let (attr_seq, after) = read_sequence(cursor)?;
let (oid, attr_rest) = read_oid(attr_seq)?;
let (set_tlv, after_set) = read_expected(attr_rest, Class::Universal, tag::SET)?;
if !after_set.is_empty() {
return Err(PdfError::other(
"CMS SignedData: Attribute has trailing bytes after attrValues SET",
));
}
let mut values: Vec<Vec<u8>> = Vec::new();
let mut vcursor = set_tlv.body;
while !vcursor.is_empty() {
let before = vcursor.len();
let (_tlv, after_v) = read_tlv(vcursor)?;
let consumed = before - after_v.len();
values.push(vcursor[..consumed].to_vec());
vcursor = after_v;
}
out.push(Attribute { oid, values });
cursor = after;
}
Ok(out)
}
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) = read_tlv(cursor)?;
let consumed = before_len - after.len();
out.push(cursor[..consumed].to_vec());
cursor = after;
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pubsec::der;
#[test]
fn parse_minimal_signed_data_v1_ias() {
let issuer_der = der::write_sequence(b"O=Round-19 Signer");
let serial = vec![0x01, 0x42];
let digest_oid = vec![2u64, 16, 840, 1, 101, 3, 4, 2, 1]; let signature_oid = vec![1u64, 2, 840, 113549, 1, 1, 1]; let signature_bytes = vec![0xAAu8; 256];
let mut si_body = der::write_integer_u64(1); let ias_body = {
let mut b = issuer_der.clone();
b.extend_from_slice(&der::write_integer_bytes(&serial));
b
};
si_body.extend_from_slice(&der::write_sequence(&ias_body));
let da_alg = {
let mut b = der::write_oid(&digest_oid);
b.extend_from_slice(&der::write_null());
der::write_sequence(&b)
};
si_body.extend_from_slice(&da_alg);
let sig_alg = {
let mut b = der::write_oid(&signature_oid);
b.extend_from_slice(&der::write_null());
der::write_sequence(&b)
};
si_body.extend_from_slice(&sig_alg);
si_body.extend_from_slice(&der::write_octet_string(&signature_bytes));
let signer_info = der::write_sequence(&si_body);
let da_set = der::write_set(&da_alg);
let payload = b"OXIDEAV-attached-econtent-bytes";
let eci_body = {
let mut b = der::write_oid(&[1u64, 2, 840, 113549, 1, 7, 1]); let octet = der::write_octet_string(payload);
b.extend_from_slice(&der::write_context_constructed(0, &octet));
b
};
let eci = der::write_sequence(&eci_body);
let si_set = der::write_set(&signer_info);
let mut sd_body = der::write_integer_u64(1);
sd_body.extend_from_slice(&da_set);
sd_body.extend_from_slice(&eci);
sd_body.extend_from_slice(&si_set);
let sd = der::write_sequence(&sd_body);
let outer_body = {
let mut b = der::write_oid(&OID_SIGNED_DATA);
b.extend_from_slice(&der::write_context_constructed(0, &sd));
b
};
let envelope = der::write_sequence(&outer_body);
let parsed = parse_signed_data(&envelope).expect("parse SignedData");
assert_eq!(parsed.version, 1);
assert_eq!(parsed.digest_algorithms.len(), 1);
assert_eq!(parsed.digest_algorithms[0].0, digest_oid);
assert_eq!(parsed.encap_content_type, vec![1, 2, 840, 113549, 1, 7, 1]);
assert_eq!(parsed.encap_content_octets.as_deref(), Some(&payload[..]));
assert!(parsed.certs.is_empty());
assert!(parsed.crls.is_empty());
assert_eq!(parsed.signer_infos.len(), 1);
let si = &parsed.signer_infos[0];
assert_eq!(si.version, 1);
match &si.sid {
SignerIdentifier::IssuerAndSerial(ias) => {
assert_eq!(ias.issuer_der, issuer_der);
assert_eq!(ias.serial, serial);
}
other => panic!("expected IAS got {other:?}"),
}
assert_eq!(si.digest_algorithm_oid, digest_oid);
assert_eq!(si.signature_algorithm_oid, signature_oid);
assert_eq!(si.signature, signature_bytes);
assert!(si.signed_attrs.is_empty());
assert!(si.signed_attrs_der.is_none());
assert!(si.unsigned_attrs.is_empty());
}
#[test]
fn parse_signed_data_v3_ski_with_signed_attrs() {
let signer_ski = vec![0xCDu8; 20];
let digest_oid = vec![2u64, 16, 840, 1, 101, 3, 4, 2, 1]; let signature_oid = vec![1u64, 2, 840, 10045, 4, 3, 2]; let signature_bytes = vec![0xBBu8; 72];
let attr_oid = vec![1u64, 2, 840, 113549, 1, 9, 3]; let attr_value = der::write_oid(&[1u64, 2, 840, 113549, 1, 7, 1]); let attr_seq_body = {
let mut b = der::write_oid(&attr_oid);
b.extend_from_slice(&der::write_set(&attr_value));
b
};
let attr_seq = der::write_sequence(&attr_seq_body);
let signed_attrs_implicit_body = attr_seq.clone();
let signed_attrs_tlv = der::write_tlv(
der::Class::ContextSpecific,
true,
0,
&signed_attrs_implicit_body,
);
let mut si_body = der::write_integer_u64(3); si_body.extend_from_slice(&der::write_context_primitive(0, &signer_ski));
let da_alg = {
let mut b = der::write_oid(&digest_oid);
b.extend_from_slice(&der::write_null());
der::write_sequence(&b)
};
si_body.extend_from_slice(&da_alg);
si_body.extend_from_slice(&signed_attrs_tlv);
let sig_alg = {
let mut b = der::write_oid(&signature_oid);
b.extend_from_slice(&der::write_null());
der::write_sequence(&b)
};
si_body.extend_from_slice(&sig_alg);
si_body.extend_from_slice(&der::write_octet_string(&signature_bytes));
let signer_info = der::write_sequence(&si_body);
let da_set = der::write_set(&da_alg);
let eci_body = der::write_oid(&[1u64, 2, 840, 113549, 1, 7, 1]);
let eci = der::write_sequence(&eci_body);
let si_set = der::write_set(&signer_info);
let mut sd_body = der::write_integer_u64(3); sd_body.extend_from_slice(&da_set);
sd_body.extend_from_slice(&eci);
sd_body.extend_from_slice(&si_set);
let sd = der::write_sequence(&sd_body);
let outer_body = {
let mut b = der::write_oid(&OID_SIGNED_DATA);
b.extend_from_slice(&der::write_context_constructed(0, &sd));
b
};
let envelope = der::write_sequence(&outer_body);
let parsed = parse_signed_data(&envelope).expect("parse v3 SKI SignedData");
assert_eq!(parsed.version, 3);
assert!(parsed.encap_content_octets.is_none(), "detached signature");
let si = &parsed.signer_infos[0];
match &si.sid {
SignerIdentifier::SubjectKeyIdentifier(b) => assert_eq!(b, &signer_ski),
other => panic!("expected SKI got {other:?}"),
}
assert_eq!(si.signed_attrs.len(), 1);
assert_eq!(si.signed_attrs[0].oid, attr_oid);
assert_eq!(si.signed_attrs[0].values.len(), 1);
assert!(si.signed_attrs_der.is_some());
assert_eq!(si.signature, signature_bytes);
}
#[test]
fn rejects_envelope_with_wrong_oid() {
let inner = der::write_sequence(&der::write_integer_u64(0));
let outer_body = {
let mut b = der::write_oid(&[1u64, 2, 840, 113549, 1, 7, 3]);
b.extend_from_slice(&der::write_context_constructed(0, &inner));
b
};
let envelope = der::write_sequence(&outer_body);
let err = parse_signed_data(&envelope).expect_err("must reject");
let msg = format!("{err}");
assert!(msg.contains("id-signedData"), "{msg}");
}
}