Skip to content

Instantly share code, notes, and snippets.

@jgoux
Created May 2, 2022 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jgoux/399aaf4c67c47bc6b05b41293f4c1534 to your computer and use it in GitHub Desktop.
Save jgoux/399aaf4c67c47bc6b05b41293f4c1534 to your computer and use it in GitHub Desktop.
import { PDFNet } from "@pdftron/pdfnet-node";
import fs from "fs/promises";
import path from "path";
import { env } from "~/app/config/env/pdftron";
import { randomUUID } from "~/app/config/uuid";
type SignPDFProps = {
file: Buffer;
signature: {
image: string;
coordinates: {
page_number: number;
x1: number;
x2: number;
y1: number;
y2: number;
};
meta?: {
Name?: string;
Location?: string;
Reason?: string;
};
};
};
async function signPDF(props: SignPDFProps) {
async function main() {
const doc = await PDFNet.PDFDoc.createFromBuffer(props.file);
await doc.initSecurityHandler();
const page = await doc.getPage(props.signature.coordinates.page_number);
// Create a new signature form field in the PDFDoc. The name argument is optional; leaving it empty causes it to be auto-generated. However, you may need the name for later. Acrobat doesn't show digsigfield in side panel if it's without a widget. Using a Rect with 0 width and 0 height, or setting the NoPrint/Invisible flags makes it invisible.
const signatureField = await doc.createDigitalSignatureField();
const signatureWidget =
await PDFNet.SignatureWidget.createWithDigitalSignatureField(
doc,
new PDFNet.Rect(
props.signature.coordinates.x1,
props.signature.coordinates.y1,
props.signature.coordinates.x2,
props.signature.coordinates.y2
),
signatureField
);
await page.annotPushBack(signatureWidget);
// Add an appearance to the signature field.
const signatureImage = await PDFNet.Image.createFromMemory2(
doc,
Buffer.from(props.signature.image.split(",")[1], "base64")
);
await signatureWidget.createSignatureAppearance(signatureImage);
const savePath = path.join("/tmp", randomUUID() + ".pdf");
try {
// Sign
await signatureField.signOnNextSave(
env.PDFTRON_SIGNATURE_CERTIFICATE_PATH,
env.PDFTRON_SIGNATURE_CERTIFICATE_PASSWORD
);
// Add meta informations to the signature field.
if (props.signature.meta?.Name) {
await signatureField.setContactInfo(props.signature.meta.Name);
}
if (props.signature.meta?.Location) {
await signatureField.setLocation(props.signature.meta.Location);
}
if (props.signature.meta?.Reason) {
await signatureField.setReason(props.signature.meta.Reason);
}
// We can't use doc.saveMemoryBuffer as it doesn't support e_incremental flag
await doc.save(savePath, PDFNet.SDFDoc.SaveOptions.e_incremental);
// Verification options
const opts = await PDFNet.VerificationOptions.create(
PDFNet.VerificationOptions.SecurityLevel.e_compatibility_and_archiving
);
await opts.addTrustedCertificateUString(
env.PDFTRON_SIGNATURE_ROOT_CERTIFICATE_PATH
);
await opts.enableOnlineCRLRevocationChecking(true);
// Timestamp
const tstConfig = await PDFNet.TimestampingConfiguration.createFromURL(
env.PDFTRON_SIGNATURE_TIMESTAMP_SERVER_URL
);
await tstConfig.setTimestampAuthorityServerUsername(
env.PDFTRON_SIGNATURE_TIMESTAMP_SERVER_USERNAME
);
await tstConfig.setTimestampAuthorityServerPassword(
env.PDFTRON_SIGNATURE_TIMESTAMP_SERVER_PASSWORD
);
await signatureField.timestampOnNextSave(tstConfig, opts);
await doc.save(savePath, PDFNet.SDFDoc.SaveOptions.e_incremental);
// LTV
const verificationResult = await signatureField.verify(opts);
if (
!(await signatureField.enableLTVOfflineVerification(verificationResult))
) {
throw new Error("Could not enable LTV");
}
await doc.save(savePath, PDFNet.SDFDoc.SaveOptions.e_incremental);
const signedPDF = await fs.readFile(savePath);
return signedPDF;
} finally {
void fs.unlink(savePath);
}
}
return PDFNet.runWithCleanup(main, env.PDFTRON_SDK_LICENSE_KEY);
}
export { signPDF };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment