sign: an empty /Contents is not an earlier signature

Only a non-empty signature value counts when deciding whether the first real
signature may drop /NeedAppearances.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76andClaude Sonnet 5.5 committed 2026-11-07 16:54:58 +01:00
1 parent a371b24f2a
commit 7b6baec065
2 files changed
+18 -1

No files matched your search

+2 -1
View File
@@ -153,7 +153,8 @@ fn sign_inner(
}
check_signing_permissions(doc, opts)?;
let mut doc = doc.clone();
let earlier_signatures = !super::validate::signature_contents(&doc).is_empty();
// A signature field whose /V holds an empty /Contents is not a signature yet.
let earlier_signatures = super::validate::signature_contents(&doc).iter().any(|der| !der.is_empty());
let root = doc.root().ok_or_else(|| SignError::Pdf("the document has no catalog".into()))?;
let alg = id.key.preferred_digest();
let reserve =
+16
View File
@@ -150,6 +150,22 @@ fn signing_drops_need_appearances() {
}
}
/// A field whose /V has an empty /Contents is no earlier signature: the first real one still
/// drops `/NeedAppearances`.
#[test]
fn an_empty_signature_value_does_not_count_as_an_earlier_signature() {
let id = pkcs12::open(&data("ec-p256.p12"), "test").unwrap();
let bytes = fixture_with(
b"<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [6 0 R 7 0 R] /NeedAppearances true >> >>",
&[
b"<< /Type /Annot /Subtype /Widget /FT /Sig /T (Empty) /Rect [10 20 100 70] /P 3 0 R /F 4 /V 8 0 R >>",
b"<< /Type /Sig /Filter /Adobe.PPKLite /SubFilter /ETSI.CAdES.detached /Contents <> >>",
],
);
let signed = pdfcraft_sign::sign(&open(&bytes), &id, &SignOptions { field: Some("Approval".into()), ..opts() }).unwrap();
assert!(form_of(&signed).get(b"NeedAppearances").is_none());
}
/// A second signer leaves the form's flags alone: changing them would count as a change to
/// what the first signature covered.
#[test]