mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-28 03:58:45 -06:00
M9: check the bundle's SignedData shape and refuse trailing bytes
A .p7b must be SignedData with version, digestAlgorithms, encapContentInfo, optional certificates and CRLs, then signerInfos (RFC 5652 §5.1), and nothing may follow it (CodeRabbit on #3561). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ddff6b64fe
commit
f96e440a47
+14
-6
@@ -74,19 +74,27 @@ fn carried_certificates(set: &Tlv) -> Result<Vec<Certificate>, SignError> {
|
||||
/// authorities hand out a chain or a set of CA certificates. Signers and CRLs, if present, are
|
||||
/// ignored; only the certificates are read. DER or BER.
|
||||
pub fn certificate_bundle(bytes: &[u8]) -> Result<Vec<Certificate>, SignError> {
|
||||
let (ci, _) = Tlv::parse_ber(bytes)?;
|
||||
let (ci, rest) = Tlv::parse_ber(bytes)?;
|
||||
// A trust file is the bundle alone: bytes after it mean a damaged or spliced file.
|
||||
if !rest.is_empty() {
|
||||
return Err(bad("data after the bundle"));
|
||||
}
|
||||
let ci = ci.expect(tag::SEQUENCE, "ContentInfo")?.children()?;
|
||||
let [ct, content] = ci.as_slice() else { return Err(bad("ContentInfo")) };
|
||||
if ct.oid()? != SIGNED_DATA {
|
||||
return Err(bad("not SignedData"));
|
||||
}
|
||||
let sd = content.expect(tag::ctx(0), "content")?.inner()?.children()?;
|
||||
// version, digestAlgorithms, encapContentInfo, then the optional [0] certificates.
|
||||
// version, digestAlgorithms, encapContentInfo, the optional [0] certificates and [1] CRLs,
|
||||
// then signerInfos (RFC 5652 §5.1).
|
||||
let [version, digests, encap, middle @ .., signers] = sd.as_slice() else { return Err(bad("SignedData")) };
|
||||
let shape = [(version, tag::INTEGER), (digests, tag::SET), (encap, tag::SEQUENCE), (signers, tag::SET)];
|
||||
if shape.iter().any(|(t, want)| t.tag != *want) || middle.iter().any(|t| t.tag != tag::ctx(0) && t.tag != tag::ctx(1)) {
|
||||
return Err(bad("SignedData"));
|
||||
}
|
||||
let mut certificates = Vec::new();
|
||||
for t in sd.iter().skip(3) {
|
||||
if t.tag == tag::ctx(0) {
|
||||
certificates.extend(carried_certificates(t)?);
|
||||
}
|
||||
for t in middle.iter().filter(|t| t.tag == tag::ctx(0)) {
|
||||
certificates.extend(carried_certificates(t)?);
|
||||
}
|
||||
if certificates.is_empty() {
|
||||
return Err(bad("the bundle holds no certificate"));
|
||||
|
||||
@@ -295,6 +295,19 @@ fn certificates_load_from_pkcs7_bundles() {
|
||||
assert!(load_certificates(&p7b(&[])).is_err());
|
||||
let not_signed_data = pdfcraft_sign::der::seq(&[&pdfcraft_sign::der::oid(cms::DATA), &pdfcraft_sign::der::explicit(0, &[])]);
|
||||
assert!(load_certificates(¬_signed_data).is_err());
|
||||
// A SignedData out of shape: no signerInfos SET, or a version that isn't an INTEGER.
|
||||
{
|
||||
use pdfcraft_sign::der::{explicit, int, oid, seq, set_of, tlv};
|
||||
let wrap = |sd: &[&[u8]]| seq(&[&oid(cms::SIGNED_DATA), &explicit(0, &seq(sd))]);
|
||||
let (certs, encap) = (tlv(0xA0, &[leaf.raw.as_slice(), &root.raw].concat()), seq(&[&oid(cms::DATA)]));
|
||||
assert_eq!(load_certificates(&wrap(&[&int(1), &set_of(&[]), &encap, &certs, &set_of(&[])])).unwrap().len(), 2);
|
||||
assert!(load_certificates(&wrap(&[&int(1), &set_of(&[]), &encap, &certs])).is_err());
|
||||
assert!(load_certificates(&wrap(&[&set_of(&[]), &set_of(&[]), &encap, &certs, &set_of(&[])])).is_err());
|
||||
}
|
||||
let mut trailing = bundle.clone();
|
||||
trailing.extend_from_slice(&[0x05, 0x00]);
|
||||
assert!(load_certificates(&trailing).is_err());
|
||||
assert!(cms::certificate_bundle(&bundle).is_ok());
|
||||
// Truncated anywhere: an error, never a panic.
|
||||
for n in 0..bundle.len() {
|
||||
let _ = load_certificates(&bundle[..n]);
|
||||
|
||||
Reference in New Issue
Block a user