@blamejs/pki 0.8.31 → 0.8.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/README.md +66 -15
  3. package/bin/pki.js +716 -30
  4. package/examples/issue-a-certificate.js +90 -0
  5. package/examples/post-quantum.js +66 -0
  6. package/examples/read-a-certificate.js +56 -0
  7. package/examples/revoke-and-check.js +86 -0
  8. package/examples/sign-and-verify-cms.js +63 -0
  9. package/index.js +18 -5
  10. package/lib/acme.js +223 -74
  11. package/lib/alt-sig.js +441 -0
  12. package/lib/asn1-der.js +48 -25
  13. package/lib/attrcert-sign.js +27 -19
  14. package/lib/cbor-det.js +24 -14
  15. package/lib/cmc-build.js +9 -2
  16. package/lib/cmc-verify.js +14 -3
  17. package/lib/cmp-build.js +17 -5
  18. package/lib/cmp-session.js +24 -11
  19. package/lib/cmp-verify.js +28 -19
  20. package/lib/cms-compress.js +3 -1
  21. package/lib/cms-decrypt.js +102 -7
  22. package/lib/cms-encrypt.js +59 -7
  23. package/lib/cms-sign.js +31 -9
  24. package/lib/cms-verify.js +22 -18
  25. package/lib/composite-kem.js +30 -18
  26. package/lib/composite-sig.js +29 -14
  27. package/lib/constants.js +26 -0
  28. package/lib/crl-sign.js +111 -19
  29. package/lib/crmf-sign.js +167 -15
  30. package/lib/csr-sign.js +117 -6
  31. package/lib/ct.js +1008 -58
  32. package/lib/est.js +29 -16
  33. package/lib/framework-error.js +12 -0
  34. package/lib/guard-async.js +1 -1
  35. package/lib/guard-bytes.js +103 -20
  36. package/lib/guard-crypto.js +205 -3
  37. package/lib/guard-encoding.js +6 -1
  38. package/lib/guard-identifier.js +55 -8
  39. package/lib/guard-intrinsic.js +43 -0
  40. package/lib/guard-json.js +122 -9
  41. package/lib/guard-limits.js +12 -1
  42. package/lib/guard-list.js +63 -8
  43. package/lib/guard-name.js +167 -20
  44. package/lib/guard-range.js +43 -5
  45. package/lib/guard-text.js +41 -0
  46. package/lib/guard-verdict.js +39 -8
  47. package/lib/hpke.js +688 -42
  48. package/lib/http-transport.js +156 -4
  49. package/lib/identity-match.js +4 -4
  50. package/lib/inspect.js +33 -6
  51. package/lib/jose.js +93 -25
  52. package/lib/key.js +18 -13
  53. package/lib/lint.js +165 -1
  54. package/lib/merkle.js +233 -27
  55. package/lib/ocsp.js +27 -14
  56. package/lib/oid.js +14 -4
  57. package/lib/path-validate.js +143 -61
  58. package/lib/pkcs12-build.js +7 -2
  59. package/lib/pki-build.js +31 -7
  60. package/lib/possession.js +575 -0
  61. package/lib/related-cert.js +708 -0
  62. package/lib/revocation-fetch.js +6 -6
  63. package/lib/scep.js +15 -5
  64. package/lib/schema-c509.js +5 -2
  65. package/lib/schema-cmc.js +1 -1
  66. package/lib/schema-cmp.js +8 -8
  67. package/lib/schema-cms.js +2 -15
  68. package/lib/schema-csr.js +69 -0
  69. package/lib/schema-engine.js +10 -3
  70. package/lib/schema-pkix.js +228 -2
  71. package/lib/schema-tsp.js +7 -6
  72. package/lib/sign-scheme.js +22 -9
  73. package/lib/sigstore.js +684 -168
  74. package/lib/sleep.js +8 -1
  75. package/lib/smime.js +22 -1
  76. package/lib/tlog.js +1235 -0
  77. package/lib/tls-cert-compress.js +25 -8
  78. package/lib/tsp-sign.js +120 -35
  79. package/lib/tuf.js +1395 -0
  80. package/lib/validator-tpm.js +4 -1
  81. package/lib/verify-seam.js +7 -1
  82. package/lib/webauthn-mds.js +41 -4
  83. package/lib/webauthn.js +43 -25
  84. package/lib/webcrypto.js +48 -46
  85. package/lib/x509-sign.js +225 -19
  86. package/package.json +2 -1
  87. package/sbom.cdx.json +6 -6
package/lib/alt-sig.js ADDED
@@ -0,0 +1,441 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.altSig
6
+ * @nav Signing
7
+ * @title Alternative signatures
8
+ * @order 237
9
+ * @slug alt-sig
10
+ * @fullname Alternative signatures: one certificate carrying two, for an algorithm migration
11
+ *
12
+ * @intro
13
+ * One certificate, two signatures. A PKI moving from one set of algorithms to
14
+ * another cannot reissue to every relying party at once, so ITU-T X.509 (2019)
15
+ * clause 9.8 lets a certificate carry a second public key, a second signature
16
+ * algorithm and a second signature in extensions. A relying party that has not
17
+ * migrated reads the native signature and ignores the rest; one that has reads
18
+ * the alternative signature instead. The shape is widely called Catalyst.
19
+ *
20
+ * `signedData` builds the bytes that alternative signature covers. They are the
21
+ * `PreTBSCertificate`: the `tbsCertificate` without its `signature`
22
+ * AlgorithmIdentifier and without the `altSignatureValue` extension. A CRL has
23
+ * the same shape in `PreTBSCertList`, where `signature` is the second element
24
+ * rather than the third. Those bytes never appear on the wire, so a verifier
25
+ * rebuilds them. This is the one place the toolkit re-encodes what it parsed, so
26
+ * it does it by keeping the original bytes of every component it retains and
27
+ * recomputing only the two SEQUENCE headers whose lengths change. Nothing is
28
+ * re-serialized from a decoded model, because a model that normalized any byte
29
+ * would either fail every verification or accept an encoding the issuer never
30
+ * signed.
31
+ *
32
+ * `verify` checks that signature under the alternative public key of the ISSUER,
33
+ * which clause 9.8.4 requires, through the same engine certification-path
34
+ * validation uses. The algorithm is not derived here: the structure states it in
35
+ * `altSignatureAlgorithm`, and the engine binds that stated algorithm to the key
36
+ * it was handed, so an algorithm that does not match the key is refused rather
37
+ * than reported as a bad signature.
38
+ *
39
+ * `subjectAltPublicKey` reads a certificate's own alternative public key, which is
40
+ * what a verifier needs to check the certificate BELOW it. `SubjectAltPublicKeyInfo`
41
+ * carries the same two components in the same order as `SubjectPublicKeyInfo`, and a
42
+ * field name is not encoded, so the extension value is an SPKI on the wire and an
43
+ * importer takes it unchanged.
44
+ *
45
+ * Clause 7.10.3 gives a CRL the same procedure with the same two extensions, and
46
+ * both verbs read a CRL as readily as a certificate. `subjectAltPublicKeyInfo` is
47
+ * certificate-only, which clause 7.2.2 states in a NOTE.
48
+ *
49
+ * `pki.x509.sign` and `pki.crl.sign` produce all of this: an `altKey` beside the
50
+ * issuing key makes the two-pass signature the clause describes, so the ordering
51
+ * it requires is not the caller's to get right.
52
+ *
53
+ * @card Build and verify the second signature an X.509 (2019) clause 9.8 certificate
54
+ * or CRL carries, for a PKI migrating between algorithm sets.
55
+ */
56
+
57
+ var asn1 = require("./asn1-der");
58
+ var oid = require("./oid");
59
+ var pkix = require("./schema-pkix");
60
+ var schema = require("./schema-engine");
61
+ var schemaX509 = require("./schema-x509");
62
+ var schemaCrl = require("./schema-crl");
63
+ var frameworkError = require("./framework-error");
64
+ var guard = require("./guard-all");
65
+ var intrinsic = require("./guard-intrinsic");
66
+ var seam = require("./verify-seam");
67
+
68
+ var AltSigError = frameworkError.AltSigError;
69
+ var b = asn1.build;
70
+ var _create = intrinsic.create;
71
+ var _assign = intrinsic.assign;
72
+ var _forEach = intrinsic.forEach;
73
+ var _map = intrinsic.map;
74
+ var _bufferFrom = intrinsic.bufferFrom;
75
+ var _bufferConcat = intrinsic.bufferConcat;
76
+ var _push = intrinsic.push;
77
+
78
+ function _err(code, message, cause) { return new AltSigError(code, message, cause); }
79
+
80
+ var _seam = seam.makeSeam("alt-sig", AltSigError, "altsig/bad-input");
81
+
82
+ var OID_SAPKI = oid.byName("subjectAltPublicKeyInfo");
83
+ var OID_ALT_ALG = oid.byName("altSignatureAlgorithm");
84
+ var OID_ALT_VAL = oid.byName("altSignatureValue");
85
+ _forEach([OID_SAPKI, OID_ALT_ALG, OID_ALT_VAL], function (o) {
86
+ if (!o) throw new Error("alt-sig: the clause 9.8 extension OIDs are not in the registry");
87
+ });
88
+
89
+ var KNOWN_VERIFY_OPTS = _create(null);
90
+
91
+ /** @internal Which structure this is, and where its extensions sit. A certificate carries them in
92
+ * `[3] EXPLICIT` at the end of the tbsCertificate; a CRL in `[0] EXPLICIT` at the end of the
93
+ * tbsCertList. The two shapes are told apart by the format detectors the parsers already publish,
94
+ * which are proven mutually exclusive, rather than by a tag probe written here. */
95
+ /** @internal `preName` is the type the preimage is an instance of, and `signatureIndex` finds the
96
+ * `signature` AlgorithmIdentifier the preimage drops. The field sits at a different element in the two
97
+ * structures because the optional `version` before it is encoded differently: a certificate's is
98
+ * `[0] EXPLICIT`, so `signature` is the third element, while a CRL's is a bare INTEGER, so it is the
99
+ * second. Each is located by the tag of the element before it rather than by a fixed offset. */
100
+ /** @internal `carriesSubjectAltKey` is which kinds may hold a `subjectAltPublicKeyInfo` extension:
101
+ * certificates only (ITU-T X.509 (2019) clause 7.2.2, NOTE). It is a row on the kind rather than a
102
+ * position in this array, so adding a third kind states its own answer and reordering the array cannot
103
+ * silently invert the rule. */
104
+ var KINDS = [
105
+ { name: "certificate", carriesSubjectAltKey: true, preName: "PreTBSCertificate", matches: schemaX509.matches, parse: schemaX509.parse, extTag: 3,
106
+ signatureIndex: function (tbs) {
107
+ var first = tbs.children[0];
108
+ return (first.tagClass === "context" && first.tagNumber === 0) ? 2 : 1;
109
+ } },
110
+ { name: "CRL", carriesSubjectAltKey: false, preName: "PreTBSCertList", matches: schemaCrl.matches, parse: schemaCrl.parse, extTag: 0,
111
+ signatureIndex: function (tbs) {
112
+ var first = tbs.children[0];
113
+ return (first.tagClass === "universal" && first.tagNumber === asn1.TAGS.INTEGER) ? 1 : 0;
114
+ } },
115
+ ];
116
+
117
+ function _classify(root) {
118
+ for (var i = 0; i < KINDS.length; i++) {
119
+ if (KINDS[i].matches(root) === true) return KINDS[i];
120
+ }
121
+ return null;
122
+ }
123
+
124
+ /** @internal The decoded root of a signed structure this module reads, with the kind it is. */
125
+ function _open(input, what) {
126
+ var der = guard.bytes.snapshot(input, _err, "altsig/bad-input", what);
127
+ var root;
128
+ try { root = asn1.decode(der); }
129
+ catch (e) { throw _err("altsig/bad-input", what + " must be DER", e); }
130
+ var kind = _classify(root);
131
+ if (kind === null) {
132
+ throw _err("altsig/bad-input", what + " must be an X.509 certificate or a CRL");
133
+ }
134
+ if (root.children.length < 3) {
135
+ throw _err("altsig/bad-input", "a signed " + kind.name + " carries a toBeSigned, an algorithm and a signature");
136
+ }
137
+ /** @internal The structure is PARSED, not merely detected. `matches` answers which format the bytes are,
138
+ * which is not the same as their being a well-formed one of it: a certificate carrying the
139
+ * altSignatureValue extension TWICE passed the detector, `_findExt` took the first signature and
140
+ * `_preimage` removed both, so an ambiguous certificate the shared parser refuses with
141
+ * `x509/duplicate-extension` received a successful verdict here. Running the parser is what holds every
142
+ * structural rule this module then relies on, rather than the handful its own walk happens to check. */
143
+ try { kind.parse(der); }
144
+ catch (e) {
145
+ throw _err("altsig/bad-input", what + " is not a well-formed " + kind.name +
146
+ ", so the extensions this verb reads from it cannot be trusted to be the ones it carries", e);
147
+ }
148
+ return { der: der, root: root, kind: kind };
149
+ }
150
+
151
+ /** @internal The extensions SEQUENCE node of a certificate or CRL, or null when the field is absent.
152
+ * The tagged field is the last child of the toBeSigned in both structures, and it is located by its
153
+ * tag rather than by position, since the optional fields before it may or may not be present. */
154
+ function _extensionsNode(tbs, extTag) {
155
+ var kids = tbs.children || [];
156
+ for (var i = kids.length - 1; i >= 0; i--) {
157
+ var k = kids[i];
158
+ if (k.tagClass === "context" && k.tagNumber === extTag && k.children && k.children.length === 1 &&
159
+ k.children[0].tagClass === "universal" && k.children[0].tagNumber === asn1.TAGS.SEQUENCE) {
160
+ return { index: i, wrapper: k, list: k.children[0] };
161
+ }
162
+ }
163
+ return null;
164
+ }
165
+
166
+ function _extOid(node) {
167
+ if (!node.children || node.children.length < 2) return null;
168
+ try { return asn1.read.oid(node.children[0]); }
169
+ catch (_e) { return null; }
170
+ }
171
+
172
+ /** @internal Every extension of the structure, as `{ oid, node }`, in the order encoded. */
173
+ function _extensionList(opened) {
174
+ var found = _extensionsNode(opened.root.children[0], opened.kind.extTag);
175
+ if (found === null) return { block: null, items: [] };
176
+ return {
177
+ block: found,
178
+ items: _map(found.list.children || [], function (n) { return { oid: _extOid(n), node: n }; }),
179
+ };
180
+ }
181
+
182
+ function _findExt(items, wanted) {
183
+ for (var i = 0; i < items.length; i++) { if (items[i].oid === wanted) return items[i]; }
184
+ return null;
185
+ }
186
+
187
+ /**
188
+ * @primitive pki.altSig.signedData
189
+ * @signature pki.altSig.signedData(structure) -> Buffer
190
+ * @since 0.8.43
191
+ * @status stable
192
+ * @spec ITU-T X.509 (2019) clause 7.2.2 / 7.10.3 / 9.8
193
+ * @defends alternative-signature-scope-confusion (CWE-347)
194
+ * @related pki.altSig.verify, pki.x509.sign, pki.crl.sign
195
+ *
196
+ * The bytes an alternative signature covers, for a certificate or a CRL. ITU-T X.509 (2019) clause
197
+ * 7.2.2 says to "exclude the signature component and the altSignatureValue extension from the public-key
198
+ * certificate, and generate the digital signature over the remaining DER encoded public-key
199
+ * certificate", and clause 7.10.3 says the same of a CRL. Two members are named `signature`, so
200
+ * `draft-truskovsky-lamps-pq-hybrid-x509` gives the excluded one a type of its own: the
201
+ * `PreTBSCertificate` of section 4 is the `tbsCertificate` without "the signature field (the third
202
+ * element in the TBSCertificate sequence)", and the `PreTBSCertList` of section 5 is the `tbsCertList`
203
+ * without its second element. The `altSignatureValue` extension is removed from the extension list of
204
+ * each, and the relative order of the extensions that remain is unchanged. What comes back is that
205
+ * structure: the toBeSigned alone, carrying one member fewer.
206
+ *
207
+ * These are the bytes Bouncy Castle emits and checks, so an alternative signature this toolkit makes
208
+ * verifies there, and one made there verifies here.
209
+ *
210
+ * A verifier has to rebuild the encoding, since it never appears on the wire. Every component retained
211
+ * keeps the bytes it had, and only the SEQUENCE headers whose lengths change are recomputed; nothing is
212
+ * re-serialized from a decoded model.
213
+ *
214
+ * A structure carrying no `altSignatureAlgorithm` extension throws `altsig/absent`: there is no stated
215
+ * algorithm, so there is no alternative signature to be checked and no question these bytes answer.
216
+ *
217
+ * @example
218
+ * var native = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
219
+ * var alt = await pki.key.generate({ name: "ML-DSA-65" });
220
+ * var nativeSpki = await pki.key.export(native.publicKey, { format: "der" });
221
+ * var altSpki = await pki.key.export(alt.publicKey, { format: "der" });
222
+ * var certDer = await pki.x509.sign({
223
+ * subject: "CN=Catalyst", subjectPublicKey: nativeSpki, serialNumber: 1n,
224
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z"),
225
+ * extensions: { subjectAltPublicKeyInfo: altSpki },
226
+ * }, { key: native.privateKey, altKey: alt.privateKey, altPublicKey: altSpki });
227
+ * pki.altSig.signedData(certDer).length > 0; // -> true
228
+ */
229
+ function signedData(structure) {
230
+ var opened = _open(structure, "the signed structure");
231
+ return _preimage(opened);
232
+ }
233
+
234
+ function _preimage(opened) {
235
+ var exts = _extensionList(opened);
236
+ if (_findExt(exts.items, OID_ALT_ALG) === null) {
237
+ throw _err("altsig/absent",
238
+ "the " + opened.kind.name + " carries no altSignatureAlgorithm extension, so it states no alternative signature (ITU-T X.509 (2019) clause 9.8.3)");
239
+ }
240
+ var tbs = opened.root.children[0];
241
+ var sigIndex = opened.kind.signatureIndex(tbs);
242
+ var sigField = tbs.children[sigIndex];
243
+ /** @internal The element dropped is held to being an AlgorithmIdentifier before it is dropped, so a
244
+ * structure whose fields do not sit where the type places them is refused rather than silently
245
+ * signed over with a different element missing. The parser `_open` ran already holds the layout;
246
+ * this keeps a future structure whose layout differs from failing quietly. */
247
+ if (!sigField || sigField.tagClass !== "universal" || sigField.tagNumber !== asn1.TAGS.SEQUENCE) {
248
+ throw _err("altsig/bad-input",
249
+ "the " + opened.kind.name + " does not carry its signature AlgorithmIdentifier where the type places it, so the " +
250
+ opened.kind.preName + " cannot be built from it");
251
+ }
252
+ var kept = [];
253
+ _forEach(tbs.children || [], function (k, i) {
254
+ if (i === sigIndex) return;
255
+ if (exts.block !== null && i === exts.block.index) return;
256
+ _push(kept, k.bytes);
257
+ });
258
+ if (exts.block !== null) {
259
+ var keptExts = [];
260
+ _forEach(exts.items, function (it) { if (it.oid !== OID_ALT_VAL) _push(keptExts, it.node.bytes); });
261
+ if (keptExts.length === 0) {
262
+ throw _err("altsig/bad-input",
263
+ "removing the altSignatureValue extension would leave an empty Extensions SEQUENCE, which the type does not allow");
264
+ }
265
+ _push(kept, b.explicit(opened.kind.extTag, b.sequence([b.raw(_bufferConcat(keptExts))])));
266
+ }
267
+ /** @internal Two headers are recomputed and no leaf is: the Extensions SEQUENCE and its tagged
268
+ * wrapper above, and the toBeSigned SEQUENCE here.
269
+ * WHAT IS EXCLUDED. ITU-T X.509 (2019) clause 7.2.2 says to "exclude the signature
270
+ * component and the altSignatureValue extension from the public-key certificate, and generate the
271
+ * digital signature over the remaining DER encoded public-key certificate". Two structures have a member
272
+ * named `signature`: the outer SEQUENCE, through `COMPONENTS OF SIGNATURE`, whose `signature` is the BIT
273
+ * STRING and whose `algorithmIdentifier` is a separately named component; and `TBSCertificate`, whose
274
+ * `signature` is an AlgorithmIdentifier. The clause names neither, and both can be built.
275
+ * draft-truskovsky-lamps-pq-hybrid-x509 settles it by defining the preimage as a type of its own.
276
+ * Section 4's `PreTBSCertificate` is the TBSCertificate without "the signature field (the third
277
+ * element in the TBSCertificate sequence)", and section 5's `PreTBSCertList` is the TBSCertList
278
+ * without its second element; the extension list in each drops altSignatureValue. The draft gives the
279
+ * reason the toBeSigned's own field is the one excluded: the signature AlgorithmIdentifier "might not
280
+ * be known at the time that the alternative signature is calculated", and being "associated with the
281
+ * final signatureValue field" it "does not need to be protected by the alternative signature". Both
282
+ * creation procedures bear that out, adding the field in a step after the alternative signature is
283
+ * computed. These are the bytes Bouncy Castle emits and checks, so an alternative signature this
284
+ * toolkit makes is one another implementation verifies, and the reverse. */
285
+ return b.sequence([b.raw(_bufferConcat(kept))]);
286
+ }
287
+
288
+ /** @internal One decoded extension value of this structure, through the shared certificate-extension
289
+ * decoders so the value is held to its type before anything acts on it. */
290
+ var NS = pkix.makeNS("altsig", AltSigError, oid);
291
+ var EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
292
+ function _decodeExt(item, label) {
293
+ var dec = EXT_DECODERS[item.oid];
294
+ var value;
295
+ try { value = asn1.read.octetString(item.node.children[item.node.children.length - 1]); }
296
+ catch (e) { throw _err("altsig/bad-input", "the " + label + " extension value must be an OCTET STRING", e); }
297
+ try { return dec(value); }
298
+ catch (e) {
299
+ if (e instanceof AltSigError) throw e;
300
+ throw _err("altsig/bad-input", "the " + label + " extension value is malformed", e);
301
+ }
302
+ }
303
+
304
+ /**
305
+ * @primitive pki.altSig.subjectAltPublicKey
306
+ * @signature pki.altSig.subjectAltPublicKey(certificate) -> Buffer
307
+ * @since 0.8.43
308
+ * @status stable
309
+ * @spec ITU-T X.509 (2019) clause 9.8.2
310
+ * @defends alternative-key-substitution (CWE-347)
311
+ * @related pki.altSig.verify, pki.x509.sign
312
+ *
313
+ * A certificate's own alternative public key, as the SPKI DER an importer takes. This is the key that
314
+ * verifies the alternative signature on a certificate this one ISSUED, which is how the alternative
315
+ * chain is followed: clause 9.8.4 requires the alternative signature to be "verified using the
316
+ * alternative public key of the issuer".
317
+ *
318
+ * `SubjectAltPublicKeyInfo` carries the same two components in the same order as
319
+ * `SubjectPublicKeyInfo`, and a field name is not encoded, so the extension value is already an SPKI
320
+ * and is returned unchanged rather than rebuilt from its parts.
321
+ *
322
+ * A certificate with no such extension throws `altsig/absent`, as does a CRL, the extension being
323
+ * certificate-only (clause 7.2.2, NOTE).
324
+ *
325
+ * @example
326
+ * var native = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
327
+ * var alt = await pki.key.generate({ name: "ML-DSA-65" });
328
+ * var nativeSpki = await pki.key.export(native.publicKey, { format: "der" });
329
+ * var altSpki = await pki.key.export(alt.publicKey, { format: "der" });
330
+ * var caDer = await pki.x509.sign({
331
+ * subject: "CN=Catalyst CA", subjectPublicKey: nativeSpki, serialNumber: 1n,
332
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z"),
333
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"], subjectAltPublicKeyInfo: altSpki },
334
+ * }, { key: native.privateKey, altKey: alt.privateKey, altPublicKey: altSpki });
335
+ * // The same bytes the CA certified, ready to verify a certificate this CA issued.
336
+ * pki.altSig.subjectAltPublicKey(caDer).length === altSpki.length; // -> true
337
+ */
338
+ function subjectAltPublicKey(certificate) {
339
+ var opened = _open(certificate, "the certificate");
340
+ /** @internal The KIND decides before any extension is read. `subjectAltPublicKeyInfo` is
341
+ * certificate-only (clause 7.2.2, NOTE) and this verb documents that a CRL is refused, but the refusal
342
+ * came from the extension being absent rather than from the structure being the wrong one: a CRL
343
+ * carrying the extension, which the pre-encoded extensions array admits under `profile: "none"`, had it
344
+ * decoded with the certificate decoder and its key handed back. A key is an identity, so where it was
345
+ * read from is the question. */
346
+ if (opened.kind.carriesSubjectAltKey !== true) {
347
+ throw _err("altsig/absent",
348
+ "a " + opened.kind.name + " carries no subjectAltPublicKeyInfo: the extension is certificate-only " +
349
+ "(ITU-T X.509 (2019) clause 7.2.2, NOTE)");
350
+ }
351
+ var exts = _extensionList(opened);
352
+ var item = _findExt(exts.items, OID_SAPKI);
353
+ if (item === null) {
354
+ throw _err("altsig/absent",
355
+ "the " + opened.kind.name + " carries no subjectAltPublicKeyInfo extension (ITU-T X.509 (2019) clause 9.8.2)");
356
+ }
357
+ return _decodeExt(item, "subjectAltPublicKeyInfo").bytes;
358
+ }
359
+
360
+ /**
361
+ * @primitive pki.altSig.verify
362
+ * @signature pki.altSig.verify(structure, issuerAltPublicKey, opts?) -> Promise<boolean>
363
+ * @since 0.8.43
364
+ * @status stable
365
+ * @spec ITU-T X.509 (2019) clause 7.2.2 / 7.10.3 / 9.8.4
366
+ * @defends alternative-signature-forgery (CWE-347)
367
+ * @related pki.altSig.signedData, pki.altSig.subjectAltPublicKey, pki.path.validate
368
+ *
369
+ * Verify the alternative signature on a certificate or a CRL. `issuerAltPublicKey` is the issuer's
370
+ * alternative public key as SPKI DER, which clause 9.8.4 requires: "it shall be verified using the
371
+ * alternative public key of the issuer". `pki.altSig.subjectAltPublicKey` reads that key out of the
372
+ * issuer's own certificate. Resolves `true` when the signature over the bytes
373
+ * `pki.altSig.signedData` describes verifies, and `false` when a signature was checked and did not.
374
+ *
375
+ * The algorithm is not derived. The structure states it in its `altSignatureAlgorithm` extension, and
376
+ * that stated algorithm is bound to the key through the same engine certification-path validation
377
+ * uses, so a key that does not match the stated algorithm resolves `false` rather than being read under
378
+ * some other algorithm. Clause 9.8.3's NOTE 1 is why the algorithm is a separate extension: it sits
379
+ * inside the bytes the alternative signature covers, so it cannot be changed without breaking it.
380
+ *
381
+ * A structure that states no alternative signature throws, since there is nothing to check and `false`
382
+ * would read as a signature that failed.
383
+ *
384
+ * This verb answers one question: whether the issuer's alternative key signed this structure. It does
385
+ * not validate a path, check revocation, or read the native signature. `pki.path.validate` answers
386
+ * those, over the native signature.
387
+ *
388
+ * @example
389
+ * var native = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
390
+ * var alt = await pki.key.generate({ name: "ML-DSA-65" });
391
+ * var nativeSpki = await pki.key.export(native.publicKey, { format: "der" });
392
+ * var altSpki = await pki.key.export(alt.publicKey, { format: "der" });
393
+ * var caDer = await pki.x509.sign({
394
+ * subject: "CN=Catalyst CA", subjectPublicKey: nativeSpki, serialNumber: 1n,
395
+ * notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z"),
396
+ * extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"], subjectAltPublicKeyInfo: altSpki },
397
+ * }, { key: native.privateKey, altKey: alt.privateKey, altPublicKey: altSpki });
398
+ * // The CA's own alternative key verifies the alternative signature it made.
399
+ * await pki.altSig.verify(caDer, pki.altSig.subjectAltPublicKey(caDer)); // -> true
400
+ */
401
+ function verify(structure, issuerAltPublicKey, opts) {
402
+ return guard.async.deferred(function () { return _verify(structure, issuerAltPublicKey, opts); });
403
+ }
404
+
405
+ function _verify(structure, issuerAltPublicKey, opts) {
406
+ guard.identifier.assertKnownKeys(opts, KNOWN_VERIFY_OPTS, _err, "altsig/bad-input",
407
+ "pki.altSig.verify has an unknown option: ");
408
+ var opened = _open(structure, "the signed structure");
409
+ var exts = _extensionList(opened);
410
+ var algItem = _findExt(exts.items, OID_ALT_ALG);
411
+ var valItem = _findExt(exts.items, OID_ALT_VAL);
412
+ if (algItem === null || valItem === null) {
413
+ throw _err("altsig/absent",
414
+ "the " + opened.kind.name + " carries no alternative signature: clause 9.8.3 requires the altSignatureAlgorithm and altSignatureValue extensions together");
415
+ }
416
+ var sigAlg = _decodeExt(algItem, "altSignatureAlgorithm");
417
+ var sigValue = _decodeExt(valItem, "altSignatureValue");
418
+ var spki = guard.bytes.snapshot(issuerAltPublicKey, _err, "altsig/bad-input", "the issuer's alternative public key");
419
+ _assertSpki(spki);
420
+ return _seam.verify(sigAlg, sigValue, spki, _preimage(opened));
421
+ }
422
+
423
+ /** @internal The alternative key is handed to an importer, so it is held to being a
424
+ * SubjectPublicKeyInfo first: bytes that are not one would reach the engine as a key it reports no
425
+ * verdict for, and a caller cannot tell that from a signature that failed. */
426
+ var SPKI_SCHEMA = pkix.spki(NS);
427
+ function _assertSpki(der) {
428
+ try { schema.walk(SPKI_SCHEMA, asn1.decode(der), NS); }
429
+ catch (e) {
430
+ throw _err("altsig/bad-input", "the issuer's alternative public key must be a SubjectPublicKeyInfo in DER", e);
431
+ }
432
+ }
433
+
434
+ module.exports = {
435
+ signedData: signedData,
436
+ verify: verify,
437
+ subjectAltPublicKey: subjectAltPublicKey,
438
+ /** @internal The extension OIDs and the preimage, for the builders that emit them. */
439
+ OID_SAPKI: OID_SAPKI, OID_ALT_ALG: OID_ALT_ALG, OID_ALT_VAL: OID_ALT_VAL,
440
+ setEngine: _seam.setEngine,
441
+ };
package/lib/asn1-der.js CHANGED
@@ -41,6 +41,7 @@ var frameworkError = require("./framework-error");
41
41
  var guard = require("./guard-all");
42
42
  var intrinsic = require("./guard-intrinsic");
43
43
  var _isArray = intrinsic.isArray;
44
+ var _unshift = intrinsic.unshift;
44
45
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
45
46
  /** @internal A Date's fields are read through the prototype's own getters, captured at load, so
46
47
  * a getter the instance carries or one replaced later cannot change what a time encodes to or
@@ -207,6 +208,19 @@ function _decodeTLV(buf, start, limit, depth, maxDepth, ber, strDepth, ctr) {
207
208
  }
208
209
  }
209
210
 
211
+ /** @internal UNIVERSAL tag 0 is reserved. X.690 sec. 8.1.5 gives the `00 00` octet pair one meaning,
212
+ * the terminator of an indefinite-length encoding, and nothing this decoder accepts uses indefinite
213
+ * length: the strict door refuses it outright, and the BER door consumes the pair as a terminator
214
+ * rather than as an element. So a universal tag-0 element is never part of a well-formed input on
215
+ * either door, and it was being handed back as an ordinary zero-length element. The rule is refused
216
+ * HERE, at the TLV, because `guard.der.element` already carried it and a reader that did not route
217
+ * through that guard inherited nothing. The class matters: a context-specific [0] is how most of
218
+ * X.509 and CMS write an optional field, so this is universal-class only. */
219
+ if (tagClassBits === CLASS_UNIVERSAL && tagNumber === 0) {
220
+ throw new Asn1Error("asn1/reserved-tag",
221
+ "universal tag 0 is the reserved end-of-contents encoding (X.690 sec. 8.1.5) and is not an element");
222
+ }
223
+
210
224
  if (tagClassBits === CLASS_UNIVERSAL && CONSTRUCTED_ONLY_UNIVERSAL_TAGS[tagNumber] && !constructed) {
211
225
  throw new Asn1Error("asn1/bad-tlv", "a universal constructed-only type (SEQUENCE/SET/EXTERNAL/EMBEDDED PDV/CHARACTER STRING) must be constructed");
212
226
  }
