atick
v1.0.6
Published
Standalone PDF digital-signature library for Node.js — PAdES/CMS signing (PFX/PEM, deferred eSign/HSM/token), RFC-3161 timestamps, long-term validation and a green-tick appearance Adobe shows as valid. Prebuilt native addon, cross-platform, no build step.
Maintainers
Readme
ATick for Node.js
Standalone PDF digital-signature library for Node.js — PAdES / CMS signing with no external services.
Also available in other languages — the same ATick engine, the same API, native to each ecosystem:
| Language | Install | Source · Docs |
|---|---|---|
| Python | pip install atick | ATick-Python · docs |
| Java | io.github.aniketc068:atick (Maven) | ATick-Java · docs |
| .NET | dotnet add package ATick | ATick-DotNet · docs |
| PHP | composer require aniketc068/atick | ATick-PHP · docs |
ATick signs PDFs the way Adobe Acrobat and the EU DSS do — PAdES baseline signatures with
timestamps and long-term validation. It ships a prebuilt native addon (N-API), so there is no
build step, no external service and nothing to compile — npm install atick and you are done.
const atick = require("atick");
const fs = require("fs");
const pdf = fs.readFileSync("doc.pdf");
const pfx = fs.readFileSync("my.pfx");
const signed = atick.signPfx(pdf, pfx, JSON.stringify({
password: "••••", cn: "Aniket Chaturvedi", reason: "Approved",
green_tick: true, page: 1, rect: [300, 55, 575, 175],
pades: true, timestamp: true, ltv: true, // PAdES-B-LT
}));
fs.writeFileSync("signed.pdf", signed);Runs server-side (Node runtime) — Next.js API routes / server actions, Express, NestJS, serverless functions, CLI tools. It is a native module, so it does not run in the browser.
The green tick your readers trust
ATick draws a verified-signature appearance with a green tick. When the certificate is valid and trusted, Adobe Reader / Acrobat shows “Signed and all signatures are valid.”
Install
npm install atickThe prebuilt native addon for your platform comes with the package — no node-gyp, no compiler,
no postinstall build.
Quick start (TypeScript)
import * as atick from "atick";
import { readFileSync, writeFileSync } from "fs";
const signed = atick.signPfx(
readFileSync("doc.pdf"),
readFileSync("my.pfx"),
JSON.stringify({ password: "••••", cn: "Aniket", pades: true, page: 1, rect: [300, 55, 575, 175] }),
);
writeFileSync("signed.pdf", signed);Full TypeScript types ship in the package (index.d.ts).
Features (A → Z)
| Feature | How |
|---|---|
| Sign with a .pfx / .p12 / .pem | atick.signPfx(pdf, pfx, options) — PKCS#12 or PEM (key + certs), auto-detected |
| PAdES levels B-B / B-T / B-LT / B-LTA | "pades":true + "timestamp":true + "ltv":true + "lta":true |
| Hash algorithm | "hash_algo":"sha256" \| "sha384" \| "sha512" |
| Timestamp authority | built in — or your own with "tsa_url":"…" (and "tsa_auth":["user","pass"]) |
| Long-term validation (LTV) | "ltv":true embeds the chain + revocation (CRL/OCSP) |
| Multi-page / custom coordinates | "placements":[[page,[x1,y1,x2,y2]], …] |
| Signature layout | "mode":"single" (one signature on many pages) · "mode":"shared" (many fields, same value) |
| Multi-signatory | sign an already-signed PDF again — each signature is its own revision, all stay valid |
| Certification (DocMDP) | "certify":1 (no changes) · 2 (form filling) · 3 (form filling + annotations) |
| Field locking (FieldMDP) | "lock_fields":["*"] or ["FieldA", …] |
| Pre-sign checks | "verify_expiry":true, "verify_crl":true, "verify_ocsp":true (or "verify":true) |
| Document metadata | atick.setMetadata(pdf, options) |
| Password protection | "encrypt_password" (+ "owner_password") for output; "open_password" for input; atick.decrypt(pdf, pw) |
| Appearance | options cn, org, ou, location, reason, text, date, dn, body, heading, image — auto-fit text, transparent logo |
| The mark | the ? (Adobe greens it), an always-green tick, or nothing — see The mark |
| CN on the left (Adobe-style) | "image":"cn" |
| Distinguished name | "dn":"CN=…, O=…, C=IN" |
| Custom-text-only appearance | "body":"*APPROVED*\nby *Aniket*" — \n = line, *x* = bold |
| Invisible signature | "placements":[] |
| Sign an already-signed PDF | sign again (incremental) — existing signatures stay valid; use a fresh "field_name" |
| Container only | atick.prepareFields(pdf, options) |
| Document timestamp | "lta":true while signing; atick.addDocTimestamp(pdf, options) afterwards (PAdES-B-LTA) |
| Fast signing | revocation cache (ON by default) — atick.setFastSigning(false) to disable |
| Deferred / eSign (2-step) | atick.prepare(pdf, options) → external CMS → atick.embed(prepared, cms) |
| Detached CMS | atick.cmsPfx(data, pfx, options) |
The API
atick.signPfx(pdf, pfx, optionsJson) // sign with a .pfx / .p12 / .pem (auto-detected)
atick.prepare(pdf, optionsJson) // deferred / eSign: returns { prepared, bytesToSign }
atick.cmsPfx(data, pfx, optionsJson) // detached CMS over data
atick.embed(prepared, cms) // embed a detached CMS into a prepared PDF
atick.prepareFields(pdf, optionsJson) // make an empty signature field (template)
atick.signField(pdf, pfx, optionsJson) // sign an existing empty field
atick.setMetadata(pdf, optionsJson) // Title / Author / Subject / Keywords / …
atick.addDocTimestamp(pdf, optionsJson) // archive DocTimeStamp (PAdES-B-LTA)
atick.setFastSigning(true | false) // revocation-cache toggle
atick.decrypt(pdf, password) // decrypt a password-protected PDF
atick.version() // engine versionAll buffers are Node Buffers; all options are a JSON string. Any failure throws an Error
whose .message is the reason.
Options (JSON)
cn, org, ou, location, reason, text, date, dn, body, heading, show_mark, green_tick, always_check,
mark_color (hex / name / [r,g,b]), mark_gradient, mark_scale, text_color, bg_color, border, font_size,
width, height, page, rect, placements ([[page,[x1,y1,x2,y2]], …]), mode (single/shared), field_name,
pades, hash_algo (sha256/384/512), timestamp, tsa_url, tsa_auth, ltv, lta, certify, lock_fields,
verify, verify_expiry, verify_crl, verify_ocsp, open_password, encrypt_password, owner_password,
contents_size.
The mark
"{… ,\"green_tick\":true}" // the "?" mark — Adobe paints it GREEN for valid+trusted, RED if invalid
"{… ,\"always_check\":true}" // the green-tick graphic as the base
"{… ,\"green_tick\":false}" // no mark — a plain signatureColour it: "mark_color":"#E53935", "blue", [255,140,0] — or a gradient "mark_gradient":["red","orange","yellow"].
Deferred signing & Indian eSign (two-step)
When the private key lives elsewhere (a token / HSM / smart-card, or an eSign ESP):
const { prepared, bytesToSign } = atick.prepare(pdf, JSON.stringify({
cn: "DS TEST", reason: "eSign", placements: [[1, [300, 55, 575, 175]]], contents_size: 16384,
}));
// the eSign InputHash is the SHA-256 of bytesToSign:
const crypto = require("crypto");
const inputHash = crypto.createHash("sha256").update(bytesToSign).digest("hex");
// ... sign with your provider / eSign ESP, get back a detached CMS ...
const signed = atick.embed(prepared, cms);PAdES levels
atick.signPfx(pdf, pfx, "{… ,\"pades\":true}") // B-B
atick.signPfx(pdf, pfx, "{… ,\"pades\":true,\"timestamp\":true}") // B-T
atick.signPfx(pdf, pfx, "{… ,\"pades\":true,\"timestamp\":true,\"ltv\":true}") // B-LT
atick.signPfx(pdf, pfx, "{… ,\"pades\":true,\"timestamp\":true,\"lta\":true}") // B-LTACompatibility — one package everywhere
Node 10.16 → the latest — the prebuilt addon uses N-API (ABI-stable), so one binary works across every Node version, no rebuild.
Every OS/arch — a prebuilt addon ships for each platform:
| platform-arch | Covers | |---|---| |
win32-x64/win32-ia32| Windows 7 → 11, 64 / 32-bit | |win32-arm64| Windows on ARM64 | |linux-x64/linux-arm64/linux-arm/linux-ia32| Linux x64 / ARM64 / ARM / 32-bit (glibc 2.17+, every distro) | |darwin-x64/darwin-arm64| macOS Intel / Apple Silicon |
Errors
try {
atick.signPfx(pdf, pfx, JSON.stringify({ password: "wrong" }));
} catch (e) {
console.error("signing failed:", e.message);
}License
ATick is dual-licensed — free for personal & open use, paid if you sell:
- Free under GNU AGPL-3.0 — personal projects, learning, internal use, and open-source projects (released publicly under AGPL-3.0).
- Commercial license (paid) — if you build a product with ATick and sell it, or use it in a closed-source / commercial product, you must buy a commercial license first. Contact [email protected] for a quote.
See LICENSING.md for details. © 2026 Aniket Chaturvedi.
