@embedpdf/core-signature 3.0.0-next.13 → 3.0.0-next.15
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/engine-BPmX1KyF.cjs +108 -0
- package/dist/engine-BPmX1KyF.cjs.map +1 -0
- package/dist/engine-C7lxkbZP.js +53 -0
- package/dist/engine-C7lxkbZP.js.map +1 -0
- package/dist/index.cjs +380 -288
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -19
- package/dist/index.d.ts +50 -19
- package/dist/index.js +337 -223
- package/dist/index.js.map +1 -1
- package/dist/oids-BUfXTCCm.cjs +87 -0
- package/dist/oids-BUfXTCCm.cjs.map +1 -0
- package/dist/oids-BhZXI2ZX.js +58 -0
- package/dist/oids-BhZXI2ZX.js.map +1 -0
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1,115 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
2
|
+
const require_engine = require("./engine-BPmX1KyF.cjs");
|
|
3
|
+
const require_oids = require("./oids-BUfXTCCm.cjs");
|
|
24
4
|
let asn1js = require("asn1js");
|
|
25
|
-
asn1js = __toESM(asn1js, 1);
|
|
5
|
+
asn1js = require_engine.__toESM(asn1js, 1);
|
|
26
6
|
let pkijs = require("pkijs");
|
|
27
|
-
pkijs = __toESM(pkijs, 1);
|
|
28
|
-
|
|
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
|
|
7
|
+
pkijs = require_engine.__toESM(pkijs, 1);
|
|
8
|
+
let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
|
|
113
9
|
//#region src/cms/parse.ts
|
|
114
10
|
var CmsError = class extends Error {
|
|
115
11
|
constructor(reason, message) {
|
|
@@ -122,50 +18,58 @@ var CmsError = class extends Error {
|
|
|
122
18
|
function parseDetachedCms(cms) {
|
|
123
19
|
return parseCmsInternal(cms).parsed;
|
|
124
20
|
}
|
|
125
|
-
function parseCmsInternal(cms) {
|
|
21
|
+
function parseCmsInternal(cms, content = "detached") {
|
|
126
22
|
if (cms.byteLength === 0) throw new CmsError("malformed", "empty CMS");
|
|
127
|
-
const asn1 = asn1js.fromBER(toArrayBuffer(cms));
|
|
23
|
+
const asn1 = asn1js.fromBER(require_engine.toArrayBuffer(cms));
|
|
128
24
|
if (asn1.offset === -1) throw new CmsError("malformed", "CMS is not valid BER");
|
|
129
25
|
let contentInfo;
|
|
130
26
|
let signedData;
|
|
131
27
|
try {
|
|
132
28
|
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`);
|
|
29
|
+
if (contentInfo.contentType !== require_oids.OID.signedData) throw new CmsError("unsupported", `CMS content type ${contentInfo.contentType} is not SignedData`);
|
|
134
30
|
signedData = new pkijs.SignedData({ schema: contentInfo.content });
|
|
135
|
-
} catch (
|
|
136
|
-
if (
|
|
137
|
-
throw new CmsError("malformed", `CMS does not parse as SignedData: ${
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error instanceof CmsError) throw error;
|
|
33
|
+
throw new CmsError("malformed", `CMS does not parse as SignedData: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
const encapsulated = signedData.encapContentInfo;
|
|
36
|
+
const expectedContentType = content === "tst-info" ? require_oids.OID.tstInfo : require_oids.OID.data;
|
|
37
|
+
let eContent = null;
|
|
38
|
+
if (content === "detached") {
|
|
39
|
+
if (encapsulated.eContent) throw new CmsError("unsupported", "CMS carries encapsulated content; a PDF signature is detached");
|
|
40
|
+
} else {
|
|
41
|
+
if (encapsulated.eContentType !== require_oids.OID.tstInfo || !encapsulated.eContent) throw new CmsError("malformed", "a timestamp token must carry a TSTInfo");
|
|
42
|
+
eContent = new Uint8Array(encapsulated.eContent.getValue());
|
|
138
43
|
}
|
|
139
|
-
if (signedData.encapContentInfo.eContent) throw new CmsError("unsupported", "CMS carries encapsulated content; a PDF signature is detached");
|
|
140
44
|
if (signedData.signerInfos.length !== 1) throw new CmsError("unsupported", `CMS has ${signedData.signerInfos.length} signers; exactly one is expected`);
|
|
141
45
|
const signerInfo = signedData.signerInfos[0];
|
|
142
|
-
const digestAlgorithm = DIGEST_BY_OID[signerInfo.digestAlgorithm.algorithmId];
|
|
46
|
+
const digestAlgorithm = require_oids.DIGEST_BY_OID[signerInfo.digestAlgorithm.algorithmId];
|
|
143
47
|
if (!digestAlgorithm) throw new CmsError("unsupported", `digest algorithm ${signerInfo.digestAlgorithm.algorithmId}`);
|
|
144
|
-
const certificates = (signedData.certificates ?? []).filter((
|
|
48
|
+
const certificates = (signedData.certificates ?? []).filter((certificate) => certificate instanceof pkijs.Certificate);
|
|
145
49
|
const signerCertificate = findSignerCertificate(signerInfo, certificates);
|
|
146
50
|
if (!signerCertificate) throw new CmsError("malformed", "the signer certificate is not carried by the CMS");
|
|
147
51
|
const signedAttrs = signerInfo.signedAttrs?.attributes ?? [];
|
|
148
52
|
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((
|
|
150
|
-
const contentType = attr(OID.contentType);
|
|
151
|
-
if (contentType
|
|
53
|
+
const attr = (type) => signedAttrs.find((attribute) => attribute.type === type);
|
|
54
|
+
const contentType = attr(require_oids.OID.contentType);
|
|
55
|
+
if (contentType) {
|
|
152
56
|
const oid = contentType.values[0];
|
|
153
|
-
if (!oid || oid.valueBlock.toString() !==
|
|
57
|
+
if (!oid || oid.valueBlock.toString() !== expectedContentType) throw new CmsError("unsupported", `CMS content-type attribute is not ${content === "tst-info" ? "id-ct-TSTInfo" : "id-data"}`);
|
|
154
58
|
}
|
|
155
|
-
const messageDigestValue = attr(OID.messageDigest)?.values[0];
|
|
59
|
+
const messageDigestValue = attr(require_oids.OID.messageDigest)?.values[0];
|
|
156
60
|
if (!messageDigestValue || !(messageDigestValue instanceof asn1js.OctetString)) throw new CmsError("malformed", "CMS has no message-digest attribute");
|
|
157
61
|
const messageDigest = new Uint8Array(messageDigestValue.valueBlock.valueHexView);
|
|
158
62
|
let signingTime = null;
|
|
159
|
-
const timeValue = attr(OID.signingTime)?.values[0];
|
|
63
|
+
const timeValue = attr(require_oids.OID.signingTime)?.values[0];
|
|
160
64
|
if (timeValue instanceof asn1js.UTCTime || timeValue instanceof asn1js.GeneralizedTime) signingTime = timeValue.toDate();
|
|
161
|
-
const essAttr = attr(OID.signingCertificateV2);
|
|
65
|
+
const essAttr = attr(require_oids.OID.signingCertificateV2);
|
|
162
66
|
let essCertHash = null;
|
|
163
67
|
if (essAttr) {
|
|
164
68
|
const members = ((essAttr.values[0]?.valueBlock?.value?.[0])?.valueBlock?.value?.[0])?.valueBlock?.value ?? [];
|
|
165
69
|
let cursor = 0;
|
|
166
70
|
const head = members[0];
|
|
167
71
|
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");
|
|
72
|
+
if (head.valueBlock.value[0].valueBlock.toString() !== require_oids.OID.sha256) throw new CmsError("unsupported", "signing-certificate-v2 uses a hash other than SHA-256");
|
|
169
73
|
cursor = 1;
|
|
170
74
|
}
|
|
171
75
|
const hash = members[cursor];
|
|
@@ -173,9 +77,9 @@ function parseCmsInternal(cms) {
|
|
|
173
77
|
essCertHash = new Uint8Array(hash.valueBlock.valueHexView);
|
|
174
78
|
}
|
|
175
79
|
let timestampToken = null;
|
|
176
|
-
const tokenAttr = signerInfo.unsignedAttrs?.attributes.find((
|
|
80
|
+
const tokenAttr = signerInfo.unsignedAttrs?.attributes.find((attribute) => attribute.type === require_oids.OID.timestampToken);
|
|
177
81
|
if (tokenAttr?.values[0]) timestampToken = new Uint8Array(tokenAttr.values[0].toBER(false));
|
|
178
|
-
const ordered = [signerCertificate, ...certificates.filter((
|
|
82
|
+
const ordered = [signerCertificate, ...certificates.filter((certificate) => certificate !== signerCertificate)];
|
|
179
83
|
return {
|
|
180
84
|
parsed: {
|
|
181
85
|
digestAlgorithm,
|
|
@@ -191,7 +95,8 @@ function parseCmsInternal(cms) {
|
|
|
191
95
|
signerInfo,
|
|
192
96
|
signerCertificate,
|
|
193
97
|
certificates: ordered,
|
|
194
|
-
essCertHash
|
|
98
|
+
essCertHash,
|
|
99
|
+
eContent
|
|
195
100
|
};
|
|
196
101
|
}
|
|
197
102
|
function certDer(cert) {
|
|
@@ -200,24 +105,24 @@ function certDer(cert) {
|
|
|
200
105
|
function findSignerCertificate(signerInfo, certificates) {
|
|
201
106
|
const sid = signerInfo.sid;
|
|
202
107
|
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;
|
|
108
|
+
for (const cert of certificates) if (cert.issuer.isEqual(sid.issuer) && require_engine.bytesEqual(new Uint8Array(cert.serialNumber.valueBlock.valueHexView), new Uint8Array(sid.serialNumber.valueBlock.valueHexView))) return cert;
|
|
204
109
|
return null;
|
|
205
110
|
}
|
|
206
111
|
const ski = sid?.valueBlock?.valueHexView;
|
|
207
112
|
if (!ski) return null;
|
|
208
113
|
for (const cert of certificates) {
|
|
209
|
-
const ext = cert.extensions?.find((
|
|
114
|
+
const ext = cert.extensions?.find((extension) => extension.extnID === require_oids.OID.subjectKeyIdentifier);
|
|
210
115
|
const value = ext ? asn1js.fromBER(ext.extnValue.valueBlock.valueHexView) : null;
|
|
211
116
|
const inner = value && value.offset !== -1 ? value.result : null;
|
|
212
|
-
if (inner && bytesEqual(new Uint8Array(inner.valueBlock.valueHexView), new Uint8Array(ski))) return cert;
|
|
117
|
+
if (inner && require_engine.bytesEqual(new Uint8Array(inner.valueBlock.valueHexView), new Uint8Array(ski))) return cert;
|
|
213
118
|
}
|
|
214
119
|
return null;
|
|
215
120
|
}
|
|
216
121
|
//#endregion
|
|
217
122
|
//#region src/cms/verify.ts
|
|
218
|
-
/** The DER of the signed attributes as they are signed: re-tagged as a
|
|
123
|
+
/** The DER of the signed attributes as they are signed: re-tagged as a set. */
|
|
219
124
|
function signedAttributesDer(signerInfo) {
|
|
220
|
-
const set = new asn1js.Set({ value: (signerInfo.signedAttrs?.attributes ?? []).map((
|
|
125
|
+
const set = new asn1js.Set({ value: (signerInfo.signedAttrs?.attributes ?? []).map((attribute) => attribute.toSchema()) });
|
|
221
126
|
return new Uint8Array(set.toBER(false));
|
|
222
127
|
}
|
|
223
128
|
/**
|
|
@@ -231,18 +136,18 @@ async function verifyCmsSignature(cms) {
|
|
|
231
136
|
let internal;
|
|
232
137
|
try {
|
|
233
138
|
internal = cms instanceof Uint8Array ? parseCmsInternal(cms) : cms;
|
|
234
|
-
} catch (
|
|
235
|
-
return
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return error instanceof CmsError && error.reason === "unsupported" ? "unsupported" : "invalid";
|
|
236
141
|
}
|
|
237
|
-
const engine = ensureEngine();
|
|
142
|
+
const engine = require_engine.ensureEngine();
|
|
238
143
|
const { signerInfo, signerCertificate, parsed, essCertHash } = internal;
|
|
239
144
|
if (essCertHash) {
|
|
240
|
-
if (!bytesEqual(new Uint8Array(await engine.digest("SHA-256", toArrayBuffer(parsed.signerCertificate))), essCertHash)) return "invalid";
|
|
145
|
+
if (!require_engine.bytesEqual(new Uint8Array(await engine.digest("SHA-256", require_engine.toArrayBuffer(parsed.signerCertificate))), essCertHash)) return "invalid";
|
|
241
146
|
}
|
|
242
147
|
try {
|
|
243
|
-
return await engine.verifyWithPublicKey(toArrayBuffer(signedAttributesDer(signerInfo)), signerInfo.signature, signerCertificate.subjectPublicKeyInfo, signerInfo.signatureAlgorithm, WEBCRYPTO_HASH[parsed.digestAlgorithm]) ? "valid" : "invalid";
|
|
244
|
-
} catch (
|
|
245
|
-
const message =
|
|
148
|
+
return await engine.verifyWithPublicKey(require_engine.toArrayBuffer(signedAttributesDer(signerInfo)), signerInfo.signature, signerCertificate.subjectPublicKeyInfo, signerInfo.signatureAlgorithm, require_oids.WEBCRYPTO_HASH[parsed.digestAlgorithm]) ? "valid" : "invalid";
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const message = error.message ?? "";
|
|
246
151
|
return /unsupported|unknown|not supported|not implemented/i.test(message) ? "unsupported" : "invalid";
|
|
247
152
|
}
|
|
248
153
|
}
|
|
@@ -255,25 +160,25 @@ async function verifyCmsSignature(cms) {
|
|
|
255
160
|
* serial number.
|
|
256
161
|
*/
|
|
257
162
|
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]}`);
|
|
163
|
+
const engine = require_engine.ensureEngine();
|
|
164
|
+
if (input.digest.byteLength !== require_oids.DIGEST_LENGTH[input.hash]) throw new Error(`digest is ${input.digest.byteLength} bytes; ${input.hash} needs ${require_oids.DIGEST_LENGTH[input.hash]}`);
|
|
260
165
|
if (input.signer.hash !== input.hash) throw new Error(`the signer hashes with ${input.signer.hash}, the document was prepared with ${input.hash}`);
|
|
261
166
|
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)));
|
|
167
|
+
const chain = input.signer.certificateChain.map((der) => pkijs.Certificate.fromBER(require_engine.toArrayBuffer(der)));
|
|
263
168
|
const signerCert = chain[0];
|
|
264
169
|
const attributes = [new pkijs.Attribute({
|
|
265
|
-
type: OID.contentType,
|
|
266
|
-
values: [new asn1js.ObjectIdentifier({ value: OID.data })]
|
|
170
|
+
type: require_oids.OID.contentType,
|
|
171
|
+
values: [new asn1js.ObjectIdentifier({ value: require_oids.OID.data })]
|
|
267
172
|
}), new pkijs.Attribute({
|
|
268
|
-
type: OID.messageDigest,
|
|
269
|
-
values: [new asn1js.OctetString({ valueHex: toArrayBuffer(input.digest) })]
|
|
173
|
+
type: require_oids.OID.messageDigest,
|
|
174
|
+
values: [new asn1js.OctetString({ valueHex: require_engine.toArrayBuffer(input.digest) })]
|
|
270
175
|
})];
|
|
271
176
|
if (input.profile === "pkcs7" && input.signingTime) attributes.push(new pkijs.Attribute({
|
|
272
|
-
type: OID.signingTime,
|
|
177
|
+
type: require_oids.OID.signingTime,
|
|
273
178
|
values: [new asn1js.UTCTime({ valueDate: input.signingTime })]
|
|
274
179
|
}));
|
|
275
180
|
if (input.profile === "cades-b") {
|
|
276
|
-
const certHash = await engine.digest("SHA-256", toArrayBuffer(input.signer.certificateChain[0]));
|
|
181
|
+
const certHash = await engine.digest("SHA-256", require_engine.toArrayBuffer(input.signer.certificateChain[0]));
|
|
277
182
|
const issuerSerial = new pkijs.IssuerSerial({
|
|
278
183
|
issuer: new pkijs.GeneralNames({ names: [new pkijs.GeneralName({
|
|
279
184
|
type: 4,
|
|
@@ -283,11 +188,11 @@ async function buildDetachedCms(input) {
|
|
|
283
188
|
});
|
|
284
189
|
const essCertId = new asn1js.Sequence({ value: [new asn1js.OctetString({ valueHex: certHash }), issuerSerial.toSchema()] });
|
|
285
190
|
attributes.push(new pkijs.Attribute({
|
|
286
|
-
type: OID.signingCertificateV2,
|
|
191
|
+
type: require_oids.OID.signingCertificateV2,
|
|
287
192
|
values: [new asn1js.Sequence({ value: [new asn1js.Sequence({ value: [essCertId] })] })]
|
|
288
193
|
}));
|
|
289
194
|
}
|
|
290
|
-
const digestAlgorithm = new pkijs.AlgorithmIdentifier({ algorithmId: OID_BY_DIGEST[input.hash] });
|
|
195
|
+
const digestAlgorithm = new pkijs.AlgorithmIdentifier({ algorithmId: require_oids.OID_BY_DIGEST[input.hash] });
|
|
291
196
|
const signerInfo = new pkijs.SignerInfo({
|
|
292
197
|
version: 1,
|
|
293
198
|
sid: new pkijs.IssuerAndSerialNumber({
|
|
@@ -301,59 +206,166 @@ async function buildDetachedCms(input) {
|
|
|
301
206
|
}),
|
|
302
207
|
signatureAlgorithm: signatureAlgorithmFor(input.signer.algorithm, input.hash)
|
|
303
208
|
});
|
|
304
|
-
const toSign = new asn1js.Set({ value: attributes.map((
|
|
209
|
+
const toSign = new asn1js.Set({ value: attributes.map((attribute) => attribute.toSchema()) }).toBER(false);
|
|
305
210
|
const raw = await input.signer.sign(new Uint8Array(toSign));
|
|
306
|
-
|
|
307
|
-
signerInfo.signature = new asn1js.OctetString({ valueHex: signature });
|
|
211
|
+
signerInfo.signature = new asn1js.OctetString({ valueHex: cmsSignatureValue(input.signer.algorithm, raw) });
|
|
308
212
|
const signedData = new pkijs.SignedData({
|
|
309
213
|
version: 1,
|
|
310
214
|
digestAlgorithms: [digestAlgorithm],
|
|
311
|
-
encapContentInfo: new pkijs.EncapsulatedContentInfo({ eContentType: OID.data }),
|
|
215
|
+
encapContentInfo: new pkijs.EncapsulatedContentInfo({ eContentType: require_oids.OID.data }),
|
|
312
216
|
certificates: chain,
|
|
313
217
|
signerInfos: [signerInfo]
|
|
314
218
|
});
|
|
315
219
|
const contentInfo = new pkijs.ContentInfo({
|
|
316
|
-
contentType: OID.signedData,
|
|
220
|
+
contentType: require_oids.OID.signedData,
|
|
317
221
|
content: signedData.toSchema(true)
|
|
318
222
|
});
|
|
319
223
|
return new Uint8Array(contentInfo.toSchema().toBER(false));
|
|
320
224
|
}
|
|
225
|
+
/** A raw signature as a SignerInfo carries it (ECDSA `r || s` becomes DER). */
|
|
226
|
+
function cmsSignatureValue(algorithm, raw) {
|
|
227
|
+
return algorithm === "ECDSA" ? pkijs.createCMSECDSASignature(require_engine.toArrayBuffer(raw)) : require_engine.toArrayBuffer(raw);
|
|
228
|
+
}
|
|
229
|
+
/** The SignerInfo signature algorithm of a raw key and hash. */
|
|
321
230
|
function signatureAlgorithmFor(algorithm, hash) {
|
|
322
231
|
switch (algorithm) {
|
|
323
232
|
case "RSA-PKCS1-v1_5": return new pkijs.AlgorithmIdentifier({
|
|
324
233
|
algorithmId: {
|
|
325
|
-
sha256: OID.sha256WithRsa,
|
|
326
|
-
sha384: OID.sha384WithRsa,
|
|
327
|
-
sha512: OID.sha512WithRsa
|
|
234
|
+
sha256: require_oids.OID.sha256WithRsa,
|
|
235
|
+
sha384: require_oids.OID.sha384WithRsa,
|
|
236
|
+
sha512: require_oids.OID.sha512WithRsa
|
|
328
237
|
}[hash],
|
|
329
238
|
algorithmParams: new asn1js.Null()
|
|
330
239
|
});
|
|
331
240
|
case "ECDSA": return new pkijs.AlgorithmIdentifier({ algorithmId: {
|
|
332
|
-
sha256: OID.ecdsaSha256,
|
|
333
|
-
sha384: OID.ecdsaSha384,
|
|
334
|
-
sha512: OID.ecdsaSha512
|
|
241
|
+
sha256: require_oids.OID.ecdsaSha256,
|
|
242
|
+
sha384: require_oids.OID.ecdsaSha384,
|
|
243
|
+
sha512: require_oids.OID.ecdsaSha512
|
|
335
244
|
}[hash] });
|
|
336
245
|
case "RSA-PSS": {
|
|
337
246
|
const hashAlgorithm = new pkijs.AlgorithmIdentifier({
|
|
338
|
-
algorithmId: OID_BY_DIGEST[hash],
|
|
247
|
+
algorithmId: require_oids.OID_BY_DIGEST[hash],
|
|
339
248
|
algorithmParams: new asn1js.Null()
|
|
340
249
|
});
|
|
341
250
|
const params = new pkijs.RSASSAPSSParams({
|
|
342
251
|
hashAlgorithm,
|
|
343
252
|
maskGenAlgorithm: new pkijs.AlgorithmIdentifier({
|
|
344
|
-
algorithmId: OID.mgf1,
|
|
253
|
+
algorithmId: require_oids.OID.mgf1,
|
|
345
254
|
algorithmParams: hashAlgorithm.toSchema()
|
|
346
255
|
}),
|
|
347
|
-
saltLength: DIGEST_LENGTH[hash]
|
|
256
|
+
saltLength: require_oids.DIGEST_LENGTH[hash]
|
|
348
257
|
});
|
|
349
258
|
return new pkijs.AlgorithmIdentifier({
|
|
350
|
-
algorithmId: OID.rsaPss,
|
|
259
|
+
algorithmId: require_oids.OID.rsaPss,
|
|
351
260
|
algorithmParams: params.toSchema()
|
|
352
261
|
});
|
|
353
262
|
}
|
|
354
263
|
}
|
|
355
264
|
}
|
|
356
265
|
//#endregion
|
|
266
|
+
//#region src/cms/timestamp.ts
|
|
267
|
+
var timestamp_exports = /* @__PURE__ */ require_engine.__exportAll({
|
|
268
|
+
buildTimestampToken: () => buildTimestampToken,
|
|
269
|
+
readTimestampInfo: () => readTimestampInfo
|
|
270
|
+
});
|
|
271
|
+
/** Read a token's TSTInfo (the encapsulated content of an RFC 3161 token). Throws `CmsError`. */
|
|
272
|
+
function readTimestampInfo(eContent) {
|
|
273
|
+
const asn1 = asn1js.fromBER(require_engine.toArrayBuffer(eContent));
|
|
274
|
+
if (asn1.offset === -1) throw new CmsError("malformed", "TSTInfo is not valid BER");
|
|
275
|
+
let info;
|
|
276
|
+
try {
|
|
277
|
+
info = new pkijs.TSTInfo({ schema: asn1.result });
|
|
278
|
+
} catch (error) {
|
|
279
|
+
throw new CmsError("malformed", `TSTInfo does not parse: ${error.message}`);
|
|
280
|
+
}
|
|
281
|
+
const algorithm = require_oids.DIGEST_BY_OID[info.messageImprint.hashAlgorithm.algorithmId];
|
|
282
|
+
if (!algorithm) throw new CmsError("unsupported", `message imprint algorithm ${info.messageImprint.hashAlgorithm.algorithmId}`);
|
|
283
|
+
return {
|
|
284
|
+
imprintAlgorithm: algorithm,
|
|
285
|
+
imprint: new Uint8Array(info.messageImprint.hashedMessage.valueBlock.valueHexView),
|
|
286
|
+
genTime: info.genTime
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Build an RFC 3161 timestamp token: a SignedData whose content is a
|
|
291
|
+
* TSTInfo stamping `digest`, signed by the authority over content-type
|
|
292
|
+
* (id-ct-TSTInfo), message-digest (of the TSTInfo) and ESS
|
|
293
|
+
* signing-certificate-v2. What a timestamp authority returns; used by the
|
|
294
|
+
* test authority, never to stamp a real document.
|
|
295
|
+
*/
|
|
296
|
+
async function buildTimestampToken(input) {
|
|
297
|
+
const engine = require_engine.ensureEngine();
|
|
298
|
+
if (input.digest.byteLength !== require_oids.DIGEST_LENGTH[input.hash]) throw new Error(`digest is ${input.digest.byteLength} bytes; ${input.hash} needs ${require_oids.DIGEST_LENGTH[input.hash]}`);
|
|
299
|
+
const authorityCert = pkijs.Certificate.fromBER(require_engine.toArrayBuffer(input.authority.certificateChain[0]));
|
|
300
|
+
const serial = globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8));
|
|
301
|
+
serial[0] &= 127;
|
|
302
|
+
const eContent = new pkijs.TSTInfo({
|
|
303
|
+
version: 1,
|
|
304
|
+
policy: "1.3.6.1.4.1.57264.1.1",
|
|
305
|
+
messageImprint: new pkijs.MessageImprint({
|
|
306
|
+
hashAlgorithm: new pkijs.AlgorithmIdentifier({ algorithmId: require_oids.OID_BY_DIGEST[input.hash] }),
|
|
307
|
+
hashedMessage: new asn1js.OctetString({ valueHex: require_engine.toArrayBuffer(input.digest) })
|
|
308
|
+
}),
|
|
309
|
+
serialNumber: new asn1js.Integer({ valueHex: require_engine.toArrayBuffer(serial) }),
|
|
310
|
+
genTime: input.genTime ?? /* @__PURE__ */ new Date()
|
|
311
|
+
}).toSchema().toBER(false);
|
|
312
|
+
const contentDigest = await engine.digest(require_oids.WEBCRYPTO_HASH[input.authority.hash], eContent);
|
|
313
|
+
const certHash = await engine.digest("SHA-256", require_engine.toArrayBuffer(input.authority.certificateChain[0]));
|
|
314
|
+
const issuerSerial = new pkijs.IssuerSerial({
|
|
315
|
+
issuer: new pkijs.GeneralNames({ names: [new pkijs.GeneralName({
|
|
316
|
+
type: 4,
|
|
317
|
+
value: authorityCert.issuer
|
|
318
|
+
})] }),
|
|
319
|
+
serialNumber: authorityCert.serialNumber
|
|
320
|
+
});
|
|
321
|
+
const attributes = [
|
|
322
|
+
new pkijs.Attribute({
|
|
323
|
+
type: require_oids.OID.contentType,
|
|
324
|
+
values: [new asn1js.ObjectIdentifier({ value: require_oids.OID.tstInfo })]
|
|
325
|
+
}),
|
|
326
|
+
new pkijs.Attribute({
|
|
327
|
+
type: require_oids.OID.messageDigest,
|
|
328
|
+
values: [new asn1js.OctetString({ valueHex: contentDigest })]
|
|
329
|
+
}),
|
|
330
|
+
new pkijs.Attribute({
|
|
331
|
+
type: require_oids.OID.signingCertificateV2,
|
|
332
|
+
values: [new asn1js.Sequence({ value: [new asn1js.Sequence({ value: [new asn1js.Sequence({ value: [new asn1js.OctetString({ valueHex: certHash }), issuerSerial.toSchema()] })] })] })]
|
|
333
|
+
})
|
|
334
|
+
];
|
|
335
|
+
const digestAlgorithm = new pkijs.AlgorithmIdentifier({ algorithmId: require_oids.OID_BY_DIGEST[input.authority.hash] });
|
|
336
|
+
const signerInfo = new pkijs.SignerInfo({
|
|
337
|
+
version: 1,
|
|
338
|
+
sid: new pkijs.IssuerAndSerialNumber({
|
|
339
|
+
issuer: authorityCert.issuer,
|
|
340
|
+
serialNumber: authorityCert.serialNumber
|
|
341
|
+
}),
|
|
342
|
+
digestAlgorithm,
|
|
343
|
+
signedAttrs: new pkijs.SignedAndUnsignedAttributes({
|
|
344
|
+
type: 0,
|
|
345
|
+
attributes
|
|
346
|
+
}),
|
|
347
|
+
signatureAlgorithm: signatureAlgorithmFor(input.authority.algorithm, input.authority.hash)
|
|
348
|
+
});
|
|
349
|
+
const toSign = new asn1js.Set({ value: attributes.map((attribute) => attribute.toSchema()) }).toBER(false);
|
|
350
|
+
const raw = await input.authority.sign(new Uint8Array(toSign));
|
|
351
|
+
signerInfo.signature = new asn1js.OctetString({ valueHex: cmsSignatureValue(input.authority.algorithm, raw) });
|
|
352
|
+
const signedData = new pkijs.SignedData({
|
|
353
|
+
version: 3,
|
|
354
|
+
digestAlgorithms: [digestAlgorithm],
|
|
355
|
+
encapContentInfo: new pkijs.EncapsulatedContentInfo({
|
|
356
|
+
eContentType: require_oids.OID.tstInfo,
|
|
357
|
+
eContent: new asn1js.OctetString({ valueHex: eContent })
|
|
358
|
+
}),
|
|
359
|
+
certificates: [authorityCert],
|
|
360
|
+
signerInfos: [signerInfo]
|
|
361
|
+
});
|
|
362
|
+
const contentInfo = new pkijs.ContentInfo({
|
|
363
|
+
contentType: require_oids.OID.signedData,
|
|
364
|
+
content: signedData.toSchema(true)
|
|
365
|
+
});
|
|
366
|
+
return new Uint8Array(contentInfo.toSchema().toBER(false));
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
357
369
|
//#region src/gate.ts
|
|
358
370
|
/**
|
|
359
371
|
* The gate between a signer's answer and `signatures.complete`: the CMS
|
|
@@ -370,14 +382,15 @@ async function verifyForCompletion(input) {
|
|
|
370
382
|
reason: "too-large",
|
|
371
383
|
detail: `the CMS is ${cms.byteLength} bytes; ${prepared.contentsSize} were reserved`
|
|
372
384
|
};
|
|
385
|
+
if (profile === "timestamp") return verifyTimestampToken(cms, prepared);
|
|
373
386
|
let internal;
|
|
374
387
|
try {
|
|
375
388
|
internal = parseCmsInternal(cms);
|
|
376
|
-
} catch (
|
|
389
|
+
} catch (error) {
|
|
377
390
|
return {
|
|
378
391
|
ok: false,
|
|
379
392
|
reason: "malformed",
|
|
380
|
-
detail:
|
|
393
|
+
detail: error instanceof CmsError ? error.message : String(error)
|
|
381
394
|
};
|
|
382
395
|
}
|
|
383
396
|
const parsed = internal.parsed;
|
|
@@ -386,7 +399,7 @@ async function verifyForCompletion(input) {
|
|
|
386
399
|
reason: "algorithm-mismatch",
|
|
387
400
|
detail: `the CMS digests with ${parsed.digestAlgorithm}, the document was prepared with ${prepared.algorithm}`
|
|
388
401
|
};
|
|
389
|
-
if (!bytesEqual(parsed.messageDigest, prepared.digest)) return {
|
|
402
|
+
if (!require_engine.bytesEqual(parsed.messageDigest, prepared.digest)) return {
|
|
390
403
|
ok: false,
|
|
391
404
|
reason: "digest-mismatch",
|
|
392
405
|
detail: "the CMS message digest is not the prepared digest"
|
|
@@ -411,6 +424,47 @@ async function verifyForCompletion(input) {
|
|
|
411
424
|
};
|
|
412
425
|
return { ok: true };
|
|
413
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* A document timestamp's token: it must stamp the prepared digest with the
|
|
429
|
+
* prepared algorithm, its own message digest must cover its TSTInfo, and
|
|
430
|
+
* its signature must verify with the authority's certificate.
|
|
431
|
+
*/
|
|
432
|
+
async function verifyTimestampToken(cms, prepared) {
|
|
433
|
+
let internal;
|
|
434
|
+
let info;
|
|
435
|
+
try {
|
|
436
|
+
internal = parseCmsInternal(cms, "tst-info");
|
|
437
|
+
info = readTimestampInfo(internal.eContent);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
return {
|
|
440
|
+
ok: false,
|
|
441
|
+
reason: "malformed",
|
|
442
|
+
detail: error instanceof CmsError ? error.message : String(error)
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (info.imprintAlgorithm !== prepared.algorithm) return {
|
|
446
|
+
ok: false,
|
|
447
|
+
reason: "algorithm-mismatch",
|
|
448
|
+
detail: `the token stamps a ${info.imprintAlgorithm} digest, the document was prepared with ${prepared.algorithm}`
|
|
449
|
+
};
|
|
450
|
+
if (!require_engine.bytesEqual(info.imprint, prepared.digest)) return {
|
|
451
|
+
ok: false,
|
|
452
|
+
reason: "digest-mismatch",
|
|
453
|
+
detail: "the token's message imprint is not the prepared digest"
|
|
454
|
+
};
|
|
455
|
+
if (!require_engine.bytesEqual(new Uint8Array(await require_engine.ensureEngine().digest(require_oids.WEBCRYPTO_HASH[internal.parsed.digestAlgorithm], require_engine.toArrayBuffer(internal.eContent))), internal.parsed.messageDigest)) return {
|
|
456
|
+
ok: false,
|
|
457
|
+
reason: "signature-invalid",
|
|
458
|
+
detail: "the token's message digest does not cover its TSTInfo"
|
|
459
|
+
};
|
|
460
|
+
const cryptography = await verifyCmsSignature(internal);
|
|
461
|
+
if (cryptography !== "valid") return {
|
|
462
|
+
ok: false,
|
|
463
|
+
reason: "signature-invalid",
|
|
464
|
+
detail: cryptography === "unsupported" ? "the signature algorithm is not supported" : "the token signature does not verify"
|
|
465
|
+
};
|
|
466
|
+
return { ok: true };
|
|
467
|
+
}
|
|
414
468
|
//#endregion
|
|
415
469
|
//#region src/trust.ts
|
|
416
470
|
/**
|
|
@@ -428,17 +482,17 @@ async function validateChain(cms, trust, at = { kind: "now" }) {
|
|
|
428
482
|
status: "unknown",
|
|
429
483
|
reason: "no trust anchors configured"
|
|
430
484
|
};
|
|
431
|
-
ensureEngine();
|
|
485
|
+
require_engine.ensureEngine();
|
|
432
486
|
let internal;
|
|
433
487
|
try {
|
|
434
488
|
internal = cms instanceof Uint8Array ? parseCmsInternal(cms) : cms;
|
|
435
|
-
} catch (
|
|
489
|
+
} catch (error) {
|
|
436
490
|
return {
|
|
437
491
|
status: "untrusted",
|
|
438
|
-
reason:
|
|
492
|
+
reason: error.message
|
|
439
493
|
};
|
|
440
494
|
}
|
|
441
|
-
const trustedCerts = anchors.map((der) => pkijs.Certificate.fromBER(toArrayBuffer(der)));
|
|
495
|
+
const trustedCerts = anchors.map((der) => pkijs.Certificate.fromBER(require_engine.toArrayBuffer(der)));
|
|
442
496
|
const chainEngine = new pkijs.CertificateChainValidationEngine({
|
|
443
497
|
certs: internal.certificates,
|
|
444
498
|
trustedCerts,
|
|
@@ -452,91 +506,103 @@ async function validateChain(cms, trust, at = { kind: "now" }) {
|
|
|
452
506
|
};
|
|
453
507
|
return {
|
|
454
508
|
status: "trusted",
|
|
455
|
-
path: (result.certificatePath ?? []).map((
|
|
509
|
+
path: (result.certificatePath ?? []).map((certificate) => new Uint8Array(certificate.toSchema(true).toBER(false)))
|
|
456
510
|
};
|
|
457
|
-
} catch (
|
|
511
|
+
} catch (error) {
|
|
458
512
|
return {
|
|
459
513
|
status: "untrusted",
|
|
460
|
-
reason:
|
|
514
|
+
reason: error.message
|
|
461
515
|
};
|
|
462
516
|
}
|
|
463
517
|
}
|
|
464
518
|
//#endregion
|
|
465
519
|
//#region src/sign.ts
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
}
|
|
520
|
+
/**
|
|
521
|
+
* The gate refused the key's answer: `SignatureRefused`, with the gate's
|
|
522
|
+
* reason in `details.reason`.
|
|
523
|
+
*/
|
|
524
|
+
function signingRefused(reason, detail) {
|
|
525
|
+
return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.SignatureRefused, `signing refused (${reason}): ${detail}`, { details: { reason } });
|
|
526
|
+
}
|
|
473
527
|
function profileFor(subFilter) {
|
|
474
|
-
|
|
528
|
+
switch (subFilter) {
|
|
529
|
+
case "adbe.pkcs7.detached": return "pkcs7";
|
|
530
|
+
case "ETSI.CAdES.detached": return "cades-b";
|
|
531
|
+
case "ETSI.RFC3161": return "timestamp";
|
|
532
|
+
default: throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `unknown subFilter '${String(subFilter)}'`, { details: { field: "subFilter" } });
|
|
533
|
+
}
|
|
475
534
|
}
|
|
476
535
|
/**
|
|
477
|
-
* Sign a field in one call: `prepare` on the engine, the
|
|
536
|
+
* Sign a field in one call: `prepare` on the engine, the key turns the
|
|
478
537
|
* digest into a CMS (or a raw signature this package wraps), the gate
|
|
479
538
|
* checks the CMS matches what was prepared, `complete` installs it. Any
|
|
480
|
-
* failure
|
|
539
|
+
* failure cancels the candidate and rethrows; the document is never left
|
|
481
540
|
* with a pending signing.
|
|
482
541
|
*/
|
|
483
542
|
async function sign(doc, input) {
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
543
|
+
const { key, ...rest } = input;
|
|
544
|
+
const timestamp = rest.kind === "timestamp";
|
|
545
|
+
if (timestamp && key.kind !== "cms") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "a document timestamp needs a timestamp authority: a CMS key that returns an RFC 3161 token", { details: { field: "key" } });
|
|
546
|
+
const subFilter = timestamp ? "ETSI.RFC3161" : rest.subFilter ?? "ETSI.CAdES.detached";
|
|
487
547
|
const profile = profileFor(subFilter);
|
|
488
|
-
const digest = rest.digest ?? (
|
|
548
|
+
const digest = rest.digest ?? (key.kind === "raw" ? key.hash : "sha256");
|
|
489
549
|
const prepared = await doc.signatures.prepare({
|
|
490
550
|
...rest,
|
|
491
|
-
subFilter,
|
|
551
|
+
...timestamp ? {} : { subFilter },
|
|
492
552
|
digest
|
|
493
553
|
});
|
|
494
554
|
try {
|
|
495
|
-
const cms =
|
|
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
|
-
});
|
|
555
|
+
const cms = await cmsFor(key, prepared, subFilter, profile);
|
|
506
556
|
const gate = await verifyForCompletion({
|
|
507
557
|
cms,
|
|
508
558
|
prepared,
|
|
509
559
|
profile
|
|
510
560
|
});
|
|
511
|
-
if (!gate.ok) throw
|
|
561
|
+
if (!gate.ok) throw signingRefused(gate.reason, gate.detail);
|
|
512
562
|
return await doc.signatures.complete({
|
|
513
563
|
signingId: prepared.signingId,
|
|
514
564
|
cms,
|
|
515
565
|
expectedVersion: prepared.expectedVersion
|
|
516
566
|
});
|
|
517
|
-
} catch (
|
|
518
|
-
await doc.signatures.
|
|
519
|
-
throw
|
|
567
|
+
} catch (error) {
|
|
568
|
+
await doc.signatures.cancel(prepared.signingId).catch(() => void 0);
|
|
569
|
+
throw error;
|
|
520
570
|
}
|
|
521
571
|
}
|
|
572
|
+
/** The key's answer for the prepared digest: a service's CMS, or one built here around a raw signature. */
|
|
573
|
+
async function cmsFor(key, prepared, subFilter, profile) {
|
|
574
|
+
if (key.kind === "cms") return key.sign({
|
|
575
|
+
digest: prepared.digest,
|
|
576
|
+
algorithm: prepared.algorithm,
|
|
577
|
+
subFilter
|
|
578
|
+
});
|
|
579
|
+
if (profile === "timestamp") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "a raw key cannot make a timestamp token");
|
|
580
|
+
return buildDetachedCms({
|
|
581
|
+
digest: prepared.digest,
|
|
582
|
+
hash: prepared.algorithm,
|
|
583
|
+
profile,
|
|
584
|
+
signer: key,
|
|
585
|
+
signingTime: profile === "pkcs7" ? /* @__PURE__ */ new Date() : void 0
|
|
586
|
+
});
|
|
587
|
+
}
|
|
522
588
|
//#endregion
|
|
523
589
|
//#region src/verdict.ts
|
|
524
590
|
/**
|
|
525
591
|
* Validate every signature of a document: integrity from the engine's
|
|
526
592
|
* bytes, cryptography from the CMS, trust from the caller's anchors.
|
|
527
593
|
*/
|
|
528
|
-
async function validateSignatures(doc,
|
|
594
|
+
async function validateSignatures(doc, options = {}) {
|
|
529
595
|
if (!doc.signatures) throw new Error("this engine does not implement signatures");
|
|
530
596
|
const snapshot = await doc.signatures.list();
|
|
531
597
|
const verdicts = [];
|
|
532
598
|
for (const signature of snapshot.signatures) {
|
|
533
599
|
if (!signature.signed) continue;
|
|
534
|
-
verdicts.push(await validateOne(doc, snapshot, signature,
|
|
600
|
+
verdicts.push(await validateOne(doc, snapshot, signature, options));
|
|
535
601
|
}
|
|
536
602
|
return verdicts;
|
|
537
603
|
}
|
|
538
|
-
async function validateOne(doc, snapshot, signature,
|
|
539
|
-
const at =
|
|
604
|
+
async function validateOne(doc, snapshot, signature, options) {
|
|
605
|
+
const at = options.at ?? { kind: "now" };
|
|
540
606
|
let integrity = "indeterminate";
|
|
541
607
|
let cryptography = "unsupported";
|
|
542
608
|
let trust = "unknown";
|
|
@@ -545,21 +611,21 @@ async function validateOne(doc, snapshot, signature, opts) {
|
|
|
545
611
|
let cms = null;
|
|
546
612
|
if (!snapshot.chainValid || signature.coverage === "malformed") integrity = snapshot.chainValid ? "invalid" : "indeterminate";
|
|
547
613
|
else try {
|
|
548
|
-
internal = parseCmsInternal(await doc.signatures.
|
|
614
|
+
internal = parseCmsInternal(await doc.signatures.getContents(signature.field));
|
|
549
615
|
cms = internal.parsed;
|
|
550
|
-
const digestMatches = bytesEqual(await doc.signatures.
|
|
616
|
+
const digestMatches = require_engine.bytesEqual(await doc.signatures.getDigest(signature.field, cms.digestAlgorithm), cms.messageDigest);
|
|
551
617
|
integrity = signature.coverage === "whole-revision" && digestMatches ? "valid" : "invalid";
|
|
552
|
-
} catch (
|
|
553
|
-
integrity =
|
|
554
|
-
if (!(
|
|
618
|
+
} catch (error) {
|
|
619
|
+
integrity = error instanceof CmsError && error.reason === "unsupported" ? "indeterminate" : "invalid";
|
|
620
|
+
if (!(error instanceof CmsError)) integrity = "indeterminate";
|
|
555
621
|
}
|
|
556
622
|
if (internal) {
|
|
557
623
|
cryptography = await verifyCmsSignature(internal);
|
|
558
|
-
const chain = await validateChain(internal,
|
|
624
|
+
const chain = await validateChain(internal, options.trust, at);
|
|
559
625
|
trust = chain.status;
|
|
560
626
|
trustReason = chain.reason;
|
|
561
627
|
}
|
|
562
|
-
const modifications = await modificationsOf(doc, snapshot, signature,
|
|
628
|
+
const modifications = await modificationsOf(doc, snapshot, signature, options.until ?? "persisted");
|
|
563
629
|
let summary;
|
|
564
630
|
if (integrity === "invalid" || cryptography === "invalid" || modifications.verdict === "forbidden") summary = "invalid";
|
|
565
631
|
else if (integrity === "indeterminate" || cryptography === "unsupported" || modifications.verdict === "indeterminate") summary = "indeterminate";
|
|
@@ -606,67 +672,15 @@ async function modificationsOf(doc, snapshot, signature, until) {
|
|
|
606
672
|
...analysis.later.revisionCount > 0 ? { laterRevisions: analysis.later.revisionCount } : {},
|
|
607
673
|
...analysis.later.undoneObjectNumbers.length > 0 ? { undone: true } : {}
|
|
608
674
|
};
|
|
609
|
-
} catch (
|
|
675
|
+
} catch (error) {
|
|
610
676
|
return {
|
|
611
677
|
verdict: "indeterminate",
|
|
612
|
-
detail: `analysis failed: ${
|
|
678
|
+
detail: `analysis failed: ${error.message}`,
|
|
613
679
|
basis: "persisted"
|
|
614
680
|
};
|
|
615
681
|
}
|
|
616
682
|
}
|
|
617
683
|
//#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
684
|
//#region src/signers/webcrypto.ts
|
|
671
685
|
/**
|
|
672
686
|
* A `RawSigner` over a WebCrypto private key: the key never leaves the
|
|
@@ -706,7 +720,7 @@ function webCryptoSigner(input) {
|
|
|
706
720
|
}[hash]
|
|
707
721
|
} : algorithm === "ECDSA" ? {
|
|
708
722
|
name: "ECDSA",
|
|
709
|
-
hash: WEBCRYPTO_HASH[hash]
|
|
723
|
+
hash: require_oids.WEBCRYPTO_HASH[hash]
|
|
710
724
|
} : { name: "RSASSA-PKCS1-v1_5" };
|
|
711
725
|
const copy = new Uint8Array(data.byteLength);
|
|
712
726
|
copy.set(data);
|
|
@@ -728,14 +742,14 @@ function hashFromWebCrypto(name) {
|
|
|
728
742
|
* self-signed certificate, never persisted. Trust it with
|
|
729
743
|
* `{ anchors: async () => [signer.certificate] }`.
|
|
730
744
|
*/
|
|
731
|
-
async function createTestSigner(
|
|
732
|
-
|
|
733
|
-
const hash =
|
|
734
|
-
const keys = await generateSigningKeyPair(
|
|
745
|
+
async function createTestSigner(options = {}) {
|
|
746
|
+
const { selfSignedCertificate } = await Promise.resolve().then(() => self_signed_exports);
|
|
747
|
+
const hash = options.hash ?? "sha256";
|
|
748
|
+
const keys = await generateSigningKeyPair(options.algorithm ?? "RSA-PKCS1-v1_5", hash, true);
|
|
735
749
|
const certificate = await selfSignedCertificate({
|
|
736
750
|
publicKey: keys.publicKey,
|
|
737
751
|
privateKey: keys.privateKey,
|
|
738
|
-
commonName:
|
|
752
|
+
commonName: options.commonName ?? "EmbedPDF test signer",
|
|
739
753
|
hash
|
|
740
754
|
});
|
|
741
755
|
return {
|
|
@@ -748,6 +762,25 @@ async function createTestSigner(opts = {}) {
|
|
|
748
762
|
privateKey: keys.privateKey
|
|
749
763
|
};
|
|
750
764
|
}
|
|
765
|
+
/**
|
|
766
|
+
* A throwaway RFC 3161 timestamp authority for tests and demos: it stamps
|
|
767
|
+
* whatever digest it is given with the current time, signed by a fresh
|
|
768
|
+
* self-signed key. The key for `sign(doc, { kind: 'timestamp', key })`;
|
|
769
|
+
* never use it to stamp a real document.
|
|
770
|
+
*/
|
|
771
|
+
async function createTestTimestampAuthority(options = {}) {
|
|
772
|
+
const authority = await createTestSigner({ commonName: options.commonName ?? "EmbedPDF test timestamp authority" });
|
|
773
|
+
const { buildTimestampToken } = await Promise.resolve().then(() => timestamp_exports);
|
|
774
|
+
return {
|
|
775
|
+
kind: "cms",
|
|
776
|
+
certificate: authority.certificate,
|
|
777
|
+
sign: ({ digest, algorithm }) => buildTimestampToken({
|
|
778
|
+
digest,
|
|
779
|
+
hash: algorithm,
|
|
780
|
+
authority
|
|
781
|
+
})
|
|
782
|
+
};
|
|
783
|
+
}
|
|
751
784
|
/** A WebCrypto signing key pair; `extractable: false` keeps the private key inside the runtime for good. */
|
|
752
785
|
async function generateSigningKeyPair(algorithm, hash, extractable) {
|
|
753
786
|
return await globalThis.crypto.subtle.generateKey(algorithm === "ECDSA" ? {
|
|
@@ -761,15 +794,16 @@ async function generateSigningKeyPair(algorithm, hash, extractable) {
|
|
|
761
794
|
0,
|
|
762
795
|
1
|
|
763
796
|
]),
|
|
764
|
-
hash: WEBCRYPTO_HASH[hash]
|
|
797
|
+
hash: require_oids.WEBCRYPTO_HASH[hash]
|
|
765
798
|
}, extractable, ["sign", "verify"]);
|
|
766
799
|
}
|
|
767
800
|
//#endregion
|
|
768
801
|
//#region src/signers/remote.ts
|
|
769
802
|
/**
|
|
770
|
-
* A `CmsSigner` over the customer's own signing service
|
|
771
|
-
* digest goes out, a finished detached CMS
|
|
772
|
-
*
|
|
803
|
+
* A `CmsSigner` over the customer's own signing service, HSM or timestamp
|
|
804
|
+
* authority: the engine's digest goes out, a finished detached CMS (or, for
|
|
805
|
+
* `'ETSI.RFC3161'`, an RFC 3161 token) comes back. Nothing but the digest
|
|
806
|
+
* (and which hash and profile it is for) ever leaves the runtime.
|
|
773
807
|
*/
|
|
774
808
|
function remoteSigner(input) {
|
|
775
809
|
return {
|
|
@@ -789,10 +823,12 @@ function remoteSigner(input) {
|
|
|
789
823
|
* unknown", which is the honest answer for a self-issued identity.
|
|
790
824
|
*/
|
|
791
825
|
async function personalSigner(input) {
|
|
826
|
+
const { ensureEngine } = await Promise.resolve().then(() => require("./engine-BPmX1KyF.cjs")).then((n) => n.engine_exports);
|
|
792
827
|
ensureEngine();
|
|
793
828
|
const hash = input.hash ?? "sha256";
|
|
794
829
|
let record = await input.store.load(input.subject);
|
|
795
830
|
if (!record) {
|
|
831
|
+
const { selfSignedCertificate } = await Promise.resolve().then(() => self_signed_exports);
|
|
796
832
|
const keys = await generateSigningKeyPair(input.algorithm ?? "RSA-PKCS1-v1_5", hash, false);
|
|
797
833
|
const certificate = await selfSignedCertificate({
|
|
798
834
|
publicKey: keys.publicKey,
|
|
@@ -834,8 +870,8 @@ function memoryKeyStore() {
|
|
|
834
870
|
* itself (structured clone), so the key survives reloads without ever
|
|
835
871
|
* existing as bytes.
|
|
836
872
|
*/
|
|
837
|
-
function indexedDbKeyStore(dbName,
|
|
838
|
-
const storeName =
|
|
873
|
+
function indexedDbKeyStore(dbName, options = {}) {
|
|
874
|
+
const storeName = options.storeName ?? "keys";
|
|
839
875
|
let opening = null;
|
|
840
876
|
const open = () => {
|
|
841
877
|
opening ?? (opening = new Promise((resolve, reject) => {
|
|
@@ -857,17 +893,73 @@ function indexedDbKeyStore(dbName, opts = {}) {
|
|
|
857
893
|
});
|
|
858
894
|
};
|
|
859
895
|
return {
|
|
860
|
-
load: async (subject) => await run("readonly", (
|
|
861
|
-
save: (subject, record) => run("readwrite", (
|
|
862
|
-
remove: (subject) => run("readwrite", (
|
|
896
|
+
load: async (subject) => await run("readonly", (store) => store.get(subject)) ?? null,
|
|
897
|
+
save: (subject, record) => run("readwrite", (store) => store.put(record, subject)).then(() => void 0),
|
|
898
|
+
remove: (subject) => run("readwrite", (store) => store.delete(subject)).then(() => void 0)
|
|
863
899
|
};
|
|
864
900
|
}
|
|
865
901
|
//#endregion
|
|
902
|
+
//#region src/signers/self-signed.ts
|
|
903
|
+
var self_signed_exports = /* @__PURE__ */ require_engine.__exportAll({
|
|
904
|
+
certificateCommonName: () => certificateCommonName,
|
|
905
|
+
selfSignedCertificate: () => selfSignedCertificate
|
|
906
|
+
});
|
|
907
|
+
/**
|
|
908
|
+
* A self-signed certificate over a WebCrypto key pair: the shape a personal
|
|
909
|
+
* signer, a test signer, and a demo all need. It is its own trust anchor and
|
|
910
|
+
* nothing else's — Acrobat shows a signature made with it as "validity
|
|
911
|
+
* unknown" until the reader trusts the certificate explicitly.
|
|
912
|
+
*/
|
|
913
|
+
async function selfSignedCertificate(input) {
|
|
914
|
+
require_engine.ensureEngine();
|
|
915
|
+
const cert = new pkijs.Certificate();
|
|
916
|
+
cert.version = 2;
|
|
917
|
+
const serial = /* @__PURE__ */ new Uint8Array(8);
|
|
918
|
+
globalThis.crypto.getRandomValues(serial);
|
|
919
|
+
serial[0] &= 127;
|
|
920
|
+
cert.serialNumber = new asn1js.Integer({ valueHex: serial.buffer });
|
|
921
|
+
const name = new pkijs.AttributeTypeAndValue({
|
|
922
|
+
type: require_oids.OID.commonName,
|
|
923
|
+
value: new asn1js.Utf8String({ value: input.commonName })
|
|
924
|
+
});
|
|
925
|
+
cert.issuer.typesAndValues.push(name);
|
|
926
|
+
cert.subject.typesAndValues.push(name);
|
|
927
|
+
const now = Date.now();
|
|
928
|
+
cert.notBefore.value = /* @__PURE__ */ new Date(now - 24 * 3600 * 1e3);
|
|
929
|
+
cert.notAfter.value = new Date(now + (input.validityDays ?? 365) * 24 * 3600 * 1e3);
|
|
930
|
+
const basicConstraints = new pkijs.BasicConstraints({ cA: true });
|
|
931
|
+
const keyUsage = new asn1js.BitString({
|
|
932
|
+
valueHex: new Uint8Array([132]).buffer,
|
|
933
|
+
unusedBits: 2
|
|
934
|
+
});
|
|
935
|
+
cert.extensions = [new pkijs.Extension({
|
|
936
|
+
extnID: require_oids.OID.basicConstraints,
|
|
937
|
+
critical: true,
|
|
938
|
+
extnValue: basicConstraints.toSchema().toBER(false),
|
|
939
|
+
parsedValue: basicConstraints
|
|
940
|
+
}), new pkijs.Extension({
|
|
941
|
+
extnID: require_oids.OID.keyUsage,
|
|
942
|
+
critical: true,
|
|
943
|
+
extnValue: keyUsage.toBER(false)
|
|
944
|
+
})];
|
|
945
|
+
await cert.subjectPublicKeyInfo.importKey(input.publicKey);
|
|
946
|
+
await cert.sign(input.privateKey, require_oids.WEBCRYPTO_HASH[input.hash]);
|
|
947
|
+
return new Uint8Array(cert.toSchema(true).toBER(false));
|
|
948
|
+
}
|
|
949
|
+
/** The subject common name of a DER certificate, or null when it has none. */
|
|
950
|
+
function certificateCommonName(der) {
|
|
951
|
+
require_engine.ensureEngine();
|
|
952
|
+
const copy = new Uint8Array(der.byteLength);
|
|
953
|
+
copy.set(der);
|
|
954
|
+
const text = (pkijs.Certificate.fromBER(copy.buffer).subject.typesAndValues.find((entry) => entry.type === require_oids.OID.commonName)?.value)?.valueBlock?.value;
|
|
955
|
+
return typeof text === "string" && text.length > 0 ? text : null;
|
|
956
|
+
}
|
|
957
|
+
//#endregion
|
|
866
958
|
exports.CmsError = CmsError;
|
|
867
|
-
exports.SigningError = SigningError;
|
|
868
959
|
exports.buildDetachedCms = buildDetachedCms;
|
|
869
960
|
exports.certificateCommonName = certificateCommonName;
|
|
870
961
|
exports.createTestSigner = createTestSigner;
|
|
962
|
+
exports.createTestTimestampAuthority = createTestTimestampAuthority;
|
|
871
963
|
exports.generateSigningKeyPair = generateSigningKeyPair;
|
|
872
964
|
exports.indexedDbKeyStore = indexedDbKeyStore;
|
|
873
965
|
exports.memoryKeyStore = memoryKeyStore;
|