@e-sig/core 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CONSUMING.md +97 -0
  2. package/LICENSE +21 -0
  3. package/README.md +298 -0
  4. package/dist/adapters.d.ts +79 -0
  5. package/dist/adapters.d.ts.map +1 -0
  6. package/dist/adapters.js +10 -0
  7. package/dist/adapters.js.map +1 -0
  8. package/dist/cert-issuer.d.ts +47 -0
  9. package/dist/cert-issuer.d.ts.map +1 -0
  10. package/dist/cert-issuer.js +132 -0
  11. package/dist/cert-issuer.js.map +1 -0
  12. package/dist/cert-lifecycle.d.ts +16 -0
  13. package/dist/cert-lifecycle.d.ts.map +1 -0
  14. package/dist/cert-lifecycle.js +30 -0
  15. package/dist/cert-lifecycle.js.map +1 -0
  16. package/dist/index.d.ts +11 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +21 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/pem-signer.d.ts +42 -0
  21. package/dist/pem-signer.d.ts.map +1 -0
  22. package/dist/pem-signer.js +276 -0
  23. package/dist/pem-signer.js.map +1 -0
  24. package/dist/render-pdf.d.ts +33 -0
  25. package/dist/render-pdf.d.ts.map +1 -0
  26. package/dist/render-pdf.js +86 -0
  27. package/dist/render-pdf.js.map +1 -0
  28. package/dist/sign-document.d.ts +54 -0
  29. package/dist/sign-document.d.ts.map +1 -0
  30. package/dist/sign-document.js +93 -0
  31. package/dist/sign-document.js.map +1 -0
  32. package/dist/sign-pdf.d.ts +46 -0
  33. package/dist/sign-pdf.d.ts.map +1 -0
  34. package/dist/sign-pdf.js +60 -0
  35. package/dist/sign-pdf.js.map +1 -0
  36. package/dist/signature-block.d.ts +23 -0
  37. package/dist/signature-block.d.ts.map +1 -0
  38. package/dist/signature-block.js +63 -0
  39. package/dist/signature-block.js.map +1 -0
  40. package/dist/timestamp.d.ts +59 -0
  41. package/dist/timestamp.d.ts.map +1 -0
  42. package/dist/timestamp.js +278 -0
  43. package/dist/timestamp.js.map +1 -0
  44. package/dist/types.d.ts +44 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +10 -0
  47. package/dist/types.js.map +1 -0
  48. package/dist/verify-pdf.d.ts +33 -0
  49. package/dist/verify-pdf.d.ts.map +1 -0
  50. package/dist/verify-pdf.js +338 -0
  51. package/dist/verify-pdf.js.map +1 -0
  52. package/package.json +92 -0
@@ -0,0 +1,86 @@
1
+ // src/lib/integrations/esig/core/render-pdf.ts
2
+ //
3
+ // Portable HTML → PDF renderer. puppeteer-core under the hood; auto-detects
4
+ // Lambda environment to use @sparticuz/chromium, falls back to system Chrome
5
+ // for local dev.
6
+ import puppeteer from "puppeteer-core";
7
+ async function resolveExecutablePath(opts) {
8
+ if (opts.executablePath)
9
+ return opts.executablePath;
10
+ if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.VERCEL_ENV) {
11
+ const chromium = (await import("@sparticuz/chromium")).default;
12
+ return await chromium.executablePath();
13
+ }
14
+ if (process.platform === "darwin") {
15
+ return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
16
+ }
17
+ for (const p of [
18
+ "/usr/bin/google-chrome",
19
+ "/usr/bin/chromium",
20
+ "/usr/bin/chromium-browser",
21
+ "/snap/bin/chromium",
22
+ ]) {
23
+ try {
24
+ const fs = await import("node:fs");
25
+ await fs.promises.access(p, fs.constants.X_OK);
26
+ return p;
27
+ }
28
+ catch {
29
+ // try next
30
+ }
31
+ }
32
+ throw new Error("renderHtmlToPdf: no Chrome/Chromium executable found. Install Chrome or pass options.executablePath.");
33
+ }
34
+ async function resolveLaunchArgs(opts) {
35
+ if (opts.launchArgs)
36
+ return opts.launchArgs;
37
+ if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.VERCEL_ENV) {
38
+ const chromium = (await import("@sparticuz/chromium")).default;
39
+ return chromium.args;
40
+ }
41
+ return ["--no-sandbox", "--disable-setuid-sandbox"];
42
+ }
43
+ export async function renderHtmlToPdf(opts) {
44
+ const executablePath = await resolveExecutablePath(opts);
45
+ const args = await resolveLaunchArgs(opts);
46
+ const jsEnabled = opts.javascriptEnabled ?? false;
47
+ const timeout = opts.timeoutMs ?? 30_000;
48
+ let browser;
49
+ try {
50
+ browser = await puppeteer.launch({ args, executablePath, headless: true });
51
+ const page = await browser.newPage();
52
+ if (!jsEnabled)
53
+ await page.setJavaScriptEnabled(false);
54
+ // "load" (not domcontentloaded) so embedded images and logos finish loading
55
+ // before the snapshot — otherwise page.pdf() can capture a blank signature
56
+ // image. The load event waits for all referenced subresources.
57
+ await page.setContent(opts.html, { waitUntil: "load", timeout });
58
+ if (jsEnabled) {
59
+ // Belt-and-suspenders: wait for web fonts to settle (only meaningful when
60
+ // scripting is enabled; reached via globalThis to avoid a DOM-lib dep).
61
+ await page
62
+ .evaluate(() => {
63
+ const d = globalThis.document;
64
+ return d?.fonts?.ready;
65
+ })
66
+ .catch(() => undefined);
67
+ }
68
+ const pdfBuf = await page.pdf({
69
+ format: opts.format ?? "Letter",
70
+ margin: opts.margin ?? {
71
+ top: "0.5in",
72
+ bottom: "0.5in",
73
+ left: "0.5in",
74
+ right: "0.5in",
75
+ },
76
+ printBackground: opts.printBackground ?? true,
77
+ });
78
+ return Buffer.from(pdfBuf);
79
+ }
80
+ finally {
81
+ if (browser) {
82
+ await browser.close().catch(() => undefined);
83
+ }
84
+ }
85
+ }
86
+ //# sourceMappingURL=render-pdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-pdf.js","sourceRoot":"","sources":["../src/render-pdf.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,iBAAiB;AAEjB,OAAO,SAA6C,MAAM,gBAAgB,CAAC;AA4B3E,KAAK,UAAU,qBAAqB,CAAC,IAA4B;IAC/D,IAAI,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC,cAAc,CAAC;IACpD,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;QACnE,MAAM,QAAQ,GAAG,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,OAAO,CAAC;QAC/D,OAAO,MAAM,QAAQ,CAAC,cAAc,EAAE,CAAC;IACzC,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,8DAA8D,CAAC;IACxE,CAAC;IACD,KAAK,MAAM,CAAC,IAAI;QACd,wBAAwB;QACxB,mBAAmB;QACnB,2BAA2B;QAC3B,oBAAoB;KACrB,EAAE,CAAC;QACF,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YACnC,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC/C,OAAO,CAAC,CAAC;QACX,CAAC;QAAC,MAAM,CAAC;YACP,WAAW;QACb,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CACb,sGAAsG,CACvG,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,IAA4B;IAC3D,IAAI,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC;IAC5C,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;QACnE,MAAM,QAAQ,GAAG,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,OAAO,CAAC;QAC/D,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IACD,OAAO,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAA4B;IAChE,MAAM,cAAc,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,IAAI,KAAK,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC;IACzC,IAAI,OAA4B,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3E,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QACvD,4EAA4E;QAC5E,2EAA2E;QAC3E,+DAA+D;QAC/D,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;QACjE,IAAI,SAAS,EAAE,CAAC;YACd,0EAA0E;YAC1E,wEAAwE;YACxE,MAAM,IAAI;iBACP,QAAQ,CAAC,GAAG,EAAE;gBACb,MAAM,CAAC,GAAI,UAAsE,CAAC,QAAQ,CAAC;gBAC3F,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC;YACzB,CAAC,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC;YAC5B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,QAAQ;YAC/B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI;gBACrB,GAAG,EAAE,OAAO;gBACZ,MAAM,EAAE,OAAO;gBACf,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,OAAO;aACf;YACD,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,IAAI;SAC9C,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;YAAS,CAAC;QACT,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { CertStore, AuditLogStore, PdfStorageStore, EsigAuditAction } from "./adapters.js";
2
+ import type { TsaTransport } from "./types.js";
3
+ export interface SignDocumentInput {
4
+ /** Fully-rendered, signature-embedded HTML (the consumer composes this). */
5
+ html: string;
6
+ /** Optional signature image to persist alongside the PDF (e.g. the drawn PNG). */
7
+ signatureImage?: {
8
+ bytes: Uint8Array;
9
+ contentType: string;
10
+ };
11
+ /** Tenant whose signing cert is used + audit/storage partition key. */
12
+ tenantId: string;
13
+ /** Cert subject CN — ASCII-clean tenant/org display name. */
14
+ subjectName: string;
15
+ /** Key-at-rest passphrase for the CertStore. */
16
+ passphrase: string;
17
+ signer: {
18
+ name: string;
19
+ email?: string;
20
+ };
21
+ actorUserId?: string | null;
22
+ certStore: CertStore;
23
+ auditStore: AuditLogStore;
24
+ storage: PdfStorageStore;
25
+ /** Storage path prefix, e.g. `${tenantId}/${documentId}`. A timestamped filename is appended. */
26
+ pathPrefix: string;
27
+ /** signPdf signature metadata. */
28
+ reason?: string;
29
+ location?: string;
30
+ signingTime?: Date;
31
+ /** Optional RFC-3161 timestamp transport. Provide one to upgrade to CAdES-T. */
32
+ tsa?: TsaTransport;
33
+ /** Audit row fields. */
34
+ action?: EsigAuditAction | string;
35
+ targetTable?: string;
36
+ targetId?: string;
37
+ ip?: string;
38
+ userAgent?: string;
39
+ sessionId?: string;
40
+ /** Extra audit metadata merged into the row (consent text, signer email, etc.). */
41
+ metadata?: Record<string, unknown>;
42
+ }
43
+ export interface SignDocumentResult {
44
+ signedPdfUrl: string;
45
+ signatureImageUrl?: string;
46
+ auditLogId: string;
47
+ certId: string;
48
+ certFingerprint: string;
49
+ /** True when an RFC-3161 token was embedded (CAdES-T); false = CAdES-B. */
50
+ timestamped: boolean;
51
+ tsaError?: string;
52
+ }
53
+ export declare function signDocument(input: SignDocumentInput): Promise<SignDocumentResult>;
54
+ //# sourceMappingURL=sign-document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-document.d.ts","sourceRoot":"","sources":["../src/sign-document.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EACb,eAAe,EACf,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,cAAc,CAAC,EAAE;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAE5D,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IAEnB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,aAAa,CAAC;IAC1B,OAAO,EAAE,eAAe,CAAC;IAEzB,iGAAiG;IACjG,UAAU,EAAE,MAAM,CAAC;IAEnB,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,gFAAgF;IAChF,GAAG,CAAC,EAAE,YAAY,CAAC;IAEnB,wBAAwB;IACxB,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,kBAAkB,CAAC,CAmF7B"}
@@ -0,0 +1,93 @@
1
+ // sign-document.ts
2
+ //
3
+ // Generic server-side signing orchestrator — the reusable spine of a
4
+ // "self-sign" endpoint, stack-agnostic over the three pluggable stores.
5
+ //
6
+ // renderHtmlToPdf → ensureActiveCert → signPdf (+ optional RFC-3161 TSA)
7
+ // → PdfStorageStore.upload → AuditLogStore.insert
8
+ //
9
+ // Document-specific glue (loading a row, building the HTML/template, RBAC, and
10
+ // updating your domain table afterward) stays in the CONSUMER. You pass in the
11
+ // fully-rendered `html`, the signer, the stores, and storage layout; you get
12
+ // back the stored URL + audit id + cert fingerprint to persist on your row.
13
+ import { renderHtmlToPdf } from "./render-pdf.js";
14
+ import { signPdf } from "./sign-pdf.js";
15
+ import { ensureActiveCert } from "./cert-lifecycle.js";
16
+ export async function signDocument(input) {
17
+ const signedAt = input.signingTime ?? new Date();
18
+ // 1. HTML → unsigned PDF.
19
+ const unsignedPdf = await renderHtmlToPdf({ html: input.html });
20
+ // 2. Ensure an active signing cert for the tenant (create/rotate as needed).
21
+ const cert = await ensureActiveCert({
22
+ store: input.certStore,
23
+ tenantId: input.tenantId,
24
+ subjectName: input.subjectName,
25
+ passphrase: input.passphrase,
26
+ });
27
+ // 3. PKCS#7 detached sign (+ optional TSA → CAdES-T).
28
+ const r = await signPdf({
29
+ pdf: unsignedPdf,
30
+ keyPem: cert.keyPem,
31
+ certPem: cert.certPem,
32
+ reason: input.reason ?? "Document acceptance",
33
+ location: input.location ?? "",
34
+ contactInfo: input.signer.email ?? "",
35
+ name: input.signer.name,
36
+ signingTime: signedAt,
37
+ tsa: input.tsa,
38
+ });
39
+ // 4. Persist the signed PDF (+ optional signature image).
40
+ const tsKey = signedAt.toISOString().replace(/[:.]/g, "-");
41
+ const pdfPath = `${input.pathPrefix}/${tsKey}.pdf`;
42
+ const pdfUp = await input.storage.upload({
43
+ path: pdfPath,
44
+ bytes: r.signedPdf,
45
+ contentType: "application/pdf",
46
+ });
47
+ let signatureImageUrl;
48
+ if (input.signatureImage) {
49
+ const sigPath = `${input.pathPrefix}/${tsKey}-signature.png`;
50
+ const sigUp = await input.storage.upload({
51
+ path: sigPath,
52
+ bytes: input.signatureImage.bytes,
53
+ contentType: input.signatureImage.contentType,
54
+ });
55
+ signatureImageUrl = sigUp.url;
56
+ }
57
+ // 5. Append the attribution audit row (ESIGN R3 / UETA §13).
58
+ const audit = await input.auditStore.insert({
59
+ tenantId: input.tenantId,
60
+ action: input.action ?? "pdf.signed",
61
+ actorUserId: input.actorUserId ?? null,
62
+ targetTable: input.targetTable,
63
+ targetId: input.targetId,
64
+ certId: cert.cert.id,
65
+ certFingerprint: cert.cert.certFingerprint,
66
+ ip: input.ip,
67
+ userAgent: input.userAgent,
68
+ sessionId: input.sessionId,
69
+ signedPdfUrl: pdfUp.url,
70
+ metadata: {
71
+ ...(input.metadata ?? {}),
72
+ ...(signatureImageUrl ? { signature_image_url: signatureImageUrl } : {}),
73
+ signer_name: input.signer.name,
74
+ ...(input.signer.email ? { signer_email: input.signer.email } : {}),
75
+ timestamp: {
76
+ attempted: !!input.tsa,
77
+ present: r.timestamped,
78
+ degraded: !!input.tsa && !r.timestamped,
79
+ error: r.tsaError ?? null,
80
+ },
81
+ },
82
+ });
83
+ return {
84
+ signedPdfUrl: pdfUp.url,
85
+ signatureImageUrl,
86
+ auditLogId: audit.id,
87
+ certId: cert.cert.id,
88
+ certFingerprint: cert.cert.certFingerprint,
89
+ timestamped: r.timestamped,
90
+ tsaError: r.tsaError,
91
+ };
92
+ }
93
+ //# sourceMappingURL=sign-document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-document.js","sourceRoot":"","sources":["../src/sign-document.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { TsaTransport } from "./types.js";
2
+ export interface SignPdfInput {
3
+ pdf: Buffer;
4
+ keyPem: string;
5
+ certPem: string;
6
+ reason: string;
7
+ location: string;
8
+ contactInfo: string;
9
+ name: string;
10
+ signingTime?: Date;
11
+ /**
12
+ * Signature length budget in bytes. 8192 fits RSA-2048 + single cert
13
+ * comfortably (actual PKCS#7 is typically 1.5–2 KB). When `tsa` is provided
14
+ * and this is omitted, the default rises to 30720 to fit the TimeStampToken.
15
+ */
16
+ signatureLength?: number;
17
+ /**
18
+ * Subfilter. Default ETSI.CAdES.detached (PAdES B-B baseline). Override
19
+ * to adbe.pkcs7.detached for legacy Adobe Reader 9+ compatibility.
20
+ */
21
+ subFilter?: string;
22
+ /**
23
+ * Optional RFC 3161 timestamp transport. When provided, the signature is
24
+ * upgraded to CAdES-T and the default `signatureLength` rises to 30720. The
25
+ * caller injects the network POST; the TSA only ever receives a SHA-256 hash,
26
+ * never PHI.
27
+ */
28
+ tsa?: TsaTransport;
29
+ /**
30
+ * Strict PAdES baseline mode: drop the PAdES-forbidden `signing-time` signed
31
+ * attribute (claimed time then comes from the TSA / verifier context). The ESS
32
+ * `signing-certificate-v2` attribute is always added regardless. Default false
33
+ * (keeps `signing-time` for backward compatibility).
34
+ */
35
+ padesStrict?: boolean;
36
+ }
37
+ export interface SignPdfResult {
38
+ signedPdf: Buffer;
39
+ placeholderBudget: number;
40
+ /** True if an RFC 3161 TimeStampToken was embedded (CAdES-T). */
41
+ timestamped: boolean;
42
+ /** Present when a non-required TSA request failed (signature is CAdES-B). */
43
+ tsaError?: string;
44
+ }
45
+ export declare function signPdf(input: SignPdfInput): Promise<SignPdfResult>;
46
+ //# sourceMappingURL=sign-pdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-pdf.d.ts","sourceRoot":"","sources":["../src/sign-pdf.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAW/C,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,WAAW,EAAE,OAAO,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAID,wBAAsB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAyCzE"}
@@ -0,0 +1,60 @@
1
+ // src/lib/integrations/esig/core/sign-pdf.ts
2
+ //
3
+ // Portable PDF signing — placeholder injection + PKCS#7 detached sign.
4
+ // Uses ETSI.CAdES.detached subfilter (modern PAdES, not legacy Adobe).
5
+ //
6
+ // RFC 3161 (CAdES-T): pass `tsa` to embed an RFC 3161 TimeStampToken. When a
7
+ // TSA is supplied the default /Contents budget is raised to fit the TST + TSA
8
+ // cert chain, and an overflow guard rejects a result whose PKCS#7 exceeded the
9
+ // placeholder (which @signpdf would otherwise silently truncate).
10
+ import { SignPdf } from "@signpdf/signpdf";
11
+ import { plainAddPlaceholder } from "@signpdf/placeholder-plain";
12
+ import { PemSigner } from "./pem-signer.js";
13
+ import { verifyPdfStructure } from "./verify-pdf.js";
14
+ /** Default /Contents budget (bytes) when no timestamp is embedded. */
15
+ const DEFAULT_SIGNATURE_LENGTH = 8192;
16
+ /**
17
+ * Default /Contents budget (bytes) when an RFC 3161 timestamp is embedded.
18
+ * A TST plus the TSA cert chain adds several KB; /Contents is hex so 30720
19
+ * bytes ≈ 15 KB of DER headroom.
20
+ */
21
+ const TIMESTAMPED_SIGNATURE_LENGTH = 30720;
22
+ const signpdfInstance = new SignPdf();
23
+ export async function signPdf(input) {
24
+ const budget = input.signatureLength ??
25
+ (input.tsa ? TIMESTAMPED_SIGNATURE_LENGTH : DEFAULT_SIGNATURE_LENGTH);
26
+ const withPlaceholder = plainAddPlaceholder({
27
+ pdfBuffer: input.pdf,
28
+ reason: input.reason,
29
+ contactInfo: input.contactInfo,
30
+ name: input.name,
31
+ location: input.location,
32
+ signatureLength: budget,
33
+ subFilter: input.subFilter ?? "ETSI.CAdES.detached",
34
+ });
35
+ const signer = new PemSigner({
36
+ keyPem: input.keyPem,
37
+ certPem: input.certPem,
38
+ tsa: input.tsa,
39
+ padesStrict: input.padesStrict,
40
+ });
41
+ const signed = await signpdfInstance.sign(withPlaceholder, signer, input.signingTime);
42
+ // Overflow guard: @signpdf silently truncates if the PKCS#7 exceeds the
43
+ // /Contents placeholder. Re-parse and fail loudly if it did not fit (this
44
+ // matters most on the timestamped path, where the TST is large). We compare
45
+ // the actual PKCS#7 size against the structurally-measured /Contents budget.
46
+ const structure = verifyPdfStructure(signed);
47
+ const actual = structure.pkcs7ActualSize ?? 0;
48
+ const measuredBudget = structure.pkcs7BudgetSize ?? budget;
49
+ if (actual >= measuredBudget) {
50
+ throw new Error(`PKCS#7 (${actual} bytes) exceeded placeholder budget (${measuredBudget}) — increase signatureLength`);
51
+ }
52
+ // placeholderBudget reports the requested budget (backward compatible).
53
+ return {
54
+ signedPdf: signed,
55
+ placeholderBudget: budget,
56
+ timestamped: signer.lastTimestamped,
57
+ tsaError: signer.lastTsaError,
58
+ };
59
+ }
60
+ //# sourceMappingURL=sign-pdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-pdf.js","sourceRoot":"","sources":["../src/sign-pdf.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,EAAE;AACF,uEAAuE;AACvE,uEAAuE;AACvE,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,kEAAkE;AAElE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGrD,sEAAsE;AACtE,MAAM,wBAAwB,GAAG,IAAI,CAAC;AACtC;;;;GAIG;AACH,MAAM,4BAA4B,GAAG,KAAK,CAAC;AA+C3C,MAAM,eAAe,GAAG,IAAI,OAAO,EAAE,CAAC;AAEtC,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAAmB;IAC/C,MAAM,MAAM,GACV,KAAK,CAAC,eAAe;QACrB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC;IACxE,MAAM,eAAe,GAAG,mBAAmB,CAAC;QAC1C,SAAS,EAAE,KAAK,CAAC,GAAG;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,eAAe,EAAE,MAAM;QACvB,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,qBAAqB;KACpD,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,WAAW,EAAE,KAAK,CAAC,WAAW;KAC/B,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAEtF,wEAAwE;IACxE,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,SAAS,CAAC,eAAe,IAAI,CAAC,CAAC;IAC9C,MAAM,cAAc,GAAG,SAAS,CAAC,eAAe,IAAI,MAAM,CAAC;IAC3D,IAAI,MAAM,IAAI,cAAc,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,WAAW,MAAM,wCAAwC,cAAc,8BAA8B,CACtG,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,OAAO;QACL,SAAS,EAAE,MAAM;QACjB,iBAAiB,EAAE,MAAM;QACzB,WAAW,EAAE,MAAM,CAAC,eAAe;QACnC,QAAQ,EAAE,MAAM,CAAC,YAAY;KAC9B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ export interface SignatureBlockEntry {
2
+ name: string;
3
+ email: string;
4
+ signatureImageDataUrl: string;
5
+ signedAt: Date;
6
+ /** Optional human role label shown above the signature ("Principal investigator", "Witness", ...). */
7
+ roleLabel?: string;
8
+ }
9
+ /**
10
+ * Render a stack of signature blocks as standalone HTML, suitable for
11
+ * concatenating into a document body before HTML→PDF rendering.
12
+ *
13
+ * Pattern: one signature per row, with name + role + image + timestamp.
14
+ * Plus a trailing audit line tying the PDF back to the platform.
15
+ */
16
+ export declare function renderSignatureBlocksHtml(opts: {
17
+ signers: SignatureBlockEntry[];
18
+ platformLabel?: string;
19
+ platformUrl?: string;
20
+ }): string;
21
+ export declare function escapeHtml(s: string): string;
22
+ export declare function assertImageDataUrl(dataUrl: string): string;
23
+ //# sourceMappingURL=signature-block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature-block.d.ts","sourceRoot":"","sources":["../src/signature-block.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,IAAI,CAAC;IACf,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAiCT;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAO5C;AASD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAS1D"}
@@ -0,0 +1,63 @@
1
+ // src/lib/integrations/esig/core/signature-block.ts
2
+ //
3
+ // Phase 21 — portable HTML signature-block rendering for the e-sig pipeline.
4
+ // Used by both single-party and multi-party flows. Project-agnostic.
5
+ /**
6
+ * Render a stack of signature blocks as standalone HTML, suitable for
7
+ * concatenating into a document body before HTML→PDF rendering.
8
+ *
9
+ * Pattern: one signature per row, with name + role + image + timestamp.
10
+ * Plus a trailing audit line tying the PDF back to the platform.
11
+ */
12
+ export function renderSignatureBlocksHtml(opts) {
13
+ const platform = opts.platformLabel ?? "self-contained e-signature pipeline";
14
+ const url = opts.platformUrl ?? "";
15
+ const rows = opts.signers
16
+ .map((s) => `
17
+ <div class="signature-row">
18
+ ${s.roleLabel ? `<p class="role-label">${escapeHtml(s.roleLabel)}</p>` : ""}
19
+ <p><strong>Signed electronically by:</strong> ${escapeHtml(s.name)}${s.email ? ` &lt;${escapeHtml(s.email)}&gt;` : ""}</p>
20
+ <img src="${assertImageDataUrl(s.signatureImageDataUrl)}" alt="Signature of ${escapeHtml(s.name)}">
21
+ <p class="signature-meta">Signed at ${s.signedAt.toISOString()}</p>
22
+ </div>`)
23
+ .join("\n");
24
+ return `<div class="signature-block">
25
+ <style>
26
+ .signature-block { margin-top: 64px; border-top: 1px solid #999; padding-top: 16px; }
27
+ .signature-row { margin: 16px 0; padding: 12px 0; border-bottom: 1px dashed #ddd; }
28
+ .signature-row:last-child { border-bottom: none; }
29
+ .signature-row img { display: block; max-height: 96px; margin: 8px 0; }
30
+ .signature-meta { color: #555; font-size: 11px; margin-top: 4px; }
31
+ .role-label { font-style: italic; color: #444; margin: 0 0 4px; }
32
+ .audit-footer { color: #555; font-size: 11px; margin-top: 12px; }
33
+ </style>
34
+ ${rows}
35
+ <p class="audit-footer">
36
+ Cryptographically sealed via the Opendelphi ${platform}${url ? ` (${escapeHtml(url)})` : ""}.
37
+ Any post-signing modification invalidates the signature panel in Adobe Reader / Preview.
38
+ </p>
39
+ </div>`;
40
+ }
41
+ export function escapeHtml(s) {
42
+ return s
43
+ .replace(/&/g, "&amp;")
44
+ .replace(/</g, "&lt;")
45
+ .replace(/>/g, "&gt;")
46
+ .replace(/"/g, "&quot;")
47
+ .replace(/'/g, "&#39;");
48
+ }
49
+ /**
50
+ * A base64 image data URL (png/jpeg/webp/gif) — the only shape allowed into the
51
+ * signature `<img src>`. Rejecting anything else prevents attribute-breakout /
52
+ * script/URL injection into the rendered (and then signed) PDF.
53
+ */
54
+ const IMAGE_DATA_URL = /^data:image\/(png|jpe?g|webp|gif);base64,[A-Za-z0-9+/]+={0,2}$/;
55
+ export function assertImageDataUrl(dataUrl) {
56
+ const compact = (dataUrl ?? "").replace(/\s+/g, "");
57
+ if (!IMAGE_DATA_URL.test(compact)) {
58
+ throw new Error("renderSignatureBlocksHtml: signatureImageDataUrl must be a base64 image data URL " +
59
+ "(data:image/png;base64,…). Refusing to interpolate untrusted content into the signed PDF.");
60
+ }
61
+ return compact;
62
+ }
63
+ //# sourceMappingURL=signature-block.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature-block.js","sourceRoot":"","sources":["../src/signature-block.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,6EAA6E;AAC7E,qEAAqE;AAWrE;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAIzC;IACC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,IAAI,qCAAqC,CAAC;IAC7E,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO;SACtB,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC;;MAEP,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,yBAAyB,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;oDAC3B,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAChE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAChD;gBACY,kBAAkB,CAAC,CAAC,CAAC,qBAAqB,CAAC,uBAAuB,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;0CAC1D,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE;SACzD,CACJ;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;;;;;;;;;;IAUL,IAAI;;kDAE0C,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;;;OAGxF,CAAC;AACR,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC;SACL,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,cAAc,GAAG,gEAAgE,CAAC;AAExF,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,OAAO,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACpD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CACb,mFAAmF;YACjF,2FAA2F,CAC9F,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,59 @@
1
+ import forge from "node-forge";
2
+ /** OID for the id-aa-timeStampToken unsigned attribute (RFC 3161). */
3
+ export declare const OID_TIMESTAMP_TOKEN = "1.2.840.113549.1.9.16.2.14";
4
+ /** Convert a forge binary string (DER) to a Uint8Array. */
5
+ export declare function derStringToUint8(der: string): Uint8Array;
6
+ /** Convert a Uint8Array to a forge binary string (DER). */
7
+ export declare function uint8ToDerString(bytes: Uint8Array): string;
8
+ /**
9
+ * Build an RFC 3161 TimeStampReq (DER, forge binary string) over the given
10
+ * signatureValue bytes.
11
+ *
12
+ * TimeStampReq ::= SEQUENCE {
13
+ * version INTEGER { v1(1) },
14
+ * messageImprint MessageImprint,
15
+ * reqPolicy TSAPolicyId OPTIONAL, -- omitted
16
+ * nonce INTEGER OPTIONAL,
17
+ * certReq BOOLEAN DEFAULT FALSE }
18
+ *
19
+ * MessageImprint ::= SEQUENCE {
20
+ * hashAlgorithm AlgorithmIdentifier,
21
+ * hashedMessage OCTET STRING }
22
+ *
23
+ * The messageImprint hash input is the RAW signatureValue bytes (a forge
24
+ * binary string), hashed with SHA-256.
25
+ */
26
+ export declare function buildTimeStampReq(sigValueBinary: string): string;
27
+ /**
28
+ * Parse an RFC 3161 TimeStampResp (DER forge binary string) and return the
29
+ * embedded TimeStampToken as a forge ASN.1 object.
30
+ *
31
+ * TimeStampResp ::= SEQUENCE {
32
+ * status PKIStatusInfo,
33
+ * timeStampToken TimeStampToken OPTIONAL }
34
+ *
35
+ * PKIStatusInfo ::= SEQUENCE {
36
+ * status PKIStatus (INTEGER),
37
+ * statusString PKIFreeText OPTIONAL,
38
+ * failInfo PKIFailureInfo OPTIONAL }
39
+ *
40
+ * Accepts status 0 (granted) or 1 (grantedWithMods); throws otherwise, with the
41
+ * integer status and (if present) failInfo bits.
42
+ */
43
+ export declare function parseTimeStampResp(respBinary: string): {
44
+ token: forge.asn1.Asn1;
45
+ };
46
+ /**
47
+ * Parse a TimeStampToken (a CMS SignedData ContentInfo whose eContentType is
48
+ * id-ct-TSTInfo) and extract best-effort metadata. Only
49
+ * `messageImprintHashHex` is treated as load-bearing (used by the verifier's
50
+ * §2.4.2 binding check); the rest are returned as undefined on any parse miss
51
+ * rather than throwing.
52
+ */
53
+ export declare function parseTstInfo(tokenAsn1: forge.asn1.Asn1): {
54
+ genTime?: string;
55
+ serialHex?: string;
56
+ tsaCommonName?: string;
57
+ messageImprintHashHex?: string;
58
+ };
59
+ //# sourceMappingURL=timestamp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timestamp.d.ts","sourceRoot":"","sources":["../src/timestamp.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,MAAM,YAAY,CAAC;AAI/B,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,+BAA+B,CAAC;AAOhE,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAIxD;AAED,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAW1D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAsDhE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAA;CAAE,CAgCjF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAsGA"}