fix(sign): every signature's VRI entry shares the store's evidence arrays instead of copying them (#7038)

This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-13 12:22:54 +01:00
1 parent b0607f5cd3
commit b167422b2e
2 files changed
+22 -10

No files matched your search

+15 -10
View File
@@ -69,12 +69,17 @@ pub fn embed(doc: &Document, evidence: &Evidence) -> Result<Vec<u8>, SignError>
let mut dss = Dict::new();
dss.set(b"Type".to_vec(), Object::name("DSS"));
dss.set(b"Filter".to_vec(), Object::name("Adobe.PPKLite"));
dss.set(b"Certs".to_vec(), Object::Ref(doc.add(Object::Array(cert_refs.clone()))));
if !ocsp_refs.is_empty() {
dss.set(b"OCSPs".to_vec(), Object::Ref(doc.add(Object::Array(ocsp_refs.clone()))));
// Each list is one indirect array the store and every signature's VRI entry point to, so the
// output does not grow with signatures times evidence.
let cert_list = Object::Ref(doc.add(Object::Array(cert_refs)));
let ocsp_list = (!ocsp_refs.is_empty()).then(|| Object::Ref(doc.add(Object::Array(ocsp_refs))));
let crl_list = (!crl_refs.is_empty()).then(|| Object::Ref(doc.add(Object::Array(crl_refs))));
dss.set(b"Certs".to_vec(), cert_list.clone());
if let Some(l) = &ocsp_list {
dss.set(b"OCSPs".to_vec(), l.clone());
}
if !crl_refs.is_empty() {
dss.set(b"CRLs".to_vec(), Object::Ref(doc.add(Object::Array(crl_refs.clone()))));
if let Some(l) = &crl_list {
dss.set(b"CRLs".to_vec(), l.clone());
}
// Keep any existing /VRI entries, then cover every signature in the file.
let mut vri =
@@ -82,12 +87,12 @@ pub fn embed(doc: &Document, evidence: &Evidence) -> Result<Vec<u8>, SignError>
for contents in crate::pdf::signature_contents(&doc) {
let mut e = Dict::new();
e.set(b"Type".to_vec(), Object::name("VRI"));
e.set(b"Cert".to_vec(), Object::Array(cert_refs.clone()));
if !ocsp_refs.is_empty() {
e.set(b"OCSP".to_vec(), Object::Array(ocsp_refs.clone()));
e.set(b"Cert".to_vec(), cert_list.clone());
if let Some(l) = &ocsp_list {
e.set(b"OCSP".to_vec(), l.clone());
}
if !crl_refs.is_empty() {
e.set(b"CRL".to_vec(), Object::Array(crl_refs.clone()));
if let Some(l) = &crl_list {
e.set(b"CRL".to_vec(), l.clone());
}
vri.set(vri_key(&contents).into_bytes(), Object::Dict(e));
}
+7
View File
@@ -1050,6 +1050,13 @@ fn embedding_ltv_evidence_adds_a_dss_and_keeps_signatures_valid() {
doc.get(root).as_dict().unwrap().get(b"DSS").map(|d| doc.resolve(d)).and_then(|d| d.as_dict().cloned()).unwrap();
assert_eq!(dss_dict.name(b"Type"), Some(b"DSS".as_slice()));
assert!(dss_dict.contains(b"Certs") && dss_dict.contains(b"OCSPs") && dss_dict.contains(b"CRLs") && dss_dict.contains(b"VRI"));
// A signature's /VRI entry points at the store's arrays instead of holding its own copy of each.
let vri = dss_dict.get(b"VRI").map(|v| doc.resolve(v)).and_then(|v| v.as_dict().cloned()).unwrap();
let entry = vri.iter().next().and_then(|(_, e)| e.as_dict().cloned()).unwrap();
for (vri_key, dss_key) in [(&b"Cert"[..], &b"Certs"[..]), (b"OCSP", b"OCSPs"), (b"CRL", b"CRLs")] {
assert!(matches!(entry.get(vri_key), Some(pdfcraft_cos::Object::Ref(_))));
assert_eq!(entry.get(vri_key), dss_dict.get(dss_key));
}
// The earlier signature still validates; the DSS counts as a permitted change.
let s = signatures(&doc, &ltv, &TrustStore::default()).into_iter().find(|s| s.signed && !s.doc_timestamp).unwrap();
assert_eq!(s.status, Status::Unknown);