@embedpdf/core-signature 3.0.0-next.13

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/dist/index.cjs ADDED
@@ -0,0 +1,886 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let asn1js = require("asn1js");
25
+ asn1js = __toESM(asn1js, 1);
26
+ let pkijs = require("pkijs");
27
+ pkijs = __toESM(pkijs, 1);
28
+ //#region src/cms/engine.ts
29
+ let ensured = false;
30
+ /**
31
+ * PKI.js needs a WebCrypto engine. Browsers and Node ≥ 19 expose
32
+ * `globalThis.crypto`; register it once, lazily, so importing this package
33
+ * has no side effect.
34
+ */
35
+ function ensureEngine() {
36
+ if (!ensured) {
37
+ ensured = true;
38
+ if (!pkijs.getCrypto()) {
39
+ const webcrypto = globalThis.crypto;
40
+ if (!webcrypto?.subtle) throw new Error("@embedpdf/core-signature needs WebCrypto (globalThis.crypto.subtle)");
41
+ pkijs.setEngine("embedpdf", new pkijs.CryptoEngine({
42
+ name: "embedpdf",
43
+ crypto: webcrypto
44
+ }));
45
+ }
46
+ }
47
+ return pkijs.getCrypto(true);
48
+ }
49
+ function toArrayBuffer(view) {
50
+ const copy = new Uint8Array(view.byteLength);
51
+ copy.set(view);
52
+ return copy.buffer;
53
+ }
54
+ function bytesEqual(a, b) {
55
+ if (a.byteLength !== b.byteLength) return false;
56
+ for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
57
+ return true;
58
+ }
59
+ //#endregion
60
+ //#region src/cms/oids.ts
61
+ const OID = {
62
+ data: "1.2.840.113549.1.7.1",
63
+ signedData: "1.2.840.113549.1.7.2",
64
+ contentType: "1.2.840.113549.1.9.3",
65
+ messageDigest: "1.2.840.113549.1.9.4",
66
+ signingTime: "1.2.840.113549.1.9.5",
67
+ signingCertificateV2: "1.2.840.113549.1.9.16.2.47",
68
+ timestampToken: "1.2.840.113549.1.9.16.2.14",
69
+ sha1: "1.3.14.3.2.26",
70
+ sha256: "2.16.840.1.101.3.4.2.1",
71
+ sha384: "2.16.840.1.101.3.4.2.2",
72
+ sha512: "2.16.840.1.101.3.4.2.3",
73
+ rsaEncryption: "1.2.840.113549.1.1.1",
74
+ sha256WithRsa: "1.2.840.113549.1.1.11",
75
+ sha384WithRsa: "1.2.840.113549.1.1.12",
76
+ sha512WithRsa: "1.2.840.113549.1.1.13",
77
+ rsaPss: "1.2.840.113549.1.1.10",
78
+ mgf1: "1.2.840.113549.1.1.8",
79
+ ecdsaSha256: "1.2.840.10045.4.3.2",
80
+ ecdsaSha384: "1.2.840.10045.4.3.3",
81
+ ecdsaSha512: "1.2.840.10045.4.3.4",
82
+ subjectKeyIdentifier: "2.5.29.14",
83
+ keyUsage: "2.5.29.15",
84
+ basicConstraints: "2.5.29.19",
85
+ commonName: "2.5.4.3"
86
+ };
87
+ const DIGEST_BY_OID = {
88
+ [OID.sha1]: "sha1",
89
+ [OID.sha256]: "sha256",
90
+ [OID.sha384]: "sha384",
91
+ [OID.sha512]: "sha512"
92
+ };
93
+ const OID_BY_DIGEST = {
94
+ sha1: OID.sha1,
95
+ sha256: OID.sha256,
96
+ sha384: OID.sha384,
97
+ sha512: OID.sha512
98
+ };
99
+ /** WebCrypto hash names. */
100
+ const WEBCRYPTO_HASH = {
101
+ sha1: "SHA-1",
102
+ sha256: "SHA-256",
103
+ sha384: "SHA-384",
104
+ sha512: "SHA-512"
105
+ };
106
+ const DIGEST_LENGTH = {
107
+ sha1: 20,
108
+ sha256: 32,
109
+ sha384: 48,
110
+ sha512: 64
111
+ };
112
+ //#endregion
113
+ //#region src/cms/parse.ts
114
+ var CmsError = class extends Error {
115
+ constructor(reason, message) {
116
+ super(message);
117
+ this.reason = reason;
118
+ this.name = "CmsError";
119
+ }
120
+ };
121
+ /** Parse a detached CMS SignedData with exactly one signer. Throws `CmsError`. */
122
+ function parseDetachedCms(cms) {
123
+ return parseCmsInternal(cms).parsed;
124
+ }
125
+ function parseCmsInternal(cms) {
126
+ if (cms.byteLength === 0) throw new CmsError("malformed", "empty CMS");
127
+ const asn1 = asn1js.fromBER(toArrayBuffer(cms));
128
+ if (asn1.offset === -1) throw new CmsError("malformed", "CMS is not valid BER");
129
+ let contentInfo;
130
+ let signedData;
131
+ try {
132
+ contentInfo = new pkijs.ContentInfo({ schema: asn1.result });
133
+ if (contentInfo.contentType !== OID.signedData) throw new CmsError("unsupported", `CMS content type ${contentInfo.contentType} is not SignedData`);
134
+ signedData = new pkijs.SignedData({ schema: contentInfo.content });
135
+ } catch (err) {
136
+ if (err instanceof CmsError) throw err;
137
+ throw new CmsError("malformed", `CMS does not parse as SignedData: ${err.message}`);
138
+ }
139
+ if (signedData.encapContentInfo.eContent) throw new CmsError("unsupported", "CMS carries encapsulated content; a PDF signature is detached");
140
+ if (signedData.signerInfos.length !== 1) throw new CmsError("unsupported", `CMS has ${signedData.signerInfos.length} signers; exactly one is expected`);
141
+ const signerInfo = signedData.signerInfos[0];
142
+ const digestAlgorithm = DIGEST_BY_OID[signerInfo.digestAlgorithm.algorithmId];
143
+ if (!digestAlgorithm) throw new CmsError("unsupported", `digest algorithm ${signerInfo.digestAlgorithm.algorithmId}`);
144
+ const certificates = (signedData.certificates ?? []).filter((c) => c instanceof pkijs.Certificate);
145
+ const signerCertificate = findSignerCertificate(signerInfo, certificates);
146
+ if (!signerCertificate) throw new CmsError("malformed", "the signer certificate is not carried by the CMS");
147
+ const signedAttrs = signerInfo.signedAttrs?.attributes ?? [];
148
+ if (signedAttrs.length === 0) throw new CmsError("unsupported", "CMS has no signed attributes; a PDF signature needs message-digest");
149
+ const attr = (type) => signedAttrs.find((a) => a.type === type);
150
+ const contentType = attr(OID.contentType);
151
+ if (contentType && contentType.values[0]?.valueBlock?.toString?.() !== OID.data) {
152
+ const oid = contentType.values[0];
153
+ if (!oid || oid.valueBlock.toString() !== OID.data) throw new CmsError("unsupported", "CMS content-type attribute is not id-data");
154
+ }
155
+ const messageDigestValue = attr(OID.messageDigest)?.values[0];
156
+ if (!messageDigestValue || !(messageDigestValue instanceof asn1js.OctetString)) throw new CmsError("malformed", "CMS has no message-digest attribute");
157
+ const messageDigest = new Uint8Array(messageDigestValue.valueBlock.valueHexView);
158
+ let signingTime = null;
159
+ const timeValue = attr(OID.signingTime)?.values[0];
160
+ if (timeValue instanceof asn1js.UTCTime || timeValue instanceof asn1js.GeneralizedTime) signingTime = timeValue.toDate();
161
+ const essAttr = attr(OID.signingCertificateV2);
162
+ let essCertHash = null;
163
+ if (essAttr) {
164
+ const members = ((essAttr.values[0]?.valueBlock?.value?.[0])?.valueBlock?.value?.[0])?.valueBlock?.value ?? [];
165
+ let cursor = 0;
166
+ const head = members[0];
167
+ if (head instanceof asn1js.Sequence && head.valueBlock.value[0] instanceof asn1js.ObjectIdentifier) {
168
+ if (head.valueBlock.value[0].valueBlock.toString() !== OID.sha256) throw new CmsError("unsupported", "signing-certificate-v2 uses a hash other than SHA-256");
169
+ cursor = 1;
170
+ }
171
+ const hash = members[cursor];
172
+ if (!(hash instanceof asn1js.OctetString)) throw new CmsError("malformed", "signing-certificate-v2 carries no certHash");
173
+ essCertHash = new Uint8Array(hash.valueBlock.valueHexView);
174
+ }
175
+ let timestampToken = null;
176
+ const tokenAttr = signerInfo.unsignedAttrs?.attributes.find((a) => a.type === OID.timestampToken);
177
+ if (tokenAttr?.values[0]) timestampToken = new Uint8Array(tokenAttr.values[0].toBER(false));
178
+ const ordered = [signerCertificate, ...certificates.filter((c) => c !== signerCertificate)];
179
+ return {
180
+ parsed: {
181
+ digestAlgorithm,
182
+ messageDigest,
183
+ signingTime,
184
+ signerCertificate: certDer(signerCertificate),
185
+ certificates: ordered.map(certDer),
186
+ signatureAlgorithm: signerInfo.signatureAlgorithm.algorithmId,
187
+ signingCertificateV2: essAttr !== void 0,
188
+ timestampToken
189
+ },
190
+ signedData,
191
+ signerInfo,
192
+ signerCertificate,
193
+ certificates: ordered,
194
+ essCertHash
195
+ };
196
+ }
197
+ function certDer(cert) {
198
+ return new Uint8Array(cert.toSchema(true).toBER(false));
199
+ }
200
+ function findSignerCertificate(signerInfo, certificates) {
201
+ const sid = signerInfo.sid;
202
+ if (sid instanceof pkijs.IssuerAndSerialNumber) {
203
+ for (const cert of certificates) if (cert.issuer.isEqual(sid.issuer) && bytesEqual(new Uint8Array(cert.serialNumber.valueBlock.valueHexView), new Uint8Array(sid.serialNumber.valueBlock.valueHexView))) return cert;
204
+ return null;
205
+ }
206
+ const ski = sid?.valueBlock?.valueHexView;
207
+ if (!ski) return null;
208
+ for (const cert of certificates) {
209
+ const ext = cert.extensions?.find((e) => e.extnID === OID.subjectKeyIdentifier);
210
+ const value = ext ? asn1js.fromBER(ext.extnValue.valueBlock.valueHexView) : null;
211
+ const inner = value && value.offset !== -1 ? value.result : null;
212
+ if (inner && bytesEqual(new Uint8Array(inner.valueBlock.valueHexView), new Uint8Array(ski))) return cert;
213
+ }
214
+ return null;
215
+ }
216
+ //#endregion
217
+ //#region src/cms/verify.ts
218
+ /** The DER of the signed attributes as they are signed: re-tagged as a SET. */
219
+ function signedAttributesDer(signerInfo) {
220
+ const set = new asn1js.Set({ value: (signerInfo.signedAttrs?.attributes ?? []).map((a) => a.toSchema()) });
221
+ return new Uint8Array(set.toBER(false));
222
+ }
223
+ /**
224
+ * Does the CMS's signature verify with the certificate it names, over the
225
+ * attributes it signed? Says nothing about the content: the message digest
226
+ * is compared by the caller (`validateSignatures`, `verifyForCompletion`).
227
+ * `unsupported` means the algorithm is unknown to this engine, which is
228
+ * never `invalid`.
229
+ */
230
+ async function verifyCmsSignature(cms) {
231
+ let internal;
232
+ try {
233
+ internal = cms instanceof Uint8Array ? parseCmsInternal(cms) : cms;
234
+ } catch (err) {
235
+ return err instanceof CmsError && err.reason === "unsupported" ? "unsupported" : "invalid";
236
+ }
237
+ const engine = ensureEngine();
238
+ const { signerInfo, signerCertificate, parsed, essCertHash } = internal;
239
+ if (essCertHash) {
240
+ if (!bytesEqual(new Uint8Array(await engine.digest("SHA-256", toArrayBuffer(parsed.signerCertificate))), essCertHash)) return "invalid";
241
+ }
242
+ try {
243
+ return await engine.verifyWithPublicKey(toArrayBuffer(signedAttributesDer(signerInfo)), signerInfo.signature, signerCertificate.subjectPublicKeyInfo, signerInfo.signatureAlgorithm, WEBCRYPTO_HASH[parsed.digestAlgorithm]) ? "valid" : "invalid";
244
+ } catch (err) {
245
+ const message = err.message ?? "";
246
+ return /unsupported|unknown|not supported|not implemented/i.test(message) ? "unsupported" : "invalid";
247
+ }
248
+ }
249
+ //#endregion
250
+ //#region src/cms/build.ts
251
+ /**
252
+ * Build a detached CMS SignedData around a raw signature: content-type,
253
+ * message-digest, and for CAdES-B the ESS signing-certificate-v2 attribute;
254
+ * the signer's certificate chain; one SignerInfo identified by issuer and
255
+ * serial number.
256
+ */
257
+ async function buildDetachedCms(input) {
258
+ const engine = ensureEngine();
259
+ if (input.digest.byteLength !== DIGEST_LENGTH[input.hash]) throw new Error(`digest is ${input.digest.byteLength} bytes; ${input.hash} needs ${DIGEST_LENGTH[input.hash]}`);
260
+ if (input.signer.hash !== input.hash) throw new Error(`the signer hashes with ${input.signer.hash}, the document was prepared with ${input.hash}`);
261
+ if (input.signer.certificateChain.length === 0) throw new Error("the signer carries no certificate");
262
+ const chain = input.signer.certificateChain.map((der) => pkijs.Certificate.fromBER(toArrayBuffer(der)));
263
+ const signerCert = chain[0];
264
+ const attributes = [new pkijs.Attribute({
265
+ type: OID.contentType,
266
+ values: [new asn1js.ObjectIdentifier({ value: OID.data })]
267
+ }), new pkijs.Attribute({
268
+ type: OID.messageDigest,
269
+ values: [new asn1js.OctetString({ valueHex: toArrayBuffer(input.digest) })]
270
+ })];
271
+ if (input.profile === "pkcs7" && input.signingTime) attributes.push(new pkijs.Attribute({
272
+ type: OID.signingTime,
273
+ values: [new asn1js.UTCTime({ valueDate: input.signingTime })]
274
+ }));
275
+ if (input.profile === "cades-b") {
276
+ const certHash = await engine.digest("SHA-256", toArrayBuffer(input.signer.certificateChain[0]));
277
+ const issuerSerial = new pkijs.IssuerSerial({
278
+ issuer: new pkijs.GeneralNames({ names: [new pkijs.GeneralName({
279
+ type: 4,
280
+ value: signerCert.issuer
281
+ })] }),
282
+ serialNumber: signerCert.serialNumber
283
+ });
284
+ const essCertId = new asn1js.Sequence({ value: [new asn1js.OctetString({ valueHex: certHash }), issuerSerial.toSchema()] });
285
+ attributes.push(new pkijs.Attribute({
286
+ type: OID.signingCertificateV2,
287
+ values: [new asn1js.Sequence({ value: [new asn1js.Sequence({ value: [essCertId] })] })]
288
+ }));
289
+ }
290
+ const digestAlgorithm = new pkijs.AlgorithmIdentifier({ algorithmId: OID_BY_DIGEST[input.hash] });
291
+ const signerInfo = new pkijs.SignerInfo({
292
+ version: 1,
293
+ sid: new pkijs.IssuerAndSerialNumber({
294
+ issuer: signerCert.issuer,
295
+ serialNumber: signerCert.serialNumber
296
+ }),
297
+ digestAlgorithm,
298
+ signedAttrs: new pkijs.SignedAndUnsignedAttributes({
299
+ type: 0,
300
+ attributes
301
+ }),
302
+ signatureAlgorithm: signatureAlgorithmFor(input.signer.algorithm, input.hash)
303
+ });
304
+ const toSign = new asn1js.Set({ value: attributes.map((a) => a.toSchema()) }).toBER(false);
305
+ const raw = await input.signer.sign(new Uint8Array(toSign));
306
+ const signature = input.signer.algorithm === "ECDSA" ? pkijs.createCMSECDSASignature(toArrayBuffer(raw)) : toArrayBuffer(raw);
307
+ signerInfo.signature = new asn1js.OctetString({ valueHex: signature });
308
+ const signedData = new pkijs.SignedData({
309
+ version: 1,
310
+ digestAlgorithms: [digestAlgorithm],
311
+ encapContentInfo: new pkijs.EncapsulatedContentInfo({ eContentType: OID.data }),
312
+ certificates: chain,
313
+ signerInfos: [signerInfo]
314
+ });
315
+ const contentInfo = new pkijs.ContentInfo({
316
+ contentType: OID.signedData,
317
+ content: signedData.toSchema(true)
318
+ });
319
+ return new Uint8Array(contentInfo.toSchema().toBER(false));
320
+ }
321
+ function signatureAlgorithmFor(algorithm, hash) {
322
+ switch (algorithm) {
323
+ case "RSA-PKCS1-v1_5": return new pkijs.AlgorithmIdentifier({
324
+ algorithmId: {
325
+ sha256: OID.sha256WithRsa,
326
+ sha384: OID.sha384WithRsa,
327
+ sha512: OID.sha512WithRsa
328
+ }[hash],
329
+ algorithmParams: new asn1js.Null()
330
+ });
331
+ case "ECDSA": return new pkijs.AlgorithmIdentifier({ algorithmId: {
332
+ sha256: OID.ecdsaSha256,
333
+ sha384: OID.ecdsaSha384,
334
+ sha512: OID.ecdsaSha512
335
+ }[hash] });
336
+ case "RSA-PSS": {
337
+ const hashAlgorithm = new pkijs.AlgorithmIdentifier({
338
+ algorithmId: OID_BY_DIGEST[hash],
339
+ algorithmParams: new asn1js.Null()
340
+ });
341
+ const params = new pkijs.RSASSAPSSParams({
342
+ hashAlgorithm,
343
+ maskGenAlgorithm: new pkijs.AlgorithmIdentifier({
344
+ algorithmId: OID.mgf1,
345
+ algorithmParams: hashAlgorithm.toSchema()
346
+ }),
347
+ saltLength: DIGEST_LENGTH[hash]
348
+ });
349
+ return new pkijs.AlgorithmIdentifier({
350
+ algorithmId: OID.rsaPss,
351
+ algorithmParams: params.toSchema()
352
+ });
353
+ }
354
+ }
355
+ }
356
+ //#endregion
357
+ //#region src/gate.ts
358
+ /**
359
+ * The gate between a signer's answer and `signatures.complete`: the CMS
360
+ * must fit the reserved room, hash with the prepared algorithm, carry the
361
+ * prepared digest as its message digest, verify with its own certificate,
362
+ * and match the profile the `/SubFilter` promises (CAdES: ESS
363
+ * signing-certificate-v2 present, no CMS signing-time). A bad CMS never
364
+ * reaches the document.
365
+ */
366
+ async function verifyForCompletion(input) {
367
+ const { cms, prepared, profile } = input;
368
+ if (cms.byteLength > prepared.contentsSize) return {
369
+ ok: false,
370
+ reason: "too-large",
371
+ detail: `the CMS is ${cms.byteLength} bytes; ${prepared.contentsSize} were reserved`
372
+ };
373
+ let internal;
374
+ try {
375
+ internal = parseCmsInternal(cms);
376
+ } catch (err) {
377
+ return {
378
+ ok: false,
379
+ reason: "malformed",
380
+ detail: err instanceof CmsError ? err.message : String(err)
381
+ };
382
+ }
383
+ const parsed = internal.parsed;
384
+ if (parsed.digestAlgorithm !== prepared.algorithm) return {
385
+ ok: false,
386
+ reason: "algorithm-mismatch",
387
+ detail: `the CMS digests with ${parsed.digestAlgorithm}, the document was prepared with ${prepared.algorithm}`
388
+ };
389
+ if (!bytesEqual(parsed.messageDigest, prepared.digest)) return {
390
+ ok: false,
391
+ reason: "digest-mismatch",
392
+ detail: "the CMS message digest is not the prepared digest"
393
+ };
394
+ if (profile === "cades-b") {
395
+ if (!parsed.signingCertificateV2) return {
396
+ ok: false,
397
+ reason: "profile-violation",
398
+ detail: "CAdES requires the ESS signing-certificate-v2 attribute"
399
+ };
400
+ if (parsed.signingTime) return {
401
+ ok: false,
402
+ reason: "profile-violation",
403
+ detail: "PAdES forbids the CMS signing-time attribute"
404
+ };
405
+ }
406
+ const cryptography = await verifyCmsSignature(internal);
407
+ if (cryptography !== "valid") return {
408
+ ok: false,
409
+ reason: "signature-invalid",
410
+ detail: cryptography === "unsupported" ? "the signature algorithm is not supported" : "the signature does not verify"
411
+ };
412
+ return { ok: true };
413
+ }
414
+ //#endregion
415
+ //#region src/trust.ts
416
+ /**
417
+ * Build and validate the certificate path from the CMS's signer to one
418
+ * of the port's anchors at `at`. No port, or a port with no anchors, is
419
+ * `unknown`: nothing was checked, and nothing was refused.
420
+ */
421
+ async function validateChain(cms, trust, at = { kind: "now" }) {
422
+ if (!trust) return {
423
+ status: "unknown",
424
+ reason: "no trust anchors configured"
425
+ };
426
+ const anchors = await trust.anchors();
427
+ if (anchors.length === 0) return {
428
+ status: "unknown",
429
+ reason: "no trust anchors configured"
430
+ };
431
+ ensureEngine();
432
+ let internal;
433
+ try {
434
+ internal = cms instanceof Uint8Array ? parseCmsInternal(cms) : cms;
435
+ } catch (err) {
436
+ return {
437
+ status: "untrusted",
438
+ reason: err.message
439
+ };
440
+ }
441
+ const trustedCerts = anchors.map((der) => pkijs.Certificate.fromBER(toArrayBuffer(der)));
442
+ const chainEngine = new pkijs.CertificateChainValidationEngine({
443
+ certs: internal.certificates,
444
+ trustedCerts,
445
+ checkDate: at.kind === "now" ? /* @__PURE__ */ new Date() : at.genTime
446
+ });
447
+ try {
448
+ const result = await chainEngine.verify();
449
+ if (!result.result) return {
450
+ status: "untrusted",
451
+ reason: result.resultMessage || `chain validation code ${result.resultCode}`
452
+ };
453
+ return {
454
+ status: "trusted",
455
+ path: (result.certificatePath ?? []).map((c) => new Uint8Array(c.toSchema(true).toBER(false)))
456
+ };
457
+ } catch (err) {
458
+ return {
459
+ status: "untrusted",
460
+ reason: err.message
461
+ };
462
+ }
463
+ }
464
+ //#endregion
465
+ //#region src/sign.ts
466
+ var SigningError = class extends Error {
467
+ constructor(reason, detail) {
468
+ super(`signing refused (${reason}): ${detail}`);
469
+ this.reason = reason;
470
+ this.name = "SigningError";
471
+ }
472
+ };
473
+ function profileFor(subFilter) {
474
+ return subFilter === "adbe.pkcs7.detached" ? "pkcs7" : "cades-b";
475
+ }
476
+ /**
477
+ * Sign a field in one call: `prepare` on the engine, the signer turns the
478
+ * digest into a CMS (or a raw signature this package wraps), the gate
479
+ * checks the CMS matches what was prepared, `complete` installs it. Any
480
+ * failure aborts the candidate and rethrows; the document is never left
481
+ * with a pending signing.
482
+ */
483
+ async function sign(doc, input) {
484
+ if (!doc.signatures) throw new Error("this engine does not implement signatures");
485
+ const { signer, ...rest } = input;
486
+ const subFilter = rest.subFilter ?? "ETSI.CAdES.detached";
487
+ const profile = profileFor(subFilter);
488
+ const digest = rest.digest ?? (signer.kind === "raw" ? signer.hash : "sha256");
489
+ const prepared = await doc.signatures.prepare({
490
+ ...rest,
491
+ subFilter,
492
+ digest
493
+ });
494
+ try {
495
+ const cms = signer.kind === "cms" ? await signer.sign({
496
+ digest: prepared.digest,
497
+ algorithm: prepared.algorithm,
498
+ subFilter
499
+ }) : await buildDetachedCms({
500
+ digest: prepared.digest,
501
+ hash: prepared.algorithm,
502
+ profile,
503
+ signer,
504
+ signingTime: profile === "pkcs7" ? /* @__PURE__ */ new Date() : void 0
505
+ });
506
+ const gate = await verifyForCompletion({
507
+ cms,
508
+ prepared,
509
+ profile
510
+ });
511
+ if (!gate.ok) throw new SigningError(gate.reason, gate.detail);
512
+ return await doc.signatures.complete({
513
+ signingId: prepared.signingId,
514
+ cms,
515
+ expectedVersion: prepared.expectedVersion
516
+ });
517
+ } catch (err) {
518
+ await doc.signatures.abort(prepared.signingId).catch(() => void 0);
519
+ throw err;
520
+ }
521
+ }
522
+ //#endregion
523
+ //#region src/verdict.ts
524
+ /**
525
+ * Validate every signature of a document: integrity from the engine's
526
+ * bytes, cryptography from the CMS, trust from the caller's anchors.
527
+ */
528
+ async function validateSignatures(doc, opts = {}) {
529
+ if (!doc.signatures) throw new Error("this engine does not implement signatures");
530
+ const snapshot = await doc.signatures.list();
531
+ const verdicts = [];
532
+ for (const signature of snapshot.signatures) {
533
+ if (!signature.signed) continue;
534
+ verdicts.push(await validateOne(doc, snapshot, signature, opts));
535
+ }
536
+ return verdicts;
537
+ }
538
+ async function validateOne(doc, snapshot, signature, opts) {
539
+ const at = opts.at ?? { kind: "now" };
540
+ let integrity = "indeterminate";
541
+ let cryptography = "unsupported";
542
+ let trust = "unknown";
543
+ let trustReason;
544
+ let internal = null;
545
+ let cms = null;
546
+ if (!snapshot.chainValid || signature.coverage === "malformed") integrity = snapshot.chainValid ? "invalid" : "indeterminate";
547
+ else try {
548
+ internal = parseCmsInternal(await doc.signatures.contents(signature.field));
549
+ cms = internal.parsed;
550
+ const digestMatches = bytesEqual(await doc.signatures.digest(signature.field, cms.digestAlgorithm), cms.messageDigest);
551
+ integrity = signature.coverage === "whole-revision" && digestMatches ? "valid" : "invalid";
552
+ } catch (err) {
553
+ integrity = err instanceof CmsError && err.reason === "unsupported" ? "indeterminate" : "invalid";
554
+ if (!(err instanceof CmsError)) integrity = "indeterminate";
555
+ }
556
+ if (internal) {
557
+ cryptography = await verifyCmsSignature(internal);
558
+ const chain = await validateChain(internal, opts.trust, at);
559
+ trust = chain.status;
560
+ trustReason = chain.reason;
561
+ }
562
+ const modifications = await modificationsOf(doc, snapshot, signature, opts.until ?? "persisted");
563
+ let summary;
564
+ if (integrity === "invalid" || cryptography === "invalid" || modifications.verdict === "forbidden") summary = "invalid";
565
+ else if (integrity === "indeterminate" || cryptography === "unsupported" || modifications.verdict === "indeterminate") summary = "indeterminate";
566
+ else summary = trust === "trusted" ? "valid" : "valid-untrusted";
567
+ return {
568
+ signature,
569
+ integrity,
570
+ cryptography,
571
+ trust,
572
+ trustReason,
573
+ validatedAt: at,
574
+ modifications,
575
+ summary,
576
+ cms
577
+ };
578
+ }
579
+ /**
580
+ * What changed after the signature, from the engine's revision analysis:
581
+ * the current document judged against the revision the signature sealed
582
+ * (net state; a certification also replays every intervening revision).
583
+ * A signature that seals the last revision is `unchanged` without analysis.
584
+ */
585
+ async function modificationsOf(doc, snapshot, signature, until) {
586
+ if (signature.revisionIndex === null) return {
587
+ verdict: "indeterminate",
588
+ detail: "the signature seals no whole revision",
589
+ basis: "persisted"
590
+ };
591
+ if (until === "persisted" && signature.revisionIndex === snapshot.revisions.length - 1) return {
592
+ verdict: "unchanged",
593
+ basis: "persisted"
594
+ };
595
+ try {
596
+ const analysis = await doc.signatures.analyze({
597
+ since: { signatureIndex: signature.index },
598
+ until
599
+ });
600
+ const primary = analysis.current.primary;
601
+ const detail = primary && primary.verdict === "forbidden" ? `object ${primary.objectNumber}, ${primary.rule}${primary.detail ? `: ${primary.detail}` : ""}` : primary && primary.verdict === "incomplete" ? `object ${primary.objectNumber}: ${primary.detail ?? "incomplete evidence"}` : void 0;
602
+ return {
603
+ verdict: analysis.current.verdict,
604
+ detail,
605
+ basis: analysis.basis.source,
606
+ ...analysis.later.revisionCount > 0 ? { laterRevisions: analysis.later.revisionCount } : {},
607
+ ...analysis.later.undoneObjectNumbers.length > 0 ? { undone: true } : {}
608
+ };
609
+ } catch (err) {
610
+ return {
611
+ verdict: "indeterminate",
612
+ detail: `analysis failed: ${err.message}`,
613
+ basis: "persisted"
614
+ };
615
+ }
616
+ }
617
+ //#endregion
618
+ //#region src/signers/self-signed.ts
619
+ /**
620
+ * A self-signed certificate over a WebCrypto key pair: the shape a personal
621
+ * signer, a test signer, and a demo all need. It is its own trust anchor and
622
+ * nothing else's — Acrobat shows a signature made with it as "validity
623
+ * unknown" until the reader trusts the certificate explicitly.
624
+ */
625
+ async function selfSignedCertificate(input) {
626
+ ensureEngine();
627
+ const cert = new pkijs.Certificate();
628
+ cert.version = 2;
629
+ const serial = /* @__PURE__ */ new Uint8Array(8);
630
+ globalThis.crypto.getRandomValues(serial);
631
+ serial[0] &= 127;
632
+ cert.serialNumber = new asn1js.Integer({ valueHex: serial.buffer });
633
+ const name = new pkijs.AttributeTypeAndValue({
634
+ type: OID.commonName,
635
+ value: new asn1js.Utf8String({ value: input.commonName })
636
+ });
637
+ cert.issuer.typesAndValues.push(name);
638
+ cert.subject.typesAndValues.push(name);
639
+ const now = Date.now();
640
+ cert.notBefore.value = /* @__PURE__ */ new Date(now - 24 * 3600 * 1e3);
641
+ cert.notAfter.value = new Date(now + (input.validityDays ?? 365) * 24 * 3600 * 1e3);
642
+ const basicConstraints = new pkijs.BasicConstraints({ cA: true });
643
+ const keyUsage = new asn1js.BitString({
644
+ valueHex: new Uint8Array([132]).buffer,
645
+ unusedBits: 2
646
+ });
647
+ cert.extensions = [new pkijs.Extension({
648
+ extnID: OID.basicConstraints,
649
+ critical: true,
650
+ extnValue: basicConstraints.toSchema().toBER(false),
651
+ parsedValue: basicConstraints
652
+ }), new pkijs.Extension({
653
+ extnID: OID.keyUsage,
654
+ critical: true,
655
+ extnValue: keyUsage.toBER(false)
656
+ })];
657
+ await cert.subjectPublicKeyInfo.importKey(input.publicKey);
658
+ await cert.sign(input.privateKey, WEBCRYPTO_HASH[input.hash]);
659
+ return new Uint8Array(cert.toSchema(true).toBER(false));
660
+ }
661
+ /** The subject common name of a DER certificate, or null when it has none. */
662
+ function certificateCommonName(der) {
663
+ ensureEngine();
664
+ const copy = new Uint8Array(der.byteLength);
665
+ copy.set(der);
666
+ const text = (pkijs.Certificate.fromBER(copy.buffer).subject.typesAndValues.find((entry) => entry.type === OID.commonName)?.value)?.valueBlock?.value;
667
+ return typeof text === "string" && text.length > 0 ? text : null;
668
+ }
669
+ //#endregion
670
+ //#region src/signers/webcrypto.ts
671
+ /**
672
+ * A `RawSigner` over a WebCrypto private key: the key never leaves the
673
+ * runtime. The algorithm and hash come from the key itself.
674
+ */
675
+ function webCryptoSigner(input) {
676
+ const keyAlgorithm = input.privateKey.algorithm;
677
+ let algorithm;
678
+ let hash;
679
+ switch (keyAlgorithm.name) {
680
+ case "RSASSA-PKCS1-v1_5":
681
+ algorithm = "RSA-PKCS1-v1_5";
682
+ hash = hashFromWebCrypto(keyAlgorithm.hash.name);
683
+ break;
684
+ case "RSA-PSS":
685
+ algorithm = "RSA-PSS";
686
+ hash = hashFromWebCrypto(keyAlgorithm.hash.name);
687
+ break;
688
+ case "ECDSA":
689
+ algorithm = "ECDSA";
690
+ hash = input.hash ?? "sha256";
691
+ break;
692
+ default: throw new Error(`unsupported WebCrypto key algorithm ${keyAlgorithm.name}`);
693
+ }
694
+ return {
695
+ kind: "raw",
696
+ certificateChain: input.certificateChain,
697
+ algorithm,
698
+ hash,
699
+ async sign(data) {
700
+ const params = algorithm === "RSA-PSS" ? {
701
+ name: "RSA-PSS",
702
+ saltLength: {
703
+ sha256: 32,
704
+ sha384: 48,
705
+ sha512: 64
706
+ }[hash]
707
+ } : algorithm === "ECDSA" ? {
708
+ name: "ECDSA",
709
+ hash: WEBCRYPTO_HASH[hash]
710
+ } : { name: "RSASSA-PKCS1-v1_5" };
711
+ const copy = new Uint8Array(data.byteLength);
712
+ copy.set(data);
713
+ const signature = await globalThis.crypto.subtle.sign(params, input.privateKey, copy);
714
+ return new Uint8Array(signature);
715
+ }
716
+ };
717
+ }
718
+ function hashFromWebCrypto(name) {
719
+ switch (name) {
720
+ case "SHA-256": return "sha256";
721
+ case "SHA-384": return "sha384";
722
+ case "SHA-512": return "sha512";
723
+ default: throw new Error(`unsupported key hash ${name}`);
724
+ }
725
+ }
726
+ /**
727
+ * A throwaway signer for tests and demos: a fresh key pair and a
728
+ * self-signed certificate, never persisted. Trust it with
729
+ * `{ anchors: async () => [signer.certificate] }`.
730
+ */
731
+ async function createTestSigner(opts = {}) {
732
+ ensureEngine();
733
+ const hash = opts.hash ?? "sha256";
734
+ const keys = await generateSigningKeyPair(opts.algorithm ?? "RSA-PKCS1-v1_5", hash, true);
735
+ const certificate = await selfSignedCertificate({
736
+ publicKey: keys.publicKey,
737
+ privateKey: keys.privateKey,
738
+ commonName: opts.commonName ?? "EmbedPDF test signer",
739
+ hash
740
+ });
741
+ return {
742
+ ...webCryptoSigner({
743
+ privateKey: keys.privateKey,
744
+ certificateChain: [certificate],
745
+ hash
746
+ }),
747
+ certificate,
748
+ privateKey: keys.privateKey
749
+ };
750
+ }
751
+ /** A WebCrypto signing key pair; `extractable: false` keeps the private key inside the runtime for good. */
752
+ async function generateSigningKeyPair(algorithm, hash, extractable) {
753
+ return await globalThis.crypto.subtle.generateKey(algorithm === "ECDSA" ? {
754
+ name: "ECDSA",
755
+ namedCurve: "P-256"
756
+ } : {
757
+ name: algorithm === "RSA-PSS" ? "RSA-PSS" : "RSASSA-PKCS1-v1_5",
758
+ modulusLength: 2048,
759
+ publicExponent: new Uint8Array([
760
+ 1,
761
+ 0,
762
+ 1
763
+ ]),
764
+ hash: WEBCRYPTO_HASH[hash]
765
+ }, extractable, ["sign", "verify"]);
766
+ }
767
+ //#endregion
768
+ //#region src/signers/remote.ts
769
+ /**
770
+ * A `CmsSigner` over the customer's own signing service or HSM: the engine's
771
+ * digest goes out, a finished detached CMS comes back. Nothing but the
772
+ * digest (and which hash and profile it is for) ever leaves the runtime.
773
+ */
774
+ function remoteSigner(input) {
775
+ return {
776
+ kind: "cms",
777
+ sign: (request) => input.sign(request)
778
+ };
779
+ }
780
+ //#endregion
781
+ //#region src/signers/personal.ts
782
+ /**
783
+ * One self-signed identity per subject (a person's name), persisted in the
784
+ * caller's store and created on first use. The private key is generated
785
+ * non-extractable: it can sign, it can be stored by the browser, it can
786
+ * never be read out. This is Preview's "sign with your own signature"
787
+ * experience with real cryptography behind it — a reader who trusts the
788
+ * certificate validates the signature; every other reader sees "validity
789
+ * unknown", which is the honest answer for a self-issued identity.
790
+ */
791
+ async function personalSigner(input) {
792
+ ensureEngine();
793
+ const hash = input.hash ?? "sha256";
794
+ let record = await input.store.load(input.subject);
795
+ if (!record) {
796
+ const keys = await generateSigningKeyPair(input.algorithm ?? "RSA-PKCS1-v1_5", hash, false);
797
+ const certificate = await selfSignedCertificate({
798
+ publicKey: keys.publicKey,
799
+ privateKey: keys.privateKey,
800
+ commonName: input.subject,
801
+ hash
802
+ });
803
+ record = {
804
+ privateKey: keys.privateKey,
805
+ certificate
806
+ };
807
+ await input.store.save(input.subject, record);
808
+ }
809
+ return {
810
+ ...webCryptoSigner({
811
+ privateKey: record.privateKey,
812
+ certificateChain: [record.certificate],
813
+ hash
814
+ }),
815
+ certificate: record.certificate,
816
+ subject: input.subject
817
+ };
818
+ }
819
+ /** An in-memory store: tests, and runtimes without IndexedDB. */
820
+ function memoryKeyStore() {
821
+ const records = /* @__PURE__ */ new Map();
822
+ return {
823
+ load: async (subject) => records.get(subject) ?? null,
824
+ save: async (subject, record) => {
825
+ records.set(subject, record);
826
+ },
827
+ remove: async (subject) => {
828
+ records.delete(subject);
829
+ }
830
+ };
831
+ }
832
+ /**
833
+ * An IndexedDB store: the browser keeps the non-extractable `CryptoKey`
834
+ * itself (structured clone), so the key survives reloads without ever
835
+ * existing as bytes.
836
+ */
837
+ function indexedDbKeyStore(dbName, opts = {}) {
838
+ const storeName = opts.storeName ?? "keys";
839
+ let opening = null;
840
+ const open = () => {
841
+ opening ?? (opening = new Promise((resolve, reject) => {
842
+ const request = indexedDB.open(dbName, 1);
843
+ request.onupgradeneeded = () => {
844
+ if (!request.result.objectStoreNames.contains(storeName)) request.result.createObjectStore(storeName);
845
+ };
846
+ request.onsuccess = () => resolve(request.result);
847
+ request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("indexedDB open failed"));
848
+ }));
849
+ return opening;
850
+ };
851
+ const run = async (mode, op) => {
852
+ const db = await open();
853
+ return new Promise((resolve, reject) => {
854
+ const request = op(db.transaction(storeName, mode).objectStore(storeName));
855
+ request.onsuccess = () => resolve(request.result);
856
+ request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("indexedDB request failed"));
857
+ });
858
+ };
859
+ return {
860
+ load: async (subject) => await run("readonly", (s) => s.get(subject)) ?? null,
861
+ save: (subject, record) => run("readwrite", (s) => s.put(record, subject)).then(() => void 0),
862
+ remove: (subject) => run("readwrite", (s) => s.delete(subject)).then(() => void 0)
863
+ };
864
+ }
865
+ //#endregion
866
+ exports.CmsError = CmsError;
867
+ exports.SigningError = SigningError;
868
+ exports.buildDetachedCms = buildDetachedCms;
869
+ exports.certificateCommonName = certificateCommonName;
870
+ exports.createTestSigner = createTestSigner;
871
+ exports.generateSigningKeyPair = generateSigningKeyPair;
872
+ exports.indexedDbKeyStore = indexedDbKeyStore;
873
+ exports.memoryKeyStore = memoryKeyStore;
874
+ exports.parseDetachedCms = parseDetachedCms;
875
+ exports.personalSigner = personalSigner;
876
+ exports.profileFor = profileFor;
877
+ exports.remoteSigner = remoteSigner;
878
+ exports.selfSignedCertificate = selfSignedCertificate;
879
+ exports.sign = sign;
880
+ exports.validateChain = validateChain;
881
+ exports.validateSignatures = validateSignatures;
882
+ exports.verifyCmsSignature = verifyCmsSignature;
883
+ exports.verifyForCompletion = verifyForCompletion;
884
+ exports.webCryptoSigner = webCryptoSigner;
885
+
886
+ //# sourceMappingURL=index.cjs.map