The creation of signature fields—that is to say, containers for
(future) signatures—is handled by the
Depending on your requirements, you may not need to call the functions in this
module explicitly; in many simple cases, pyHanko’s
signing functionality takes care of that for you.
However, if you want more control, or you need some of the more advanced functionality (such as seed value support or field locking) that the PDF standard offers, you might want to read on.
General API design¶
As the name suggests, a
SigFieldSpec is a
specification for a new signature field.
These objects are designed to be immutable and stateless.
SigFieldSpec object is instantiated by
SigFieldSpec() with the following keyword
sig_field_name: the field’s name. This is the only mandatory parameter; it must not contain any period (
Hence, to create a signature field specification for an invisible signature
Sig1, and add it to a file
document.pdf, you would proceed
from pyhanko.sign.fields import SigFieldSpec, append_signature_field from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter with open('document.pdf', 'rb+') as doc: w = IncrementalPdfFileWriter(doc) append_signature_field(w, SigFieldSpec(sig_field_name="Sig1")) w.write_in_place()
The position of a signature field is essentially only relevant for visible
SigFieldSpec parameters determine where a signature widget will
on_page: index of the page on which the signature field should appear (default:
box: bounding box of the signature field, represented as a 4-tuple
(x1, y1, x2, y2)in Cartesian coordinates (i.e. the vertical axis runs bottom to top).
In contrast with the CLI, pages are zero-indexed in the API.
Seed value settings¶
The PDF standard provides a way for document authors to provide so-called “seed values” for signature fields. These instruct the signer about the possible values for certain signature properties and metadata. They can be purely informative, but can also be used to restrict the signer in various ways.
Below is a non-exhaustive list of things that seed values can do.
Put restrictions on the signer’s certificate, including
the subject’s distinguished name,
key usage extensions.
Force the signer to embed a timestamp (together with a suggested time stamping server URL).
Offer the signer a list of choices to choose from when selecting a reason for signing.
Instruct the signer to use a particular signature (sub-)handler (e.g. tell the signer to produce PAdES-style signatures).
Most of these recommendations can be marked as mandatory using flags. In this case, they also introduce a validation burden.
Before deciding whether seed values are right for your use case, please consider the following factors.
Seed values are a (relatively) obscure feature of the PDF specification, and not all PDF software offers support for it. Using mandatory seed values is therefore probably only viable in a closed, controlled environment with well-defined document workflows. When using seed values in an advisory manner, you may want to provide alternative hints, perhaps in the form of written instructions in the document, or in the form of other metadata.
At this time, pyHanko only supports a subset of the seed value specification in the standard, but this should be resolved in due time. The extent of what is supported is recorded in the API reference for
Since incremental updates can modify documents in arbitrary ways, mandatory seed values can only be (reliably) enforced if the author includes a certification signature, to prevent later signers from surreptitiously changing the rules.
If this is not an option for whatever reason, then you’ll have to make sure that the entity validating the signatures is aware of the restrictions the author intended through out-of-band means.
Seed values for a new signature field are configured through the
SigFieldSpec. This attribute takes a
SigSeedValueSpec object, containing
the desired seed value configuration.
For a detailed overview of the seed values that can be specified, follow the
links to the API reference; we only discuss the most important points below.
The mandatory seed values are indicated by the
flags attribute, which takes a
SigSeedValFlags object as its value.
This is a subclass of
Flag, so you can combine different flags using
Restrictions and suggestions pertaining to the signer’s certificate deserve
special mention, since they’re a bit special.
These are encoded the
SigSeedValueSpec, in the form of a
This class has a
attribute of its own, indicating which of the
SigCertConstraints are to be
Its value is a
In other words, the enforceability of certificate constraints is not
controlled by the
SigSeedValueSpec, but by the
flags attribute of the
SigCertConstraints object inside the
This mirrors the way in which these restrictions are defined in the PDF
Since this is all rather abstract, let’s discuss a concrete example. The code below shows how you might instantiate a signature field specification for a ballot form of sorts, subject to the following requirements.
Only people with voting rights should be able to sign the ballot. This is enforced by requiring that the certificates be issued by a specific certificate authority.
The signer can either vote for or against the proposed measure, or abstain. For the sake of the example, let’s encode that by one of three possible reasons for signing.
Since we want to avoid cast ballots being modified after the fact, we require a strong hash function to be used (at least
from pyhanko.sign import fields from oscrypto import keys franchising_ca = keys.parse_certificate(b'<certificate data goes here>') sv = fields.SigSeedValueSpec( reasons=[ 'I vote in favour of the proposed measure', 'I vote against the proposed measure', 'I formally abstain from voting on the proposed measure' ], cert=fields.SigCertConstraints( issuers=[franchising_ca], flags=fields.SigCertConstraintFlags.ISSUER ), digest_methods=['sha256', 'sha384', 'sha512'], flags=fields.SigSeedValFlags.REASONS | fields.SigSeedValFlags.DIGEST_METHOD ) sp = fields.SigFieldSpec('BallotSignature', seed_value_dict=sv)
Note the use of the bitwise-or operator
| to combine multiple flags.
Document modification policy settings¶
Broadly speaking, the PDF specification outlines two ways to specify the degree to which a document may be modified after a signature is applied, without these modifications affecting the validity of the signature.
The document modification detection policy (DocMDP) is an integer between one and three, indicating on a document-wide level which classes of modification are permissible. The three levels are defined as follows:
level 1: no modifications are allowed;
level 2: form filling and signing are allowed;
level 3: form filling, signing and commenting are allowed.
The default value is 2.
The field modification detection policy (FieldMDP), as the name suggests, specifies the form fields that can be modified after signing. FieldMDPs can be inclusive or exclusive, and as such allow fairly granular control.
When creating a signature field, the document author can suggest policies that the signer should apply in the signature object. While the signer should follow these restrictions, this doesn’t always happen1, so they shouldn’t be relied upon if the signing of the document is out of your control.
There are a number of caveats that apply to MDP settings in general; see Some background on PDF signatures.
Traditionally, the DocMDP settings are exclusive to certification signatures (i.e. the first, specially marked signature included by the document author), but in PDF 2.0 it is possible for approval (counter)signatures to set the DocMDP level to a stricter value than the one already in force.
In pyHanko, these settings are controlled by the
The example below specifies a field with instructions for the signer to
lock a field called
SomeTextField, and set the DocMDP value for that
FORM_FILLING (i.e. level 2).
PyHanko will respect these settings when signing, but other software might not.
from pyhanko.sign import fields fields.SigFieldSpec( 'Sig1', box=(10, 74, 140, 134), field_mdp_spec=fields.FieldMDPSpec( fields.FieldMDPAction.INCLUDE, fields=['SomeTextField'] ), doc_mdp_update_value=fields.MDPPerm.FORM_FILLING )
doc_mdp_update_value value is
more or less self-explanatory, since it’s little more than a numerical constant.
The value passed to
is an instance of
FieldMDPSpec objects take two parameters:
Right now, pyHanko doesn’t yet reject such signatures, but it may in the future.