@kitsy/pdf 0.0.0-stage → 0.1.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 (132) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +16 -0
  4. package/README.md +229 -2
  5. package/THIRD_PARTY_NOTICES.md +48 -0
  6. package/dist/chunk-2K5X7L5J.js +43 -0
  7. package/dist/chunk-2K5X7L5J.js.map +1 -0
  8. package/dist/chunk-4EAYBJ6G.js +362 -0
  9. package/dist/chunk-4EAYBJ6G.js.map +1 -0
  10. package/dist/chunk-4LBJTDV5.js +165 -0
  11. package/dist/chunk-4LBJTDV5.js.map +1 -0
  12. package/dist/chunk-4USCGC24.js +304 -0
  13. package/dist/chunk-4USCGC24.js.map +1 -0
  14. package/dist/chunk-5OIMR74G.js +273 -0
  15. package/dist/chunk-5OIMR74G.js.map +1 -0
  16. package/dist/chunk-6NFNAKWA.js +257 -0
  17. package/dist/chunk-6NFNAKWA.js.map +1 -0
  18. package/dist/chunk-7MTEMQLL.js +47 -0
  19. package/dist/chunk-7MTEMQLL.js.map +1 -0
  20. package/dist/chunk-7V4SWTUK.js +83 -0
  21. package/dist/chunk-7V4SWTUK.js.map +1 -0
  22. package/dist/chunk-7XH7MFZ4.js +295 -0
  23. package/dist/chunk-7XH7MFZ4.js.map +1 -0
  24. package/dist/chunk-BK3MYSUS.js +61 -0
  25. package/dist/chunk-BK3MYSUS.js.map +1 -0
  26. package/dist/chunk-BLHNKR3S.js +35 -0
  27. package/dist/chunk-BLHNKR3S.js.map +1 -0
  28. package/dist/chunk-CCJ43SIX.js +342 -0
  29. package/dist/chunk-CCJ43SIX.js.map +1 -0
  30. package/dist/chunk-CNEDWATL.js +210 -0
  31. package/dist/chunk-CNEDWATL.js.map +1 -0
  32. package/dist/chunk-CYTWDJE2.js +152 -0
  33. package/dist/chunk-CYTWDJE2.js.map +1 -0
  34. package/dist/chunk-DKVOICYZ.js +45 -0
  35. package/dist/chunk-DKVOICYZ.js.map +1 -0
  36. package/dist/chunk-DPEJU4P6.js +79 -0
  37. package/dist/chunk-DPEJU4P6.js.map +1 -0
  38. package/dist/chunk-DQPABZTU.js +22 -0
  39. package/dist/chunk-DQPABZTU.js.map +1 -0
  40. package/dist/chunk-EKHEY3LI.js +46 -0
  41. package/dist/chunk-EKHEY3LI.js.map +1 -0
  42. package/dist/chunk-G467LFKC.js +191 -0
  43. package/dist/chunk-G467LFKC.js.map +1 -0
  44. package/dist/chunk-GLYWSVSB.js +250 -0
  45. package/dist/chunk-GLYWSVSB.js.map +1 -0
  46. package/dist/chunk-GXBIMUO7.js +135 -0
  47. package/dist/chunk-GXBIMUO7.js.map +1 -0
  48. package/dist/chunk-H6EERRCR.js +180 -0
  49. package/dist/chunk-H6EERRCR.js.map +1 -0
  50. package/dist/chunk-HDTM3LXD.js +35 -0
  51. package/dist/chunk-HDTM3LXD.js.map +1 -0
  52. package/dist/chunk-IGMX56C3.js +180 -0
  53. package/dist/chunk-IGMX56C3.js.map +1 -0
  54. package/dist/chunk-J4IRICCJ.js +74 -0
  55. package/dist/chunk-J4IRICCJ.js.map +1 -0
  56. package/dist/chunk-JXKQ7S6W.js +331 -0
  57. package/dist/chunk-JXKQ7S6W.js.map +1 -0
  58. package/dist/chunk-KY6DXS6S.js +34 -0
  59. package/dist/chunk-KY6DXS6S.js.map +1 -0
  60. package/dist/chunk-NOFGCYGS.js +22 -0
  61. package/dist/chunk-NOFGCYGS.js.map +1 -0
  62. package/dist/chunk-OSHUJRKD.js +131 -0
  63. package/dist/chunk-OSHUJRKD.js.map +1 -0
  64. package/dist/chunk-PMH7OWPF.js +40 -0
  65. package/dist/chunk-PMH7OWPF.js.map +1 -0
  66. package/dist/chunk-PRBKSX3A.js +299 -0
  67. package/dist/chunk-PRBKSX3A.js.map +1 -0
  68. package/dist/chunk-SVPUX7AM.js +48 -0
  69. package/dist/chunk-SVPUX7AM.js.map +1 -0
  70. package/dist/chunk-TR6HJ4NO.js +659 -0
  71. package/dist/chunk-TR6HJ4NO.js.map +1 -0
  72. package/dist/chunk-TV3E4YUF.js +69 -0
  73. package/dist/chunk-TV3E4YUF.js.map +1 -0
  74. package/dist/chunk-U2ROTOPF.js +256 -0
  75. package/dist/chunk-U2ROTOPF.js.map +1 -0
  76. package/dist/chunk-VCTN77K2.js +112 -0
  77. package/dist/chunk-VCTN77K2.js.map +1 -0
  78. package/dist/chunk-WEOPDD3P.js +120 -0
  79. package/dist/chunk-WEOPDD3P.js.map +1 -0
  80. package/dist/chunk-ZBZ7OC6I.js +228 -0
  81. package/dist/chunk-ZBZ7OC6I.js.map +1 -0
  82. package/dist/config-CJYo46X9.d.ts +19 -0
  83. package/dist/index.d.ts +487 -0
  84. package/dist/index.js +23 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/react/certificate-sign.d.ts +5 -0
  87. package/dist/react/certificate-sign.js +14 -0
  88. package/dist/react/certificate-sign.js.map +1 -0
  89. package/dist/react/compress.d.ts +5 -0
  90. package/dist/react/compress.js +8 -0
  91. package/dist/react/compress.js.map +1 -0
  92. package/dist/react/fill-form.d.ts +5 -0
  93. package/dist/react/fill-form.js +8 -0
  94. package/dist/react/fill-form.js.map +1 -0
  95. package/dist/react/images-to-pdf.d.ts +5 -0
  96. package/dist/react/images-to-pdf.js +7 -0
  97. package/dist/react/images-to-pdf.js.map +1 -0
  98. package/dist/react/merge.d.ts +5 -0
  99. package/dist/react/merge.js +8 -0
  100. package/dist/react/merge.js.map +1 -0
  101. package/dist/react/metadata.d.ts +5 -0
  102. package/dist/react/metadata.js +8 -0
  103. package/dist/react/metadata.js.map +1 -0
  104. package/dist/react/organize.d.ts +5 -0
  105. package/dist/react/organize.js +12 -0
  106. package/dist/react/organize.js.map +1 -0
  107. package/dist/react/pdf-to-images.d.ts +5 -0
  108. package/dist/react/pdf-to-images.js +13 -0
  109. package/dist/react/pdf-to-images.js.map +1 -0
  110. package/dist/react/protect.d.ts +5 -0
  111. package/dist/react/protect.js +8 -0
  112. package/dist/react/protect.js.map +1 -0
  113. package/dist/react/sign.d.ts +5 -0
  114. package/dist/react/sign.js +13 -0
  115. package/dist/react/sign.js.map +1 -0
  116. package/dist/react/split.d.ts +5 -0
  117. package/dist/react/split.js +11 -0
  118. package/dist/react/split.js.map +1 -0
  119. package/dist/react/verify-signatures.d.ts +5 -0
  120. package/dist/react/verify-signatures.js +8 -0
  121. package/dist/react/verify-signatures.js.map +1 -0
  122. package/dist/react/watermark.d.ts +5 -0
  123. package/dist/react/watermark.js +10 -0
  124. package/dist/react/watermark.js.map +1 -0
  125. package/dist/react.d.ts +40 -0
  126. package/dist/react.js +40 -0
  127. package/dist/react.js.map +1 -0
  128. package/dist/styles.css +2 -0
  129. package/dist/vite.d.ts +5 -0
  130. package/dist/vite.js +9 -0
  131. package/dist/vite.js.map +1 -0
  132. package/package.json +163 -4
