mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 18:56:25 +00:00
fix(sign): judge a shared signature value once per listing; dedupe DSS evidence by object number
This commit is contained in:
@@ -268,6 +268,9 @@ pub fn list_cached(doc: &Document, bytes: &[u8], trust: &TrustStore, cache: &Dig
|
||||
let pages = annot_pages(doc);
|
||||
let mut out = Vec::new();
|
||||
let mut field_values: HashSet<ObjRef> = HashSet::new();
|
||||
// Fields that name one signature value are judged once: validation does not depend on the
|
||||
// field, and repeating it per field would repeat the digest, chain and evidence work.
|
||||
let mut judged: HashMap<ObjRef, SignatureInfo> = HashMap::new();
|
||||
for f in sig_fields(doc) {
|
||||
if let Some(v_ref) = f.dict.get(b"V").and_then(Object::as_ref) {
|
||||
field_values.insert(v_ref);
|
||||
@@ -310,9 +313,19 @@ pub fn list_cached(doc: &Document, bytes: &[u8], trust: &TrustStore, cache: &Dig
|
||||
modification: Modification::None,
|
||||
details: Vec::new(),
|
||||
};
|
||||
let shared = f.dict.get(b"V").and_then(Object::as_ref).filter(|_| v.is_some());
|
||||
if let Some(first) = shared.and_then(|r| judged.get(&r)) {
|
||||
let own =
|
||||
SignatureInfo { field: info.field, signed: info.signed, page: info.page, rect: info.rect, visible: info.visible, ..first.clone() };
|
||||
out.push(own);
|
||||
continue;
|
||||
}
|
||||
if let Some(v) = v {
|
||||
validate_into(doc, bytes, trust, &v, &mut info, cache);
|
||||
}
|
||||
if let Some(r) = shared {
|
||||
judged.insert(r, info.clone());
|
||||
}
|
||||
out.push(info);
|
||||
}
|
||||
// Standalone document timestamps (ISO 32000-2 §12.8.2.2) live outside AcroForm fields.
|
||||
|
||||
@@ -340,7 +340,9 @@ pub(crate) const MAX_DSS_BYTES: usize = 64 << 20;
|
||||
pub(crate) struct DssEvidence<'a> {
|
||||
doc: &'a Document,
|
||||
items: std::vec::IntoIter<Object>,
|
||||
seen: HashSet<ObjRef>,
|
||||
/// Object numbers read: `Document::get` resolves a reference by number, so `5 0 R` and `5 1 R`
|
||||
/// are one stream.
|
||||
seen: HashSet<u32>,
|
||||
taken: usize,
|
||||
left: usize,
|
||||
/// A cap stopped the array being read in full.
|
||||
@@ -364,7 +366,7 @@ impl Iterator for DssEvidence<'_> {
|
||||
// Repeats are skipped before the caps count anything, so repeating one stream cannot use
|
||||
// them up; the scan of the array itself is bounded by the parser's cap on array length.
|
||||
for item in self.items.by_ref() {
|
||||
if item.as_ref().is_some_and(|r| !self.seen.insert(r)) {
|
||||
if item.as_ref().is_some_and(|r| !self.seen.insert(r.num)) {
|
||||
continue;
|
||||
}
|
||||
let o = self.doc.resolve(&item);
|
||||
|
||||
@@ -2954,3 +2954,35 @@ fn signing_a_field_with_a_null_value_after_another_signature_keeps_the_first_val
|
||||
all.iter().map(|s| (&s.status, &s.modification)).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
/// Fields that name one signature value are listed each, under their own name, with the verdict
|
||||
/// the value earned once.
|
||||
#[test]
|
||||
fn fields_sharing_a_signature_value_are_listed_with_one_verdict() {
|
||||
let id = pkcs12::open(&data("rsa-aes.p12"), "test").unwrap();
|
||||
let trust = TrustStore { certs: vec![id.certificate.clone()] };
|
||||
let signed = pdfcraft_sign::sign(&open(&fixture()), &id, &SignOptions { field: Some("Approval".into()), ..opts() }).unwrap();
|
||||
let v = open(&signed).get(pdfcraft_cos::ObjRef { num: 6, generation: 0 }).as_dict().and_then(|d| d.get(b"V").cloned()).unwrap();
|
||||
let shared = edit_after(&signed, |doc| {
|
||||
let mut w = pdfcraft_cos::Dict::new();
|
||||
w.set(b"Type".to_vec(), Object::name("Annot"));
|
||||
w.set(b"Subtype".to_vec(), Object::name("Widget"));
|
||||
w.set(b"FT".to_vec(), Object::name("Sig"));
|
||||
w.set(b"T".to_vec(), PdfString::text("Twin"));
|
||||
w.set(b"V".to_vec(), v.clone());
|
||||
w.set(b"Rect".to_vec(), Object::Array(vec![Object::Int(0); 4]));
|
||||
let w = doc.add(Object::Dict(w));
|
||||
let root = doc.root().unwrap();
|
||||
let af = doc.get(root).as_dict().and_then(|c| c.get(b"AcroForm").cloned()).unwrap();
|
||||
let af = doc.resolve(&af).as_dict().cloned().unwrap();
|
||||
let mut fields = af.get(b"Fields").map(|f| doc.resolve(f)).and_then(|f| f.as_array().cloned()).unwrap();
|
||||
fields.push(Object::Ref(w));
|
||||
let mut af = af;
|
||||
af.set(b"Fields".to_vec(), Object::Array(fields));
|
||||
doc.update_dict(root, |c| c.set(b"AcroForm".to_vec(), Object::Dict(af))).unwrap();
|
||||
});
|
||||
let list = signatures(&open(&shared), &shared, &trust);
|
||||
let (a, b) = (list.iter().find(|s| s.field == "Approval").unwrap(), list.iter().find(|s| s.field == "Twin").unwrap());
|
||||
assert!(a.signed && b.signed);
|
||||
assert_eq!((a.status, &a.modification, &a.signer, a.signed_len), (b.status, &b.modification, &b.signer, b.signed_len));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user