@blamejs/pki 0.2.29 → 0.2.31

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.
@@ -0,0 +1,479 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.schema.c509
6
+ * @nav Schema
7
+ * @title C509
8
+ * @intro C509 CBOR-encoded certificates (draft-ietf-cose-cbor-encoded-cert). A compact CBOR
9
+ * re-encoding of an X.509 v3 certificate: a deterministic-CBOR array of exactly 11 elements
10
+ * (10 TBS fields + the issuer signature). Two modes -- c509CertificateType 2 = natively-signed
11
+ * C509, 3 = a CBOR re-encoding of a DER X.509 certificate that inverts byte-for-byte to the
12
+ * original DER (so the original signature still verifies). It decodes CBOR, not DER, so it is
13
+ * reached by an explicit pki.schema.c509.parse call and is NOT auto-routed by pki.schema.parse.
14
+ * @card Composes the shipped pki.cbor codec (core-deterministic, fail-closed) + the X.509 model.
15
+ */
16
+
17
+ var cbor = require("./cbor-det");
18
+ var asn1 = require("./asn1-der");
19
+ var oid = require("./oid");
20
+ var constants = require("./constants");
21
+ var frameworkError = require("./framework-error");
22
+ var validator = require("./validator-all");
23
+ var webcrypto = require("./webcrypto");
24
+
25
+ var b = asn1.build;
26
+
27
+ var C = constants;
28
+ var C509Error = frameworkError.C509Error;
29
+ function _err(code, message, cause) { return new C509Error(code, message, cause); }
30
+ // The ECMAScript Date window is +/- 8.64e15 ms = +/- 8.64e12 seconds; a C509 ~time is an unsigned
31
+ // epoch-seconds value, so only the upper bound can be exceeded (mirrors cbor-det read.time).
32
+ var MAX_EPOCH_SECONDS = 8640000000000n;
33
+
34
+ // The C509 integer registries (draft-20 sec. 8.6/sec. 8.8/sec. 8.14/sec. 8.15): a C509 int is a compact ALIAS of an
35
+ // OID, resolved to the SAME name oid.byName returns for the DER form. Declared as int -> registered
36
+ // OID NAME (never a dotted-decimal literal -- the oid-dotted-decimal-literal gate); oid.byName then
37
+ // yields the dotted string. A row whose target name is not registered fails closed at module load.
38
+ function _name(n) { var d = oid.byName(n); if (!d) { throw new Error("schema-c509: unregistered OID name " + JSON.stringify(n)); } return n; }
39
+
40
+ // sec. 8.14 issuerSignatureAlgorithm / signature (the subset v1 covers; negative = legacy SHA-1 values).
41
+ var SIG_ALG_BY_INT = {
42
+ 0: _name("ecdsaWithSHA256"),
43
+ 1: _name("ecdsaWithSHA384"),
44
+ 2: _name("ecdsaWithSHA512"),
45
+ };
46
+ // sec. 8.15 subjectPublicKeyAlgorithm (int -> {alg, curve?} so the reconstruction can rebuild the SPKI).
47
+ var PK_ALG_BY_INT = {
48
+ 0: { alg: _name("rsaEncryption") },
49
+ 1: { alg: _name("ecPublicKey"), curve: _name("prime256v1") },
50
+ 2: { alg: _name("ecPublicKey"), curve: _name("secp384r1") },
51
+ 3: { alg: _name("ecPublicKey"), curve: _name("secp521r1") },
52
+ };
53
+ // EC curve -> field size in bytes (the SEC1 coordinate width), for point-length validation.
54
+ var EC_FIELD_BYTES = { "prime256v1": 32, "secp384r1": 48, "secp521r1": 66 };
55
+
56
+ // sec. 8.6 attribute types (abs(int) -> name; the sign selects the X.509 string type).
57
+ var ATTR_BY_INT = {
58
+ 1: _name("commonName"),
59
+ 2: _name("surname"),
60
+ 3: _name("serialNumber"),
61
+ 4: _name("countryName"),
62
+ 6: _name("localityName"),
63
+ 7: _name("stateOrProvinceName"),
64
+ 8: _name("organizationName"),
65
+ 9: _name("organizationalUnitName"),
66
+ 10: _name("title"),
67
+ };
68
+ // sec. 8.8 extension types (abs(int) -> name; the sign selects criticality).
69
+ var EXT_BY_INT = {
70
+ 1: _name("subjectKeyIdentifier"),
71
+ 2: _name("keyUsage"),
72
+ 3: _name("subjectAltName"),
73
+ 4: _name("basicConstraints"),
74
+ 7: _name("keyUsage"),
75
+ 10: _name("authorityKeyIdentifier"),
76
+ };
77
+
78
+ // ---- field readers (the unwrapped ~biguint / ~time / ~oid contracts; draft-20 sec. 3.1) ----
79
+
80
+ // ~biguint (sec. 3.1.2): a BARE byte string (major type 2), big-endian magnitude, the non-negative
81
+ // leading 0x00 OMITTED. NOT the shipped read.biguint (which requires the tag-2 wrapper and rejects
82
+ // <= 8-byte content). A leading 0x00 is non-minimal; content over the cap is rejected.
83
+ function _biguint(node, code, label) {
84
+ if (!node || node.majorType !== 2) throw _err(code, label + " must be an unwrapped CBOR byte string (~biguint)");
85
+ var b = node.content;
86
+ if (b.length > C.LIMITS.CBOR_MAX_BIGUINT_BYTES) throw _err(code, label + " exceeds the ~biguint byte cap");
87
+ if (b.length > 1 && b[0] === 0x00) throw _err("c509/non-minimal-serial", label + " has a redundant leading 0x00 (~biguint omits the sign octet)");
88
+ return b.length ? BigInt("0x" + b.toString("hex")) : 0n;
89
+ }
90
+
91
+ // ~time (sec. 3.1.5): a BARE unsigned integer (major type 0), epoch seconds. A major-type-1 / tag / float
92
+ // MUST reject. Bound to the Date window; the CBOR simple null (permitted only for notAfter) -> null.
93
+ function _time(node, allowNull, label) {
94
+ if (allowNull && node.majorType === 7 && node.ai === 22) return null; // CBOR simple null (0xF6)
95
+ if (!node || node.majorType !== 0) throw _err("c509/bad-validity", label + " must be an unwrapped CBOR epoch integer (~time)");
96
+ var secs = node.argument;
97
+ if (secs > MAX_EPOCH_SECONDS) throw _err("c509/bad-validity", label + " is outside the representable Date range");
98
+ return new Date(C.TIME.seconds(Number(secs)));
99
+ }
100
+
101
+ // ~oid (sec. 3.1.3 etc.): a BARE byte string carrying the BER OID content octets (RFC 9090), no tag-111
102
+ // head. Compose asn1.decodeOidContent -> dotted -> oid.name (the same the tag reader does internally).
103
+ function _oidName(node, code, label) {
104
+ if (!node || node.majorType !== 2) throw _err(code, label + " must be an unwrapped CBOR byte string (~oid)");
105
+ var dotted;
106
+ try { dotted = asn1.decodeOidContent(node.content); }
107
+ catch (e) { throw _err(code, label + " is not a valid BER OID content encoding", e); }
108
+ return { oid: dotted, name: oid.name(dotted) || dotted };
109
+ }
110
+
111
+ // AlgorithmIdentifier (sec. 3.1.3/sec. 3.1.7): int (registry) | ~oid (bare bytes) | [ ~oid, params ].
112
+ function _algorithm(node, byInt, code, label) {
113
+ if (node.majorType === 0 || node.majorType === 1) {
114
+ var i = Number(cbor.read.int(node));
115
+ var mapped = byInt[i]; // registry keyed by the signed int (negatives are legacy SHA-1 rows)
116
+ if (mapped === undefined) throw _err("c509/unknown-algorithm", label + " integer " + i + " has no C509 registry row");
117
+ if (typeof mapped === "string") return { name: mapped, oid: oid.byName(mapped) };
118
+ return { name: mapped.alg, oid: oid.byName(mapped.alg), curve: mapped.curve || null };
119
+ }
120
+ if (node.majorType === 2) { var r = _oidName(node, code, label); return { name: r.name, oid: r.oid }; }
121
+ if (node.majorType === 4 && node.children && node.children.length === 2) {
122
+ var a = _oidName(node.children[0], code, label);
123
+ // The [~oid, params] form carries the DER parameters as a CBOR byte string; a non-byte-string here
124
+ // is malformed and cannot be reconstructed (b.raw would append garbage) -- fail closed.
125
+ if (node.children[1].majorType !== 2) throw _err(code, label + " algorithm parameters must be a CBOR byte string");
126
+ return { name: a.name, oid: a.oid, parameters: node.children[1].content };
127
+ }
128
+ throw _err(code, label + " is not a C509 AlgorithmIdentifier (int / ~oid / [~oid, params])");
129
+ }
130
+
131
+ // SpecialText attribute value (sec. 3.1.4/sec. 3.1.6): text | bytes (even-length-hex optimization) | tag-48
132
+ // (a MAC address, RFC 9542). v1 surfaces the value; the DN string uses the text form.
133
+ function _specialText(node) {
134
+ if (node.majorType === 3) return { text: cbor.read.textString(node) };
135
+ if (node.majorType === 2) return { hex: node.content.toString("hex") };
136
+ if (node.majorType === 6 && Number(node.argument) === 48) {
137
+ // A tag-48 MAC address (RFC 9542) MUST wrap a CBOR byte string of 6 (EUI-48/MAC-48) or 8 (EUI-64)
138
+ // bytes; anything else is malformed and cannot reconstruct a well-formed EUI-64 commonName.
139
+ if (!node.children || !node.children[0] || node.children[0].majorType !== 2) {
140
+ throw _err("c509/bad-name", "a tag-48 MAC-address value must wrap a CBOR byte string");
141
+ }
142
+ var euiBytes = node.children[0].content;
143
+ if (euiBytes.length !== 6 && euiBytes.length !== 8) throw _err("c509/bad-name", "a tag-48 MAC address must be 6 (EUI-48) or 8 (EUI-64) bytes");
144
+ return { eui64: euiBytes };
145
+ }
146
+ throw _err("c509/bad-name", "an attribute value is not a C509 SpecialText (text / bytes / tag-48)");
147
+ }
148
+
149
+ // A single Name (sec. 3.1.4/sec. 3.1.6): the CBOR simple null (issuer only) | a bare SpecialText single
150
+ // commonName | an array of RDNAttributes. Surfaces { dn, rdns, eui64? } shape-compatible with x509.
151
+ function _name509(node, isSubject) {
152
+ if (!isSubject && node.majorType === 7 && node.ai === 22) return null; // issuer == subject (self-signed)
153
+ // A bare SpecialText (not an array) is a single commonName attribute (attributeType == +1).
154
+ if (node.majorType === 3 || node.majorType === 2 || node.majorType === 6) {
155
+ var sv = _specialText(node);
156
+ if (sv.eui64) return { rdns: [{ type: "commonName", eui64: sv.eui64 }], eui64: sv.eui64, dn: "CN=" + _macToEui64String(sv.eui64) };
157
+ var val = sv.text !== undefined ? sv.text : sv.hex;
158
+ return { rdns: [{ type: "commonName", value: val }], dn: "CN=" + val };
159
+ }
160
+ if (node.majorType !== 4) throw _err("c509/bad-name", "a C509 Name must be null, a SpecialText, or an array of RDN attributes");
161
+ var rdns = [];
162
+ var parts = [];
163
+ var kids = node.children || [];
164
+ // Each RDN attribute is an (attributeType, attributeValue) pair; an odd-length array is a dangling
165
+ // attribute type with no value -- reject rather than silently drop the trailing element.
166
+ if (kids.length % 2 !== 0) throw _err("c509/bad-name", "a C509 Name array must be attribute-type/value pairs (dangling attribute type)");
167
+ for (var i = 0; i + 1 < kids.length; i += 2) {
168
+ var ti = Number(cbor.read.int(kids[i]));
169
+ var tname = ATTR_BY_INT[Math.abs(ti)];
170
+ if (tname === undefined) throw _err("c509/bad-name", "attribute type integer " + ti + " has no C509 registry row");
171
+ var v = _specialText(kids[i + 1]);
172
+ var vv = v.text !== undefined ? v.text : (v.hex !== undefined ? v.hex : _macToEui64String(v.eui64));
173
+ rdns.push({ type: tname, value: vv, printable: ti < 0 });
174
+ parts.push(_shortName(tname) + "=" + vv);
175
+ }
176
+ return { rdns: rdns, dn: parts.join(",") };
177
+ }
178
+ function _shortName(n) { return n === "commonName" ? "CN" : n === "countryName" ? "C" : n === "organizationName" ? "O" : n === "organizationalUnitName" ? "OU" : n === "localityName" ? "L" : n === "stateOrProvinceName" ? "ST" : n; }
179
+
180
+ // extensions (sec. 3.1.10/sec. 3.3/sec. 8.8): [ * Extension ] | a single keyUsage int-shortcut.
181
+ function _extensions(node) {
182
+ // The keyUsage int-shortcut (sec. 3.1.10): a bare int -> one keyUsage extension, criticality from the
183
+ // sign, value = abs(int) (Appendix A.1.1: the single int 1 -> non-critical keyUsage digitalSignature).
184
+ if (node.majorType === 0 || node.majorType === 1) {
185
+ var iv = Number(cbor.read.int(node));
186
+ return [{ name: "keyUsage", oid: oid.byName("keyUsage"), critical: iv < 0, keyUsageBits: Math.abs(iv) }];
187
+ }
188
+ if (node.majorType !== 4) throw _err("c509/bad-extensions", "C509 extensions must be an array or a keyUsage int shortcut");
189
+ var out = [];
190
+ var kids = node.children || [];
191
+ // Each extension is an (extensionID, extensionValue) pair; an odd-length array is a dangling
192
+ // extension identifier with no value -- reject rather than silently drop the trailing element.
193
+ if (kids.length % 2 !== 0) throw _err("c509/bad-extensions", "a C509 extensions array must be id/value pairs (dangling extension identifier)");
194
+ for (var i = 0; i + 1 < kids.length; i += 2) {
195
+ var idNode = kids[i], valNode = kids[i + 1];
196
+ var name, extOid, critical, valContent;
197
+ if (idNode.majorType === 0 || idNode.majorType === 1) {
198
+ var ei = Number(cbor.read.int(idNode));
199
+ name = EXT_BY_INT[Math.abs(ei)];
200
+ if (name === undefined) throw _err("c509/bad-extensions", "extension type integer " + ei + " has no C509 registry row");
201
+ extOid = oid.byName(name); critical = ei < 0;
202
+ // An int extension value is a Defined CBOR item; v1 reconstructs only a byte-string value (a
203
+ // non-byte-string value surfaces as null and fails closed at the type-3 reconstruction).
204
+ valContent = valNode.content;
205
+ } else {
206
+ var r = _oidName(idNode, "c509/bad-extensions", "an extension id");
207
+ name = r.name; extOid = r.oid;
208
+ // ~oid extension (sec. 3.1.10): the extnValue is a byte string -- BARE (non-critical) or wrapped in a
209
+ // single-element array (critical). Validate the shape so a malformed value fails closed at decode.
210
+ critical = valNode.majorType === 4;
211
+ if (critical) {
212
+ if (!valNode.children || valNode.children.length !== 1 || valNode.children[0].majorType !== 2) {
213
+ throw _err("c509/bad-extensions", "a critical ~oid extension value must wrap a single byte string");
214
+ }
215
+ valContent = valNode.children[0].content;
216
+ } else {
217
+ if (valNode.majorType !== 2) throw _err("c509/bad-extensions", "a non-critical ~oid extension value must be a byte string");
218
+ valContent = valNode.content;
219
+ }
220
+ }
221
+ out.push({ name: name, oid: extOid, critical: critical, value: valContent || null });
222
+ }
223
+ return out;
224
+ }
225
+
226
+ // ---- type-3 DER reconstruction (byte-exact inversion; draft-20 sec. 3 / sec. 5) ----
227
+ // Type 3 is an INVERTIBLE transform: the reconstruction reproduces the ORIGINAL DER Certificate
228
+ // byte-for-byte (so the original signature verifies). A field outside the covered set fails closed
229
+ // (c509/non-invertible) -- never a partial or best-effort DER.
230
+
231
+ // A C509 tag-48 MAC (RFC 9542) reconstructs to the commonName EUI-64 string: a 6-byte value is a 48-bit
232
+ // MAC expanded to the EUI-64 HH-HH-HH-FF-FE-HH-HH-HH by inserting FF-FE; an 8-byte value is the EUI-64.
233
+ function _macToEui64String(buf) {
234
+ var bytes = buf.length === 6 ? Buffer.concat([buf.subarray(0, 3), Buffer.from([0xff, 0xfe]), buf.subarray(3)]) : buf;
235
+ var s = [];
236
+ for (var i = 0; i < bytes.length; i++) { var h = bytes[i].toString(16).toUpperCase(); if (h.length < 2) h = "0" + h; s.push(h); }
237
+ return s.join("-");
238
+ }
239
+
240
+ // One RDN attribute value -> its DER string. The C509 sign convention: a positive attribute int ->
241
+ // utf8String, a negative -> printableString; countryName / serialNumber are PrintableString-restricted.
242
+ function _reconAttrValue(rdn) {
243
+ if (rdn.eui64) return b.utf8(_macToEui64String(rdn.eui64));
244
+ if (rdn.type === "countryName" || rdn.type === "serialNumber") return b.printable(String(rdn.value));
245
+ return rdn.printable ? b.printable(String(rdn.value)) : b.utf8(String(rdn.value));
246
+ }
247
+
248
+ // A Name -> the DER RDNSequence (SEQUENCE OF SET OF SEQUENCE{ type, value }); one attribute per RDN.
249
+ function _reconName(name) {
250
+ return b.sequence(name.rdns.map(function (rdn) {
251
+ return b.set([b.sequence([b.oid(oid.byName(rdn.type)), _reconAttrValue(rdn)])]);
252
+ }));
253
+ }
254
+
255
+ // A validity instant -> UTCTime (RFC 5280 sec. 4.1.2.5: year < 2050) or GeneralizedTime. A null notAfter
256
+ // -> the no-well-defined-expiry sentinel 99991231235959Z.
257
+ function _reconTime(date) {
258
+ if (date === null) return b.generalizedTime(new Date(Date.UTC(9999, 11, 31, 23, 59, 59)));
259
+ return date.getUTCFullYear() < 2050 ? b.utcTime(date) : b.generalizedTime(date);
260
+ }
261
+
262
+ // subjectPublicKeyInfo -> DER. EC: rebuild AlgorithmIdentifier{ ecPublicKey, namedCurve } + the BIT
263
+ // STRING point, de-compressing a C509 0xFE/0xFD marker back to the original uncompressed 0x04||X||Y.
264
+ function _reconSpki(spkAlg, keyBytes, rsaKey) {
265
+ if (spkAlg.name === "ecPublicKey") {
266
+ var fieldSize = EC_FIELD_BYTES[spkAlg.curve];
267
+ if (!fieldSize) throw _err("c509/non-invertible", "unsupported EC curve " + spkAlg.curve);
268
+ if (!keyBytes || keyBytes.length === 0) throw _err("c509/non-invertible", "the EC subjectPublicKey byte string is empty");
269
+ var head = keyBytes[0], point;
270
+ // The point length must match the curve field size for its encoding -- an uncompressed 0x04 point is
271
+ // 1 + 2*fieldSize, a compressed 0x02/0x03/0xFE/0xFD point is 1 + fieldSize -- so a truncated / padded
272
+ // point cannot be re-emitted as a valid (or byte-exact) SubjectPublicKeyInfo.
273
+ if (head === 0x04) {
274
+ if (keyBytes.length !== 1 + 2 * fieldSize) throw _err("c509/non-invertible", "uncompressed EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
275
+ point = keyBytes;
276
+ } else if (head === 0x02 || head === 0x03) {
277
+ if (keyBytes.length !== 1 + fieldSize) throw _err("c509/non-invertible", "compressed EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
278
+ point = keyBytes;
279
+ } else if (head === 0xfe || head === 0xfd) { // C509 marker -> de-compress
280
+ if (keyBytes.length !== 1 + fieldSize) throw _err("c509/non-invertible", "C509-marked EC point length " + keyBytes.length + " does not match " + spkAlg.curve);
281
+ var sec1 = Buffer.concat([Buffer.from([head === 0xfe ? 0x02 : 0x03]), keyBytes.subarray(1)]);
282
+ point = webcrypto.decompressEcPoint(sec1, spkAlg.curve, _err, "c509/non-invertible");
283
+ } else throw _err("c509/non-invertible", "unrecognized EC point encoding 0x" + head.toString(16));
284
+ return b.sequence([b.sequence([b.oid(oid.byName("ecPublicKey")), b.oid(oid.byName(spkAlg.curve))]), b.bitString(point, 0)]);
285
+ }
286
+ if (spkAlg.name === "rsaEncryption") {
287
+ // draft-20 sec. 3.2.1: the RSA key is [modulus, exponent] ~biguints, OR just the modulus ~biguint
288
+ // when the exponent is 65537 (parse has already resolved rsaKey to { modulus, exponent }). Reconstruct
289
+ // AlgorithmIdentifier{ rsaEncryption, NULL } + the BIT STRING wrapping RSAPublicKey ::= SEQUENCE {
290
+ // modulus INTEGER, publicExponent INTEGER }.
291
+ var rsaPk = b.sequence([b.integer(rsaKey.modulus), b.integer(rsaKey.exponent)]);
292
+ return b.sequence([b.sequence([b.oid(oid.byName("rsaEncryption")), b.nullValue()]), b.bitString(rsaPk, 0)]);
293
+ }
294
+ throw _err("c509/non-invertible", "subjectPublicKey algorithm " + spkAlg.name + " is not in the type-3 reconstruction covered set");
295
+ }
296
+
297
+ // The keyUsage int value -> the DER KeyUsage BIT STRING (RFC 5280 sec. 4.2.1.3): bit i of the value ->
298
+ // BIT STRING named bit i (bit 0 = digitalSignature = the MSB of the first content octet).
299
+ function _reconKeyUsageBits(value) {
300
+ // The keyUsage value indexes the 9 named bits of RFC 5280 sec. 4.2.1.3 (digitalSignature..decipherOnly);
301
+ // a non-positive, non-integer, or > 0x1FF value is not a valid KeyUsage and would also corrupt the
302
+ // 32-bit bitwise re-encoding below (a value past 2^31 wraps). Fail closed before the bit walk.
303
+ if (!Number.isInteger(value) || value <= 0 || value > 0x1ff) throw _err("c509/non-invertible", "a keyUsage value must be a positive integer within the 9 defined bits");
304
+ var hi = 0; for (var t = value; t; t >>= 1) hi++; // number of significant bits
305
+ hi -= 1; // highest set bit index
306
+ var nbytes = (hi >> 3) + 1;
307
+ var buf = Buffer.alloc(nbytes);
308
+ for (var bit = 0; bit <= hi; bit++) { if (value & (1 << bit)) buf[bit >> 3] |= 0x80 >> (bit & 7); }
309
+ return b.bitString(buf, 7 - (hi & 7));
310
+ }
311
+
312
+ // extensions -> the [3] EXPLICIT SEQUENCE OF Extension DER.
313
+ function _reconExtensions(exts) {
314
+ var items = exts.map(function (ext) {
315
+ var extnValue;
316
+ if (ext.name === "keyUsage" && typeof ext.keyUsageBits === "number") extnValue = _reconKeyUsageBits(ext.keyUsageBits);
317
+ else if (Buffer.isBuffer(ext.value)) extnValue = ext.value; // raw DER extnValue bytes
318
+ else throw _err("c509/non-invertible", "extension " + ext.name + " has no reconstructable value in the covered set");
319
+ var fields = [b.oid(ext.oid || oid.byName(ext.name))];
320
+ if (ext.critical) fields.push(b.boolean(true));
321
+ fields.push(b.octetString(extnValue));
322
+ return b.sequence(fields);
323
+ });
324
+ return b.explicit(3, b.sequence(items));
325
+ }
326
+
327
+ // An AlgorithmIdentifier -> DER SEQUENCE { algorithm OID, parameters? }. A C509 [~oid, params]
328
+ // algorithm carries its DER parameters bytes, which MUST be reproduced so the reconstruction inverts
329
+ // byte-exact (silently dropping them would change the signed bytes); the int / ~oid forms carry no
330
+ // parameters (ecdsaWith* and the like omit them).
331
+ function _reconAlgId(alg) {
332
+ var fields = [b.oid(alg.oid)];
333
+ if (alg.parameters && alg.parameters.length) {
334
+ // AlgorithmIdentifier.parameters is ANY -- exactly one well-formed DER element. Validate the supplied
335
+ // bytes decode as a single element (the strict decoder rejects trailing bytes / malformed encodings)
336
+ // before re-emitting them, so a malformed or multi-element parameter blob fails closed rather than
337
+ // producing an invalid reconstructed AlgorithmIdentifier.
338
+ try { asn1.decode(alg.parameters); }
339
+ catch (e) { throw _err("c509/non-invertible", "algorithm parameters are not a single well-formed DER element", e); }
340
+ fields.push(b.raw(alg.parameters));
341
+ }
342
+ return b.sequence(fields);
343
+ }
344
+
345
+ // The full type-3 -> DER Certificate reconstruction, byte-for-byte.
346
+ function _reconstructDer(r, sigNode) {
347
+ var sigAlgSeq = _reconAlgId(r.signatureAlgorithm);
348
+ var tbsFields = [
349
+ b.explicit(0, b.integer(2n)), // version v3 (type-3 is X.509 v3)
350
+ b.integer(r.serialNumber),
351
+ sigAlgSeq,
352
+ _reconName(r.issuer && r.issuer.rdns ? r.issuer : r.subject), // null issuer -> issuer == subject
353
+ b.sequence([_reconTime(r.validity.notBefore), _reconTime(r.validity.notAfter)]),
354
+ _reconName(r.subject),
355
+ _reconSpki(r.subjectPublicKeyAlgorithm, r.subjectPublicKey, r.rsaPublicKey),
356
+ ];
357
+ // RFC 5280 sec. 4.1: the [3] extensions field is OPTIONAL and, when present, SHALL contain at least one
358
+ // extension -- an empty C509 extensions array reconstructs to an OMITTED field, not an empty SEQUENCE.
359
+ if (r.extensions.length) tbsFields.push(_reconExtensions(r.extensions));
360
+ var tbs = b.sequence(tbsFields);
361
+ // The signature is re-wrapped as a DER ECDSA-Sig-Value from the fixed-width r||s, so only an ECDSA
362
+ // signature algorithm is in the type-3 reconstruction covered set (an RSA/EdDSA signature is raw bytes,
363
+ // not r||s -- rejected rather than mis-wrapped). A wrong-length r||s surfaces the caller's typed code.
364
+ if (!/^ecdsa/i.test(r.signatureAlgorithm.name || "")) {
365
+ throw _err("c509/non-invertible", "type-3 signature reconstruction covers only ECDSA; got " + r.signatureAlgorithm.name);
366
+ }
367
+ // The fixed-width r||s must split at a real curve field width -- P-256/384/521 = 64/96/132 bytes
368
+ // (RFC 9053 sec. 2.1). A width that is not 2x a supported field size is not a valid ECDSA signature and
369
+ // cannot be re-wrapped byte-exact; surface the caller's typed code rather than split at a bogus offset.
370
+ var coordLen = r.signatureValue.length / 2;
371
+ if (coordLen !== 32 && coordLen !== 48 && coordLen !== 66) {
372
+ throw _err("c509/bad-signature", "the type-3 ECDSA signature width " + r.signatureValue.length + " is not a valid fixed-width r||s (expected 64/96/132 for P-256/384/521)");
373
+ }
374
+ var sigValue = validator.sig.rawToEcdsaDer(r.signatureValue, coordLen);
375
+ return b.sequence([tbs, sigAlgSeq, b.bitString(sigValue, 0)]);
376
+ }
377
+
378
+ // ---- the parse ----
379
+
380
+ /**
381
+ * @primitive pki.schema.c509.parse
382
+ * @signature pki.schema.c509.parse(bytes) -> { certificateType, serialNumber, serialNumberHex, ... }
383
+ * @since 0.2.30
384
+ * @status experimental
385
+ * @spec draft-ietf-cose-cbor-encoded-cert, RFC 8949, RFC 9090, RFC 5280
386
+ *
387
+ * Decode a C509 certificate (draft-ietf-cose-cbor-encoded-cert) from its deterministic-CBOR bytes.
388
+ * Returns the decoded fields (c509CertificateType 2 native or 3 re-encoded); a malformed shape throws a
389
+ * typed C509Error carrying the inner cbor/asn1 fault as .cause. It decodes CBOR, not DER, so it is
390
+ * reached by an explicit call and is not auto-routed by pki.schema.parse. The type-2 signedData and the
391
+ * raw signature are surfaced RAW (a native verifier hashes them without re-serialization).
392
+ *
393
+ * @example
394
+ * // the RFC 7925 profiled certificate from draft-ietf-cose-cbor-encoded-cert Appendix A.1 (type 3)
395
+ * var bytes = Buffer.from(
396
+ * "8b03" + "4301f50d" + "00" + "6b52464320746573742043" + "41" + "1a63b0cd00" + "1a6955b900" +
397
+ * "d830460123456789ab" + "01" + "5821feb1216ab96e5b3b3340f5bdf02e693f16213a04525ed44450b1019c2dfd3838ab" +
398
+ * "01" + "5840d4320b1d6849e309219d30037e138166f2508247dddae76ccceea55053c108e90d551f6d60106f1abb484cfbe6256c178e4ac3314ea19191e8b607da5ae3bda16",
399
+ * "hex");
400
+ * var c = pki.schema.c509.parse(bytes);
401
+ * c.certificateType; // 3
402
+ */
403
+ function parse(input) {
404
+ var root;
405
+ try { root = cbor.decode(input); }
406
+ catch (e) { throw _err("c509/not-a-certificate", "the input is not well-formed deterministic CBOR", e); }
407
+ if (root.majorType !== 4 || !root.children) throw _err("c509/not-a-certificate", "a C509 certificate must be a CBOR array");
408
+ var f = root.children;
409
+ if (f.length !== 11) throw _err("c509/bad-tbs", "a C509 certificate must be an array of exactly 11 elements, got " + f.length);
410
+
411
+ var type = Number(cbor.read.int(f[0]));
412
+ if (type !== 2 && type !== 3) throw _err("c509/bad-certificate-type", "c509CertificateType must be 2 (native) or 3 (re-encoded), got " + type);
413
+
414
+ var serialBytes = f[1];
415
+ var serial = _biguint(serialBytes, "c509/bad-serial", "certificateSerialNumber");
416
+ var sHex = serialBytes.content.toString("hex");
417
+
418
+ var sigAlg = _algorithm(f[2], SIG_ALG_BY_INT, "c509/unknown-algorithm", "issuerSignatureAlgorithm");
419
+ var issuer = _name509(f[3], false);
420
+ var notBefore = _time(f[4], false, "validityNotBefore");
421
+ var notAfter = _time(f[5], true, "validityNotAfter");
422
+ var subject = _name509(f[6], true);
423
+ var spkAlg = _algorithm(f[7], PK_ALG_BY_INT, "c509/unknown-algorithm", "subjectPublicKeyAlgorithm");
424
+ var subjectPublicKey = null, rsaKey = null;
425
+ if (spkAlg.name === "rsaEncryption") {
426
+ // draft-20 sec. 3.2.1: [modulus, exponent] ~biguints, OR a bare modulus ~biguint (exponent = 65537).
427
+ if (f[8].majorType === 2) rsaKey = { modulus: _biguint(f[8], "c509/bad-spki", "RSA modulus"), exponent: 65537n };
428
+ else if (f[8].majorType === 4 && f[8].children && f[8].children.length === 2) {
429
+ rsaKey = { modulus: _biguint(f[8].children[0], "c509/bad-spki", "RSA modulus"), exponent: _biguint(f[8].children[1], "c509/bad-spki", "RSA exponent") };
430
+ } else throw _err("c509/bad-spki", "an RSA subjectPublicKey must be a ~biguint modulus or [modulus, exponent]");
431
+ if (rsaKey.modulus < 1n || rsaKey.exponent < 1n) throw _err("c509/bad-spki", "an RSA modulus and public exponent must be positive");
432
+ } else {
433
+ if (f[8].majorType !== 2) throw _err("c509/bad-spki", "subjectPublicKey must be a CBOR byte string");
434
+ subjectPublicKey = f[8].content;
435
+ }
436
+ var extensions = _extensions(f[9]);
437
+ if (f[10].majorType !== 2) throw _err("c509/bad-signature", "issuerSignatureValue must be a CBOR byte string");
438
+ var signatureValue = f[10].content;
439
+
440
+ var result = {
441
+ certificateType: type,
442
+ serialNumber: serial,
443
+ serialNumberHex: sHex,
444
+ signatureAlgorithm: sigAlg,
445
+ issuer: issuer,
446
+ validity: { notBefore: notBefore, notAfter: notAfter },
447
+ subject: subject,
448
+ subjectPublicKeyAlgorithm: spkAlg,
449
+ subjectPublicKey: subjectPublicKey,
450
+ rsaPublicKey: rsaKey,
451
+ extensions: extensions,
452
+ signatureValue: signatureValue,
453
+ };
454
+
455
+ // Native (type-2) signed region (sec. 3.1.12): the RAW bytes of the CBOR-sequence elements 0..9 (NOT
456
+ // the outer array head, NOT the signature) -- a zero-copy subarray a native verifier hashes.
457
+ if (type === 2) {
458
+ var b0 = f[0].bytes, b9 = f[9].bytes;
459
+ var start = b0.byteOffset - input.byteOffset;
460
+ var end = b9.byteOffset - input.byteOffset + b9.length;
461
+ result.signedData = input.subarray(start, end);
462
+ }
463
+
464
+ // Type-3 is an invertible re-encoding of a DER X.509 certificate: reconstruct the original DER
465
+ // byte-for-byte so the original signature verifies and x509.parse recovers the certificate.
466
+ if (type === 3) result.reconstructedDer = _reconstructDer(result, f[10]);
467
+
468
+ return result;
469
+ }
470
+
471
+ // matches(node) -- a STRUCTURAL probe over a DECODED CBOR node: an array of 11 whose first element is
472
+ // a major-type-0/1 int equal to 2 or 3. Not wired into the DER orchestrator (C509 is CBOR, not DER).
473
+ function matches(node) {
474
+ return !!node && node.majorType === 4 && !!node.children && node.children.length === 11 &&
475
+ (node.children[0].majorType === 0 || node.children[0].majorType === 1) &&
476
+ (Number(node.children[0].argument) === 2 || Number(node.children[0].argument) === 3);
477
+ }
478
+
479
+ module.exports = { parse: parse, matches: matches };
@@ -571,7 +571,10 @@ function distributionPointName(ns, node, code) {
571
571
  // inside. Each decoder takes that raw Buffer and returns the decoded value, or
572
572
  // throws a typed `<prefix>/bad-*` code, mirroring the CRL's fail-closed
573
573
  // `decodeExt` pattern (asn1.decode -> read.* / schema.walk, wrapped). Returns
574
- // { byOid } keyed by dotted OID so a consumer dispatches by ext.oid.
574
+ // { byOid } keyed by dotted OID so a consumer dispatches by ext.oid. The decoded
575
+ // set spans the RFC 5280 sec. 4.2.1 extensions plus the RFC 3739 sec. 3.2.6 /
576
+ // ETSI EN 319 412-5 qualified-certificate `qcStatements` extension (each known
577
+ // statement decoded, an unknown statementId preserved opaque).
575
578
  var _T = asn1.TAGS;
576
579
 
577
580
  // assertPolicyQualifiers(qNode, fail) -- RFC 5280 sec. 4.2.1.4: a PolicyInformation's
@@ -916,7 +919,120 @@ function certExtensionDecoders(ns) {
916
919
  if (typeof d !== "string") throw new TypeError("certExtensionDecoders: " + JSON.stringify(nm) + " is not a registered OID name");
917
920
  return d;
918
921
  }
922
+ // qcStatements ::= SEQUENCE OF QCStatement (RFC 3739 sec. 3.2.6). QCStatement ::= SEQUENCE {
923
+ // statementId OBJECT IDENTIFIER, statementInfo ANY DEFINED BY statementId OPTIONAL }. Known statementIds
924
+ // (RFC 3739 id-qcs + the ETSI EN 319 412-5 esi4 catalog) decode their statementInfo; an unknown statementId
925
+ // is preserved OPAQUE (the RFC 3739 open type), never rejected. The DEFINED-BY selection is an OID re-dispatch.
926
+ function _qcStr(node, tag, C, what) {
927
+ if (!node || node.tagClass !== "universal" || node.tagNumber !== tag) throw ns.E(C, what);
928
+ try { return asn1.read.string(node); } catch (e) { throw ns.E(C, what, e); }
929
+ }
930
+ function _qcOidSeq(node, C, what) { // a non-empty SEQUENCE OF OBJECT IDENTIFIER
931
+ if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, what + " must be a non-empty SEQUENCE OF OBJECT IDENTIFIER");
932
+ return node.children.map(function (c) {
933
+ if (c.tagClass !== "universal" || c.tagNumber !== _T.OBJECT_IDENTIFIER) throw ns.E(C, what + " members must be OBJECT IDENTIFIERs");
934
+ try { return asn1.read.oid(c); } catch (e) { throw ns.E(C, what + " member is not a valid OID", e); }
935
+ });
936
+ }
937
+ function _qcPresenceOnly(nm) { return function (node, C) { if (node) throw ns.E(C, nm + " carries no statementInfo (ETSI EN 319 412-5)"); return null; }; }
938
+ function _qcSemantics(node, C) { // SemanticsInformation, WITH COMPONENTS at least one field present
939
+ if (node === null) return null; // statementInfo OPTIONAL for id-qcs syntax statements
940
+ if (node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "SemanticsInformation must be a SEQUENCE with at least one field (RFC 3739 sec. 3.2.6.1)");
941
+ var sid = null, i = 0;
942
+ if (node.children[0].tagClass === "universal" && node.children[0].tagNumber === _T.OBJECT_IDENTIFIER) {
943
+ try { sid = asn1.read.oid(node.children[0]); } catch (e) { throw ns.E(C, "SemanticsInformation semanticsIdentifier must be an OID", e); }
944
+ i++;
945
+ }
946
+ var nras = [];
947
+ if (node.children[i]) {
948
+ // nameRegistrationAuthorities ::= SEQUENCE SIZE (1..MAX) OF GeneralName -- decode + fully validate
949
+ // each element through the shared GeneralName decoder (context tag [0]..[8] + per-alternative
950
+ // structure), rather than surfacing arbitrary bytes as a name-registration authority.
951
+ nras = schema.walk(generalNames(ns, { decodeValue: true, code: C }), node.children[i], ns).result.names;
952
+ i++;
953
+ }
954
+ if (i !== node.children.length) throw ns.E(C, "SemanticsInformation has unexpected trailing fields");
955
+ return { semanticsIdentifier: sid, nameRegistrationAuthorities: nras };
956
+ }
957
+ var qcInfoByOid = {};
958
+ qcInfoByOid[O("qcCompliance")] = _qcPresenceOnly("QcCompliance");
959
+ qcInfoByOid[O("qcSSCD")] = _qcPresenceOnly("QcSSCD");
960
+ qcInfoByOid[O("qcsPkixQCSyntaxV1")] = _qcSemantics;
961
+ qcInfoByOid[O("qcsPkixQCSyntaxV2")] = _qcSemantics;
962
+ qcInfoByOid[O("qcType")] = function (node, C) {
963
+ var oids = _qcOidSeq(node, C, "QcType"); // SIZE(1) in EN 319 412-5 V2.5.1; accept >=1 for legacy certs
964
+ return { types: oids, typeNames: oids.map(function (d) { return ns.oid.name(d) || null; }) };
965
+ };
966
+ qcInfoByOid[O("qcRetentionPeriod")] = function (node, C) {
967
+ if (!node) throw ns.E(C, "QcRetentionPeriod requires an INTEGER statementInfo");
968
+ return { years: guard.range.uint31(readInt(node, C, "QcRetentionPeriod"), ns.E, C, "QcRetentionPeriod (years)") };
969
+ };
970
+ qcInfoByOid[O("qcLimitValue")] = function (node, C) { // MonetaryValue ::= SEQUENCE { currency, amount, exponent }
971
+ if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || node.children.length !== 3) throw ns.E(C, "QcLimitValue must be a MonetaryValue SEQUENCE { currency, amount, exponent }");
972
+ var cur = node.children[0], currency;
973
+ if (cur.tagClass === "universal" && cur.tagNumber === _T.PRINTABLE_STRING) {
974
+ // Route through _qcStr so a disallowed PrintableString byte surfaces the decoder's typed
975
+ // bad-qc-statement error, not a leaked asn1/bad-printable-string from the raw leaf read.
976
+ currency = _qcStr(cur, _T.PRINTABLE_STRING, C, "QcLimitValue alphabetic currency must be a PrintableString");
977
+ if (currency.length !== 3) throw ns.E(C, "QcLimitValue alphabetic currency must be a 3-letter ISO 4217 code");
978
+ } else if (cur.tagClass === "universal" && cur.tagNumber === _T.INTEGER) {
979
+ currency = guard.range.int(readInt(cur, C, "QcLimitValue numeric currency"), 1n, 999n, ns.E, C, "QcLimitValue numeric currency (ISO 4217, 1..999)");
980
+ } else throw ns.E(C, "QcLimitValue currency must be a PrintableString(3) or INTEGER(1..999)");
981
+ return {
982
+ currency: currency,
983
+ // MonetaryValue amount and exponent are unconstrained INTEGERs (the amount non-negative in practice);
984
+ // bound each only to the safe-integer range so it narrows to a Number losslessly -- an artificial
985
+ // tighter bound (uint31, or a small exponent window) would false-reject a valid MonetaryValue, and a
986
+ // wider one would round silently and mis-state the limit.
987
+ amount: guard.range.int(readInt(node.children[1], C, "QcLimitValue amount"), 0n, 9007199254740991n, ns.E, C, "QcLimitValue amount"),
988
+ exponent: guard.range.int(readInt(node.children[2], C, "QcLimitValue exponent"), -9007199254740991n, 9007199254740991n, ns.E, C, "QcLimitValue exponent"),
989
+ };
990
+ };
991
+ qcInfoByOid[O("qcCClegislation")] = function (node, C) { // SEQUENCE OF CountryName (PrintableString SIZE 2)
992
+ if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "QcCClegislation must be a non-empty SEQUENCE OF CountryName");
993
+ return { countries: node.children.map(function (c) { var v = _qcStr(c, _T.PRINTABLE_STRING, C, "QcCClegislation CountryName must be a PrintableString"); if (v.length !== 2) throw ns.E(C, "QcCClegislation CountryName must be a 2-letter ISO 3166-1 code"); return v; }) };
994
+ };
995
+ qcInfoByOid[O("qcPDS")] = function (node, C) { // PdsLocations ::= SEQUENCE OF PdsLocation { url IA5, language Printable(2) }
996
+ if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "QcPDS must be a non-empty SEQUENCE OF PdsLocation");
997
+ return { locations: node.children.map(function (loc) {
998
+ if (loc.tagClass !== "universal" || loc.tagNumber !== _T.SEQUENCE || !loc.children || loc.children.length !== 2) throw ns.E(C, "a QcPDS PdsLocation must be a SEQUENCE { url, language }");
999
+ var lang = _qcStr(loc.children[1], _T.PRINTABLE_STRING, C, "a QcPDS language must be a PrintableString");
1000
+ if (lang.length !== 2) throw ns.E(C, "a QcPDS language must be a 2-letter ISO 639-1 code");
1001
+ return { url: _qcStr(loc.children[0], _T.IA5_STRING, C, "a QcPDS url must be an IA5String"), language: lang };
1002
+ }) };
1003
+ };
1004
+ // ETSI EN 319 412-5 V2.5.1 additions: QcIdentMethod (SEQUENCE OF OID, like QcType) and
1005
+ // QcQSCDlegislation (SEQUENCE OF CountryName, like QcCClegislation) -- registered ids, decoded.
1006
+ qcInfoByOid[O("qcIdentMethod")] = function (node, C) {
1007
+ var oids = _qcOidSeq(node, C, "QcIdentMethod");
1008
+ return { methods: oids, methodNames: oids.map(function (d) { return ns.oid.name(d) || null; }) };
1009
+ };
1010
+ qcInfoByOid[O("qcQSCDlegislation")] = function (node, C) {
1011
+ if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "QcQSCDlegislation must be a non-empty SEQUENCE OF CountryName");
1012
+ return { countries: node.children.map(function (c) { var v = _qcStr(c, _T.PRINTABLE_STRING, C, "QcQSCDlegislation CountryName must be a PrintableString"); if (v.length !== 2) throw ns.E(C, "QcQSCDlegislation CountryName must be a 2-letter ISO 3166-1 code"); return v; }) };
1013
+ };
1014
+ function qcStatements(buf) {
1015
+ var C = ns.prefix + "/bad-qc-statements", CS = ns.prefix + "/bad-qc-statement";
1016
+ var kids = seqChildren(buf, C, "QCStatements");
1017
+ if (!kids.length) throw ns.E(C, "QCStatements must contain at least one QCStatement (RFC 3739 sec. 3.2.6)");
1018
+ var out = []; // QCStatements ::= SEQUENCE OF QCStatement -- RFC 3739 imposes no statementId uniqueness; decode every statement in wire order (a linter may flag a repeat, the decoder must not drop it)
1019
+ for (var i = 0; i < kids.length; i++) {
1020
+ var st = kids[i];
1021
+ if (st.tagClass !== "universal" || st.tagNumber !== _T.SEQUENCE || !st.children || st.children.length < 1 || st.children.length > 2) {
1022
+ throw ns.E(CS, "each QCStatement must be a SEQUENCE of a statementId and an OPTIONAL statementInfo");
1023
+ }
1024
+ var id;
1025
+ try { id = asn1.read.oid(st.children[0]); } catch (e) { throw ns.E(CS, "a QCStatement statementId must be an OBJECT IDENTIFIER", e); }
1026
+ var infoNode = st.children.length === 2 ? st.children[1] : null;
1027
+ var decoder = qcInfoByOid[id];
1028
+ var info = decoder ? decoder(infoNode, CS) : { opaque: true, bytes: infoNode ? infoNode.bytes : null };
1029
+ out.push({ statementId: id, name: ns.oid.name(id) || null, info: info });
1030
+ }
1031
+ return out;
1032
+ }
1033
+
919
1034
  var byOid = {};