@@ -0,0 +1,304 @@
1
+ import { PasswordInput } from './chunk-2K5X7L5J.js';
2
+ import { readPkcs12, CertificatePasswordError, signPdfWithCertificate } from './chunk-U2ROTOPF.js';
3
+ import { openRenderDocument, renderThumbnail } from './chunk-DPEJU4P6.js';
4
+ import { describeCertificate, webCryptoSigner, verifyPdfSignatures } from './chunk-5OIMR74G.js';
5
+ import { useSinglePdf } from './chunk-BK3MYSUS.js';
6
+ import { PdfToolPage, PdfToolColumns, SideTips, SelectedPdfCard, FileDropzone, cardClass, SummaryList, secondaryButtonClass, primaryButtonClass, fieldLabelClass, inputClass, StatusBanner, BackToPdfTools } from './chunk-H6EERRCR.js';
7
+ import { downloadBlob, toBaseName, formatBytes } from './chunk-EKHEY3LI.js';
8
+ import { useRef, useState, useEffect, useCallback } from 'react';
9
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
+
11
+ var REASONS = [
12
+ "I am the author of this document",
13
+ "I approve this document",
14
+ "I have reviewed this document",
15
+ "I agree to the terms of this document"
16
+ ];
17
+ var BOX = { width: 0.38, height: 0.09 };
18
+ var POSITIONS = {
19
+ "bottom-right": { x: 0.98 - BOX.width, y: 0.97 - BOX.height, label: "Bottom right" },
20
+ "bottom-left": { x: 0.02, y: 0.97 - BOX.height, label: "Bottom left" },
21
+ "top-right": { x: 0.98 - BOX.width, y: 0.03, label: "Top right" },
22
+ "top-left": { x: 0.02, y: 0.03, label: "Top left" }
23
+ };
24
+ var formatDate = (date) => date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
25
+ function usePagePreview(bytes, pageIndex) {
26
+ const [preview, setPreview] = useState(null);
27
+ useEffect(() => {
28
+ if (!bytes) {
29
+ setPreview(null);
30
+ return void 0;
31
+ }
32
+ let cancelled = false;
33
+ let url = null;
34
+ let doc = null;
35
+ (async () => {
36
+ doc = await openRenderDocument(bytes);
37
+ const thumb = await renderThumbnail(doc, pageIndex + 1, 220);
38
+ url = thumb.url;
39
+ if (cancelled) URL.revokeObjectURL(url);
40
+ else setPreview(thumb);
41
+ })().catch(() => {
42
+ });
43
+ return () => {
44
+ cancelled = true;
45
+ if (url) URL.revokeObjectURL(url);
46
+ doc?.destroy();
47
+ };
48
+ }, [bytes, pageIndex]);
49
+ return preview;
50
+ }
51
+ function CertSignPdf() {
52
+ const certInputRef = useRef(null);
53
+ const [certFile, setCertFile] = useState(null);
54
+ const [certPassword, setCertPassword] = useState("");
55
+ const [signer, setSigner] = useState(null);
56
+ const [certificate, setCertificate] = useState(null);
57
+ const [reason, setReason] = useState(REASONS[1]);
58
+ const [customReason, setCustomReason] = useState("");
59
+ const [location, setLocation] = useState("");
60
+ const [visible, setVisible] = useState(true);
61
+ const [pageIndex, setPageIndex] = useState(0);
62
+ const [position, setPosition] = useState("bottom-right");
63
+ const [isWorking, setIsWorking] = useState(false);
64
+ const [status, setStatus] = useState({
65
+ tone: "info",
66
+ text: "Add a PDF and your certificate (.pfx or .p12) to sign it digitally."
67
+ });
68
+ useEffect(() => {
69
+ document.title = "Digital Signature (Certificate) | Apps";
70
+ }, []);
71
+ const onError = useCallback((text) => setStatus({ tone: "error", text }), []);
72
+ const onLoaded = useCallback(({ file, pageCount }) => {
73
+ setPageIndex(Math.max(0, pageCount - 1));
74
+ setStatus({ tone: "success", text: `${file.name} loaded. Now open your certificate.` });
75
+ }, []);
76
+ const pdf = useSinglePdf({ onError, onLoaded });
77
+ const preview = usePagePreview(visible ? pdf.bytes : null, pageIndex);
78
+ const forgetCertificate = () => {
79
+ setSigner(null);
80
+ setCertificate(null);
81
+ setCertPassword("");
82
+ };
83
+ const chooseCertificate = (file) => {
84
+ if (!file) return;
85
+ forgetCertificate();
86
+ setCertFile(file);
87
+ };
88
+ const openCertificate = async () => {
89
+ if (!certFile) return;
90
+ setIsWorking(true);
91
+ try {
92
+ const identity = readPkcs12(new Uint8Array(await certFile.arrayBuffer()), certPassword);
93
+ const summary = describeCertificate(identity.certificate);
94
+ const nextSigner = await webCryptoSigner(identity);
95
+ identity.privateKeyPkcs8.fill(0);
96
+ setSigner(nextSigner);
97
+ setCertificate(summary);
98
+ setCertPassword("");
99
+ setStatus({ tone: "success", text: `Certificate for ${summary.commonName || "(no name)"} opened.` });
100
+ } catch (error) {
101
+ setStatus({
102
+ tone: "error",
103
+ text: error instanceof CertificatePasswordError ? error.message : error?.message || "Could not open the certificate."
104
+ });
105
+ } finally {
106
+ setIsWorking(false);
107
+ }
108
+ };
109
+ const now = /* @__PURE__ */ new Date();
110
+ const expired = certificate && (now > certificate.notAfter || now < certificate.notBefore);
111
+ const finalReason = reason === "custom" ? customReason.trim() : reason;
112
+ const canSign = Boolean(pdf.bytes && signer && !pdf.signed && !expired);
113
+ const handleSign = async (event) => {
114
+ event.preventDefault();
115
+ if (!canSign) return;
116
+ setIsWorking(true);
117
+ setStatus({ tone: "info", text: "Signing your PDF..." });
118
+ try {
119
+ const spot2 = POSITIONS[position];
120
+ const signed = await signPdfWithCertificate(pdf.bytes, signer, {
121
+ reason: finalReason,
122
+ location: location.trim(),
123
+ visible: visible ? { pageIndex, x: spot2.x, y: spot2.y, ...BOX } : null
124
+ });
125
+ const [check] = await verifyPdfSignatures(signed);
126
+ if (!check?.intact || !check.coversWholeFile) {
127
+ throw new Error("The signed file did not pass verification. Nothing was downloaded.");
128
+ }
129
+ const blob = new Blob([signed], { type: "application/pdf" });
130
+ downloadBlob(blob, `${toBaseName(pdf.file.name, "document")}-signed.pdf`);
131
+ setStatus({
132
+ tone: "success",
133
+ text: `Signed and verified (${formatBytes(blob.size)}). Your download has started.`
134
+ });
135
+ } catch (error) {
136
+ setStatus({ tone: "error", text: `Signing failed: ${error?.message || "Please try again."}` });
137
+ } finally {
138
+ setIsWorking(false);
139
+ }
140
+ };
141
+ const busy = isWorking || pdf.isReading;
142
+ const spot = POSITIONS[position];
143
+ return /* @__PURE__ */ jsx(
144
+ PdfToolPage,
145
+ {
146
+ title: "Digital Signature (Certificate)",
147
+ description: "Sign a PDF with your digital certificate (.pfx / .p12). Viewers such as Adobe Reader show who signed it and flag any later change. Your certificate and its private key never leave your browser.",
148
+ stats: [
149
+ { value: "PAdES", label: "Standard" },
150
+ { value: certificate ? "Ready" : "-", label: "Certificate" }
151
+ ],
152
+ children: /* @__PURE__ */ jsx(
153
+ PdfToolColumns,
154
+ {
155
+ main: /* @__PURE__ */ jsxs("form", { onSubmit: handleSign, children: [
156
+ pdf.file ? /* @__PURE__ */ jsx(SelectedPdfCard, { file: pdf.file, pageCount: pdf.pageCount, onRemove: pdf.clear, disabled: busy }) : /* @__PURE__ */ jsx(
157
+ FileDropzone,
158
+ {
159
+ accept: ".pdf,application/pdf",
160
+ disabled: busy,
161
+ onFiles: pdf.selectFiles,
162
+ title: "Drag and drop the PDF to sign",
163
+ hint: "or click to browse from your device"
164
+ }
165
+ ),
166
+ pdf.signed ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-rose-700 dark:text-rose-300", children: "Adding a second signature to an already-signed PDF is not supported yet." }) : null,
167
+ /* @__PURE__ */ jsxs("section", { className: `mt-5 ${cardClass}`, children: [
168
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-slate-900 dark:text-slate-100", children: "1. Your certificate" }),
169
+ /* @__PURE__ */ jsx(
170
+ "input",
171
+ {
172
+ ref: certInputRef,
173
+ type: "file",
174
+ accept: ".pfx,.p12,application/x-pkcs12",
175
+ className: "hidden",
176
+ onChange: (event) => {
177
+ chooseCertificate(event.target.files?.[0]);
178
+ event.target.value = "";
179
+ }
180
+ }
181
+ ),
182
+ certificate ? /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
183
+ /* @__PURE__ */ jsx(
184
+ SummaryList,
185
+ {
186
+ items: [
187
+ { label: "Signer", value: certificate.commonName || "(no name)" },
188
+ { label: "Organisation", value: certificate.organization || "-" },
189
+ { label: "Issued by", value: certificate.issuerCommonName || certificate.issuerOrganization || "-" },
190
+ { label: "Valid", value: `${formatDate(certificate.notBefore)} \u2013 ${formatDate(certificate.notAfter)}` }
191
+ ]
192
+ }
193
+ ),
194
+ expired ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-rose-700 dark:text-rose-300", children: "This certificate is not valid today, so it cannot be used to sign." }) : certificate.selfSigned ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-amber-700 dark:text-amber-300", children: "Self-signed certificate: the signature is valid, but viewers will show the signer as untrusted." }) : null,
195
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: forgetCertificate, disabled: busy, className: `mt-3 ${secondaryButtonClass}`, children: [
196
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-rotate" }),
197
+ "Use a different certificate"
198
+ ] })
199
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "mt-3 grid gap-3 sm:grid-cols-[auto_minmax(0,1fr)_auto] sm:items-end", children: [
200
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => certInputRef.current?.click(), disabled: busy, className: secondaryButtonClass, children: [
201
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-id-card" }),
202
+ certFile ? certFile.name : "Choose .pfx / .p12"
203
+ ] }),
204
+ /* @__PURE__ */ jsx(
205
+ PasswordInput,
206
+ {
207
+ id: "certificate-password",
208
+ label: "Certificate password",
209
+ value: certPassword,
210
+ onChange: setCertPassword,
211
+ disabled: busy || !certFile,
212
+ autoComplete: "off"
213
+ }
214
+ ),
215
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: openCertificate, disabled: busy || !certFile, className: primaryButtonClass, children: [
216
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-unlock-keyhole" }),
217
+ "Open"
218
+ ] })
219
+ ] })
220
+ ] }),
221
+ /* @__PURE__ */ jsxs("fieldset", { className: `mt-5 ${cardClass}`, disabled: busy, children: [
222
+ /* @__PURE__ */ jsx("legend", { className: "px-1 text-sm font-semibold text-slate-900 dark:text-slate-100", children: "2. Signature details" }),
223
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
224
+ /* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
225
+ "Reason",
226
+ /* @__PURE__ */ jsxs("select", { value: reason, onChange: (event) => setReason(event.target.value), className: `mt-2 ${inputClass}`, children: [
227
+ REASONS.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option }, option)),
228
+ /* @__PURE__ */ jsx("option", { value: "custom", children: "Other\u2026" })
229
+ ] })
230
+ ] }),
231
+ /* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
232
+ "Location (optional)",
233
+ /* @__PURE__ */ jsx("input", { type: "text", value: location, maxLength: 80, onChange: (event) => setLocation(event.target.value), placeholder: "City", className: `mt-2 ${inputClass}` })
234
+ ] }),
235
+ reason === "custom" ? /* @__PURE__ */ jsxs("label", { className: `sm:col-span-2 ${fieldLabelClass}`, children: [
236
+ "Your reason",
237
+ /* @__PURE__ */ jsx("input", { type: "text", value: customReason, maxLength: 120, onChange: (event) => setCustomReason(event.target.value), className: `mt-2 ${inputClass}` })
238
+ ] }) : null
239
+ ] }),
240
+ /* @__PURE__ */ jsxs("label", { className: "mt-4 flex items-center gap-3 text-sm text-slate-700 dark:text-slate-300", children: [
241
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: visible, onChange: (event) => setVisible(event.target.checked), className: "h-4 w-4 accent-[#1F8CFF]" }),
242
+ "Show a signature box on the page"
243
+ ] }),
244
+ visible && pdf.pageCount ? /* @__PURE__ */ jsxs("div", { className: "mt-3 grid gap-4 sm:grid-cols-[minmax(0,1fr)_auto]", children: [
245
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
246
+ /* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
247
+ "Page",
248
+ /* @__PURE__ */ jsx("select", { value: pageIndex, onChange: (event) => setPageIndex(Number(event.target.value)), className: `mt-2 ${inputClass}`, children: Array.from({ length: pdf.pageCount }, (_, index) => /* @__PURE__ */ jsxs("option", { value: index, children: [
249
+ "Page ",
250
+ index + 1,
251
+ index === pdf.pageCount - 1 ? " (last)" : ""
252
+ ] }, index)) })
253
+ ] }),
254
+ /* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
255
+ "Position",
256
+ /* @__PURE__ */ jsx("select", { value: position, onChange: (event) => setPosition(event.target.value), className: `mt-2 ${inputClass}`, children: Object.entries(POSITIONS).map(([value, option]) => /* @__PURE__ */ jsx("option", { value, children: option.label }, value)) })
257
+ ] })
258
+ ] }),
259
+ preview ? /* @__PURE__ */ jsxs("div", { className: "relative mx-auto w-28 shadow-sm", style: { aspectRatio: `${preview.width} / ${preview.height}` }, children: [
260
+ /* @__PURE__ */ jsx("img", { src: preview.url, alt: `Page ${pageIndex + 1} preview`, className: "absolute inset-0 h-full w-full" }),
261
+ /* @__PURE__ */ jsx(
262
+ "div",
263
+ {
264
+ className: "absolute border-2 border-[#1F8CFF] bg-[#1F8CFF]/15",
265
+ style: { left: `${spot.x * 100}%`, top: `${spot.y * 100}%`, width: `${BOX.width * 100}%`, height: `${BOX.height * 100}%` },
266
+ "aria-hidden": "true"
267
+ }
268
+ )
269
+ ] }) : null
270
+ ] }) : null
271
+ ] }),
272
+ /* @__PURE__ */ jsx(StatusBanner, { status }),
273
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 flex flex-wrap gap-3", children: [
274
+ /* @__PURE__ */ jsxs("button", { type: "submit", disabled: busy || !canSign || reason === "custom" && !finalReason, className: primaryButtonClass, children: [
275
+ /* @__PURE__ */ jsx("i", { className: `fa-solid ${isWorking ? "fa-spinner fa-spin" : "fa-file-signature"}` }),
276
+ isWorking ? "Working..." : "Sign & Download"
277
+ ] }),
278
+ /* @__PURE__ */ jsx(BackToPdfTools, {})
279
+ ] })
280
+ ] }),
281
+ side: /* @__PURE__ */ jsxs(Fragment, { children: [
282
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "About digital signatures" }),
283
+ /* @__PURE__ */ jsx(
284
+ SideTips,
285
+ {
286
+ tips: [
287
+ "This is a cryptographic signature (PAdES), not just an image. Any later change to the file is detected.",
288
+ "Whether viewers show it as trusted depends on who issued your certificate, not on this tool.",
289
+ "Your .pfx file, its password, and its private key stay in this browser tab and are never uploaded.",
290
+ "USB token DSCs (ePass, ProxKey, etc.) need a small local helper; that support is coming.",
291
+ "Check any signed PDF with Verify Signatures."
292
+ ]
293
+ }
294
+ )
295
+ ] })
296
+ }
297
+ )
298
+ }
299
+ );
300
+ }
301
+
302
+ export { CertSignPdf };
303
+ //# sourceMappingURL=chunk-4USCGC24.js.map
304
+ //# sourceMappingURL=chunk-4USCGC24.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/certsign/CertSignPdf.tsx"],"names":["spot"],"mappings":"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","file":"chunk-4USCGC24.js","sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { downloadBlob, formatBytes, toBaseName } from \"../shared/files\";\nimport { PasswordInput } from \"../shared/PasswordInput\";\nimport { openRenderDocument, renderThumbnail } from \"../shared/pdfRender\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SelectedPdfCard,\n SideTips,\n StatusBanner,\n SummaryList,\n cardClass,\n fieldLabelClass,\n inputClass,\n primaryButtonClass,\n secondaryButtonClass,\n} from \"../shared/ToolLayout\";\nimport { useSinglePdf } from \"../shared/useSinglePdf\";\nimport { describeCertificate, webCryptoSigner } from \"./cms\";\nimport { signPdfWithCertificate } from \"./padesCore\";\nimport { CertificatePasswordError, readPkcs12 } from \"./pkcs12\";\nimport { verifyPdfSignatures } from \"./verifyCore\";\n\nconst REASONS = [\n \"I am the author of this document\",\n \"I approve this document\",\n \"I have reviewed this document\",\n \"I agree to the terms of this document\",\n];\n\nconst BOX = { width: 0.38, height: 0.09 };\nconst POSITIONS = {\n \"bottom-right\": { x: 0.98 - BOX.width, y: 0.97 - BOX.height, label: \"Bottom right\" },\n \"bottom-left\": { x: 0.02, y: 0.97 - BOX.height, label: \"Bottom left\" },\n \"top-right\": { x: 0.98 - BOX.width, y: 0.03, label: \"Top right\" },\n \"top-left\": { x: 0.02, y: 0.03, label: \"Top left\" },\n};\n\nconst formatDate = (date: Date) =>\n date.toLocaleDateString(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" });\n\nfunction usePagePreview(bytes, pageIndex) {\n const [preview, setPreview] = useState(null);\n useEffect(() => {\n if (!bytes) {\n setPreview(null);\n return undefined;\n }\n let cancelled = false;\n let url = null;\n let doc = null;\n (async () => {\n doc = await openRenderDocument(bytes);\n const thumb = await renderThumbnail(doc, pageIndex + 1, 220);\n url = thumb.url;\n if (cancelled) URL.revokeObjectURL(url);\n else setPreview(thumb);\n })().catch(() => {});\n return () => {\n cancelled = true;\n if (url) URL.revokeObjectURL(url);\n doc?.destroy();\n };\n }, [bytes, pageIndex]);\n return preview;\n}\n\nexport default function CertSignPdf() {\n const certInputRef = useRef(null);\n const [certFile, setCertFile] = useState(null);\n const [certPassword, setCertPassword] = useState(\"\");\n const [signer, setSigner] = useState(null);\n const [certificate, setCertificate] = useState(null);\n const [reason, setReason] = useState(REASONS[1]);\n const [customReason, setCustomReason] = useState(\"\");\n const [location, setLocation] = useState(\"\");\n const [visible, setVisible] = useState(true);\n const [pageIndex, setPageIndex] = useState(0);\n const [position, setPosition] = useState(\"bottom-right\");\n const [isWorking, setIsWorking] = useState(false);\n const [status, setStatus] = useState({\n tone: \"info\",\n text: \"Add a PDF and your certificate (.pfx or .p12) to sign it digitally.\",\n });\n\n useEffect(() => {\n document.title = \"Digital Signature (Certificate) | Apps\";\n }, []);\n\n const onError = useCallback((text) => setStatus({ tone: \"error\", text }), []);\n const onLoaded = useCallback(({ file, pageCount }) => {\n setPageIndex(Math.max(0, pageCount - 1));\n setStatus({ tone: \"success\", text: `${file.name} loaded. Now open your certificate.` });\n }, []);\n const pdf = useSinglePdf({ onError, onLoaded });\n const preview = usePagePreview(visible ? pdf.bytes : null, pageIndex);\n\n const forgetCertificate = () => {\n setSigner(null);\n setCertificate(null);\n setCertPassword(\"\");\n };\n\n const chooseCertificate = (file) => {\n if (!file) return;\n forgetCertificate();\n setCertFile(file);\n };\n\n const openCertificate = async () => {\n if (!certFile) return;\n setIsWorking(true);\n try {\n const identity = readPkcs12(new Uint8Array(await certFile.arrayBuffer()), certPassword);\n const summary = describeCertificate(identity.certificate);\n // The key is imported as non-extractable; drop our copy of its bytes.\n const nextSigner = await webCryptoSigner(identity);\n identity.privateKeyPkcs8.fill(0);\n setSigner(nextSigner);\n setCertificate(summary);\n setCertPassword(\"\");\n setStatus({ tone: \"success\", text: `Certificate for ${summary.commonName || \"(no name)\"} opened.` });\n } catch (error) {\n setStatus({\n tone: \"error\",\n text: error instanceof CertificatePasswordError ? error.message : error?.message || \"Could not open the certificate.\",\n });\n } finally {\n setIsWorking(false);\n }\n };\n\n const now = new Date();\n const expired = certificate && (now > certificate.notAfter || now < certificate.notBefore);\n const finalReason = reason === \"custom\" ? customReason.trim() : reason;\n const canSign = Boolean(pdf.bytes && signer && !pdf.signed && !expired);\n\n const handleSign = async (event) => {\n event.preventDefault();\n if (!canSign) return;\n setIsWorking(true);\n setStatus({ tone: \"info\", text: \"Signing your PDF...\" });\n try {\n const spot = POSITIONS[position];\n const signed = await signPdfWithCertificate(pdf.bytes, signer, {\n reason: finalReason,\n location: location.trim(),\n visible: visible ? { pageIndex, x: spot.x, y: spot.y, ...BOX } : null,\n });\n // Check our own output before handing it over.\n const [check] = await verifyPdfSignatures(signed);\n if (!check?.intact || !check.coversWholeFile) {\n throw new Error(\"The signed file did not pass verification. Nothing was downloaded.\");\n }\n const blob = new Blob([signed as BlobPart], { type: \"application/pdf\" });\n downloadBlob(blob, `${toBaseName(pdf.file.name, \"document\")}-signed.pdf`);\n setStatus({\n tone: \"success\",\n text: `Signed and verified (${formatBytes(blob.size)}). Your download has started.`,\n });\n } catch (error) {\n setStatus({ tone: \"error\", text: `Signing failed: ${error?.message || \"Please try again.\"}` });\n } finally {\n setIsWorking(false);\n }\n };\n\n const busy = isWorking || pdf.isReading;\n const spot = POSITIONS[position];\n\n return (\n <PdfToolPage\n title=\"Digital Signature (Certificate)\"\n description=\"Sign a PDF with your digital certificate (.pfx / .p12). Viewers such as Adobe Reader show who signed it and flag any later change. Your certificate and its private key never leave your browser.\"\n stats={[\n { value: \"PAdES\", label: \"Standard\" },\n { value: certificate ? \"Ready\" : \"-\", label: \"Certificate\" },\n ]}\n >\n <PdfToolColumns\n main={\n <form onSubmit={handleSign}>\n {pdf.file ? (\n <SelectedPdfCard file={pdf.file} pageCount={pdf.pageCount} onRemove={pdf.clear} disabled={busy} />\n ) : (\n <FileDropzone\n accept=\".pdf,application/pdf\"\n disabled={busy}\n onFiles={pdf.selectFiles}\n title=\"Drag and drop the PDF to sign\"\n hint=\"or click to browse from your device\"\n />\n )}\n {pdf.signed ? (\n <p className=\"mt-2 text-xs text-rose-700 dark:text-rose-300\">\n Adding a second signature to an already-signed PDF is not supported yet.\n </p>\n ) : null}\n\n <section className={`mt-5 ${cardClass}`}>\n <h3 className=\"text-sm font-semibold text-slate-900 dark:text-slate-100\">1. Your certificate</h3>\n <input\n ref={certInputRef}\n type=\"file\"\n accept=\".pfx,.p12,application/x-pkcs12\"\n className=\"hidden\"\n onChange={(event) => {\n chooseCertificate(event.target.files?.[0]);\n event.target.value = \"\";\n }}\n />\n {certificate ? (\n <div className=\"mt-3\">\n <SummaryList\n items={[\n { label: \"Signer\", value: certificate.commonName || \"(no name)\" },\n { label: \"Organisation\", value: certificate.organization || \"-\" },\n { label: \"Issued by\", value: certificate.issuerCommonName || certificate.issuerOrganization || \"-\" },\n { label: \"Valid\", value: `${formatDate(certificate.notBefore)} – ${formatDate(certificate.notAfter)}` },\n ]}\n />\n {expired ? (\n <p className=\"mt-2 text-xs text-rose-700 dark:text-rose-300\">This certificate is not valid today, so it cannot be used to sign.</p>\n ) : certificate.selfSigned ? (\n <p className=\"mt-2 text-xs text-amber-700 dark:text-amber-300\">\n Self-signed certificate: the signature is valid, but viewers will show the signer as untrusted.\n </p>\n ) : null}\n <button type=\"button\" onClick={forgetCertificate} disabled={busy} className={`mt-3 ${secondaryButtonClass}`}>\n <i className=\"fa-solid fa-rotate\" />\n Use a different certificate\n </button>\n </div>\n ) : (\n <div className=\"mt-3 grid gap-3 sm:grid-cols-[auto_minmax(0,1fr)_auto] sm:items-end\">\n <button type=\"button\" onClick={() => certInputRef.current?.click()} disabled={busy} className={secondaryButtonClass}>\n <i className=\"fa-solid fa-id-card\" />\n {certFile ? certFile.name : \"Choose .pfx / .p12\"}\n </button>\n <PasswordInput\n id=\"certificate-password\"\n label=\"Certificate password\"\n value={certPassword}\n onChange={setCertPassword}\n disabled={busy || !certFile}\n autoComplete=\"off\"\n />\n <button type=\"button\" onClick={openCertificate} disabled={busy || !certFile} className={primaryButtonClass}>\n <i className=\"fa-solid fa-unlock-keyhole\" />\n Open\n </button>\n </div>\n )}\n </section>\n\n <fieldset className={`mt-5 ${cardClass}`} disabled={busy}>\n <legend className=\"px-1 text-sm font-semibold text-slate-900 dark:text-slate-100\">2. Signature details</legend>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <label className={fieldLabelClass}>\n Reason\n <select value={reason} onChange={(event) => setReason(event.target.value)} className={`mt-2 ${inputClass}`}>\n {REASONS.map((option) => (\n <option key={option} value={option}>\n {option}\n </option>\n ))}\n <option value=\"custom\">Other…</option>\n </select>\n </label>\n <label className={fieldLabelClass}>\n Location (optional)\n <input type=\"text\" value={location} maxLength={80} onChange={(event) => setLocation(event.target.value)} placeholder=\"City\" className={`mt-2 ${inputClass}`} />\n </label>\n {reason === \"custom\" ? (\n <label className={`sm:col-span-2 ${fieldLabelClass}`}>\n Your reason\n <input type=\"text\" value={customReason} maxLength={120} onChange={(event) => setCustomReason(event.target.value)} className={`mt-2 ${inputClass}`} />\n </label>\n ) : null}\n </div>\n\n <label className=\"mt-4 flex items-center gap-3 text-sm text-slate-700 dark:text-slate-300\">\n <input type=\"checkbox\" checked={visible} onChange={(event) => setVisible(event.target.checked)} className=\"h-4 w-4 accent-[#1F8CFF]\" />\n Show a signature box on the page\n </label>\n {visible && pdf.pageCount ? (\n <div className=\"mt-3 grid gap-4 sm:grid-cols-[minmax(0,1fr)_auto]\">\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <label className={fieldLabelClass}>\n Page\n <select value={pageIndex} onChange={(event) => setPageIndex(Number(event.target.value))} className={`mt-2 ${inputClass}`}>\n {Array.from({ length: pdf.pageCount }, (_, index) => (\n <option key={index} value={index}>\n Page {index + 1}\n {index === pdf.pageCount - 1 ? \" (last)\" : \"\"}\n </option>\n ))}\n </select>\n </label>\n <label className={fieldLabelClass}>\n Position\n <select value={position} onChange={(event) => setPosition(event.target.value)} className={`mt-2 ${inputClass}`}>\n {Object.entries(POSITIONS).map(([value, option]) => (\n <option key={value} value={value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n </div>\n {preview ? (\n <div className=\"relative mx-auto w-28 shadow-sm\" style={{ aspectRatio: `${preview.width} / ${preview.height}` }}>\n <img src={preview.url} alt={`Page ${pageIndex + 1} preview`} className=\"absolute inset-0 h-full w-full\" />\n <div\n className=\"absolute border-2 border-[#1F8CFF] bg-[#1F8CFF]/15\"\n style={{ left: `${spot.x * 100}%`, top: `${spot.y * 100}%`, width: `${BOX.width * 100}%`, height: `${BOX.height * 100}%` }}\n aria-hidden=\"true\"\n />\n </div>\n ) : null}\n </div>\n ) : null}\n </fieldset>\n\n <StatusBanner status={status} />\n\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <button type=\"submit\" disabled={busy || !canSign || (reason === \"custom\" && !finalReason)} className={primaryButtonClass}>\n <i className={`fa-solid ${isWorking ? \"fa-spinner fa-spin\" : \"fa-file-signature\"}`} />\n {isWorking ? \"Working...\" : \"Sign & Download\"}\n </button>\n <BackToPdfTools />\n </div>\n </form>\n }\n side={\n <>\n <h2 className=\"text-base font-semibold text-slate-900 dark:text-slate-100\">About digital signatures</h2>\n <SideTips\n tips={[\n \"This is a cryptographic signature (PAdES), not just an image. Any later change to the file is detected.\",\n \"Whether viewers show it as trusted depends on who issued your certificate, not on this tool.\",\n \"Your .pfx file, its password, and its private key stay in this browser tab and are never uploaded.\",\n \"USB token DSCs (ePass, ProxKey, etc.) need a small local helper; that support is coming.\",\n \"Check any signed PDF with Verify Signatures.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}
@@ -0,0 +1,273 @@
1
+ import { signatureFields } from './chunk-7MTEMQLL.js';
2
+ import * as asn1js from 'asn1js';
3
+ import * as pkijs from 'pkijs';
4
+ import { PDFDocument, PDFArray, PDFNumber, PDFHexString, PDFString, PDFName } from 'pdf-lib';
5
+
6
+ var OID = {
7
+ data: "1.2.840.113549.1.7.1",
8
+ signedData: "1.2.840.113549.1.7.2",
9
+ contentType: "1.2.840.113549.1.9.3",
10
+ messageDigest: "1.2.840.113549.1.9.4",
11
+ signingTime: "1.2.840.113549.1.9.5",
12
+ signingCertificateV2: "1.2.840.113549.1.9.16.2.47",
13
+ sha256: "2.16.840.1.101.3.4.2.1",
14
+ sha256WithRSA: "1.2.840.113549.1.1.11",
15
+ commonName: "2.5.4.3",
16
+ organization: "2.5.4.10",
17
+ organizationalUnit: "2.5.4.11",
18
+ email: "1.2.840.113549.1.9.1"
19
+ };
20
+ var buffer = (bytes) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
21
+ async function sha256(data) {
22
+ return new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", data));
23
+ }
24
+ function compareBytes(a, b) {
25
+ for (let i = 0; i < Math.min(a.length, b.length); i += 1) {
26
+ if (a[i] !== b[i]) return a[i] - b[i];
27
+ }
28
+ return a.length - b.length;
29
+ }
30
+ function parseCertificate(der) {
31
+ try {
32
+ return pkijs.Certificate.fromBER(buffer(der));
33
+ } catch {
34
+ throw new Error("Could not read the signing certificate.");
35
+ }
36
+ }
37
+ async function signingCertificateV2(der, cert) {
38
+ const issuerSerial = new asn1js.Sequence({
39
+ value: [
40
+ new asn1js.Sequence({
41
+ value: [new asn1js.Constructed({ idBlock: { tagClass: 3, tagNumber: 4 }, value: [cert.issuer.toSchema()] })]
42
+ }),
43
+ cert.serialNumber
44
+ ]
45
+ });
46
+ const essCertId = new asn1js.Sequence({
47
+ value: [new asn1js.OctetString({ valueHex: buffer(await sha256(der)) }), issuerSerial]
48
+ });
49
+ return new asn1js.Sequence({ value: [new asn1js.Sequence({ value: [essCertId] })] });
50
+ }
51
+ async function buildCmsSignature(documentDigest, signer) {
52
+ const cert = parseCertificate(signer.certificate);
53
+ const chain = (signer.chain || []).map(parseCertificate);
54
+ const attributes = [
55
+ new pkijs.Attribute({ type: OID.contentType, values: [new asn1js.ObjectIdentifier({ value: OID.data })] }),
56
+ new pkijs.Attribute({
57
+ type: OID.messageDigest,
58
+ values: [new asn1js.OctetString({ valueHex: buffer(documentDigest) })]
59
+ }),
60
+ new pkijs.Attribute({
61
+ type: OID.signingCertificateV2,
62
+ values: [await signingCertificateV2(signer.certificate, cert)]
63
+ })
64
+ ].map((attribute) => ({ attribute, der: new Uint8Array(attribute.toSchema().toBER()) })).sort((a, b) => compareBytes(a.der, b.der)).map(({ attribute }) => attribute);
65
+ const toBeSigned = new Uint8Array(new asn1js.Set({ value: attributes.map((a) => a.toSchema()) }).toBER());
66
+ const signature = await signer.sign(toBeSigned);
67
+ const signerInfo = new pkijs.SignerInfo({
68
+ version: 1,
69
+ sid: new pkijs.IssuerAndSerialNumber({ issuer: cert.issuer, serialNumber: cert.serialNumber }),
70
+ digestAlgorithm: new pkijs.AlgorithmIdentifier({ algorithmId: OID.sha256 }),
71
+ signedAttrs: new pkijs.SignedAndUnsignedAttributes({ type: 0, attributes }),
72
+ signatureAlgorithm: new pkijs.AlgorithmIdentifier({ algorithmId: OID.sha256WithRSA, algorithmParams: new asn1js.Null() }),
73
+ signature: new asn1js.OctetString({ valueHex: buffer(signature) })
74
+ });
75
+ const signedData = new pkijs.SignedData({
76
+ version: 1,
77
+ encapContentInfo: new pkijs.EncapsulatedContentInfo({ eContentType: OID.data }),
78
+ digestAlgorithms: [new pkijs.AlgorithmIdentifier({ algorithmId: OID.sha256 })],
79
+ certificates: [cert, ...chain],
80
+ signerInfos: [signerInfo]
81
+ });
82
+ const contentInfo = new pkijs.ContentInfo({ contentType: OID.signedData, content: signedData.toSchema(true) });
83
+ return new Uint8Array(contentInfo.toSchema().toBER());
84
+ }
85
+ async function webCryptoSigner(identity) {
86
+ const key = await globalThis.crypto.subtle.importKey(
87
+ "pkcs8",
88
+ identity.privateKeyPkcs8,
89
+ { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
90
+ false,
91
+ ["sign"]
92
+ );
93
+ return {
94
+ certificate: identity.certificate,
95
+ chain: identity.chain,
96
+ sign: async (data) => new Uint8Array(await globalThis.crypto.subtle.sign("RSASSA-PKCS1-v1_5", key, data))
97
+ };
98
+ }
99
+ function nameValue(name, oid) {
100
+ const entry = name.typesAndValues.find((item) => item.type === oid);
101
+ return entry ? String(entry.value.valueBlock.value) : "";
102
+ }
103
+ function describeCertificate(der) {
104
+ const cert = parseCertificate(der);
105
+ const hex = (bytes) => Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join(":");
106
+ let keyBits = 0;
107
+ try {
108
+ const rsa = cert.subjectPublicKeyInfo.parsedKey;
109
+ const modulus = new Uint8Array(rsa?.modulus?.valueBlock?.valueHexView || []);
110
+ keyBits = (modulus[0] === 0 ? modulus.length - 1 : modulus.length) * 8;
111
+ } catch {
112
+ keyBits = 0;
113
+ }
114
+ return {
115
+ commonName: nameValue(cert.subject, OID.commonName),
116
+ organization: nameValue(cert.subject, OID.organization),
117
+ organizationalUnit: nameValue(cert.subject, OID.organizationalUnit),
118
+ email: nameValue(cert.subject, OID.email),
119
+ issuerCommonName: nameValue(cert.issuer, OID.commonName),
120
+ issuerOrganization: nameValue(cert.issuer, OID.organization),
121
+ serialNumber: hex(cert.serialNumber.valueBlock.valueHexView.slice().buffer),
122
+ notBefore: cert.notBefore.value,
123
+ notAfter: cert.notAfter.value,
124
+ selfSigned: cert.subject.isEqual(cert.issuer),
125
+ keyBits
126
+ };
127
+ }
128
+ var N = (name) => PDFName.of(name);
129
+ var DIGESTS = {
130
+ "1.3.14.3.2.26": "SHA-1",
131
+ [OID.sha256]: "SHA-256",
132
+ "2.16.840.1.101.3.4.2.2": "SHA-384",
133
+ "2.16.840.1.101.3.4.2.3": "SHA-512"
134
+ };
135
+ function text(value) {
136
+ return value instanceof PDFString || value instanceof PDFHexString ? value.decodeText() : "";
137
+ }
138
+ function parsePdfDate(value) {
139
+ const match = /^D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?([Zz]|[+-]\d{2}'?\d{2}'?)?/.exec(value || "");
140
+ if (!match) return null;
141
+ const [, y, mo = "01", d = "01", h = "00", mi = "00", s = "00", tz = "Z"] = match;
142
+ let offset = "Z";
143
+ if (/^[+-]/.test(tz)) {
144
+ const digits = tz.replace(/'/g, "");
145
+ offset = `${digits.slice(0, 3)}:${digits.slice(3, 5) || "00"}`;
146
+ }
147
+ const date = /* @__PURE__ */ new Date(`${y}-${mo}-${d}T${h}:${mi}:${s}${offset}`);
148
+ return Number.isNaN(date.getTime()) ? null : date;
149
+ }
150
+ function derLength(bytes) {
151
+ if (bytes.length < 2) return 0;
152
+ const first = bytes[1];
153
+ if (first < 128) return 2 + first;
154
+ const count = first & 127;
155
+ let length = 0;
156
+ for (let i = 0; i < count; i += 1) length = length * 256 + bytes[2 + i];
157
+ return 2 + count + length;
158
+ }
159
+ function isHexOrSpace(byte) {
160
+ return byte >= 48 && byte <= 57 || // 0-9
161
+ byte >= 65 && byte <= 70 || // A-F
162
+ byte >= 97 && byte <= 102 || // a-f
163
+ byte === 32 || byte === 10 || byte === 13 || byte === 9 || byte === 12 || byte === 0;
164
+ }
165
+ function cmsTime(value) {
166
+ if (value instanceof asn1js.UTCTime || value instanceof asn1js.GeneralizedTime) {
167
+ const date = value.toDate();
168
+ return Number.isNaN(date.getTime()) ? null : date;
169
+ }
170
+ return null;
171
+ }
172
+ function attributeValue(signerInfo, oid) {
173
+ return signerInfo.signedAttrs?.attributes.find((attribute) => attribute.type === oid)?.values[0];
174
+ }
175
+ async function checkSignature(file, name, dict) {
176
+ const check = {
177
+ fieldName: name,
178
+ subFilter: dict.lookup(N("SubFilter"))?.decodeText?.() ?? "",
179
+ intact: null,
180
+ coversWholeFile: false,
181
+ signer: null,
182
+ certificateValidAtSigning: null,
183
+ signingTime: parsePdfDate(text(dict.lookup(N("M")))),
184
+ reason: text(dict.lookup(N("Reason"))),
185
+ location: text(dict.lookup(N("Location"))),
186
+ problem: ""
187
+ };
188
+ const byteRange = dict.lookup(N("ByteRange"));
189
+ const contents = dict.lookup(N("Contents"));
190
+ const range = byteRange instanceof PDFArray ? byteRange.asArray().map((item) => item instanceof PDFNumber ? item.asNumber() : NaN) : [];
191
+ if (range.length !== 4 || range.some((value) => !Number.isInteger(value) || value < 0) || !(contents instanceof PDFHexString || contents instanceof PDFString)) {
192
+ check.problem = "The signature is incomplete or malformed.";
193
+ return check;
194
+ }
195
+ const [start, firstLength, secondStart, secondLength] = range;
196
+ const structureOk = start === 0 && firstLength < secondStart && secondStart + secondLength <= file.length && file[firstLength] === 60 && file[secondStart - 1] === 62 && // The unsigned gap must be exactly the /Contents hex string. Anything else
197
+ // in it (a signature-wrapping attack) would be unsigned content.
198
+ file.subarray(firstLength + 1, secondStart - 1).every(isHexOrSpace);
199
+ check.coversWholeFile = structureOk && secondStart + secondLength === file.length;
200
+ if (!structureOk) {
201
+ check.intact = false;
202
+ check.problem = "The signed byte range does not match the file.";
203
+ return check;
204
+ }
205
+ if (!/^(ETSI\.CAdES\.detached|adbe\.pkcs7\.detached)$/.test(check.subFilter)) {
206
+ check.problem = `The ${check.subFilter || "unknown"} signature format is not supported here.`;
207
+ return check;
208
+ }
209
+ const signed = new Uint8Array(firstLength + secondLength);
210
+ signed.set(file.subarray(0, firstLength), 0);
211
+ signed.set(file.subarray(secondStart, secondStart + secondLength), firstLength);
212
+ const raw = contents.asBytes();
213
+ const der = raw.subarray(0, derLength(raw));
214
+ let signedData;
215
+ try {
216
+ const contentInfo = pkijs.ContentInfo.fromBER(der.slice().buffer);
217
+ signedData = new pkijs.SignedData({ schema: contentInfo.content });
218
+ } catch {
219
+ check.intact = false;
220
+ check.problem = "The signature data could not be read.";
221
+ return check;
222
+ }
223
+ const signerInfo = signedData.signerInfos[0];
224
+ const sid = signerInfo?.sid;
225
+ const signerCert = (signedData.certificates || []).find(
226
+ (cert) => cert instanceof pkijs.Certificate && sid instanceof pkijs.IssuerAndSerialNumber && cert.issuer.isEqual(sid.issuer) && cert.serialNumber.isEqual(sid.serialNumber)
227
+ );
228
+ if (signerCert) {
229
+ check.signer = describeCertificate(new Uint8Array(signerCert.toSchema().toBER()));
230
+ }
231
+ const signingTimeAttr = attributeValue(signerInfo, OID.signingTime);
232
+ if (!check.signingTime) check.signingTime = cmsTime(signingTimeAttr);
233
+ if (check.signer && check.signingTime) {
234
+ check.certificateValidAtSigning = check.signingTime >= check.signer.notBefore && check.signingTime <= check.signer.notAfter;
235
+ }
236
+ const digestName = DIGESTS[signerInfo.digestAlgorithm.algorithmId];
237
+ if (!digestName) {
238
+ check.problem = "The signature uses an unsupported digest algorithm.";
239
+ return check;
240
+ }
241
+ try {
242
+ const result = await signedData.verify({
243
+ signer: 0,
244
+ data: signed.slice().buffer,
245
+ checkChain: false,
246
+ extendedMode: true
247
+ });
248
+ check.intact = Boolean(result.signatureVerified);
249
+ if (!check.intact) check.problem = "The document was changed after it was signed, or the signature is corrupt.";
250
+ } catch (error) {
251
+ check.intact = false;
252
+ check.problem = error?.code === 15 || /message digest/i.test(String(error?.message || error?.date || "")) ? "The document was changed after it was signed." : "The signature does not match the document or its certificate.";
253
+ }
254
+ return check;
255
+ }
256
+ async function verifyPdfSignatures(bytes) {
257
+ const file = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
258
+ let doc;
259
+ try {
260
+ doc = await PDFDocument.load(file, { ignoreEncryption: true, updateMetadata: false });
261
+ } catch {
262
+ throw new Error("This file is not a readable PDF.");
263
+ }
264
+ const checks = [];
265
+ for (const field of signatureFields(doc)) {
266
+ if (field.value) checks.push(await checkSignature(file, field.name, field.value));
267
+ }
268
+ return checks;
269
+ }
270
+
271
+ export { buildCmsSignature, describeCertificate, parsePdfDate, sha256, verifyPdfSignatures, webCryptoSigner };
272
+ //# sourceMappingURL=chunk-5OIMR74G.js.map
273
+ //# sourceMappingURL=chunk-5OIMR74G.js.map