@blamejs/pki 0.6.35 → 0.6.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/lib/schema-c509.js +168 -38
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,34 @@ All notable changes to `@blamejs/pki` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## v0.6.37 — 2026-09-05
|
|
8
|
+
|
|
9
|
+
A natively signed C509 certificate is refused when it writes an extension in the generic OID form that the registry can carry as an identifier.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- pki.schema.c509.parse refuses a natively signed certificate (c509CertificateType 2) that writes an extension as an OID and a DER value when a C509 registry identifier carries the same extension value. Draft section 3.7 reserves the generic form for re-encoded certificates, where it stays accepted. An extension whose OID has no registry identifier, and one whose value the compact encodings cannot represent under that certificate type's rules, both keep the generic form.
|
|
14
|
+
- pki.schema.c509.encode chooses an extension's form under the rules of the certificate type it is writing. It previously judged whether a value could be written compactly using the rules for a re-encoded certificate, so a natively signed certificate could be given a compact form its own decoder does not accept.
|
|
15
|
+
- pki.schema.c509.encode resolves an extension named only by its OID to the matching registry identifier. It looked the identifier up by name alone, so an extension supplied without one was written in the generic OID form even where a compact encoding carried it.
|
|
16
|
+
- pki.schema.c509 decides what belongs to the C509 registries by dotted OID rather than by the name pki.oid.register maps that OID to. Registering a different name for a built-in OID previously changed what the toolkit emitted for an unrelated certificate, and for commonName, a curve, ecPublicKey or an ECDSA signature algorithm it stopped a certificate converting to C509 at all.
|
|
17
|
+
|
|
18
|
+
## v0.6.36 — 2026-09-04
|
|
19
|
+
|
|
20
|
+
A natively signed C509 certificate is refused when it spells an algorithm generically instead of using the registry entry that names the same value.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- The subjectPublicKeyAlgorithm on a pki.schema.c509.parse result carries a parameters buffer when the certificate used a C509 registry integer, holding the parameters that entry names: the DER NULL for rsaEncryption and the namedCurve OID for an EC key. The generic OID forms already surfaced this field, so it is now present for every form that has parameters.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- pki.schema.c509.parse refuses a natively signed certificate (c509CertificateType 2) that writes an algorithm as an OID, or as an OID with parameters, when a C509 registry entry names that same value. Draft section 3.7 reserves the generic form for re-encoded certificates, where it stays accepted. An algorithm with no registry entry, and a registry OID whose parameters differ from the entry's, both keep the generic form.
|
|
29
|
+
- pki.schema.c509.parse refuses an rsaEncryption subjectPublicKeyAlgorithm that does not carry the DER NULL parameters RFC 3279 section 2.3.1 requires, including the bare OID form that carries none. The type-3 reconstruction previously emitted the OID and a NULL regardless, so the rebuilt DER described an algorithm identifier the certificate had not encoded.
|
|
30
|
+
- pki.schema.c509.encode writes an algorithm back as the form naming the same value. It previously chose the form by algorithm name alone, so re-encoding a certificate field by field could move an algorithm that carried no parameters onto a registry entry that names some, and could miss the entry for a key identified by its namedCurve parameters rather than by a curve name.
|
|
31
|
+
- pki.schema.c509.encode refuses a serialNumber, RSA modulus or RSA exponent that is not a BigInt. A string was previously read as hexadecimal and truncated to an even number of digits, so a caller passing "abc" received a certificate carrying the serial 0xab, and a number was silently converted. A missing modulus or exponent reached the emitter as an untyped fault rather than a c509 error naming the field.
|
|
32
|
+
- pki.schema.c509.encode reports every field it reads off a result as a typed c509 error naming that field. An extensions entry or an RDN that is not an object, an extension carrying neither an OID nor a registry name, an extension OID that is not a dotted-decimal string, and a keyUsageBits that is not a safe integer previously surfaced as a TypeError, a RangeError, or a fault from the OID or CBOR builders. An RDN value that is not a string is refused rather than converted, so a caller passing a number no longer receives a certificate naming its decimal spelling.
|
|
33
|
+
- pki.schema.c509.encode refuses a sparse extensions or rdns array. Array iteration skips a hole, so such a list emitted fewer entries than it held without reporting anything.
|
|
34
|
+
|
|
7
35
|
## v0.6.35 — 2026-09-04
|
|
8
36
|
|
|
9
37
|
Three redundant C509 spellings are refused, so a certificate has one encoding and an issuer signature cannot cover two different C509 byte strings.
|
package/lib/schema-c509.js
CHANGED
|
@@ -32,6 +32,20 @@ var b = asn1.build;
|
|
|
32
32
|
var C = constants;
|
|
33
33
|
var C509Error = frameworkError.C509Error;
|
|
34
34
|
function _err(code, message, cause) { return new C509Error(code, message, cause); }
|
|
35
|
+
/** @internal Name a caller-supplied value in a diagnostic without converting it: a Symbol has no
|
|
36
|
+
* string form and a BigInt has no JSON form, so formatting either one would throw from the message. */
|
|
37
|
+
function _describe(v) {
|
|
38
|
+
if (v === null) return "null";
|
|
39
|
+
var t = typeof v;
|
|
40
|
+
if (t === "string") return JSON.stringify(v);
|
|
41
|
+
return t === "number" || t === "boolean" || t === "undefined" ? String(v) : t;
|
|
42
|
+
}
|
|
43
|
+
/** @internal A hole is skipped by forEach, so a sparse list would emit fewer items than it holds. */
|
|
44
|
+
function _assertDense(list, code, label) {
|
|
45
|
+
for (var i = 0; i < list.length; i++) {
|
|
46
|
+
if (!(i in list)) throw _err(code, label + " must not be a sparse array; index " + i + " is a hole");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
35
49
|
var MAX_EPOCH_SECONDS = 8640000000000n;
|
|
36
50
|
var NO_EXPIRY_SECONDS = 253402300799n;
|
|
37
51
|
|
|
@@ -49,6 +63,11 @@ var PK_ALG_BY_INT = {
|
|
|
49
63
|
3: { alg: _name("ecPublicKey"), curve: _name("secp521r1") },
|
|
50
64
|
};
|
|
51
65
|
var EC_FIELD_BYTES = Object.assign(Object.create(null), { "prime256v1": 32, "secp384r1": 48, "secp521r1": 66 });
|
|
66
|
+
/** @internal Curve identity follows the dotted OID; pki.oid.register can rename one at any time. */
|
|
67
|
+
var EC_CURVE_BY_OID = Object.create(null);
|
|
68
|
+
Object.keys(EC_FIELD_BYTES).forEach(function (n) { EC_CURVE_BY_OID[oid.byName(n)] = n; });
|
|
69
|
+
var SIG_ALG_BY_OID = Object.create(null);
|
|
70
|
+
Object.keys(SIG_ALG_BY_INT).forEach(function (k) { SIG_ALG_BY_OID[oid.byName(SIG_ALG_BY_INT[k])] = SIG_ALG_BY_INT[k]; });
|
|
52
71
|
|
|
53
72
|
var ATTR_BY_INT = {
|
|
54
73
|
0: _name("emailAddress"),
|
|
@@ -178,19 +197,58 @@ function _oidName(node, code, label) {
|
|
|
178
197
|
return { oid: dotted, name: oid.name(dotted) || dotted };
|
|
179
198
|
}
|
|
180
199
|
|
|
181
|
-
|
|
200
|
+
/**
|
|
201
|
+
* @internal Draft sec. 8.14 and 8.15 give each registry row a full DER AlgorithmIdentifier, so the
|
|
202
|
+
* row's parameters are part of the value it denotes: a signature row carries none, the rsaEncryption
|
|
203
|
+
* row carries NULL, and an ecPublicKey row carries its namedCurve.
|
|
204
|
+
*/
|
|
205
|
+
function _rowParameters(row) {
|
|
206
|
+
if (typeof row === "string") return null;
|
|
207
|
+
if (row.curve) return asn1.build.oid(oid.byName(row.curve));
|
|
208
|
+
return asn1.build.nullValue();
|
|
209
|
+
}
|
|
210
|
+
function _rowParametersMatch(row, params) {
|
|
211
|
+
if (row === undefined) return false;
|
|
212
|
+
var rowParams = _rowParameters(row);
|
|
213
|
+
return rowParams === null ? params === null : (params !== null && rowParams.equals(params));
|
|
214
|
+
}
|
|
215
|
+
function _specificInt(byInt, dotted, params, curve) {
|
|
216
|
+
var keys = Object.keys(byInt);
|
|
217
|
+
for (var i = 0; i < keys.length; i++) {
|
|
218
|
+
var row = byInt[keys[i]];
|
|
219
|
+
if (oid.byName(typeof row === "string" ? row : row.alg) !== dotted) continue;
|
|
220
|
+
var hit = params === null && curve ? (typeof row !== "string" && row.curve === curve)
|
|
221
|
+
: _rowParametersMatch(row, params);
|
|
222
|
+
if (hit) return Number(keys[i]);
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
function _hasSpecificForm(byInt, dotted, params) {
|
|
227
|
+
return _specificInt(byInt, dotted, params) !== undefined;
|
|
228
|
+
}
|
|
229
|
+
function _assertSpecificForm(byInt, dotted, params, isNative, label) {
|
|
230
|
+
if (!isNative || !_hasSpecificForm(byInt, dotted, params)) return;
|
|
231
|
+
throw _err("c509/non-specific-encoding", label + " has a C509 registry row, and a natively signed certificate uses only the specific CBOR encoding (draft sec. 3.7)");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function _algorithm(node, byInt, code, label, isNative) {
|
|
182
235
|
if (node.majorType === 0 || node.majorType === 1) {
|
|
183
236
|
var i = Number(cbor.read.int(node));
|
|
184
237
|
var mapped = byInt[i];
|
|
185
238
|
if (mapped === undefined) throw _err("c509/unknown-algorithm", label + " integer " + i + " has no C509 registry row");
|
|
186
239
|
if (typeof mapped === "string") return { name: mapped, oid: oid.byName(mapped) };
|
|
187
|
-
return { name: mapped.alg, oid: oid.byName(mapped.alg), curve: mapped.curve || null };
|
|
240
|
+
return { name: mapped.alg, oid: oid.byName(mapped.alg), curve: mapped.curve || null, parameters: _rowParameters(mapped) };
|
|
241
|
+
}
|
|
242
|
+
if (node.majorType === 2) {
|
|
243
|
+
var r = _oidName(node, code, label);
|
|
244
|
+
_assertSpecificForm(byInt, r.oid, null, isNative, label);
|
|
245
|
+
return { name: r.name, oid: r.oid };
|
|
188
246
|
}
|
|
189
|
-
if (node.majorType === 2) { var r = _oidName(node, code, label); return { name: r.name, oid: r.oid }; }
|
|
190
247
|
if (node.majorType === 4 && node.children && node.children.length === 2) {
|
|
191
248
|
var a = _oidName(node.children[0], code, label);
|
|
192
249
|
if (node.children[1].majorType !== 2) throw _err(code, label + " algorithm parameters must be a CBOR byte string");
|
|
193
250
|
if (node.children[1].content.length === 0) throw _err(code, label + " algorithm parameters must not be an empty byte string; omit them with the bare ~oid form (draft sec. 3.1.3)");
|
|
251
|
+
_assertSpecificForm(byInt, a.oid, node.children[1].content, isNative, label);
|
|
194
252
|
return { name: a.name, oid: a.oid, parameters: node.children[1].content };
|
|
195
253
|
}
|
|
196
254
|
throw _err(code, label + " is not a C509 AlgorithmIdentifier (int / ~oid / [~oid, params])");
|
|
@@ -1298,7 +1356,9 @@ function _sdaFromDer(node) {
|
|
|
1298
1356
|
var setNode = attr.children[1];
|
|
1299
1357
|
if (setNode.tagClass !== "universal" || setNode.tagNumber !== asn1.TAGS.SET || !setNode.children || setNode.children.length < 1) return null;
|
|
1300
1358
|
var dotted = asn1.read.oid(attr.children[0]);
|
|
1301
|
-
var
|
|
1359
|
+
var canonAttr = ATTR_NAME_BY_OID[dotted];
|
|
1360
|
+
var nm = canonAttr === undefined ? oid.name(dotted) : canonAttr;
|
|
1361
|
+
var tint = canonAttr === undefined ? undefined : ATTR_TO_INT[canonAttr];
|
|
1302
1362
|
var intForm = _sdaTryIntForm(tint, nm, setNode.children);
|
|
1303
1363
|
if (intForm != null) {
|
|
1304
1364
|
out.push(cbor.build.int(BigInt(intForm.sign < 0 ? -tint : tint)));
|
|
@@ -1311,12 +1371,24 @@ function _sdaFromDer(node) {
|
|
|
1311
1371
|
return cbor.build.array(out);
|
|
1312
1372
|
}
|
|
1313
1373
|
|
|
1314
|
-
|
|
1374
|
+
/**
|
|
1375
|
+
* @internal Draft sec. 3.7 for extensions. The specific form was available when the registry names
|
|
1376
|
+
* this extension AND the int form can carry this exact value: a compact-codec extension only when
|
|
1377
|
+
* the codec represents it, and one without a codec always, since its int form carries the same
|
|
1378
|
+
* bytes. An extension the codec cannot represent keeps the generic form on every certificate type.
|
|
1379
|
+
*/
|
|
1380
|
+
function _assertSpecificExtension(dotted, valContent, isNative) {
|
|
1381
|
+
var canon = EXT_NAME_BY_OID[dotted];
|
|
1382
|
+
if (!isNative || canon === undefined) return;
|
|
1383
|
+
if (EXT_COMPACT[canon] && (!Buffer.isBuffer(valContent) || _tryCompactExtValue(canon, valContent, isNative) == null)) return;
|
|
1384
|
+
throw _err("c509/non-specific-encoding", "extension " + canon + " has a C509 registry id that carries this value, and a natively signed certificate uses only the specific CBOR encoding (draft sec. 3.7)");
|
|
1385
|
+
}
|
|
1386
|
+
function _tryCompactExtValue(name, der, isNative) {
|
|
1315
1387
|
var compact = _extValueFromDer(name, der);
|
|
1316
1388
|
if (compact == null) return null;
|
|
1317
1389
|
var recon;
|
|
1318
1390
|
try {
|
|
1319
|
-
recon = _extValueToDer(name, cbor.decode(compact));
|
|
1391
|
+
recon = _extValueToDer(name, cbor.decode(compact), isNative);
|
|
1320
1392
|
} catch (_e) {
|
|
1321
1393
|
return null;
|
|
1322
1394
|
}
|
|
@@ -1365,6 +1437,7 @@ function _extensions(node, isNative) {
|
|
|
1365
1437
|
if (valNode.majorType !== 2) throw _err("c509/bad-extensions", "a non-critical ~oid extension value must be a byte string");
|
|
1366
1438
|
valContent = valNode.content;
|
|
1367
1439
|
}
|
|
1440
|
+
_assertSpecificExtension(extOid, valContent, isNative);
|
|
1368
1441
|
}
|
|
1369
1442
|
out.push({ name: name, oid: extOid, critical: critical, value: valContent || null });
|
|
1370
1443
|
}
|
|
@@ -1420,6 +1493,10 @@ function _reconSpki(spkAlg, keyBytes, rsaKey) {
|
|
|
1420
1493
|
return b.sequence([b.sequence([b.oid(oid.byName("ecPublicKey")), b.oid(oid.byName(spkAlg.curve))]), b.bitString(point, 0)]);
|
|
1421
1494
|
}
|
|
1422
1495
|
if (spkAlg.name === "rsaEncryption") {
|
|
1496
|
+
/** @internal RFC 3279 sec. 2.3.1 fixes rsaEncryption parameters to the DER NULL. */
|
|
1497
|
+
if (!spkAlg.parameters || !spkAlg.parameters.equals(b.nullValue())) {
|
|
1498
|
+
throw _err("c509/bad-spki", "rsaEncryption parameters must be the DER NULL (RFC 3279 sec. 2.3.1); the bare ~oid form carries none");
|
|
1499
|
+
}
|
|
1423
1500
|
var rsaPk = b.sequence([b.integer(rsaKey.modulus), b.integer(rsaKey.exponent)]);
|
|
1424
1501
|
return b.sequence([b.sequence([b.oid(oid.byName("rsaEncryption")), b.nullValue()]), b.bitString(rsaPk, 0)]);
|
|
1425
1502
|
}
|
|
@@ -1523,7 +1600,7 @@ function parse(input) {
|
|
|
1523
1600
|
var serial = _biguint(serialBytes, "c509/bad-serial", "certificateSerialNumber");
|
|
1524
1601
|
var sHex = serialBytes.content.toString("hex");
|
|
1525
1602
|
|
|
1526
|
-
var sigAlg = _algorithm(f[2], SIG_ALG_BY_INT, "c509/unknown-algorithm", "issuerSignatureAlgorithm");
|
|
1603
|
+
var sigAlg = _algorithm(f[2], SIG_ALG_BY_INT, "c509/unknown-algorithm", "issuerSignatureAlgorithm", type === 2);
|
|
1527
1604
|
var issuer = _name509(f[3], false, type === 2);
|
|
1528
1605
|
var notBefore = _time(f[4], false, "validityNotBefore");
|
|
1529
1606
|
var notAfter = _time(f[5], true, "validityNotAfter");
|
|
@@ -1531,7 +1608,7 @@ function parse(input) {
|
|
|
1531
1608
|
if (issuer === null && (!subject || !subject.rdns || subject.rdns.length === 0)) {
|
|
1532
1609
|
throw _err("c509/bad-name", "a self-signed C509 (issuer == subject) requires a non-empty subject, since it is also the issuer (RFC 5280 sec. 4.1.2.4)");
|
|
1533
1610
|
}
|
|
1534
|
-
var spkAlg = _algorithm(f[7], PK_ALG_BY_INT, "c509/unknown-algorithm", "subjectPublicKeyAlgorithm");
|
|
1611
|
+
var spkAlg = _algorithm(f[7], PK_ALG_BY_INT, "c509/unknown-algorithm", "subjectPublicKeyAlgorithm", type === 2);
|
|
1535
1612
|
var subjectPublicKey = null, rsaKey = null;
|
|
1536
1613
|
if (spkAlg.name === "rsaEncryption") {
|
|
1537
1614
|
if (f[8].majorType === 2) rsaKey = { modulus: _biguint(f[8], "c509/bad-spki", "RSA modulus"), exponent: 65537n };
|
|
@@ -1578,29 +1655,41 @@ function matches(node) {
|
|
|
1578
1655
|
}
|
|
1579
1656
|
|
|
1580
1657
|
|
|
1581
|
-
var SIG_ALG_TO_INT = Object.assign(Object.create(null), { ecdsaWithSHA256: 0, ecdsaWithSHA384: 1, ecdsaWithSHA512: 2 });
|
|
1582
|
-
var PK_ALG_TO_INT = Object.assign(Object.create(null), { rsaEncryption: 0, "ecPublicKey|prime256v1": 1, "ecPublicKey|secp384r1": 2, "ecPublicKey|secp521r1": 3 });
|
|
1583
1658
|
var ATTR_TO_INT = Object.create(null);
|
|
1584
1659
|
Object.keys(ATTR_BY_INT).forEach(function (k) { ATTR_TO_INT[ATTR_BY_INT[k]] = Number(k); });
|
|
1660
|
+
/** @internal An attribute's C509 registry membership follows its dotted OID, not its display name. */
|
|
1661
|
+
var ATTR_NAME_BY_OID = Object.create(null);
|
|
1662
|
+
Object.keys(ATTR_BY_INT).forEach(function (k) { ATTR_NAME_BY_OID[oid.byName(ATTR_BY_INT[k])] = ATTR_BY_INT[k]; });
|
|
1585
1663
|
var EXT_TO_INT = Object.create(null);
|
|
1586
1664
|
Object.keys(EXT_BY_INT).forEach(function (k) { if (EXT_COMPACT[EXT_BY_INT[k]]) EXT_TO_INT[EXT_BY_INT[k]] = Number(k); });
|
|
1665
|
+
/** @internal An extension's C509 registry membership follows its dotted OID, not its display name. */
|
|
1666
|
+
var EXT_NAME_BY_OID = Object.create(null);
|
|
1667
|
+
Object.keys(EXT_BY_INT).forEach(function (k) { EXT_NAME_BY_OID[oid.byName(EXT_BY_INT[k])] = EXT_BY_INT[k]; });
|
|
1587
1668
|
|
|
1588
1669
|
function _minBytes(n) {
|
|
1670
|
+
if (typeof n !== "bigint") throw _err("c509/bad-serial", "a ~biguint value must be a BigInt, got " + _describe(n));
|
|
1589
1671
|
if (n < 0n) throw _err("c509/bad-serial", "a ~biguint value must be non-negative");
|
|
1590
1672
|
if (n === 0n) return Buffer.alloc(0);
|
|
1591
1673
|
var hex = n.toString(16); if (hex.length % 2) hex = "0" + hex;
|
|
1592
1674
|
return Buffer.from(hex, "hex");
|
|
1593
1675
|
}
|
|
1594
|
-
function _encAlgorithm(alg,
|
|
1595
|
-
var
|
|
1596
|
-
if (
|
|
1597
|
-
var
|
|
1598
|
-
|
|
1676
|
+
function _encAlgorithm(alg, byInt) {
|
|
1677
|
+
var dotted = alg.oid || oid.byName(alg.name);
|
|
1678
|
+
if (!dotted) throw _err("c509/bad-input", "algorithm " + alg.name + " carries no OID and its name is not in the OID registry");
|
|
1679
|
+
var supplied = alg.parameters && alg.parameters.length ? alg.parameters : null;
|
|
1680
|
+
var i = _specificInt(byInt, dotted, supplied, alg.curve);
|
|
1681
|
+
if (i !== undefined) return cbor.build.int(BigInt(i));
|
|
1682
|
+
var oidBytes = cbor.build.byteString(asn1.encodeOidContent(dotted));
|
|
1683
|
+
if (supplied !== null) return cbor.build.array([oidBytes, cbor.build.byteString(supplied)]);
|
|
1599
1684
|
return oidBytes;
|
|
1600
1685
|
}
|
|
1601
1686
|
function _encSpecialText(rdn) {
|
|
1602
|
-
if (rdn.eui64
|
|
1603
|
-
|
|
1687
|
+
if (rdn.eui64 != null) {
|
|
1688
|
+
if (!Buffer.isBuffer(rdn.eui64)) throw _err("c509/bad-name", "an RDN eui64 must be a Buffer, got " + _describe(rdn.eui64));
|
|
1689
|
+
return cbor.build.tag(48, cbor.build.byteString(rdn.eui64));
|
|
1690
|
+
}
|
|
1691
|
+
if (typeof rdn.value !== "string") throw _err("c509/bad-name", "an RDN value must be a string, got " + _describe(rdn.value));
|
|
1692
|
+
var v = rdn.value;
|
|
1604
1693
|
if (v.length >= 2 && _isEvenHexLower(v)) return cbor.build.byteString(Buffer.from(v, "hex"));
|
|
1605
1694
|
return cbor.build.textString(v);
|
|
1606
1695
|
}
|
|
@@ -1610,11 +1699,15 @@ function _encName(name, isSubject) {
|
|
|
1610
1699
|
throw _err("c509/bad-name", "the subject Name is required");
|
|
1611
1700
|
}
|
|
1612
1701
|
var rdns = name.rdns || [];
|
|
1702
|
+
_assertDense(rdns, "c509/bad-name", "the rdns array");
|
|
1703
|
+
rdns.forEach(function (rdn) {
|
|
1704
|
+
if (!rdn || typeof rdn !== "object") throw _err("c509/bad-name", "each RDN must be an object, got " + _describe(rdn));
|
|
1705
|
+
});
|
|
1613
1706
|
if (rdns.length === 1 && rdns[0].type === "commonName" && !rdns[0].printable) return _encSpecialText(rdns[0]);
|
|
1614
1707
|
var items = [];
|
|
1615
1708
|
rdns.forEach(function (rdn) {
|
|
1616
1709
|
var ai = ATTR_TO_INT[rdn.type];
|
|
1617
|
-
if (ai === undefined) throw _err("c509/bad-name", "attribute type " + rdn.type + " has no C509 registry int");
|
|
1710
|
+
if (ai === undefined) throw _err("c509/bad-name", "attribute type " + _describe(rdn.type) + " has no C509 registry int");
|
|
1618
1711
|
items.push(cbor.build.int(BigInt(rdn.printable ? -ai : ai)));
|
|
1619
1712
|
items.push(_encSpecialText(rdn));
|
|
1620
1713
|
});
|
|
@@ -1629,19 +1722,52 @@ function _encSpk(r) {
|
|
|
1629
1722
|
if (!Buffer.isBuffer(r.subjectPublicKey)) throw _err("c509/bad-spki", "the subjectPublicKey bytes are missing");
|
|
1630
1723
|
return cbor.build.byteString(r.subjectPublicKey);
|
|
1631
1724
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1725
|
+
/** @internal An extension names itself by name, by OID, or by both; the registry is keyed by name. */
|
|
1726
|
+
function _extIdentity(ext) {
|
|
1727
|
+
var dotted = ext.oid || (typeof ext.name === "string" ? oid.byName(ext.name) : null);
|
|
1728
|
+
var canon = dotted ? EXT_NAME_BY_OID[dotted] : undefined;
|
|
1729
|
+
if (canon !== undefined) return { dotted: dotted, name: canon };
|
|
1730
|
+
return { dotted: dotted, name: typeof ext.name === "string" ? ext.name : (dotted ? oid.name(dotted) : null) };
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* @internal The bits a lone keyUsage is written as. Derived from a DER value only when the compact
|
|
1734
|
+
* form reproduces that value byte for byte, since the bare integer covers the nine defined bits and
|
|
1735
|
+
* a wider BIT STRING would lose the rest.
|
|
1736
|
+
*/
|
|
1737
|
+
function _extKeyUsageBits(ext, isNative) {
|
|
1738
|
+
if (typeof ext.keyUsageBits === "number") return ext.keyUsageBits;
|
|
1739
|
+
if (!Buffer.isBuffer(ext.value) || _tryCompactExtValue("keyUsage", ext.value, isNative) == null) return null;
|
|
1740
|
+
return _keyUsageBitsFromDer(ext.value);
|
|
1741
|
+
}
|
|
1742
|
+
function _encExtensions(exts, isNative) {
|
|
1743
|
+
_assertDense(exts, "c509/bad-input", "the extensions array");
|
|
1744
|
+
exts.forEach(function (ext) {
|
|
1745
|
+
if (!ext || typeof ext !== "object") throw _err("c509/bad-input", "each extension must be an object, got " + _describe(ext));
|
|
1746
|
+
if (ext.oid != null) {
|
|
1747
|
+
if (typeof ext.oid !== "string") throw _err("c509/bad-input", "an extension oid must be a dotted-decimal string, got " + _describe(ext.oid));
|
|
1748
|
+
try { asn1.encodeOidContent(ext.oid); }
|
|
1749
|
+
catch (_oe) { throw _err("c509/bad-input", "an extension oid must be a dotted-decimal string, got " + _describe(ext.oid), _oe); }
|
|
1750
|
+
}
|
|
1751
|
+
if (ext.keyUsageBits != null && (typeof ext.keyUsageBits !== "number" || !Number.isSafeInteger(ext.keyUsageBits))) {
|
|
1752
|
+
throw _err("c509/bad-extensions", "keyUsageBits must be a safe integer, got " + _describe(ext.keyUsageBits));
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1755
|
+
if (exts.length === 1 && _extIdentity(exts[0]).name === "keyUsage") {
|
|
1756
|
+
var onlyBits = _extKeyUsageBits(exts[0], isNative);
|
|
1757
|
+
if (typeof onlyBits === "number") return cbor.build.int(BigInt(exts[0].critical ? -onlyBits : onlyBits));
|
|
1635
1758
|
}
|
|
1636
1759
|
var items = [];
|
|
1637
1760
|
exts.forEach(function (ext) {
|
|
1638
|
-
var
|
|
1639
|
-
var
|
|
1761
|
+
var ident = _extIdentity(ext);
|
|
1762
|
+
var extDotted = ident.dotted, extName = ident.name;
|
|
1763
|
+
var ei = extName == null ? undefined : EXT_TO_INT[extName];
|
|
1764
|
+
var compact = (ei !== undefined && Buffer.isBuffer(ext.value)) ? _tryCompactExtValue(extName, ext.value, isNative) : null;
|
|
1640
1765
|
if (compact != null) {
|
|
1641
1766
|
items.push(cbor.build.int(BigInt(ext.critical ? -ei : ei)));
|
|
1642
1767
|
items.push(compact);
|
|
1643
1768
|
} else {
|
|
1644
|
-
|
|
1769
|
+
if (!extDotted) throw _err("c509/bad-input", "an extension must carry an OID or a name in the OID registry, got " + _describe(ext.name));
|
|
1770
|
+
items.push(cbor.build.byteString(asn1.encodeOidContent(extDotted)));
|
|
1645
1771
|
if (!Buffer.isBuffer(ext.value)) throw _err("c509/non-invertible", "extension " + (ext.oid || ext.name) + " has no byte-string value to encode");
|
|
1646
1772
|
var bs = cbor.build.byteString(ext.value);
|
|
1647
1773
|
items.push(ext.critical ? cbor.build.array([bs]) : bs);
|
|
@@ -1673,18 +1799,17 @@ function _encodeC509Array(r) {
|
|
|
1673
1799
|
return out;
|
|
1674
1800
|
}
|
|
1675
1801
|
_requireResultShape(r);
|
|
1676
|
-
var pkKey = r.subjectPublicKeyAlgorithm.curve ? r.subjectPublicKeyAlgorithm.name + "|" + r.subjectPublicKeyAlgorithm.curve : r.subjectPublicKeyAlgorithm.name;
|
|
1677
1802
|
var arr = cbor.build.array([
|
|
1678
1803
|
cbor.build.int(BigInt(r.certificateType)),
|
|
1679
1804
|
cbor.build.byteString(r.serialNumberHex != null ? _hexBytes(r.serialNumberHex, "c509/bad-serial", "serialNumberHex") : _minBytes(r.serialNumber)),
|
|
1680
|
-
_encAlgorithm(r.signatureAlgorithm,
|
|
1805
|
+
_encAlgorithm(r.signatureAlgorithm, SIG_ALG_BY_INT),
|
|
1681
1806
|
_encName(r.issuer, false),
|
|
1682
1807
|
_validityUint(r.validity.notBefore, "validityNotBefore"),
|
|
1683
1808
|
r.validity.notAfter === null ? cbor.build.nullValue() : _validityUint(r.validity.notAfter, "validityNotAfter"),
|
|
1684
1809
|
_encName(r.subject, true),
|
|
1685
|
-
_encAlgorithm(r.subjectPublicKeyAlgorithm,
|
|
1810
|
+
_encAlgorithm(r.subjectPublicKeyAlgorithm, PK_ALG_BY_INT),
|
|
1686
1811
|
_encSpk(r),
|
|
1687
|
-
_encExtensions(r.extensions),
|
|
1812
|
+
_encExtensions(r.extensions, Number(r.certificateType) === 2),
|
|
1688
1813
|
cbor.build.byteString(r.signatureValue),
|
|
1689
1814
|
]);
|
|
1690
1815
|
parse(arr);
|
|
@@ -1748,7 +1873,9 @@ function _c509NameFromDer(nameBytes) {
|
|
|
1748
1873
|
(node.children || []).forEach(function (rdnSet) {
|
|
1749
1874
|
if (!rdnSet.children || rdnSet.children.length !== 1) throw _err("c509/non-invertible", "a C509 Name requires single-attribute RDNs");
|
|
1750
1875
|
var attr = rdnSet.children[0];
|
|
1751
|
-
var
|
|
1876
|
+
var attrDotted = asn1.read.oid(attr.children[0]);
|
|
1877
|
+
var attrCanon = ATTR_NAME_BY_OID[attrDotted];
|
|
1878
|
+
var attrName = attrCanon === undefined ? oid.name(attrDotted) : attrCanon;
|
|
1752
1879
|
if (attrName == null || ATTR_TO_INT[attrName] === undefined) throw _err("c509/non-invertible", "attribute type " + attrName + " has no C509 registry integer");
|
|
1753
1880
|
var valNode = attr.children[1];
|
|
1754
1881
|
var value;
|
|
@@ -1795,19 +1922,19 @@ function _minCurveForMag(mag) {
|
|
|
1795
1922
|
for (var i = 0; i < CURVE_ORDER.length; i++) { if (mag <= WEBCRYPTO_FIELD_BYTES[CURVE_ORDER[i]]) return CURVE_ORDER[i]; }
|
|
1796
1923
|
return null;
|
|
1797
1924
|
}
|
|
1798
|
-
function _resolveIssuerSigCurve(c, opts) {
|
|
1925
|
+
function _resolveIssuerSigCurve(c, opts, sigAlgName) {
|
|
1799
1926
|
var mag = _sigMagWidth(c.signatureValue.bytes), curve;
|
|
1800
1927
|
if (opts && opts.issuerCurve != null) {
|
|
1801
1928
|
curve = String(opts.issuerCurve);
|
|
1802
1929
|
if (NODE_TO_WEBCRYPTO[curve]) curve = NODE_TO_WEBCRYPTO[curve];
|
|
1803
1930
|
if (!WEBCRYPTO_FIELD_BYTES[curve]) throw _err("c509/bad-input", "opts.issuerCurve must be P-256 / P-384 / P-521 (or prime256v1 / secp384r1 / secp521r1); got " + opts.issuerCurve);
|
|
1804
1931
|
} else {
|
|
1805
|
-
curve = SIG_ALG_TO_CURVE[
|
|
1806
|
-
if (!curve) throw _err("c509/non-invertible", "cannot resolve the issuer signing curve for signature algorithm " +
|
|
1932
|
+
curve = SIG_ALG_TO_CURVE[sigAlgName];
|
|
1933
|
+
if (!curve) throw _err("c509/non-invertible", "cannot resolve the issuer signing curve for signature algorithm " + sigAlgName);
|
|
1807
1934
|
var minCurve = _minCurveForMag(mag);
|
|
1808
1935
|
if (!minCurve) throw _err("c509/non-invertible", "the ECDSA signature r/s width " + mag + " exceeds every supported curve field");
|
|
1809
|
-
if (WEBCRYPTO_FIELD_BYTES[curve] < mag) throw _err("c509/non-invertible", "the issuer signed with a non-standard digest/curve pairing (r/s width " + mag + " exceeds the " + curve + " field implied by " +
|
|
1810
|
-
if (WEBCRYPTO_FIELD_BYTES[curve] > WEBCRYPTO_FIELD_BYTES[minCurve]) throw _err("c509/non-invertible", "signature algorithm " +
|
|
1936
|
+
if (WEBCRYPTO_FIELD_BYTES[curve] < mag) throw _err("c509/non-invertible", "the issuer signed with a non-standard digest/curve pairing (r/s width " + mag + " exceeds the " + curve + " field implied by " + sigAlgName + "); pass opts.issuerCurve (P-256 / P-384 / P-521)");
|
|
1937
|
+
if (WEBCRYPTO_FIELD_BYTES[curve] > WEBCRYPTO_FIELD_BYTES[minCurve]) throw _err("c509/non-invertible", "signature algorithm " + sigAlgName + " does not uniquely determine the issuer curve (r/s width " + mag + " also fits " + minCurve + "); pass opts.issuerCurve (P-256 / P-384 / P-521)");
|
|
1811
1938
|
}
|
|
1812
1939
|
if (mag > WEBCRYPTO_FIELD_BYTES[curve]) throw _err("c509/non-invertible", "the ECDSA signature r/s width " + mag + " does not fit the resolved " + curve + " field");
|
|
1813
1940
|
return curve;
|
|
@@ -1822,13 +1949,16 @@ _derToType3 = function (input, opts) {
|
|
|
1822
1949
|
var c;
|
|
1823
1950
|
try { c = x509.parse(input); } catch (e) { throw _err("c509/bad-input", "the input is not a valid X.509 certificate", e); }
|
|
1824
1951
|
if (c.version !== 3) throw _err("c509/non-invertible", "C509 covers X.509 v3 certificates; got v" + c.version);
|
|
1825
|
-
|
|
1826
|
-
|
|
1952
|
+
var sigCanon = SIG_ALG_BY_OID[c.signatureAlgorithm.oid];
|
|
1953
|
+
var sigAlgName = sigCanon === undefined ? c.signatureAlgorithm.name : sigCanon;
|
|
1954
|
+
if (String(sigAlgName || "").toLowerCase().indexOf("ecdsa") !== 0) throw _err("c509/non-invertible", "type-3 C509 encoding covers only ECDSA-signed certificates; got " + (sigAlgName || "an unregistered algorithm"));
|
|
1955
|
+
if (c.subjectPublicKeyInfo.algorithm.oid !== oid.byName("ecPublicKey")) throw _err("c509/non-invertible", "type-3 C509 encoding covers only EC (ecPublicKey) certificates in v1; got " + (c.subjectPublicKeyInfo.algorithm.name || "an unregistered algorithm"));
|
|
1827
1956
|
var curveOid = asn1.read.oid(asn1.decode(c.subjectPublicKeyInfo.algorithm.parameters));
|
|
1828
|
-
var
|
|
1957
|
+
var curveCanon = EC_CURVE_BY_OID[curveOid];
|
|
1958
|
+
var curve = curveCanon === undefined ? oid.name(curveOid) : curveCanon;
|
|
1829
1959
|
var coordLen = EC_FIELD_BYTES[curve];
|
|
1830
1960
|
if (!coordLen) throw _err("c509/non-invertible", "unsupported EC subject curve " + (curve || curveOid));
|
|
1831
|
-
var sigCurve = _resolveIssuerSigCurve(c, opts);
|
|
1961
|
+
var sigCurve = _resolveIssuerSigCurve(c, opts, sigAlgName);
|
|
1832
1962
|
var spkiNode = asn1.decode(c.subjectPublicKeyInfo.bytes);
|
|
1833
1963
|
return {
|
|
1834
1964
|
certificateType: 3,
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:82faf919-c224-4430-992b-dd3055e65ed3",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-05T03:43:35.188Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.6.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.6.37",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.6.
|
|
25
|
+
"version": "0.6.37",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.6.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.6.37",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.6.
|
|
57
|
+
"ref": "@blamejs/pki@0.6.37",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|