How to Secure and Sign PDF Documents in TypeScript

How to Secure and Sign PDF Documents in TypeScript

This guide shows how to encrypt, certify, sign, and verify PDF documents with Aspose.PDF FOSS for TypeScript. Document.Save() and Document.Open() handle public-key encryption to recipient certificates, while Document.Certify(), Document.Sign(), and Document.VerifySignatures() handle PAdES-style digital signatures. It requires Node.js 22 or later.

Step-by-Step Guide

Step 1: Install the Package

asposefoss/pdf is not yet published — build from source until it ships. See the project README for build instructions.

Verify the installation by importing the Document class in a new TypeScript file — this line should resolve without error once the package is installed:

import { Document } from '@asposefoss/pdf';

Step 2: Import Required Classes

Import Document to open, save, sign, and verify the file; the signing and encryption steps below pass plain option objects, so no further imports are needed:

import { Document } from '@asposefoss/pdf';

Step 3: Encrypt a Document to Recipient Certificates

Document.Save() accepts an encrypt option carrying one or more recipients, each identified by a certificate (PEM string or DER bytes), an algorithm, and shared permissions:

const doc = Document.OpenFile('report.pdf');

const bytes = doc.Save({
  encrypt: {
    recipients: [{ certificate: recipientCertPem }],
    algorithm: 'aes256', // 'aes256' (default) | 'aes128' | 'rc4'
    permissions: { copying: false }, // shared across all recipients
  },
});

Step 4: Open an Encrypted Document

Pass a recipient option to Document.Open() with either the matching private key and certificate, or a PKCS#12 bundle:

const opened = Document.Open(bytes, {
  recipient: { privateKey, certificate: recipientCertPem },
  // or: recipient: { pkcs12: p12Bytes, passphrase: '…' },
});

console.log(opened.Permissions); // recovered permission flags (not enforced)

Step 5: Certify and Sign a Document

Document.Certify() and Document.Sign() are both asynchronous and take an identity (certificate + privateKey) plus options describing the signature field, appearance, and reason. Certifying should happen first, over the whole file; further approval signatures are appended incrementally afterward:

async function certifyAndSign(sourcePath: string, signaturePageIndex: number): Promise<void> {
  const certifying = Document.OpenFile(sourcePath);
  await certifying.Certify(
    { certificate: authorCert, privateKey: authorKey },
    {
      permissions: 'form-fill',
      reason: 'Certifying the document',
      fieldName: 'Certification',
      appearance: { page: signaturePageIndex, rect: [400, 100, 550, 140] },
    },
  );
  const certifiedBytes = certifying.Save();

  const approving = Document.Open(certifiedBytes);
  await approving.Sign(
    { certificate: approverCert, privateKey: approverKey },
    { reason: 'Approved for publication', fieldName: 'Approval', subFilter: 'PAdES' },
  );
  approving.WriteTo('signed.pdf');
}

Step 6: Verify Signatures

Document.VerifySignatures() is asynchronous and returns one SignatureReport per signature field, each reporting cryptographic integrity, signature validity, and whether it coversWholeFile:

async function verify(path: string): Promise<void> {
  const doc = Document.OpenFile(path);
  const reports = await doc.VerifySignatures();
  for (const r of reports) {
    console.log(`${r.name}: integrity=${r.integrity} signature=${r.signature} `
      + `coversWholeFile=${r.coversWholeFile} docMDP=${r.docMDP}`);
  }
}

Common Issues and Fixes

Document.Sign() / Document.Certify() throw or hang. Both are asynchronous — await the call. Forgetting await leaves the returned promise unresolved and the write happens before signing completes.

A later approval signature invalidates the certification. Sign incrementally: save the certified bytes first (certifying.Save()), reopen them with Document.Open(), then call Sign() on that reopened document — writing a full rewrite instead of an incremental append breaks the certification’s /ByteRange digest.

VerifySignatures() reports coversWholeFile: false for the first signature but true for the last. This is expected for a certify-then- sign chain: the certification was signed before the approval was appended, so only the most recent signature’s /ByteRange extends to the end of the file.

Decryption fails with the right private key. Confirm the certificate passed to recipient matches the exact certificate the document was encrypted to in Document.Save()’s recipients list — a certificate reissued with a new key pair will not decrypt data encrypted under the old one.

Frequently Asked Questions

Can a document be encrypted to more than one recipient?

Yes — recipients in Document.Save()’s encrypt option accepts an array; any recipient’s matching private key can open the resulting file.

What signature format does Document.Sign() produce?

Passing subFilter: 'PAdES' in the sign options produces a PAdES-compatible signature; omitting it uses the library’s default signature subfilter.

How do I check whether a document is certified versus just signed?

Inspect the docMDP field on the SignatureReport returned by Document.VerifySignatures() — a certification signature reports a DocMDP permission verdict; an approval-only signature does not.

Are recovered Permissions after Document.Open() enforced by the library?

No — opened.Permissions reports the permission flags recorded in the encrypted file for inspection; enforcing them in an application is the caller’s responsibility.

See Also