One command-line tool over both layers of the library - com.pd4ml.pdf.cos structural
inspection and COS-path queries, and com.pd4ml.pdf.sign digital signing and PAdES‑LT/LTV
- with no code to write.
1. Invocation
Throughout this reference, pd4ml.jar stands for the pd4ml library jar and
bouncycastle-runtime-1.84.jar for its companion Bouncy Castle runtime jar (see the
project root's own README.md) - both assumed to be in the current directory in every
example below. com.pd4ml.pdf.sign.cli.PdfSignCli isn't pd4ml.jar's own
Main-Class (that's com.pd4ml.tools.Pd4Cmd, a different tool), so it's run
with -cp, not -jar; unlike this project's other CLI tools, signing genuinely
needs Bouncy Castle on the classpath - -cp, unlike -jar, does not follow a
jar's own manifest Class-Path entry, so it has to be named explicitly here. (On Windows,
use ; instead of : to separate classpath entries.) Running with no
arguments, or help/--help/-h as the first argument, prints the
same usage summary this page expands on.
Four commands: inspect, fields, sign,
ltv. Flags use --key=value syntax (e.g. --reason=Approved); a
flag with no =value (e.g. --visible, --ltv) is a boolean switch,
true by its mere presence. There is no short-flag (-r) form.
Exit codes
Success.
The operation failed for a business/runtime reason - bad password, malformed PDF, signing error, LTV chain-reconstruction failure… The message on stderr explains why.
Usage error - missing/invalid arguments or flags. Usage text is printed to stderr alongside the message.
2. inspect structural summary + COS-path queries
Parses input.pdf with com.pd4ml.pdf.cos.parser.COSParser and prints:
- PDF version
- indirect object count
- whether the document is encrypted (and, if so, whether the given/empty password was accepted)
- page count (
/Root/Pages/Count) - whether an
/AcroFormis present - whether a
/DSS(LTV validation store) is present
Each trailing positional argument (not starting with --) is evaluated as a
COS path expression against the parsed document, and the result printed. COS path
syntax mirrors a simplified filesystem-like path over the object graph:
| Expression | Meaning |
|---|---|
/Root/Pages/Count | trailer → Root → Pages → Count, resolving indirect references at each step |
/Root/Pages/Kids[0]/MediaBox | first kid's /MediaBox array |
trailer/Size | a value straight from the trailer dictionary (not via /Root) |
trailer/ID[0] | the first element of the trailer's /ID array |
A stream value's decoded bytes (or raw bytes, if the filter chain isn't fully understood - e.g.
DCTDecode image data) are previewed, truncated to 200 characters.
--password=<pw>- Tried as both the user and owner password if the PDF is encrypted; omit for an empty password (the common case for a document with no open password but a restricted owner password).
Examples
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli inspect report.pdf
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli inspect report.pdf "/Root/Pages/Count" "/Root/Pages/Kids[0]/MediaBox"
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli inspect protected.pdf --password=secret "trailer/Size"
↑ Back to top
3. fields list signature fields
Walks /Root/AcroForm/Fields, printing every field of type /Sig
(non-signature fields are skipped):
- field name (
/T) - unsigned (an empty field, ready to be filled by a future
signcall using--field=<that name>) or signed - for a signed field:
/SubFilter(normallyadbe.pkcs7.detached), signer name / reason / location / contact (whichever are present), the signing date (/M), the/ByteRangearray, and - if this is the document's certifying signature -CERTIFYING signature, DocMDP P=<level>
If the AcroForm has no fields at all, or none of type /Sig, this says so plainly rather
than printing nothing.
Example
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli fields signed.pdf
Field: Signature1
status: signed
subfilter: adbe.pkcs7.detached
signer name: Jane Doe
reason: Approved
signed at: D:20260825102208+00'00'
byte range: [0 1483 34251 516]
CERTIFYING signature, DocMDP P=1
Useful before a co-signing (sign --field=<new name>) or LTV
(ltv --field=<name>) call, to see what field names already exist.
4. sign apply a digital signature
Key source exactly one required
| Flags | Source |
|---|---|
--p12=<file>--password=<pw>[--alias=<a>] |
PKCS#12 (.p12/.pfx) keystore. --alias selects a
specific entry; omitted, the first private-key alias found is used. |
--pkcs11-config=<file>--pkcs11-pin=<pin>[--pkcs11-alias=<a>] |
PKCS#11 hardware token/HSM, via a standard SunPKCS11 config file
(name=... / library=... / slot=...). The private key
never leaves the token. |
Cloud KMS signing (RemoteSigner/CertificateUtils.forRemoteKey) is
not available from pd4signcli - a KMS client is inherently
vendor-specific code that can't be expressed as a flag. Use the library API directly (see the
PD4ML Signing Manual and
PD4ML Signing Examples's Ex09_CloudKmsSigning).
Signature metadata
--reason=<text>/Reason--location=<text>/Location--contact=<text>/ContactInfo--signer=<text>/Name--field=<name>- AcroForm field name (default
Signature1) --digest=SHA256|SHA384|SHA512- message digest (default
SHA256); the JCA signature algorithm (RSA vs. ECDSA) is derived automatically from the certificate's key type --placeholder=<bytes>- bytes reserved for the CMS blob (default
16384); raise this for a long certificate chain and/or a timestamp - see Troubleshooting
Certification (DocMDP)
| Flag value | Effect |
|---|---|
--certify=NONE | (default) an ordinary signature, no certification |
--certify=NO_CHANGES | DocMDP P=1 - no further changes of any kind permitted |
--certify=FORM_FILL | DocMDP P=2 - form filling (and further signing) still permitted |
--certify=FORM_FILL_ANNOTS | DocMDP P=3 - form filling, signing, and annotations still permitted |
Only valid on the first signature applied to a document; certifying an already-certified document fails with a clear error.
Timestamp (RFC 3161)
--tsa=<url>- TSA endpoint, e.g.
http://timestamp.digicert.com --tsa-user=<user>--tsa-password=<pw>- optional HTTP Basic Auth credentials some TSAs require
Visible appearance
--visible- enables a visible signature widget (omit entirely for an invisible signature)
--page=<n>- zero-based page index (default
0) --rect=llx,lly,width,height- position/size in PDF points
--image=<file>- stamp image (PNG/JPEG)
--text=<text>- caption text (used alone, or below the image if both given)
LTV right after signing
--ltv- fetch OCSP/CRL and embed PAdES-LT validation info immediately after signing, using the identity's own certificate chain
- (plus any ltv option)
- passed straight through
Examples
Invisible, minimal:
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli sign in.pdf out.pdf --p12=signer.p12 --password=changeit
Visible, certified, timestamped:
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli sign in.pdf out.pdf \
--p12=signer.p12 --password=changeit \
--reason=Approved --location="Berlin, DE" --signer="Jane Doe" \
--certify=NO_CHANGES --tsa=http://timestamp.digicert.com \
--visible --page=0 --rect=36,36,220,70 --image=stamp.png --text="Jane Doe"
PKCS#11 token:
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli sign in.pdf out.pdf --pkcs11-config=token.cfg --pkcs11-pin=123456
Sign and add LTV in one call:
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli sign in.pdf out.pdf --p12=signer.p12 --password=changeit --ltv
↑ Back to top
5. ltv add long-term validation to an already-signed PDF
Adds a /DSS (Document Security Store) + /VRI update to a PDF that was
already signed - by this tool, an earlier run, or any other PAdES-compliant
signer - as a second, independent incremental update. No original signing context
(SigningIdentity, SignResult) is required: the certificate chain is
reconstructed directly from the target signature's own embedded CMS SignedData.
--field=<name>- selects which signed field to build LTV for, when the document has more than one signed field. If omitted and exactly one signed field exists, that one is used automatically; if omitted and more than one exists, the command fails and lists the available names.
LTV options shared with sign --ltv
--no-ocsp- skip OCSP fetching entirely
--no-crl- skip CRL fetching entirely
--crl-only-as-fallback- only download a certificate's CRL when its OCSP responder was unreachable/absent (default: fetch both independently, for redundant evidence)
--include-root- also fetch/embed revocation info for the self-signed root/trust-anchor certificate (default: skipped - a trust anchor is trusted by its presence in the verifier's trust store, not by a signature over it)
Fetch failures for an individual certificate (no AIA/CRLDP extension, an unreachable responder, a non-2xx HTTP response, …) are printed as warnings and do not abort the run - LTV proceeds with whatever evidence was obtainable. If nothing could be fetched at all, a summary warning says so and only the certificate chain is embedded.
Example
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli ltv signed.pdf signed-ltv.pdf
$ java -cp "pd4ml.jar:bouncycastle-runtime-1.84.jar" com.pd4ml.pdf.sign.cli.PdfSignCli ltv signed.pdf signed-ltv.pdf --field=Signature1 --include-root
↑ Back to top
6. Troubleshooting
Raise --placeholder=<bytes> on the sign call. A long certificate
chain and/or an RFC 3161 timestamp can easily exceed the default 16 KB; 65536
is a safe generous value.
--certify=... was used on a document that already has a DocMDP-certifying signature.
Only the very first signature on a document may certify it; drop --certify for
subsequent (co-)signatures.
Run fields first to see the available names, then pass the right one.
The certificate chain has no usable Authority Information Access / CRL Distribution Points extensions reachable from this network (common for internal/private CAs, or for a signing certificate deliberately issued without revocation info, or simply no network route to the responder from where this command runs). The chain is still embedded; only revocation evidence is missing.
Not available from pd4signcli - see the library's RemoteSigner
interface and CertificateUtils.forRemoteKey (PD4ML
Signing Examples, Ex09_CloudKmsSigning).
pd4signcli inspect reads and COS-path-queries a PDF but has no write-side commands
for arbitrary COS edits - only sign/ltv, which write
signature-specific structure. For generic dictionary/array edits (rotate a page, add a custom key,
adjust metadata, ...), see the pd4coscli Reference
(com.pd4ml.pdf.cos.cli.CosCli's set/delete commands).
For importing or exporting annotations and form field values via XFDF, see the
pd4xfdfcli Reference
(com.pd4ml.pdf.xfdf.cli.Pd4XfdfCli).
7. See also
- PD4ML Signing Manual - the programmer's manual behind this CLI
- PD4ML Signing Examples - 18 runnable use-case examples
- PD4ML COS API Manual - standalone
com.pd4ml.pdf.cosprogrammer's manual - pd4coscli Reference - command-line
reference for
com.pd4ml.pdf.cos.cli.CosCli - pd4xfdfcli Reference - importing or exporting annotations and form field values via XFDF