1035
+ byOid[O("qcStatements")] = qcStatements;
920
1036
  byOid[O("basicConstraints")] = basicConstraints;
921
1037
  byOid[O("keyUsage")] = keyUsage;
922
1038
  byOid[O("nameConstraints")] = nameConstraints;
package/lib/sigstore.js CHANGED
@@ -33,6 +33,7 @@ var x509 = require("./schema-x509");
33
33
  var pathValidate = require("./path-validate");
34
34
  var merkle = require("./merkle");
35
35
  var oid = require("./oid");
36
+ var edwardsPoint = require("./edwards-point");
36
37
 
37
38
  var C = constants;
38
39
  var SigstoreError = frameworkError.SigstoreError;
@@ -184,8 +185,18 @@ function _rawVerify(keyObj, data, derSig) {
184
185
  return nodeCrypto.verify(hash, data, { key: keyObj, dsaEncoding: "der" }, derSig);
185
186
  }
186
187
  function _pubFromSpki(spkiDer, label) {
187
- try { return nodeCrypto.createPublicKey({ key: spkiDer, format: "der", type: "spki" }); }
188
+ var key;
189
+ try { key = nodeCrypto.createPublicKey({ key: spkiDer, format: "der", type: "spki" }); }
188
190
  catch (e) { throw _err("sigstore/bad-key", "invalid " + label + " public key", e); }
191
+ // node imports a low-order (e.g. all-zeroes) OKP public key without complaint, and such a key
192
+ // verifies a FORGED EdDSA signature -- route an Ed25519/Ed448 SPKI through the shared full-order,
193
+ // on-curve Edwards-point gate (as the webauthn and path-validation EdDSA paths already do) before it
194
+ // can be handed to verify.
195
+ var t = key.asymmetricKeyType;
196
+ if (t === "ed25519" || t === "ed448") {
197
+ edwardsPoint.validateSpki(spkiDer, t === "ed25519" ? 6 : 7, SigstoreError, "sigstore/bad-key");
198
+ }
199
+ return key;
189
200
  }
190
201
  // Parse a certificate, re-typing a malformed-DER fault to a sigstore/* error so
191
202
  // the caller never sees a raw certificate/* / asn1/* leak from this boundary.