Skip to content

Latest commit

 

History

History
433 lines (342 loc) · 24.7 KB

cli.md

File metadata and controls

433 lines (342 loc) · 24.7 KB

CLI

Install with dotnet tool install -g SimpleSign.Cli.

PDF Signatures

# 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 stamp

--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 Signatures

# 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.pem

cades 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 Signatures

# 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.pem

xades 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.

Two-Phase Signing

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.

Validation Output

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

Trust Profiles

A trust profile sets, under one name, the policy and the trust of a trust framework. Each comes from its package (IofeProfile, EidasProfile, IcpBrasilProfile, GovBrProfile; the library takes them with WithProfile and ValidationOptions.Profile); simplesign profiles lists them:

Profile Signing policy Validation policy Trust
iofe iofe iofe INDECOPI's trusted list, checked against the signer bundled with SimpleSign.Peru.Iofe
eidas eidas eidas The EU LOTL and its national lists, checked against the signers bundled with SimpleSign.Europe.Eidas (pivot LOTLs followed); validate shows each signature's qualification (QESig, AdESig-QC…) — see eidas.md
icp-brasil icp-brasil icp-brasil The AC Raiz roots and the ICP-Brasil chain validation
gov-br — (Gov.br signs on its own service) govbr The Gov.br roots and their chain validation
sandbox default default A running SimpleSign Sandbox's LOTL (--sandbox URL)
simplesign sign contrato.pdf --output assinado.pdf --cert cert.pfx --password "$PFX_PASSWORD" --profile iofe --tsa <ACT URL>
simplesign validate assinado.pdf --profile iofe
simplesign validate documento.pdf --profile icp-brasil --trust-profile gov-br
  • An explicit option wins: --signing-policy / --policy over the profile's policy, --tsl / --lotl over its trust.
  • --trust-profile NAME (repeatable) adds another profile's trust; the policy stays --profile's.
  • --sandbox URL reads a running Sandbox once: its origins, LDAP and HTTPS chain are allowed (as --allow-origin, --allow-ldap, --tls-trust would), --profile sandbox trusts its LOTL, and --profile iofe reads the Sandbox's list the IOFE way instead of INDECOPI's (to test INDECOPI's rules with the Sandbox's certificates).

Signing Policy

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
eidas strict, the signer and every TSA chained to a trust anchor (the EU lists), TSA valid and not revoked, a warning on a non-qualified certificate (QcCompliance)

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, icp-brasil or govbr.

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.

JSON result

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.

Network

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.pem

Validation Policy, Trust Store and Verification Report

validate, 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; without --tsl-signer, INDECOPI's bundled signer is used
--profile NAME, --trust-profile NAME, --sandbox URL A trust profile (see Trust Profiles)
--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 es

A 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).