Install with dotnet tool install -g SimpleSign.Cli.
# Sign a PDF
simplesign sign contract.pdf --cert mycert.pfx --password secret --tsa http://timestamp.digicert.com
# Claimed signing time, document information, a larger /Contents and structured signer metadata
simplesign sign contract.pdf --cert mycert.pfx --password secret \
--signing-time 2026-11-09T10:00:00-03:00 --doc-info Title=Contract --contents-size 32768 \
--signer-id 12345678909 --signer-id-type CPF --legal-basis "Lei 14.063/2020" --commitment origin
# Explicit signature policy (signature-policy-identifier with the document's hash)
simplesign sign contract.pdf --cert mycert.pfx --password secret \
--policy-oid 1.2.3.4.5 --policy-hash 9F86D081884C7D65... --policy-uri https://example.com/policy.der
# B-LTA with a separate archive TSA; accept a lower level when the TSA or revocation data are unreachable
simplesign sign contract.pdf --cert mycert.pfx --password secret --tsa http://timestamp.digicert.com \
--archival --archive-tsa https://tsa.example.com/tsr --return-lower-level
# A TSA that asks for credentials (also on `cades sign` and `xades sign`; secrets accept env:NAME)
simplesign sign contract.pdf --cert mycert.pfx --password secret --tsa https://tsa.example.com/tsr \
--tsa-user alice --tsa-password env:TSA_PASSWORD # or --tsa-token env:TSA_TOKEN, or --tsa-cert client.pfx
# Validate (add --trust to accept your own root CA, --no-system-roots to trust only it, --json for machine-readable output)
simplesign validate signed.pdf
simplesign validate signed.pdf --trust my-root-ca.pem --json
# Sign every PDF of a folder (BatchSigner), four at a time, into folder/signed
simplesign sign-dir contracts/ --cert mycert.pfx --password secret --tsa http://timestamp.digicert.com --max-concurrency 4
# Two-phase signing: the key is elsewhere (browser, token, remote service). See "Two-Phase Signing" below.
simplesign sign contract.pdf --prepare --cert signer.cer --session-key env:SESSION_KEY # writes .session and .tbs
# ...the signer hashes contract.pdf.tbs with SHA-256 and signs it (RSA PKCS#1 v1.5, or ECDSA in DER)...
simplesign deferred complete contract.pdf.session --session-key env:SESSION_KEY --signature contract.sig -o signed.pdf
# Validate a directory of signed PDFs
simplesign validate-dir ./documents/
# Inspect
simplesign inspect signed.pdf
# Extract CMS from signed PDF
simplesign extract signed.pdf --output-dir ./extracted
# Convert HTML to PDF (relative images/CSS resolve against the HTML file's folder)
simplesign html-to-pdf page.html --output page.pdf
# ... with an embedded Unicode font, footer, and PDF/A-2b for archiving
simplesign html-to-pdf page.html --font "Noto Sans=NotoSans-Regular.ttf" --footer "{page}/{pages}" --pdfa
# Explain a signature term (omit the term for the full glossary)
simplesign explain ByteRange
# Show version
simplesign version--visible draws a stamp on the page; without --page, --pos-x or --pos-y it is placed automatically at the
bottom of the page. Everything else is optional:
| Option | What it does |
|---|---|
--page, --pos-x, --pos-y |
Page (1 is the first) and lower-left corner, in points |
--stamp-width, --stamp-height |
Fixed size in points (default: computed from the text) |
--text-align left|center|right, --vertical-align top|middle|bottom |
Text alignment |
--no-shrink-to-fit, --max-text-length <N> |
Clip instead of shrinking the text; cut longer texts (default 40) |
--signer-display-name <NAME>, --extra-line <TEXT> (repeatable) |
The name and more lines of text |
--no-date, --hide-reason, --hide-location |
Leave the date, reason or location off the stamp (they stay in the signature) |
--signed-by-label, --reason-label, --location-label, --date-label |
The labels |
--date-format, --time-zone, --date-culture |
How the date is written |
--font, --font-size, --label-font-size |
A PDF standard font and its sizes |
--text-color, --background-color, --border-color (R,G,B from 0 to 1), --border-width |
Colors and border |
--background-image <PNG|JPEG>, --image-fit stretch|contain|cover |
An image behind the text |
--qr-url <URL> |
A QR code with a verification URL |
simplesign sign contract.pdf --cert mycert.pfx --password secret --visible --page 2 --pos-x 72 --pos-y 72 \
--stamp-width 220 --stamp-height 70 --vertical-align middle --extra-line "CRM 12345" --date-culture pt-BR# CAdES-B-B (basic)
simplesign cades sign document.pdf --cert mycert.pfx
# CAdES-B-T (with timestamp)
simplesign cades sign document.pdf --cert mycert.pfx \
--tsa http://timestamp.digicert.com --level timestamped
# CAdES-B-LT (long-term with LTV)
simplesign cades sign document.pdf --cert mycert.pfx \
--tsa http://timestamp.digicert.com --level longterm --chain chain.pem
# CAdES-B-LTA (with archival timestamp)
simplesign cades sign document.pdf --cert mycert.pfx \
--tsa http://timestamp.digicert.com --level archive
# Enveloped mode — data embedded inside the CMS (.p7m)
simplesign cades sign document.pdf --cert mycert.pfx --content-type enveloped
# Signer location, role and a description of the content
simplesign cades sign document.pdf --cert mycert.pfx \
--signer-country BR --signer-locality "São Paulo" --signer-postal-address "Av. Paulista, 1000" \
--signer-role Director --mime-type application/pdf --content-description "Contract"
# Roles vouched for by a third party (embedded as given, not verified)
simplesign cades sign document.pdf --cert mycert.pfx \
--attribute-certificate role.ac --signed-assertion assertion.xml
# Timestamp the content before signing
simplesign cades sign document.pdf --cert mycert.pfx \
--tsa http://timestamp.digicert.com --content-timestamp
# Carry the policy document in the signature (its SHA-256 goes in the policy identifier)
simplesign cades sign document.pdf --cert mycert.pfx \
--policy-oid 1.2.3.4.5 --policy-document policy.der --policy-specification 1.2.3.4.5.1
# B-LT asking the CRL before OCSP (also on `sign` and `xades sign`)
simplesign cades sign document.pdf --cert mycert.pfx \
--tsa http://timestamp.digicert.com --level longterm --revocation-preference crl
# Add a second signer of the same content, or countersign the first signer
simplesign cades sign document.pdf.p7s --cosign --data document.pdf --cert second.pfx --password secret
simplesign cades sign document.pdf.p7s --countersign --signer-index 0 --cert notary.pfx --password secret
# Claimed signing time, RSASSA-PSS and an extended form (-C, -X-L or -A; also on xades sign)
simplesign cades sign document.pdf --cert mycert.pfx --password secret --signing-time 2026-11-09T10:00:00Z \
--signature-algorithm rsassa-pss --tsa http://timestamp.digicert.com --level longterm --extended-form extended-long
# Validate a CAdES signature
simplesign cades validate document.pdf.p7s --data document.pdf
# Validate with custom trust anchors (the operating system's roots stay trusted; add --no-system-roots to trust only these)
simplesign cades validate document.pdf.p7s --data document.pdf --trust root-ca.pemcades validate checks every signer and countersignature. With --json, signatures lists them (signer-1,
signer-2…, each with its counterSignatures: signer-1.1…); the top-level fields are those of the first signer.
The exit code is 0 only when all are valid.
# XAdES-B-B enveloped (default form)
simplesign xades sign invoice.xml --cert mycert.pfx
# XAdES-B-T detached
simplesign xades sign data.xml --cert mycert.pfx \
--tsa http://timestamp.digicert.com --level timestamped \
--form detached --data-uri data.xml
# XAdES-B-LT enveloping
simplesign xades sign data.xml --cert mycert.pfx \
--tsa http://timestamp.digicert.com --level longterm \
--form enveloping --chain chain.pem
# Production place and Canonical XML 1.0 instead of Exclusive C14N
simplesign xades sign invoice.xml --cert mycert.pfx \
--place-city "São Paulo" --place-state SP --place-country BR --canonicalization inclusive
# More files signed with the document: one inside the signature, one referenced by URI
simplesign xades sign invoice.xml --cert mycert.pfx \
--embed-object attachment.pdf,application/pdf --external-object terms.txt=terms.txt,text/plain
# Timestamp the signed data before signing; carry the policy document in the signature
simplesign xades sign invoice.xml --cert mycert.pfx \
--tsa http://timestamp.digicert.com --data-objects-timestamp \
--policy-oid 1.2.3.4.5 --policy-document policy.xml --policy-specification https://example.com/policy-syntax
# Roles vouched for by a third party (embedded as given, not verified)
simplesign xades sign invoice.xml --cert mycert.pfx \
--attribute-certificate role.ac --signed-assertion assertion.xml
# Countersign a signature of an already signed document (--countersign-id picks one of several)
simplesign xades sign signed.xml --cert witness.pfx --countersign --output countersigned.xml
# SigningCertificate V1 for older validators, the data's MIME type, and a PKCS#11 token as the key
simplesign xades sign invoice.xml --pkcs11-lib auto --signing-certificate-v1 --data-mime-type application/xml
# Validate a XAdES signature
simplesign xades validate signed.xml
# Validate a signature with external data objects: give each by its URI
simplesign xades validate signed.xml --external-object terms.txt=terms.txt
# Validate detached with original data
simplesign xades validate signed.xml --data data.xml
# Validate with custom trust anchors (the operating system's roots stay trusted; add --no-system-roots to trust only these)
simplesign xades validate signed.xml --trust root-ca.pemxades validate checks every ds:Signature of the document and their countersignatures. With --json,
signatures lists them, each with its counterSignatures; the top-level fields are those of the first. The exit
code is 0 only when all are valid.
When the private key is on another machine, sign, cades sign and xades sign take --prepare: the command
does not sign. It runs the signing policy and writes the session (<input>.session, or --session) and the data to
sign (<input>.tbs, or --to-sign). Every other option of the command keeps its meaning — level, TSA, field and
appearance, signed attributes, signature policy, co-signature and countersignature.
# Phase one. --cert is the signer's certificate without a key (DER or PEM); --chain adds its chain.
simplesign sign contract.pdf --prepare --cert signer.cer --chain chain.pem --session-key env:SESSION_KEY \
--tsa http://timestamp.digicert.com --ltv --reason "Approved"
simplesign cades sign report.pdf --prepare --cert signer.cer --session-key env:SESSION_KEY --content-type enveloped
simplesign xades sign signed.xml --prepare --cert signer.cer --session-key env:SESSION_KEY --countersign
# The signer hashes the .tbs file with the digest algorithm the command printed and signs it:
# RSA PKCS#1 v1.5 or PSS, or ECDSA as a DER SEQUENCE { r, s }.
# Phase two, for the three formats: the format is read from the session.
simplesign deferred complete contract.pdf.session --session-key env:SESSION_KEY --signature contract.sig -o signed.pdf| Option | On | What it does |
|---|---|---|
--prepare |
sign, cades sign, xades sign |
Prepare instead of signing. Not with --password, a token, a cloud certificate or --output. |
--session-key <KEY> |
prepare and complete | The HMAC key of the session: 32 bytes or more as hex or base64, or env:NAME. The same key completes it. |
--session, --to-sign |
prepare | Where the session and the data to sign go. |
--content-outside-session |
cades sign --prepare |
Keep the content out of the session; give it to deferred complete --content. |
--detached-data-outside-session |
xades sign --prepare |
Keep the detached document and external data objects out of the session; give them to deferred complete --detached-data and --external-object URI=PATH. |
--signature <PATH> |
deferred complete |
The raw signature of the data to sign. |
--tsa, --archive-tsa with --tsa-user, --tsa-token or --tsa-cert |
deferred complete |
The TSA of phase one again, only to give its credentials: they are never stored in the session. |
--signing-policy, --trust, --expected-tsa, --audit-log |
deferred complete |
The checks of the TSA certificate; the signer was checked in phase one. |
--json |
prepare and complete | The paths and algorithms of phase one; the requested and achieved levels of phase two. |
The session is authenticated, not encrypted, and holds the document: keep it on the server. deferred prepare is the
earlier, PDF-only form of sign --prepare and stays available.
contract-signed.pdf 1/1 valid
├── Document
│ ├── Signatures: 1 user + 0 timestamps
│ ├── Encrypted: No
│ ├── DocMDP: Not locked
│ ├── PDF/A: None
│ └── ✓ DSS (embedded)
└── Signature1 ✓ VALID
├── Signer: CN=Jane Doe, O=Acme Corp
├── SubFilter: ETSI.CAdES.detached
├── PAdES: B-T (Timestamp)
├── Certificate
│ ├── Subject: CN=Jane Doe, O=Acme Corp
│ ├── Issuer: DigiCert SHA2 Assured ID CA
│ ├── Serial: 0A:1B:2C:3D
│ ├── Key: RSA 2048-bit
│ ├── Valid: 2024-01-01 – 2026-01-01
│ └── NonRepudiation: ✓
├── ESS CertV2: ✓
├── Validation
│ ├── Integrity: ✓ Valid
│ ├── Signature: ✓ Valid
│ ├── Chain: ✓ Valid
│ ├── Revoked: ✓ Not revoked (OCSP)
│ └── Timestamp: ✓ 2025-04-28 14:30:00 UTC
├── Timestamp
│ ├── Time: 2025-04-28 14:30:00 UTC
│ ├── TSA: CN=DigiCert Timestamp 2023
│ └── Token Size: 4.2 KB
├── Algorithm: SHA-256
├── Byte Range: [0, 1234, 5678, 9012] ✓
└── Signed at: 2025-04-28 14:30:00 UTC
sign, cades sign, xades sign, sign-dir and deferred prepare/complete check the certificate before signing.
--signing-policy chooses the checks: a JSON file, or a built-in name (simplesign policy export signing --preset NAME
writes it out):
| Value | Checks |
|---|---|
default |
Validity of every certificate of the chain, key usage digitalSignature or nonRepudiation, RSA ≥ 2048 bits, SHA-2/SHA-3 |
strict |
default plus the revocation of the whole chain (CRL/OCSP); give the chain up to the root |
minimal |
Only the dates of the signing certificate (tests and migrations) |
iofe |
INDECOPI's guide v4.0: strict, CRLs with nextUpdate, no SHA-1 in the chain, the signer and every TSA chained to a trust anchor, TSA valid and not revoked |
iofe-nonrepudiation |
iofe, and the signing certificate must have nonRepudiation (what the guide asks of an automated agent, section 4.2) |
What the policy checks against comes from these options:
| Option | Means |
|---|---|
--trust <file> (repeatable), --trust-dir <dir>, --tsl <url|file> --tsl-signer <file> [--tsl-profile iofe] |
The trust anchors the signer and each TSA must chain to (ProspectiveCertificateChain); --tsl-profile iofe reads the list as IofeExtension does (INDECOPI's unspecified TSAs too, never a code-signing certificate) |
--expected-tsa <file> |
The certificate the TSA must sign with; a timestamp from another is refused (TimestampUntrusted) |
--audit-log <file> |
Appends a hash-chained record of each signature made or refused; simplesign audit verify <file> checks the chain |
augment <file> --level BT|BLT|BLTA --tsa URL raises an existing PAdES, CAdES or XAdES signature (the format is read
from the file): a document time-stamp, a DSS and a new document time-stamp for a PDF; the signature time-stamp,
validation data and archive time-stamp for CAdES and XAdES. --data gives a detached signature's data (B-LTA needs
it), --signature-id picks a ds:Signature, --renew renews a B-LTA, --json prints the levels before and after.
validate --policy takes a file or default, strict (time-stamps out of order and an unavailable signature policy fail), iofe or iofe-nonrepudiation.
A refusal prints the reason (KeyTooWeak, KeyUsageNotPermitted, ChainCertificateNotValid, CertificateRevoked,
RevocationStatusUnknown, AlgorithmNotPermitted, UntrustedRoot, TimestampUntrusted, TimestampCertificateRevoked…) and what to do about it. simplesign explain <TERM> explains the
ETSI sub-indications of a validation report, such as CRYPTO_CONSTRAINTS_FAILURE_NO_POE.
With --json, sign, cades sign and xades sign print one JSON object instead of text, for scripts and
test runners:
{ "success": true, "format": "pades", "output": "contract_signed.pdf", "signer": "CN=…",
"requestedLevel": "B-LT", "achievedLevel": "B-LT", "warnings": [], "error": null }A refusal is "success": false with "error": { "type": "SigningException", "reason": "CertificateRevoked", "message": "…" }; the exit code is not 0.
The library refuses requests to localhost and private addresses (the SSRF guard). To use a local test
environment, such as the SimpleSign Sandbox, every command accepts:
| Option | Environment variable | Effect |
|---|---|---|
--allow-origin <URL> (repeatable) |
SIMPLESIGN_ALLOW_ORIGINS (comma-separated) |
A loopback origin (scheme, host, port) the TSA, CRL, OCSP and AIA requests may reach |
--allow-ldap |
SIMPLESIGN_ALLOW_LDAP=true |
CRLs over ldap:// |
--tls-trust <PEM> |
SIMPLESIGN_TLS_TRUST |
A root, and its intermediates, accepted for HTTPS next to the system's |
curl -s http://localhost:5300/chain.pem > sandbox-chain.pem
simplesign sign contract.pdf --cert revoked-ocsp.pfx --password sandbox --signing-policy strict-signing-policy.json --json \
--allow-origin http://localhost:5300 --allow-origin https://localhost:5301 --allow-origin ldap://localhost:5302 \
--allow-ldap --tls-trust sandbox-chain.pemvalidate, validate-dir, cades validate and xades validate share these options:
| Option | What it does |
|---|---|
--policy <file> |
Validation policy: JSON, root ValidationConstraints; see ADR 0024 |
--policy-signer <cert> |
The policy must be signed with this certificate (detached CMS in <policy>.p7s) |
--trust-dir <dir> |
Folder of trust anchor certificates (.cer, .crt, .der, .pem) |
--trust-dir-signer <cert> |
The folder must carry manifest.json (SHA-256 of every file) signed with this certificate |
--tsl <url|file> + --tsl-signer <cert> |
Trusted list (ETSI TS 119 612 / TS 102 231): its active CA and TSA services become trust anchors. An expired list (here or under --lotl) is kept: the policy's TLNotExpired judges it (WARN by default, FAIL in iofe), as EU DSS does |
--tsl-profile iofe |
Read --tsl as INDECOPI's list (IofeExtension.DefaultListOptions): its unspecified TSAs become anchors too, code-signing certificates never |
--lotl <url> + --lotl-signer <cert> |
List of trusted lists (e.g. the EU LOTL): the national lists it points to are checked with the certificates it gives and their CA and TSA services become trust anchors; --lotl-territory PT (repeatable) loads only some |
--lang es |
Write the verification report in Spanish |
--check-revocation (CAdES, XAdES) |
Check the revocation of the signer and TSA chains |
--level basic|timestamps|lt|lta |
How much is validated: the signature and chain only, plus signature timestamps, plus revocation and long-term data, plus archive timestamps (default) |
--revocation-strategy ocsp-first|crl-first |
Which revocation source is asked first (default ocsp-first); the other one answers when the first gives nothing |
--etsi-report <file> |
Write the ETSI TS 119 102-2 signature validation report (XML, namespace http://uri.etsi.org/19102/v1.2.1#) |
--simple-report <file> |
Write the simple report (JSON): per signature the indication, sub-indication, signer, times, policy, errors and warnings |
--html-report <file> |
Write a readable report (one self-contained HTML page): per signature the result and its reason, integrity of the signed data, claimed and proved signing time, signer certificate with its public key (PEM) and SHA-256 fingerprint, timestamps, revocation data, policy, errors and warnings |
--report-language <code> |
Language of the HTML report: en (default), pt or es |
--revocation-for-untrusted |
Also check the revocation of chains that do not reach a trust anchor (off by default) |
Every signature gets a verification report: the indication (TOTAL-PASSED, TOTAL-FAILED, INDETERMINATE), the
ETSI EN 319 102-1 sub-indication, the signer's public key (algorithm, size, SHA-256 of the SubjectPublicKeyInfo and
the PEM) and each check of the validation. With --json the same data is in subIndication, publicKey and
constraints.
simplesign validate contrato.pdf --policy iofe --tsl tsl-pe.xml --tsl-signer indecopi-tsl.cer --tsl-profile iofe --lang esA policy file lists constraints grouped by what they apply to, each with a level; a constraint left out is not
evaluated, and one SimpleSign does not evaluate yet is reported as NOT_CHECKED. Start from the default
(simplesign policy export validation -o policy.json) and change what you need:
{
"ValidationConstraints": {
"Name": "INDECOPI",
"Description": "Key usage required; obsolete algorithms fail.",
"SignatureConstraints": {
"BasicSignatureConstraints": {
"SigningCertificate": {
"NotExpired": { "Level": "FAIL" },
"KeyUsage": { "Level": "FAIL", "Id": [ "digitalSignature", "nonRepudiation" ] }
}
}
},
"Cryptographic": {
"Level": "FAIL",
"AcceptableDigestAlgo": { "Algo": [ "SHA1", "SHA256", "SHA384", "SHA512" ] },
"MiniPublicKeySize": { "Algo": [ { "Value": "RSA", "Size": 2048 } ] },
"AlgoExpirationDate": {
"Format": "yyyy-MM-dd",
"Algo": [ { "Value": "SHA1", "Date": "2012-08-01" }, "SHA256", "SHA384", "SHA512", { "Value": "RSA", "Size": 2048 } ]
}
}
}
}--signing-policy <file> on the sign commands takes the signing policy (root SigningConstraints, see
PAdES signing); simplesign policy export signing writes the default one.
timestamp verify also tells whether the tsa name of the token matches the TSA certificate (RFC 3161 §2.4.2).