diff --git a/crates/sign/src/pdf.rs b/crates/sign/src/pdf.rs index c53e08917..8b0efadf8 100644 --- a/crates/sign/src/pdf.rs +++ b/crates/sign/src/pdf.rs @@ -1788,7 +1788,10 @@ fn change_kind(doc: &Document, old: &Document, d: &Dict, before: Option<&Dict>) // A signature widget that already held a signature is what the signature shows: moving it or // swapping its appearance can hide the signed page under it. Judged by the signed revision, // so relabelling the widget now does not change that. - if before.is_some_and(|b| b.name(b"Subtype") == Some(b"Widget") && b.contains(b"V") && b.name(b"FT") == Some(b"Sig")) && before != Some(d) { + if before.is_some_and(|b| { + b.name(b"Subtype") == Some(b"Widget") && b.name(b"FT") == Some(b"Sig") && b.get(b"V").is_some_and(|v| old.resolve(v).as_dict().is_some()) + }) && before != Some(d) + { return kind::PAGE_CONTENT; } match ty { diff --git a/crates/sign/tests/it/pdf.rs b/crates/sign/tests/it/pdf.rs index 735965899..de98fa590 100644 --- a/crates/sign/tests/it/pdf.rs +++ b/crates/sign/tests/it/pdf.rs @@ -2602,3 +2602,19 @@ fn freeing_a_signed_object_or_rewriting_the_signed_widget_is_a_page_content_chan assert_eq!(moved.status, Status::Invalid, "{:?}", moved.modification); assert!(matches!(&moved.modification, Modification::Disallowed(k) if k.contains(&kind::PAGE_CONTENT)), "{:?}", moved.modification); } + +/// An existing signature field whose `/V` is null is unsigned: signing it rewrites its widget, +/// and that is a signature, not a change to what an earlier signature shows. +#[test] +fn signing_a_field_with_a_null_value_after_another_signature_keeps_the_first_valid() { + let id = pkcs12::open(&data("rsa-aes.p12"), "test").unwrap(); + let trust = TrustStore { certs: vec![id.certificate.clone()] }; + let first = pdfcraft_sign::sign(&open(&fixture()), &id, &opts()).unwrap(); + let second = pdfcraft_sign::sign(&open(&first), &id, &SignOptions { field: Some("Approval".into()), ..opts() }).unwrap(); + let all = signatures(&open(&second), &second, &trust); + assert!( + all.iter().filter(|s| s.signed).all(|s| s.status == Status::Valid), + "{:?}", + all.iter().map(|s| (&s.status, &s.modification)).collect::>() + ); +}