Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ reports/bench-results.json

# Temporary files
tmp/

# opencode tooling lockfiles (machine-local)
.opencode/package-lock.json
.opencode/node_modules/
110 changes: 58 additions & 52 deletions bun.lock

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions content/docs/guides/signatures/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,79 @@ const fullySigned = await pdf2.sign({ signer: signer2 });
await writeFile("signed.pdf", fullySigned.bytes);
```

## Finalizing Multi-Signer Documents

In multi-signer workflows, each recipient signs at B-T level (fast, no
revocation lookups per signer). Once everyone has signed, finalize the
document in one pass.

### Add Validation Data (B-T → B-LT)

`addValidationData()` gathers certificates, OCSP responses, and CRLs for
every signed signature field and writes them as a single DSS incremental
update:

```ts
const { bytes, warnings, signatureCount } = await pdf.addValidationData();

console.log(`Embedded LTV for ${signatureCount} signatures`);
```

Validation data is fetched once per issuer (signers sharing a CA don't
trigger duplicate lookups) and merged with any existing DSS.

### Add a Document Timestamp

`addTimestamp()` appends a `/DocTimeStamp` signature whose ByteRange covers
the entire document, sealing all prior signatures with a TSA-attested time:

```ts
const tsa = new HttpTimestampAuthority("http://timestamp.digicert.com");

const { bytes } = await pdf.addTimestamp({
timestampAuthority: tsa,
longTermValidation: true, // embed LTV for the timestamp's own chain
});
```

Pass `fieldName` to fill a pre-allocated empty signature field — useful when
the form structure must be locked down before a certification signature:

```ts
await pdf.addTimestamp({
timestampAuthority: tsa,
fieldName: "ArchivalTimestamp", // reserved earlier via createSignatureField()
});
```

### Full B-LTA Finalization

`addArchivalData()` combines both steps: it gathers validation data for all
existing signatures, adds an archival document timestamp, and embeds the
timestamp's own validation data:

```ts
const tsa = new HttpTimestampAuthority("http://timestamp.digicert.com");

const { bytes, warnings, signatureCount } = await pdf.addArchivalData({
timestampAuthority: tsa,
});

await writeFile("sealed.pdf", bytes);
```

These methods refuse to run when the document cannot be saved incrementally
(for example, linearized documents or documents recovered from corruption)
and throw a `SignatureError` instead — a full rewrite would invalidate every
existing signature.

<Callout type="warn" title="Error recovery">
If `addTimestamp()`, `addValidationData()`, or `addArchivalData()` throws after partial progress
(for example, the timestamp authority is unreachable after a DSS update was already written), the
in-memory `PDF` instance may be out of sync with its bytes. Don't keep using it: discard the
instance and reload from the last known-good bytes with `PDF.load()`.
</Callout>

## Check Existing Signatures

```ts
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,26 @@
"@noble/hashes": "^2.2.0",
"@scure/base": "^2.2.0",
"asn1js": "^3.0.10",
"lru-cache": "^11.4.0",
"lru-cache": "^11.5.1",
"pako": "^2.1.0",
"pkijs": "^3.4.0"
},
"devDependencies": {
"@cantoo/pdf-lib": "^2.6.5",
"@google-cloud/kms": "^5.5.0",
"@google-cloud/secret-manager": "^6.1.2",
"@cantoo/pdf-lib": "^2.7.1",
"@google-cloud/kms": "^5.5.1",
"@google-cloud/secret-manager": "^6.1.3",
"@types/bun": "^1.3.14",
"@types/pako": "^2.0.4",
"@vitest/coverage-v8": "4.0.16",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"oxfmt": "^0.50.0",
"oxlint": "~1.39.0",
"oxlint-tsgolint": "^0.11.1",
"oxlint-tsgolint": "^0.11.5",
"pdf-lib": "^1.17.1",
"tsdown": "^0.22.0",
"tsdown": "^0.22.2",
"typescript": "^5.9.3",
"vitest": "^4.1.6"
"vitest": "^4.1.8"
},
"peerDependencies": {
"@google-cloud/kms": "^5.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/api/pdf-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,8 +373,8 @@ export class PDFPage {

if (rotate) {
const value = rotate.value % 360;
// Normalize to 0, 90, 180, 270

// Normalize to 0, 90, 180, 270
if (value === 90 || value === -270) {
return 90;
}
Expand Down
Loading
Loading