fix(sign): a widget counts as signed only when its /V resolves to a dictionary

This commit is contained in:
Dylan M. Taylor
2026-10-23 10:22:33 -04:00
parent 39ee93fe70
commit 5162da1b9f
2 changed files with 20 additions and 1 deletions
+4 -1
View File
@@ -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 {
+16
View File
@@ -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::<Vec<_>>()
);
}