@kitsy/pdf 0.0.0-stage → 0.2.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.
- package/CHANGELOG.md +32 -0
- package/LICENSE +201 -0
- package/NOTICE +16 -0
- package/README.md +256 -2
- package/THIRD_PARTY_NOTICES.md +48 -0
- package/dist/chunk-2K5X7L5J.js +43 -0
- package/dist/chunk-2K5X7L5J.js.map +1 -0
- package/dist/chunk-4EAYBJ6G.js +362 -0
- package/dist/chunk-4EAYBJ6G.js.map +1 -0
- package/dist/chunk-4LBJTDV5.js +165 -0
- package/dist/chunk-4LBJTDV5.js.map +1 -0
- package/dist/chunk-4ZEZ2GLD.js +353 -0
- package/dist/chunk-4ZEZ2GLD.js.map +1 -0
- package/dist/chunk-5OIMR74G.js +273 -0
- package/dist/chunk-5OIMR74G.js.map +1 -0
- package/dist/chunk-6NFNAKWA.js +257 -0
- package/dist/chunk-6NFNAKWA.js.map +1 -0
- package/dist/chunk-7MTEMQLL.js +47 -0
- package/dist/chunk-7MTEMQLL.js.map +1 -0
- package/dist/chunk-7V4SWTUK.js +83 -0
- package/dist/chunk-7V4SWTUK.js.map +1 -0
- package/dist/chunk-7XH7MFZ4.js +295 -0
- package/dist/chunk-7XH7MFZ4.js.map +1 -0
- package/dist/chunk-BK3MYSUS.js +61 -0
- package/dist/chunk-BK3MYSUS.js.map +1 -0
- package/dist/chunk-BLHNKR3S.js +35 -0
- package/dist/chunk-BLHNKR3S.js.map +1 -0
- package/dist/chunk-CCJ43SIX.js +342 -0
- package/dist/chunk-CCJ43SIX.js.map +1 -0
- package/dist/chunk-CNEDWATL.js +210 -0
- package/dist/chunk-CNEDWATL.js.map +1 -0
- package/dist/chunk-CYTWDJE2.js +152 -0
- package/dist/chunk-CYTWDJE2.js.map +1 -0
- package/dist/chunk-DKVOICYZ.js +45 -0
- package/dist/chunk-DKVOICYZ.js.map +1 -0
- package/dist/chunk-DPEJU4P6.js +79 -0
- package/dist/chunk-DPEJU4P6.js.map +1 -0
- package/dist/chunk-DQPABZTU.js +22 -0
- package/dist/chunk-DQPABZTU.js.map +1 -0
- package/dist/chunk-EKHEY3LI.js +46 -0
- package/dist/chunk-EKHEY3LI.js.map +1 -0
- package/dist/chunk-FIEYAKPI.js +421 -0
- package/dist/chunk-FIEYAKPI.js.map +1 -0
- package/dist/chunk-G467LFKC.js +191 -0
- package/dist/chunk-G467LFKC.js.map +1 -0
- package/dist/chunk-GLYWSVSB.js +250 -0
- package/dist/chunk-GLYWSVSB.js.map +1 -0
- package/dist/chunk-GXBIMUO7.js +135 -0
- package/dist/chunk-GXBIMUO7.js.map +1 -0
- package/dist/chunk-H6EERRCR.js +180 -0
- package/dist/chunk-H6EERRCR.js.map +1 -0
- package/dist/chunk-HDTM3LXD.js +35 -0
- package/dist/chunk-HDTM3LXD.js.map +1 -0
- package/dist/chunk-IGMX56C3.js +180 -0
- package/dist/chunk-IGMX56C3.js.map +1 -0
- package/dist/chunk-J4IRICCJ.js +74 -0
- package/dist/chunk-J4IRICCJ.js.map +1 -0
- package/dist/chunk-JXKQ7S6W.js +331 -0
- package/dist/chunk-JXKQ7S6W.js.map +1 -0
- package/dist/chunk-KY6DXS6S.js +34 -0
- package/dist/chunk-KY6DXS6S.js.map +1 -0
- package/dist/chunk-NOFGCYGS.js +22 -0
- package/dist/chunk-NOFGCYGS.js.map +1 -0
- package/dist/chunk-OSHUJRKD.js +131 -0
- package/dist/chunk-OSHUJRKD.js.map +1 -0
- package/dist/chunk-PMH7OWPF.js +40 -0
- package/dist/chunk-PMH7OWPF.js.map +1 -0
- package/dist/chunk-PRBKSX3A.js +299 -0
- package/dist/chunk-PRBKSX3A.js.map +1 -0
- package/dist/chunk-SVPUX7AM.js +48 -0
- package/dist/chunk-SVPUX7AM.js.map +1 -0
- package/dist/chunk-TR6HJ4NO.js +659 -0
- package/dist/chunk-TR6HJ4NO.js.map +1 -0
- package/dist/chunk-TV3E4YUF.js +69 -0
- package/dist/chunk-TV3E4YUF.js.map +1 -0
- package/dist/chunk-VCTN77K2.js +112 -0
- package/dist/chunk-VCTN77K2.js.map +1 -0
- package/dist/chunk-WEOPDD3P.js +120 -0
- package/dist/chunk-WEOPDD3P.js.map +1 -0
- package/dist/chunk-ZBZ7OC6I.js +228 -0
- package/dist/chunk-ZBZ7OC6I.js.map +1 -0
- package/dist/config-CJYo46X9.d.ts +19 -0
- package/dist/index.d.ts +517 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/react/certificate-sign.d.ts +5 -0
- package/dist/react/certificate-sign.js +14 -0
- package/dist/react/certificate-sign.js.map +1 -0
- package/dist/react/compress.d.ts +5 -0
- package/dist/react/compress.js +8 -0
- package/dist/react/compress.js.map +1 -0
- package/dist/react/fill-form.d.ts +5 -0
- package/dist/react/fill-form.js +8 -0
- package/dist/react/fill-form.js.map +1 -0
- package/dist/react/images-to-pdf.d.ts +5 -0
- package/dist/react/images-to-pdf.js +7 -0
- package/dist/react/images-to-pdf.js.map +1 -0
- package/dist/react/merge.d.ts +5 -0
- package/dist/react/merge.js +8 -0
- package/dist/react/merge.js.map +1 -0
- package/dist/react/metadata.d.ts +5 -0
- package/dist/react/metadata.js +8 -0
- package/dist/react/metadata.js.map +1 -0
- package/dist/react/organize.d.ts +5 -0
- package/dist/react/organize.js +12 -0
- package/dist/react/organize.js.map +1 -0
- package/dist/react/pdf-to-images.d.ts +5 -0
- package/dist/react/pdf-to-images.js +13 -0
- package/dist/react/pdf-to-images.js.map +1 -0
- package/dist/react/protect.d.ts +5 -0
- package/dist/react/protect.js +8 -0
- package/dist/react/protect.js.map +1 -0
- package/dist/react/sign.d.ts +5 -0
- package/dist/react/sign.js +13 -0
- package/dist/react/sign.js.map +1 -0
- package/dist/react/split.d.ts +5 -0
- package/dist/react/split.js +11 -0
- package/dist/react/split.js.map +1 -0
- package/dist/react/verify-signatures.d.ts +5 -0
- package/dist/react/verify-signatures.js +8 -0
- package/dist/react/verify-signatures.js.map +1 -0
- package/dist/react/watermark.d.ts +5 -0
- package/dist/react/watermark.js +10 -0
- package/dist/react/watermark.js.map +1 -0
- package/dist/react.d.ts +40 -0
- package/dist/react.js +40 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +9 -0
- package/dist/vite.js.map +1 -0
- package/package.json +163 -4
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { placementToVisual } from './chunk-SVPUX7AM.js';
|
|
2
|
+
import { describeCertificate, buildCmsSignature, sha256 } from './chunk-5OIMR74G.js';
|
|
3
|
+
import { visualSize, textOrigin } from './chunk-PMH7OWPF.js';
|
|
4
|
+
import { hasDigitalSignatures, signatureFields } from './chunk-7MTEMQLL.js';
|
|
5
|
+
import { loadPdf } from './chunk-EKHEY3LI.js';
|
|
6
|
+
import forge from 'node-forge/lib/forge.js';
|
|
7
|
+
import 'node-forge/lib/asn1.js';
|
|
8
|
+
import 'node-forge/lib/pki.js';
|
|
9
|
+
import 'node-forge/lib/pkcs12.js';
|
|
10
|
+
import { PDFString, PDFHexString, PDFDict, PDFArray, PDFNumber, PDFName, StandardFonts, pushGraphicsState, setStrokingRgbColor, setLineWidth, rectangle, stroke, setFillingRgbColor, beginText, setFontAndSize, moveText, showText, endText, popGraphicsState } from 'pdf-lib';
|
|
11
|
+
|
|
12
|
+
var CertificatePasswordError = class extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super("That password does not open this certificate file.");
|
|
15
|
+
this.name = "CertificatePasswordError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var toBinary = (bytes) => {
|
|
19
|
+
let out = "";
|
|
20
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
21
|
+
out += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
};
|
|
25
|
+
var fromBinary = (text) => Uint8Array.from(text, (char) => char.charCodeAt(0));
|
|
26
|
+
function parseKeepingCertificateBytes(p12Asn1, password) {
|
|
27
|
+
const derByCert = /* @__PURE__ */ new Map();
|
|
28
|
+
const original = forge.pki.certificateFromAsn1;
|
|
29
|
+
forge.pki.certificateFromAsn1 = (asn1, computeHash) => {
|
|
30
|
+
const cert = original(asn1, computeHash);
|
|
31
|
+
derByCert.set(cert, fromBinary(forge.asn1.toDer(asn1).getBytes()));
|
|
32
|
+
return cert;
|
|
33
|
+
};
|
|
34
|
+
try {
|
|
35
|
+
return { p12: forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, password), derByCert };
|
|
36
|
+
} finally {
|
|
37
|
+
forge.pki.certificateFromAsn1 = original;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function readPkcs12(bytes, password) {
|
|
41
|
+
const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
42
|
+
let p12Asn1;
|
|
43
|
+
try {
|
|
44
|
+
p12Asn1 = forge.asn1.fromDer(forge.util.createBuffer(toBinary(data)), false);
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error("This is not a .pfx / .p12 certificate file.");
|
|
47
|
+
}
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = parseKeepingCertificateBytes(p12Asn1, password);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const message = String(error?.message || "");
|
|
53
|
+
if (/MAC could not be verified|Invalid password|wrong password|decrypt/i.test(message)) {
|
|
54
|
+
throw new CertificatePasswordError();
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Could not read this certificate file (${message || "unknown format"}).`);
|
|
57
|
+
}
|
|
58
|
+
const { p12, derByCert } = parsed;
|
|
59
|
+
const keyBags = [
|
|
60
|
+
...p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag })[forge.pki.oids.pkcs8ShroudedKeyBag] || [],
|
|
61
|
+
...p12.getBags({ bagType: forge.pki.oids.keyBag })[forge.pki.oids.keyBag] || []
|
|
62
|
+
];
|
|
63
|
+
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag })[forge.pki.oids.certBag] || [];
|
|
64
|
+
const key = keyBags.find((bag) => bag.key)?.key;
|
|
65
|
+
if (!key) {
|
|
66
|
+
if (keyBags.length) {
|
|
67
|
+
throw new Error("Only RSA certificates are supported. This file holds a different key type.");
|
|
68
|
+
}
|
|
69
|
+
throw new Error("This file has no private key. Export the certificate together with its private key.");
|
|
70
|
+
}
|
|
71
|
+
const certs = certBags.filter((bag) => bag.cert);
|
|
72
|
+
const leaf = certs.find(
|
|
73
|
+
(bag) => bag.cert.publicKey?.n && bag.cert.publicKey.n.equals(key.n) && bag.cert.publicKey.e.equals(key.e)
|
|
74
|
+
);
|
|
75
|
+
if (!leaf) {
|
|
76
|
+
throw new Error("No certificate in this file matches its private key.");
|
|
77
|
+
}
|
|
78
|
+
const pkcs8 = forge.pki.wrapRsaPrivateKey(forge.pki.privateKeyToAsn1(key));
|
|
79
|
+
return {
|
|
80
|
+
certificate: derByCert.get(leaf.cert),
|
|
81
|
+
chain: certs.filter((bag) => bag !== leaf).map((bag) => derByCert.get(bag.cert)),
|
|
82
|
+
privateKeyPkcs8: fromBinary(forge.asn1.toDer(pkcs8).getBytes())
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
var N = (name) => PDFName.of(name);
|
|
86
|
+
var PLACEHOLDER = 1234567890;
|
|
87
|
+
var latin1 = new TextDecoder("latin1");
|
|
88
|
+
function pad(value) {
|
|
89
|
+
return String(value).padStart(2, "0");
|
|
90
|
+
}
|
|
91
|
+
function displayDate(date) {
|
|
92
|
+
const offset = -date.getTimezoneOffset();
|
|
93
|
+
const sign = offset >= 0 ? "+" : "-";
|
|
94
|
+
const abs = Math.abs(offset);
|
|
95
|
+
return `${date.getFullYear()}.${pad(date.getMonth() + 1)}.${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())} ${sign}${pad(Math.floor(abs / 60))}'${pad(abs % 60)}'`;
|
|
96
|
+
}
|
|
97
|
+
function uniqueFieldName(doc) {
|
|
98
|
+
const taken = new Set(doc.getForm().getFields().map((field) => field.getName()));
|
|
99
|
+
signatureFields(doc).forEach((field) => taken.add(field.name));
|
|
100
|
+
let index = 1;
|
|
101
|
+
while (taken.has(`Signature${index}`)) index += 1;
|
|
102
|
+
return `Signature${index}`;
|
|
103
|
+
}
|
|
104
|
+
function safeText(font, value) {
|
|
105
|
+
return Array.from(value).map((char) => {
|
|
106
|
+
try {
|
|
107
|
+
font.encodeText(char);
|
|
108
|
+
return char;
|
|
109
|
+
} catch {
|
|
110
|
+
return "?";
|
|
111
|
+
}
|
|
112
|
+
}).join("");
|
|
113
|
+
}
|
|
114
|
+
async function appearance(doc, page, box, lines, angle) {
|
|
115
|
+
const font = await doc.embedFont(StandardFonts.Helvetica);
|
|
116
|
+
const { width, height } = box;
|
|
117
|
+
const padding = Math.min(6, width * 0.05, height * 0.08);
|
|
118
|
+
const shown = lines.map((line) => safeText(font, line));
|
|
119
|
+
const widest = Math.max(...shown.map((line) => font.widthOfTextAtSize(line, 10)), 1);
|
|
120
|
+
const size = Math.max(4, Math.min(10, 10 * (width - padding * 2) / widest, (height - padding * 2) / (shown.length * 1.25)));
|
|
121
|
+
const operators = [
|
|
122
|
+
pushGraphicsState(),
|
|
123
|
+
setStrokingRgbColor(0.12, 0.35, 0.75),
|
|
124
|
+
setLineWidth(0.8),
|
|
125
|
+
rectangle(0.4, 0.4, width - 0.8, height - 0.8),
|
|
126
|
+
stroke(),
|
|
127
|
+
setFillingRgbColor(0.06, 0.09, 0.16),
|
|
128
|
+
beginText(),
|
|
129
|
+
setFontAndSize(N("F1"), size),
|
|
130
|
+
moveText(padding, height - padding - size)
|
|
131
|
+
];
|
|
132
|
+
shown.forEach((line, index) => {
|
|
133
|
+
if (index > 0) operators.push(moveText(0, -size * 1.25));
|
|
134
|
+
operators.push(showText(font.encodeText(line)));
|
|
135
|
+
});
|
|
136
|
+
operators.push(endText(), popGraphicsState());
|
|
137
|
+
const radians = angle * Math.PI / 180;
|
|
138
|
+
const cos = Math.round(Math.cos(radians));
|
|
139
|
+
const sin = Math.round(Math.sin(radians));
|
|
140
|
+
const stream = doc.context.formXObject(operators, {
|
|
141
|
+
BBox: [0, 0, width, height],
|
|
142
|
+
// Counter the page's /Rotate so the box reads upright on screen.
|
|
143
|
+
Matrix: [cos, sin, -sin, cos, 0, 0],
|
|
144
|
+
Resources: { Font: { F1: font.ref } }
|
|
145
|
+
});
|
|
146
|
+
return doc.context.register(stream);
|
|
147
|
+
}
|
|
148
|
+
function findPlaceholders(bytes, contentsLength) {
|
|
149
|
+
const text = latin1.decode(bytes);
|
|
150
|
+
const zeros = "0".repeat(contentsLength);
|
|
151
|
+
const contentsStart = text.lastIndexOf(`<${zeros}>`);
|
|
152
|
+
if (contentsStart < 0) throw new Error("Could not prepare the signature (contents placeholder not found).");
|
|
153
|
+
const contentsEnd = contentsStart + contentsLength + 2;
|
|
154
|
+
const pattern = /\/ByteRange\s*\[\s*0\s+1234567890\s+1234567890\s+1234567890\s*\]/g;
|
|
155
|
+
let match;
|
|
156
|
+
let last = null;
|
|
157
|
+
while (match = pattern.exec(text)) last = match;
|
|
158
|
+
if (!last) throw new Error("Could not prepare the signature (byte range placeholder not found).");
|
|
159
|
+
const open = last.index + last[0].indexOf("[");
|
|
160
|
+
const close = last.index + last[0].length - 1;
|
|
161
|
+
return { contentsStart, contentsEnd, byteRangeOpen: open, byteRangeClose: close };
|
|
162
|
+
}
|
|
163
|
+
async function signPdfWithCertificate(bytes, signer, options = {}) {
|
|
164
|
+
const doc = await loadPdf(bytes, { updateMetadata: false });
|
|
165
|
+
if (hasDigitalSignatures(doc)) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
"This PDF is already digitally signed. Adding another signature here would invalidate the existing one."
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
const certificate = describeCertificate(signer.certificate);
|
|
171
|
+
const signingTime = options.signingTime ?? /* @__PURE__ */ new Date();
|
|
172
|
+
const certBytes = signer.certificate.length + (signer.chain || []).reduce((sum, der) => sum + der.length, 0);
|
|
173
|
+
const reserved = certBytes + 8192;
|
|
174
|
+
const signatureDict = doc.context.obj({
|
|
175
|
+
Type: "Sig",
|
|
176
|
+
Filter: "Adobe.PPKLite",
|
|
177
|
+
SubFilter: "ETSI.CAdES.detached",
|
|
178
|
+
ByteRange: [0, PLACEHOLDER, PLACEHOLDER, PLACEHOLDER],
|
|
179
|
+
Contents: PDFHexString.of("0".repeat(reserved * 2)),
|
|
180
|
+
M: PDFString.fromDate(signingTime)
|
|
181
|
+
});
|
|
182
|
+
if (certificate.commonName) signatureDict.set(N("Name"), PDFHexString.fromText(certificate.commonName));
|
|
183
|
+
if (options.reason) signatureDict.set(N("Reason"), PDFHexString.fromText(options.reason));
|
|
184
|
+
if (options.location) signatureDict.set(N("Location"), PDFHexString.fromText(options.location));
|
|
185
|
+
if (options.contactInfo) signatureDict.set(N("ContactInfo"), PDFHexString.fromText(options.contactInfo));
|
|
186
|
+
const signatureRef = doc.context.register(signatureDict);
|
|
187
|
+
const pages = doc.getPages();
|
|
188
|
+
const pageIndex = options.visible ? options.visible.pageIndex : 0;
|
|
189
|
+
const page = pages[pageIndex];
|
|
190
|
+
if (!page) throw new Error(`Page ${pageIndex + 1} does not exist.`);
|
|
191
|
+
let rect = [0, 0, 0, 0];
|
|
192
|
+
let appearanceRef;
|
|
193
|
+
if (options.visible) {
|
|
194
|
+
const { visualWidth, visualHeight } = visualSize(page);
|
|
195
|
+
const box = placementToVisual(options.visible, visualWidth, visualHeight);
|
|
196
|
+
const a = textOrigin(page, box.vx, box.vy);
|
|
197
|
+
const b = textOrigin(page, box.vx + box.width, box.vy + box.height);
|
|
198
|
+
rect = [Math.min(a.x, b.x), Math.min(a.y, b.y), Math.max(a.x, b.x), Math.max(a.y, b.y)];
|
|
199
|
+
const lines = [
|
|
200
|
+
`Digitally signed by ${certificate.commonName || "(no name)"}`,
|
|
201
|
+
`Date: ${displayDate(signingTime)}`,
|
|
202
|
+
...options.reason ? [`Reason: ${options.reason}`] : [],
|
|
203
|
+
...options.location ? [`Location: ${options.location}`] : []
|
|
204
|
+
];
|
|
205
|
+
appearanceRef = await appearance(doc, page, box, lines, a.angle);
|
|
206
|
+
} else {
|
|
207
|
+
appearanceRef = doc.context.register(doc.context.formXObject([], { BBox: [0, 0, 0, 0] }));
|
|
208
|
+
}
|
|
209
|
+
const widget = doc.context.obj({
|
|
210
|
+
Type: "Annot",
|
|
211
|
+
Subtype: "Widget",
|
|
212
|
+
FT: "Sig",
|
|
213
|
+
T: PDFHexString.fromText(uniqueFieldName(doc)),
|
|
214
|
+
V: signatureRef,
|
|
215
|
+
F: 132,
|
|
216
|
+
// Print + Locked
|
|
217
|
+
P: page.ref,
|
|
218
|
+
Rect: rect,
|
|
219
|
+
AP: { N: appearanceRef }
|
|
220
|
+
});
|
|
221
|
+
const widgetRef = doc.context.register(widget);
|
|
222
|
+
page.node.addAnnot(widgetRef);
|
|
223
|
+
let acroForm = doc.catalog.lookup(N("AcroForm"));
|
|
224
|
+
if (!(acroForm instanceof PDFDict)) {
|
|
225
|
+
acroForm = doc.context.obj({ Fields: [] });
|
|
226
|
+
doc.catalog.set(N("AcroForm"), doc.context.register(acroForm));
|
|
227
|
+
}
|
|
228
|
+
const form = acroForm;
|
|
229
|
+
let fields = form.lookup(N("Fields"));
|
|
230
|
+
if (!(fields instanceof PDFArray)) {
|
|
231
|
+
fields = doc.context.obj([]);
|
|
232
|
+
form.set(N("Fields"), fields);
|
|
233
|
+
}
|
|
234
|
+
fields.push(widgetRef);
|
|
235
|
+
form.set(N("SigFlags"), PDFNumber.of(3));
|
|
236
|
+
const prepared = await doc.save({ useObjectStreams: false, updateFieldAppearances: false });
|
|
237
|
+
const { contentsStart, contentsEnd, byteRangeOpen, byteRangeClose } = findPlaceholders(prepared, reserved * 2);
|
|
238
|
+
const byteRange = [0, contentsStart, contentsEnd, prepared.length - contentsEnd];
|
|
239
|
+
const byteRangeText = ` ${byteRange.join(" ")}`.padEnd(byteRangeClose - byteRangeOpen - 1, " ");
|
|
240
|
+
const output = new Uint8Array(prepared);
|
|
241
|
+
output.set(new TextEncoder().encode(byteRangeText), byteRangeOpen + 1);
|
|
242
|
+
const signed = new Uint8Array(contentsStart + (output.length - contentsEnd));
|
|
243
|
+
signed.set(output.subarray(0, contentsStart), 0);
|
|
244
|
+
signed.set(output.subarray(contentsEnd), contentsStart);
|
|
245
|
+
const cms = await buildCmsSignature(await sha256(signed), signer);
|
|
246
|
+
if (cms.length > reserved) {
|
|
247
|
+
throw new Error("The signature is larger than the space reserved for it.");
|
|
248
|
+
}
|
|
249
|
+
const hex = Array.from(cms, (byte) => byte.toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
250
|
+
output.set(new TextEncoder().encode(hex), contentsStart + 1);
|
|
251
|
+
return output;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/certsign/heySigner.ts
|
|
255
|
+
var HEY_HUB_URL = "http://127.0.0.1:8525";
|
|
256
|
+
var CLIENT = "kitsy-pdf";
|
|
257
|
+
var MESSAGES = {
|
|
258
|
+
no_hub: "Couldn't reach hey on this computer. Make sure it's running (hey up dsc), and if your browser asked about access to your local network, allow it.",
|
|
259
|
+
not_set_up: "The token signer isn't set up yet. Run: hey up dsc",
|
|
260
|
+
not_running: "The token signer is stopped. Run: hey up dsc",
|
|
261
|
+
declined: "The signature was cancelled.",
|
|
262
|
+
not_found: "The certificate wasn't found. Is the token plugged in?",
|
|
263
|
+
pin_incorrect: "The PIN was not correct.",
|
|
264
|
+
pin_locked: "The token PIN is locked. Unlock it with the token's own software.",
|
|
265
|
+
unsupported_key: "Only RSA certificates are supported.",
|
|
266
|
+
no_dialog: "The signer couldn't show its confirmation dialog on this computer.",
|
|
267
|
+
failed: "The token couldn't sign."
|
|
268
|
+
};
|
|
269
|
+
var HeyDscError = class extends Error {
|
|
270
|
+
code;
|
|
271
|
+
constructor(code, message) {
|
|
272
|
+
super(message || MESSAGES[code]);
|
|
273
|
+
this.name = "HeyDscError";
|
|
274
|
+
this.code = code;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var base64ToBytes = (value) => Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
|
|
278
|
+
function bytesToBase64(bytes) {
|
|
279
|
+
let binary = "";
|
|
280
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
281
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
282
|
+
}
|
|
283
|
+
return btoa(binary);
|
|
284
|
+
}
|
|
285
|
+
async function call(path, init, options) {
|
|
286
|
+
const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
287
|
+
const url = `${(options.hubUrl ?? HEY_HUB_URL).replace(/\/+$/, "")}/svc/dsc${path}`;
|
|
288
|
+
let response;
|
|
289
|
+
try {
|
|
290
|
+
response = await doFetch(url, {
|
|
291
|
+
...init,
|
|
292
|
+
mode: "cors",
|
|
293
|
+
credentials: "omit",
|
|
294
|
+
cache: "no-store",
|
|
295
|
+
signal: options.signal,
|
|
296
|
+
headers: { "X-Hey-Client": CLIENT, ...init.headers || {} }
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (error?.name === "AbortError") throw error;
|
|
300
|
+
throw new HeyDscError("no_hub");
|
|
301
|
+
}
|
|
302
|
+
let body = null;
|
|
303
|
+
try {
|
|
304
|
+
body = await response.json();
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
if (response.ok) return body;
|
|
308
|
+
const code = body?.error;
|
|
309
|
+
if (code && code in MESSAGES) throw new HeyDscError(code, body?.message);
|
|
310
|
+
if (response.status === 404) throw new HeyDscError("not_set_up");
|
|
311
|
+
if (response.status === 503) throw new HeyDscError("not_running");
|
|
312
|
+
throw new HeyDscError("failed", body?.message);
|
|
313
|
+
}
|
|
314
|
+
async function findTokenCertificates(options = {}) {
|
|
315
|
+
const body = await call("/certificates", { method: "GET" }, options);
|
|
316
|
+
const out = [];
|
|
317
|
+
for (const item of body?.certificates ?? []) {
|
|
318
|
+
try {
|
|
319
|
+
const certificate = base64ToBytes(item.certificate);
|
|
320
|
+
out.push({
|
|
321
|
+
id: String(item.id),
|
|
322
|
+
certificate,
|
|
323
|
+
label: String(item.label || "Token"),
|
|
324
|
+
source: String(item.source || ""),
|
|
325
|
+
summary: describeCertificate(certificate)
|
|
326
|
+
});
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
function heyTokenSigner(token, { document = "", ...options } = {}) {
|
|
333
|
+
return {
|
|
334
|
+
certificate: token.certificate,
|
|
335
|
+
async sign(toBeSigned) {
|
|
336
|
+
const body = await call(
|
|
337
|
+
"/sign",
|
|
338
|
+
{
|
|
339
|
+
method: "POST",
|
|
340
|
+
headers: { "Content-Type": "application/json" },
|
|
341
|
+
body: JSON.stringify({ certificateId: token.id, data: bytesToBase64(toBeSigned), document })
|
|
342
|
+
},
|
|
343
|
+
options
|
|
344
|
+
);
|
|
345
|
+
if (!body?.signature) throw new HeyDscError("failed");
|
|
346
|
+
return base64ToBytes(body.signature);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export { CertificatePasswordError, HEY_HUB_URL, HeyDscError, displayDate, findTokenCertificates, heyTokenSigner, readPkcs12, signPdfWithCertificate };
|
|
352
|
+
//# sourceMappingURL=chunk-4ZEZ2GLD.js.map
|
|
353
|
+
//# sourceMappingURL=chunk-4ZEZ2GLD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/certsign/pkcs12.ts","../src/certsign/padesCore.ts","../src/certsign/heySigner.ts"],"names":[],"mappings":"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","file":"chunk-4ZEZ2GLD.js","sourcesContent":["// Read a .pfx / .p12 certificate file. node-forge is used only to decrypt\n// the container: Windows and many CA exports use legacy PKCS#12 encryption\n// (3DES / RC2 with SHA-1), which WebCrypto cannot decrypt. All signing and\n// verification happens in WebCrypto/pkijs, never in forge.\nimport forge from \"node-forge/lib/forge.js\";\nimport \"node-forge/lib/asn1.js\";\nimport \"node-forge/lib/pki.js\";\nimport \"node-forge/lib/pkcs12.js\";\n\nexport class CertificatePasswordError extends Error {\n constructor() {\n super(\"That password does not open this certificate file.\");\n this.name = \"CertificatePasswordError\";\n }\n}\n\nexport type CertificateIdentity = {\n /** DER of the signer certificate, byte-for-byte as stored in the file. */\n certificate: Uint8Array;\n /** DER of the other certificates in the file (issuers), if any. */\n chain: Uint8Array[];\n /** PKCS#8 DER of the RSA private key, for WebCrypto importKey. */\n privateKeyPkcs8: Uint8Array;\n};\n\nconst toBinary = (bytes: Uint8Array) => {\n let out = \"\";\n for (let i = 0; i < bytes.length; i += 0x8000) {\n out += String.fromCharCode(...bytes.subarray(i, i + 0x8000));\n }\n return out;\n};\n\nconst fromBinary = (text: string) => Uint8Array.from(text, (char) => char.charCodeAt(0));\n\n/**\n * forge rebuilds parts of a certificate when re-encoding it, which can change\n * its bytes (e.g. an omitted NULL algorithm parameter) and so break the CA's\n * signature on it. Capture each certificate's original ASN.1 while parsing and\n * DER-encode that instead.\n */\nfunction parseKeepingCertificateBytes(p12Asn1, password: string) {\n const derByCert = new Map();\n const original = forge.pki.certificateFromAsn1;\n forge.pki.certificateFromAsn1 = (asn1, computeHash) => {\n const cert = original(asn1, computeHash);\n derByCert.set(cert, fromBinary(forge.asn1.toDer(asn1).getBytes()));\n return cert;\n };\n try {\n return { p12: forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, password), derByCert };\n } finally {\n forge.pki.certificateFromAsn1 = original;\n }\n}\n\nexport function readPkcs12(bytes: ArrayBuffer | Uint8Array, password: string): CertificateIdentity {\n const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\n let p12Asn1;\n try {\n p12Asn1 = forge.asn1.fromDer(forge.util.createBuffer(toBinary(data)), false);\n } catch {\n throw new Error(\"This is not a .pfx / .p12 certificate file.\");\n }\n\n let parsed;\n try {\n parsed = parseKeepingCertificateBytes(p12Asn1, password);\n } catch (error) {\n const message = String(error?.message || \"\");\n if (/MAC could not be verified|Invalid password|wrong password|decrypt/i.test(message)) {\n throw new CertificatePasswordError();\n }\n throw new Error(`Could not read this certificate file (${message || \"unknown format\"}).`);\n }\n const { p12, derByCert } = parsed;\n\n const keyBags = [\n ...(p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag })[forge.pki.oids.pkcs8ShroudedKeyBag] || []),\n ...(p12.getBags({ bagType: forge.pki.oids.keyBag })[forge.pki.oids.keyBag] || []),\n ];\n const certBags = p12.getBags({ bagType: forge.pki.oids.certBag })[forge.pki.oids.certBag] || [];\n\n const key = keyBags.find((bag) => bag.key)?.key;\n if (!key) {\n if (keyBags.length) {\n throw new Error(\"Only RSA certificates are supported. This file holds a different key type.\");\n }\n throw new Error(\"This file has no private key. Export the certificate together with its private key.\");\n }\n\n const certs = certBags.filter((bag) => bag.cert);\n const leaf = certs.find(\n (bag) => bag.cert.publicKey?.n && bag.cert.publicKey.n.equals(key.n) && bag.cert.publicKey.e.equals(key.e)\n );\n if (!leaf) {\n throw new Error(\"No certificate in this file matches its private key.\");\n }\n\n const pkcs8 = forge.pki.wrapRsaPrivateKey(forge.pki.privateKeyToAsn1(key));\n return {\n certificate: derByCert.get(leaf.cert),\n chain: certs.filter((bag) => bag !== leaf).map((bag) => derByCert.get(bag.cert)),\n privateKeyPkcs8: fromBinary(forge.asn1.toDer(pkcs8).getBytes()),\n };\n}\n","// Sign a PDF with a certificate: PAdES baseline (ETSI.CAdES.detached).\nimport {\n PDFArray,\n PDFDict,\n PDFDocument,\n PDFHexString,\n PDFName,\n PDFNumber,\n PDFRef,\n PDFString,\n StandardFonts,\n beginText,\n endText,\n popGraphicsState,\n pushGraphicsState,\n rectangle,\n setFillingRgbColor,\n setFontAndSize,\n setLineWidth,\n setStrokingRgbColor,\n showText,\n moveText,\n stroke,\n} from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\nimport { textOrigin, visualSize } from \"../shared/pageGeometry\";\nimport { hasDigitalSignatures, signatureFields } from \"../shared/signatureFields\";\nimport { placementToVisual } from \"../sign/signPdfCore\";\nimport { buildCmsSignature, describeCertificate, sha256, type RawSigner } from \"./cms\";\n\nconst N = (name: string) => PDFName.of(name);\nconst PLACEHOLDER = 1234567890;\nconst latin1 = new TextDecoder(\"latin1\");\n\nexport type VisibleSignature = {\n pageIndex: number;\n /** Fractions (0-1) of the visible page, from its top-left corner. */\n x: number;\n y: number;\n width: number;\n height: number;\n};\n\nexport type CertificateSignOptions = {\n reason?: string;\n location?: string;\n contactInfo?: string;\n /** Omit for an invisible signature. */\n visible?: VisibleSignature | null;\n signingTime?: Date;\n};\n\nfunction pad(value: number) {\n return String(value).padStart(2, \"0\");\n}\n\n/** \"2026.11.01 14:05:09 +05'30'\" in the signer's local time. */\nexport function displayDate(date: Date) {\n const offset = -date.getTimezoneOffset();\n const sign = offset >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offset);\n return (\n `${date.getFullYear()}.${pad(date.getMonth() + 1)}.${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())} ` +\n `${sign}${pad(Math.floor(abs / 60))}'${pad(abs % 60)}'`\n );\n}\n\nfunction uniqueFieldName(doc: PDFDocument) {\n const taken = new Set(doc.getForm().getFields().map((field) => field.getName()));\n signatureFields(doc).forEach((field) => taken.add(field.name));\n let index = 1;\n while (taken.has(`Signature${index}`)) index += 1;\n return `Signature${index}`;\n}\n\n/** Text the standard Helvetica font can encode; anything else becomes \"?\". */\nfunction safeText(font, value: string) {\n return Array.from(value)\n .map((char) => {\n try {\n font.encodeText(char);\n return char;\n } catch {\n return \"?\";\n }\n })\n .join(\"\");\n}\n\nasync function appearance(doc: PDFDocument, page, box, lines: string[], angle: number) {\n const font = await doc.embedFont(StandardFonts.Helvetica);\n const { width, height } = box;\n const padding = Math.min(6, width * 0.05, height * 0.08);\n const shown = lines.map((line) => safeText(font, line));\n const widest = Math.max(...shown.map((line) => font.widthOfTextAtSize(line, 10)), 1);\n const size = Math.max(4, Math.min(10, (10 * (width - padding * 2)) / widest, (height - padding * 2) / (shown.length * 1.25)));\n\n const operators = [\n pushGraphicsState(),\n setStrokingRgbColor(0.12, 0.35, 0.75),\n setLineWidth(0.8),\n rectangle(0.4, 0.4, width - 0.8, height - 0.8),\n stroke(),\n setFillingRgbColor(0.06, 0.09, 0.16),\n beginText(),\n setFontAndSize(N(\"F1\"), size),\n moveText(padding, height - padding - size),\n ];\n shown.forEach((line, index) => {\n if (index > 0) operators.push(moveText(0, -size * 1.25));\n operators.push(showText(font.encodeText(line)));\n });\n operators.push(endText(), popGraphicsState());\n\n const radians = (angle * Math.PI) / 180;\n const cos = Math.round(Math.cos(radians));\n const sin = Math.round(Math.sin(radians));\n const stream = doc.context.formXObject(operators, {\n BBox: [0, 0, width, height],\n // Counter the page's /Rotate so the box reads upright on screen.\n Matrix: [cos, sin, -sin, cos, 0, 0],\n Resources: { Font: { F1: font.ref } },\n });\n return doc.context.register(stream);\n}\n\nfunction findPlaceholders(bytes: Uint8Array, contentsLength: number) {\n const text = latin1.decode(bytes);\n const zeros = \"0\".repeat(contentsLength);\n const contentsStart = text.lastIndexOf(`<${zeros}>`);\n if (contentsStart < 0) throw new Error(\"Could not prepare the signature (contents placeholder not found).\");\n const contentsEnd = contentsStart + contentsLength + 2;\n\n const pattern = /\\/ByteRange\\s*\\[\\s*0\\s+1234567890\\s+1234567890\\s+1234567890\\s*\\]/g;\n let match;\n let last = null;\n while ((match = pattern.exec(text))) last = match;\n if (!last) throw new Error(\"Could not prepare the signature (byte range placeholder not found).\");\n const open = last.index + last[0].indexOf(\"[\");\n const close = last.index + last[0].length - 1;\n return { contentsStart, contentsEnd, byteRangeOpen: open, byteRangeClose: close };\n}\n\n/**\n * Add a digital signature. Rejects PDFs that are already signed: rewriting\n * the file (as pdf-lib does) would invalidate the existing signature.\n */\nexport async function signPdfWithCertificate(\n bytes: ArrayBuffer | Uint8Array,\n signer: RawSigner,\n options: CertificateSignOptions = {}\n) {\n const doc = await loadPdf(bytes, { updateMetadata: false });\n if (hasDigitalSignatures(doc)) {\n throw new Error(\n \"This PDF is already digitally signed. Adding another signature here would invalidate the existing one.\"\n );\n }\n\n const certificate = describeCertificate(signer.certificate);\n const signingTime = options.signingTime ?? new Date();\n const certBytes = signer.certificate.length + (signer.chain || []).reduce((sum, der) => sum + der.length, 0);\n // Room for the certificates, signature, attributes, and a future timestamp.\n const reserved = certBytes + 8192;\n\n const signatureDict = doc.context.obj({\n Type: \"Sig\",\n Filter: \"Adobe.PPKLite\",\n SubFilter: \"ETSI.CAdES.detached\",\n ByteRange: [0, PLACEHOLDER, PLACEHOLDER, PLACEHOLDER],\n Contents: PDFHexString.of(\"0\".repeat(reserved * 2)),\n M: PDFString.fromDate(signingTime),\n }) as PDFDict;\n if (certificate.commonName) signatureDict.set(N(\"Name\"), PDFHexString.fromText(certificate.commonName));\n if (options.reason) signatureDict.set(N(\"Reason\"), PDFHexString.fromText(options.reason));\n if (options.location) signatureDict.set(N(\"Location\"), PDFHexString.fromText(options.location));\n if (options.contactInfo) signatureDict.set(N(\"ContactInfo\"), PDFHexString.fromText(options.contactInfo));\n const signatureRef = doc.context.register(signatureDict);\n\n const pages = doc.getPages();\n const pageIndex = options.visible ? options.visible.pageIndex : 0;\n const page = pages[pageIndex];\n if (!page) throw new Error(`Page ${pageIndex + 1} does not exist.`);\n\n let rect = [0, 0, 0, 0];\n let appearanceRef: PDFRef;\n if (options.visible) {\n const { visualWidth, visualHeight } = visualSize(page);\n const box = placementToVisual(options.visible, visualWidth, visualHeight);\n const a = textOrigin(page, box.vx, box.vy);\n const b = textOrigin(page, box.vx + box.width, box.vy + box.height);\n rect = [Math.min(a.x, b.x), Math.min(a.y, b.y), Math.max(a.x, b.x), Math.max(a.y, b.y)];\n const lines = [\n `Digitally signed by ${certificate.commonName || \"(no name)\"}`,\n `Date: ${displayDate(signingTime)}`,\n ...(options.reason ? [`Reason: ${options.reason}`] : []),\n ...(options.location ? [`Location: ${options.location}`] : []),\n ];\n appearanceRef = await appearance(doc, page, box, lines, a.angle);\n } else {\n appearanceRef = doc.context.register(doc.context.formXObject([], { BBox: [0, 0, 0, 0] }));\n }\n\n const widget = doc.context.obj({\n Type: \"Annot\",\n Subtype: \"Widget\",\n FT: \"Sig\",\n T: PDFHexString.fromText(uniqueFieldName(doc)),\n V: signatureRef,\n F: 132, // Print + Locked\n P: page.ref,\n Rect: rect,\n AP: { N: appearanceRef },\n });\n const widgetRef = doc.context.register(widget);\n page.node.addAnnot(widgetRef);\n\n let acroForm = doc.catalog.lookup(N(\"AcroForm\"));\n if (!(acroForm instanceof PDFDict)) {\n acroForm = doc.context.obj({ Fields: [] }) as PDFDict;\n doc.catalog.set(N(\"AcroForm\"), doc.context.register(acroForm));\n }\n const form = acroForm as PDFDict;\n let fields = form.lookup(N(\"Fields\"));\n if (!(fields instanceof PDFArray)) {\n fields = doc.context.obj([]);\n form.set(N(\"Fields\"), fields);\n }\n (fields as PDFArray).push(widgetRef);\n form.set(N(\"SigFlags\"), PDFNumber.of(3));\n\n // No object streams: the signature dictionary must stay plain text so its\n // placeholders can be found and filled in place.\n const prepared = await doc.save({ useObjectStreams: false, updateFieldAppearances: false });\n const { contentsStart, contentsEnd, byteRangeOpen, byteRangeClose } = findPlaceholders(prepared, reserved * 2);\n\n const byteRange = [0, contentsStart, contentsEnd, prepared.length - contentsEnd];\n const byteRangeText = ` ${byteRange.join(\" \")}`.padEnd(byteRangeClose - byteRangeOpen - 1, \" \");\n const output = new Uint8Array(prepared);\n output.set(new TextEncoder().encode(byteRangeText), byteRangeOpen + 1);\n\n const signed = new Uint8Array(contentsStart + (output.length - contentsEnd));\n signed.set(output.subarray(0, contentsStart), 0);\n signed.set(output.subarray(contentsEnd), contentsStart);\n const cms = await buildCmsSignature(await sha256(signed), signer);\n if (cms.length > reserved) {\n throw new Error(\"The signature is larger than the space reserved for it.\");\n }\n const hex = Array.from(cms, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\").toUpperCase();\n output.set(new TextEncoder().encode(hex), contentsStart + 1);\n\n return output;\n}\n","// Sign with a DSC on a USB token through hey (https://github.com/kitsyai/hey)\n// and its dsc service (https://github.com/kitsyai/dsc). The page never sees\n// the private key: dsc asks the user in a native dialog, and the token signs.\nimport type { RawSigner } from \"./cms\";\nimport { describeCertificate, type CertificateSummary } from \"./cms\";\n\nexport const HEY_HUB_URL = \"http://127.0.0.1:8525\";\nconst CLIENT = \"kitsy-pdf\";\n\nexport type TokenCertificate = {\n id: string;\n certificate: Uint8Array;\n /** Where the key lives, e.g. the token label or \"Windows certificate store\". */\n label: string;\n source: string;\n summary: CertificateSummary;\n};\n\nexport type HeyDscErrorCode =\n | \"no_hub\" // hey's hub is not running (or the browser blocked the request)\n | \"not_set_up\" // the hub runs, but dsc was never started on this computer\n | \"not_running\" // dsc was set up but is stopped\n | \"declined\"\n | \"not_found\"\n | \"pin_incorrect\"\n | \"pin_locked\"\n | \"unsupported_key\"\n | \"no_dialog\"\n | \"failed\";\n\nconst MESSAGES: Record<HeyDscErrorCode, string> = {\n no_hub:\n \"Couldn't reach hey on this computer. Make sure it's running (hey up dsc), and if your browser asked about access to your local network, allow it.\",\n not_set_up: \"The token signer isn't set up yet. Run: hey up dsc\",\n not_running: \"The token signer is stopped. Run: hey up dsc\",\n declined: \"The signature was cancelled.\",\n not_found: \"The certificate wasn't found. Is the token plugged in?\",\n pin_incorrect: \"The PIN was not correct.\",\n pin_locked: \"The token PIN is locked. Unlock it with the token's own software.\",\n unsupported_key: \"Only RSA certificates are supported.\",\n no_dialog: \"The signer couldn't show its confirmation dialog on this computer.\",\n failed: \"The token couldn't sign.\",\n};\n\nexport class HeyDscError extends Error {\n code: HeyDscErrorCode;\n constructor(code: HeyDscErrorCode, message?: string) {\n super(message || MESSAGES[code]);\n this.name = \"HeyDscError\";\n this.code = code;\n }\n}\n\ntype Options = { hubUrl?: string; fetch?: typeof fetch; signal?: AbortSignal };\n\nconst base64ToBytes = (value: string) => Uint8Array.from(atob(value), (c) => c.charCodeAt(0));\n\nfunction bytesToBase64(bytes: Uint8Array) {\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += 0x8000) {\n binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));\n }\n return btoa(binary);\n}\n\nasync function call(path: string, init: RequestInit, options: Options) {\n const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n const url = `${(options.hubUrl ?? HEY_HUB_URL).replace(/\\/+$/, \"\")}/svc/dsc${path}`;\n let response: Response;\n try {\n response = await doFetch(url, {\n ...init,\n mode: \"cors\",\n credentials: \"omit\",\n cache: \"no-store\",\n signal: options.signal,\n headers: { \"X-Hey-Client\": CLIENT, ...(init.headers || {}) },\n });\n } catch (error) {\n if (error?.name === \"AbortError\") throw error;\n throw new HeyDscError(\"no_hub\");\n }\n let body: any = null;\n try {\n body = await response.json();\n } catch {\n // The hub answers 404/503 with plain text.\n }\n if (response.ok) return body;\n const code = body?.error as HeyDscErrorCode | undefined;\n if (code && code in MESSAGES) throw new HeyDscError(code, body?.message);\n if (response.status === 404) throw new HeyDscError(\"not_set_up\");\n if (response.status === 503) throw new HeyDscError(\"not_running\");\n throw new HeyDscError(\"failed\", body?.message);\n}\n\n/** Lists the signing certificates on connected tokens. No PIN is asked. */\nexport async function findTokenCertificates(options: Options = {}): Promise<TokenCertificate[]> {\n const body = await call(\"/certificates\", { method: \"GET\" }, options);\n const out: TokenCertificate[] = [];\n for (const item of body?.certificates ?? []) {\n try {\n const certificate = base64ToBytes(item.certificate);\n out.push({\n id: String(item.id),\n certificate,\n label: String(item.label || \"Token\"),\n source: String(item.source || \"\"),\n summary: describeCertificate(certificate),\n });\n } catch {\n // Skip anything that isn't a readable certificate.\n }\n }\n return out;\n}\n\n/**\n * A RawSigner backed by the token. Each sign() call makes dsc show its\n * consent dialog (and, on macOS/Linux, ask for the PIN); `document` is the\n * file name shown there.\n */\nexport function heyTokenSigner(token: TokenCertificate, { document = \"\", ...options }: Options & { document?: string } = {}): RawSigner {\n return {\n certificate: token.certificate,\n async sign(toBeSigned: Uint8Array) {\n const body = await call(\n \"/sign\",\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ certificateId: token.id, data: bytesToBase64(toBeSigned), document }),\n },\n options\n );\n if (!body?.signature) throw new HeyDscError(\"failed\");\n return base64ToBytes(body.signature);\n },\n };\n}\n"]}
|