mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
Follow-up to #1374. Signing an existing field refused a field whose /V is a signature dictionary and signed one whose /V is missing or null, but a /V that is neither (an empty string, a number, a name, an array) was treated as unsigned and overwritten with the new signature. Signing such a field by name now fails with "the value of the field ... is not a signature" and leaves the document as it is. A missing or null /V, direct or indirect, still signs, and a signed field is still refused as already signed. The signature list is unchanged: it still shows such a field as unsigned, since it carries no signature. Adds a_field_whose_value_is_not_a_signature_is_refused_by_name, which fails before this change.
This commit is contained in:
1 parent
6f49967459
commit
452ab51ab8
2 files changed
+39
No files matched your search
@@ -491,6 +491,12 @@ impl Field {
|
||||
fn signature(&self, doc: &Document) -> Option<Dict> {
|
||||
self.dict.get(b"V").map(|v| doc.resolve(v)).and_then(|v| v.as_dict().cloned())
|
||||
}
|
||||
|
||||
/// True when /V holds something that is neither null nor a signature dictionary: signing the
|
||||
/// field would overwrite a value we can't read as a signature.
|
||||
fn has_foreign_value(&self, doc: &Document) -> bool {
|
||||
self.dict.get(b"V").map(|v| doc.resolve(v)).is_some_and(|v| !matches!(*v, Object::Null) && v.as_dict().is_none())
|
||||
}
|
||||
}
|
||||
|
||||
/// Every signature field, depth first in `/Fields` order.
|
||||
@@ -1722,6 +1728,9 @@ fn sign_inner(
|
||||
if f.signature(&doc).is_some() {
|
||||
return Err(SignError::Pdf(format!("the field {:?} is already signed", f.name)));
|
||||
}
|
||||
if f.has_foreign_value(&doc) {
|
||||
return Err(SignError::Pdf(format!("the value of the field {:?} is not a signature", f.name)));
|
||||
}
|
||||
let fr = f.r.ok_or_else(|| SignError::Pdf("the field is not an indirect object".into()))?;
|
||||
doc.update_dict(fr, |d| d.set(b"V".to_vec(), Object::Ref(sig)))?;
|
||||
let (wr, wd) = f.widgets.first().cloned().ok_or_else(|| SignError::Pdf("the field has no widget".into()))?;
|
||||
|
||||
@@ -156,6 +156,36 @@ fn a_field_with_a_null_value_is_unsigned_and_can_be_signed() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Follow-up to #1374: a signature field whose /V is neither null nor a signature dictionary is
|
||||
/// refused by name with an error saying so, instead of being overwritten or called signed.
|
||||
#[test]
|
||||
fn a_field_whose_value_is_not_a_signature_is_refused_by_name() {
|
||||
let id = pkcs12::open(&data("ec-p256.p12"), "test").unwrap();
|
||||
let field = pdfcraft_cos::ObjRef { num: 6, generation: 0 };
|
||||
let values = [
|
||||
("empty string", Object::String(PdfString::literal(""))),
|
||||
("number", Object::Int(5)),
|
||||
("name", Object::name("Off")),
|
||||
("array", Object::Array(Vec::new())),
|
||||
];
|
||||
for (what, value) in values {
|
||||
let direct = edit_after(&fixture(), |doc| doc.update_dict(field, |d| d.set(b"V".to_vec(), value.clone())).unwrap());
|
||||
let indirect = edit_after(&fixture(), |doc| {
|
||||
let v = doc.add(value.clone());
|
||||
doc.update_dict(field, |d| d.set(b"V".to_vec(), Object::Ref(v))).unwrap();
|
||||
});
|
||||
for (how, bytes) in [("direct", direct), ("indirect", indirect)] {
|
||||
let doc = open(&bytes);
|
||||
let listed = signatures(&doc, &bytes, &TrustStore::default());
|
||||
assert!(listed.iter().any(|s| s.field == "Approval" && !s.signed), "{how} {what}");
|
||||
match pdfcraft_sign::sign(&doc, &id, &SignOptions { field: Some("Approval".into()), ..opts() }) {
|
||||
Err(SignError::Pdf(e)) => assert_eq!(e, "the value of the field \"Approval\" is not a signature", "{how} {what}"),
|
||||
other => panic!("{how} {what}: {:?}", other.map(|b| b.len())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a revision that changes object `num` with `f`.
|
||||
fn edit_after(signed: &[u8], f: impl FnOnce(&mut Document)) -> Vec<u8> {
|
||||
let mut doc = open(signed);
|
||||
|
||||
Reference in new issue
Block a user