@@ -472,10 +486,18 @@ function readOid(node) {
472
486
  }
473
487
 
474
488
  function decodeOidContent(buf) {
475
- if (buf.length === 0) throw new OidError("oid/empty", "OBJECT IDENTIFIER content is empty");
489
+ /** @internal This verb is exported, so `buf` is a caller's object. `guard.bytes.view` refuses anything
490
+ * whose indexed elements are not its bytes, and `guard.bytes.lengthOf` reads the count through the
491
+ * captured TypedArray prototype getter, so the count and the bytes come from the same view. A Buffer
492
+ * accepts an own `length` property that differs from its real size, and read from `.length` a buffer
493
+ * whose bytes encode 1.2.840.3 and whose `length` says 3 decoded as 1.2.840. An OID selects a signature
494
+ * algorithm and an extension decoder, so the bytes have to decide. */
495
+ buf = guard.bytes.view(buf, OidError, "oid/bad-input", "OBJECT IDENTIFIER content");
496
+ var len = guard.bytes.lengthOf(buf);
497
+ if (len === 0) throw new OidError("oid/empty", "OBJECT IDENTIFIER content is empty");
476
498
  var arcs = [];
477
499
  var arcStart = 0;
478
- for (var i = 0; i < buf.length; i++) {
500
+ for (var i = 0; i < len; i++) {
479
501
  var b = buf[i];
480
502
  if (i === arcStart && b === 0x80) throw new OidError("oid/non-minimal", "non-minimal sub-identifier (leading 0x80)");
481
503
  if (i - arcStart >= constants.LIMITS.OID_MAX_SUBIDENTIFIER_BYTES) {
@@ -493,7 +515,7 @@ function decodeOidContent(buf) {
493
515
  arcStart = i + 1;
494
516
  }
495
517
  }
496
- if (arcStart !== buf.length) throw new OidError("oid/truncated", "OBJECT IDENTIFIER ends mid sub-identifier");
518
+ if (arcStart !== len) throw new OidError("oid/truncated", "OBJECT IDENTIFIER ends mid sub-identifier");
497
519
  var first = arcs[0];
498
520
  var a1, a2;
499
521
  if (first < 40n) { a1 = 0n; a2 = first; }
@@ -540,17 +562,9 @@ function readNumericString(node) {
540
562
  * so the bytes written would not carry the string the caller handed in, and `_decodeUtf8Strict`
541
563
  * refuses exactly those bytes on the way back. The builder answers the same way rather than
542
564
  * silently changing the text, which is the rule `_decodeUtf16be` already applies to BMPString. */
565
+ /** @internal @guard-via guard\.text\.assertWellFormedUtf16\( */
543
566
  function _assertWellFormedUtf16(s) {
544
- for (var i = 0; i < s.length; i++) {
545
- var c = _charCodeAt(s, i);
546
- if (c >= 0xD800 && c <= 0xDBFF) {
547
- var n = (i + 1) < s.length ? _charCodeAt(s, i + 1) : -1;
548
- if (!(n >= 0xDC00 && n <= 0xDFFF)) throw new Asn1Error("asn1/bad-utf8-string", "UTF8String has an unpaired high surrogate");
549
- i++;
550
- } else if (c >= 0xDC00 && c <= 0xDFFF) {
551
- throw new Asn1Error("asn1/bad-utf8-string", "UTF8String has an unpaired low surrogate");
552
- }
553
- }
567
+ return guard.text.assertWellFormedUtf16(s, Asn1Error, "asn1/bad-utf8-string", "UTF8String");
554
568
  }
555
569
 
556
570
  function _decodeUtf8Strict(buf) {
@@ -688,7 +702,7 @@ function encodeLength(n) {
688
702
  if (n < 0x80) return Buffer.from([n]);
689
703
  var bytes = [];
690
704
  var v = n;
691
- while (v > 0) { bytes.unshift(v & 0xff); v = Math.floor(v / 256); }
705
+ while (v > 0) { _unshift(bytes, v & 0xff); v = Math.floor(v / 256); }
692
706
  if (bytes.length > 126) throw new Asn1Error("asn1/length-too-large", "length needs more than 126 octets");
693
707
  return Buffer.concat([Buffer.from([0x80 | bytes.length]), Buffer.from(bytes)]);
694
708
  }
@@ -728,7 +742,7 @@ function encodeIdentifier(classBits, constructed, tagNumber) {
728
742
  if (tagNumber < 0x1f) return Buffer.from([lead | tagNumber]);
729
743
  var body = [];
730
744
  var v = tagNumber;
731
- do { body.unshift(v & 0x7f); v = Math.floor(v / 128); } while (v > 0);
745
+ do { _unshift(body, v & 0x7f); v = Math.floor(v / 128); } while (v > 0);
732
746
  if (body.length > 4) throw new Asn1Error("asn1/tag-too-large", "high-tag-number too large");
733
747
  for (var i = 0; i < body.length - 1; i++) body[i] |= 0x80;
734
748
  return Buffer.concat([Buffer.from([lead | 0x1f]), Buffer.from(body)]);
@@ -816,8 +830,8 @@ function intToDer(v) {
816
830
  var bytes = [];
817
831
  if (v > 0n) {
818
832
  var t = v;
819
- while (t > 0n) { bytes.unshift(Number(t & 0xffn)); t >>= 8n; }
820
- if (bytes[0] & 0x80) bytes.unshift(0x00);
833
+ while (t > 0n) { _unshift(bytes, Number(t & 0xffn)); t >>= 8n; }
834
+ if (bytes[0] & 0x80) _unshift(bytes, 0x00);
821
835
  } else {
822
836
  var len = 1;
823
837
  while (v < -(1n << BigInt(8 * len - 1))) len += 1;
@@ -831,12 +845,15 @@ function encodeOidContent(dotted) {
831
845
  guard.identifier.assertCanonicalOid(dotted, _oidErr, "oid/bad-input", "OID", "oid/bad-arc", "oid/too-many-subidentifiers");
832
846
  var arcs = dotted.split(".").map(function (p) { return BigInt(p); });
833
847
  var a1 = arcs[0], a2 = arcs[1];
834
- var subids = [a1 * 40n + a2].concat(arcs.slice(2));
848
+ /** @internal The sub-identifier list through the capture: assembled by a live
849
+ * `Array.prototype.concat`, the arcs this encodes were that method's to choose, and the result is
850
+ * the OID a signature or an algorithm is named by. */
851
+ var subids = intrinsic.concat([a1 * 40n + a2], arcs.slice(2));
835
852
  var out = [];
836
853
  for (var s = 0; s < subids.length; s++) {
837
854
  var body = [];
838
855
  var v = subids[s];
839
- do { body.unshift(Number(v & 0x7fn)); v >>= 7n; } while (v > 0n);
856
+ do { _unshift(body, Number(v & 0x7fn)); v >>= 7n; } while (v > 0n);
840
857
  if (body.length > constants.LIMITS.OID_MAX_SUBIDENTIFIER_BYTES) {
841
858
  throw new OidError("oid/subidentifier-too-large",
842
859
  "OID sub-identifier exceeds " + constants.LIMITS.OID_MAX_SUBIDENTIFIER_BYTES + " octets");
@@ -987,15 +1004,21 @@ var build = intrinsic.assign(intrinsic.create(null), {
987
1004
  sequence: function (children) {
988
1005
  return encodeConstructed(CLASS_UNIVERSAL, TAGS.SEQUENCE, _asBufferArray(children, "build.sequence"));
989
1006
  },
1007
+ /** @internal The DER SET ordering goes through the captured copy-and-sort. Read off
1008
+ * `Array.prototype`, `slice` and `sort` decide the bytes every SET OF in the toolkit emits, and these
1009
+ * two builders are where nearly all of them are built: a replacement reorders or substitutes members
1010
+ * after whatever validated them, on content a caller goes on to sign. */
1011
+ /** @internal The COMPARATOR goes through the captured operation, as the copy and the sort beside it
1012
+ * do. A DER SET OF is ordered by its members' encoded bytes, so whatever decides "before" decides
1013
+ * the bytes emitted: a replacement answering a fixed order sets the order of every SET and SET OF
1014
+ * this toolkit builds, after whatever validated the members and on bytes a verb then signs. */
990
1015
  set: function (children) {
991
- var arr = _asBufferArray(children, "build.set").slice();
992
- arr.sort(Buffer.compare);
993
- return encodeConstructed(CLASS_UNIVERSAL, TAGS.SET, arr);
1016
+ return encodeConstructed(CLASS_UNIVERSAL, TAGS.SET,
1017
+ guard.list.sortedCopy(_asBufferArray(children, "build.set"), intrinsic.compare));
994
1018
  },
995
1019
  setOf: function (children) {
996
- var arr = _asBufferArray(children, "build.setOf").slice();
997
- arr.sort(Buffer.compare);
998
- return encodeConstructed(CLASS_UNIVERSAL, TAGS.SET, arr);
1020
+ return encodeConstructed(CLASS_UNIVERSAL, TAGS.SET,
1021
+ guard.list.sortedCopy(_asBufferArray(children, "build.setOf"), intrinsic.compare));
999
1022
  },
1000
1023
  explicit: function (tagNumber, inner) { return encodeTLV(CLASS_CONTEXT, true, tagNumber, _asBuffer(inner, "build.explicit")); },
1001
1024
  contextPrimitive: function (tagNumber, content) { return encodeTLV(CLASS_CONTEXT, false, tagNumber, _asBuffer(content, "build.contextPrimitive")); },