mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
Merge PR #4133: fix(sign): let an LTV update declare the ESIC extension in the catalog
This commit is contained in:
+17
-1
@@ -1817,6 +1817,18 @@ fn page_change_kind(doc: &Document, old: &Document, d: &Dict, before: Option<&Di
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the catalog `d` keeps every developer extension the signed catalog `b` declared, each
|
||||
/// with the same value: it may only declare more.
|
||||
fn extensions_kept(doc: &Document, old: &Document, d: &Dict, b: &Dict) -> bool {
|
||||
let now = d.get(b"Extensions").map(|e| doc.resolve(e)).and_then(|e| e.as_dict().cloned());
|
||||
let was = b.get(b"Extensions").map(|e| old.resolve(e)).and_then(|e| e.as_dict().cloned());
|
||||
match (was, now) {
|
||||
(None, _) => true,
|
||||
(Some(was), Some(now)) => was.iter().all(|(k, v)| now.get(k) == Some(v)),
|
||||
(Some(_), None) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn change_kind(doc: &Document, old: &Document, d: &Dict, before: Option<&Dict>) -> &'static str {
|
||||
let ty = d.name(b"Type");
|
||||
// A later revision cannot make an existing object a signature by relabelling it: a signature
|
||||
@@ -1835,9 +1847,13 @@ fn change_kind(doc: &Document, old: &Document, d: &Dict, before: Option<&Dict>)
|
||||
// Only a new signature value or policy: rewriting a signed one is not a signature (#1788).
|
||||
Some(b"Sig" | b"DocTimeStamp" | b"SigRef" | b"TransformParams") if before.is_none() => return kind::SIGNATURE,
|
||||
Some(b"Catalog") => {
|
||||
let keys: &[&[u8]] = &[b"AcroForm", b"DSS", b"Perms", b"Metadata", b"NeedsRendering"];
|
||||
let keys: &[&[u8]] = &[b"AcroForm", b"DSS", b"Perms", b"Metadata", b"NeedsRendering", b"Extensions"];
|
||||
return match before {
|
||||
Some(b) if without(d, keys) != without(b, keys) => kind::STRUCTURE,
|
||||
// Developer extensions (ISO 32000-2 §7.12) only declare what the file uses. A
|
||||
// writer adding a /DSS also declares the ESIC extension it belongs to, so a
|
||||
// declaration may be added, but none that was signed may change or go.
|
||||
Some(b) if !extensions_kept(doc, old, d, b) => kind::STRUCTURE,
|
||||
Some(b) => {
|
||||
if d.get(b"AcroForm") != b.get(b"AcroForm") {
|
||||
// Compare the forms themselves, inline or not.
|
||||
|
||||
@@ -731,6 +731,35 @@ fn cached_discovery_still_validates_current_timestamp_bytes_ranges_and_trust() {
|
||||
assert_eq!(check(&doc, &tampered, &trusted).status, Status::Invalid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ltv_writers_may_declare_the_esic_extension_but_not_change_a_signed_one() {
|
||||
let id = pkcs12::open(&data("rsa-aes.p12"), "test").unwrap();
|
||||
let trust = TrustStore { certs: vec![id.certificate.clone()] };
|
||||
let check = |bytes: &[u8]| signatures(&open(bytes), bytes, &trust).into_iter().find(|s| s.signed).unwrap();
|
||||
let ext = |base: &[u8], level: i64| {
|
||||
let mut esic = pdfcraft_cos::Dict::new();
|
||||
esic.set(b"BaseVersion".to_vec(), Object::name("1.7"));
|
||||
esic.set(b"ExtensionLevel".to_vec(), Object::Int(level));
|
||||
let mut e = pdfcraft_cos::Dict::new();
|
||||
e.set(b"ESIC".to_vec(), Object::Dict(esic));
|
||||
edit_after(base, |doc| {
|
||||
let root = doc.root().unwrap();
|
||||
doc.update_dict(root, |c| c.set(b"Extensions".to_vec(), Object::Dict(e))).unwrap();
|
||||
})
|
||||
};
|
||||
// As PAdES LTV writers save it: the store and the ESIC declaration in one revision.
|
||||
let signed = pdfcraft_sign::sign(&open(&fixture()), &id, &opts()).unwrap();
|
||||
let evidence = pdfcraft_sign::dss::Evidence { certs: vec![id.certificate.raw.clone()], ocsps: Vec::new(), crls: Vec::new() };
|
||||
let ltv = pdfcraft_sign::dss::embed(&open(&ext(&signed, 2)), &evidence).unwrap();
|
||||
let s = check(<v);
|
||||
assert_eq!(s.status, Status::Valid, "{:?}", s.details);
|
||||
assert!(matches!(&s.modification, Modification::Allowed(k) if !k.contains(&kind::STRUCTURE)), "{:?}", s.modification);
|
||||
// A declaration that was signed may not change afterwards.
|
||||
let signed_with = pdfcraft_sign::sign(&open(&ext(&fixture(), 2)), &id, &opts()).unwrap();
|
||||
let s = check(&ext(&signed_with, 3));
|
||||
assert_eq!((s.status, s.modification), (Status::Invalid, Modification::Disallowed(vec![kind::STRUCTURE])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedding_ltv_evidence_adds_a_dss_and_keeps_signatures_valid() {
|
||||
use pdfcraft_sign::dss::{self, Evidence};
|
||||
|
||||
Reference in New Issue
Block a user