@blamejs/pki 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/lib/ct.js +123 -0
- package/lib/ip-utils.js +59 -0
- package/lib/lint.js +6 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ 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.2.13 — 2026-07-13
|
|
8
|
+
|
|
9
|
+
The certificate linter validates IP-literal common names with a strict in-house checker instead of node:net, so the toolkit pulls in no networking module.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- pki.lint validates an IP-literal common name with a strict in-house IPv4/IPv6 checker (lib/ip-utils) instead of node:net, so the toolkit needs no networking module; the validator now also recognizes IPv4-mapped and dual-stack IPv6 literals.
|
|
14
|
+
|
|
15
|
+
## v0.2.12 — 2026-07-13
|
|
16
|
+
|
|
17
|
+
pki.ct.verifySct verifies a Signed Certificate Timestamp's signature against a Certificate Transparency log's public key.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- pki.ct.verifySct(entry, sct, logPublicKey) verifies a Signed Certificate Timestamp's signature against a CT log's public key (RFC 6962 section 3.2), composing pki.ct.reconstructSignedData, the shared ECDSA-Sig-Value conformance gate, and pki.webcrypto. Resolves true/false on the cryptographic verdict; throws a typed CtError on a structural fault.
|
|
22
|
+
|
|
7
23
|
## v0.2.11 — 2026-07-13
|
|
8
24
|
|
|
9
25
|
The pki command-line tool gains inspect, lint, convert, and verify -- front-ends over the certificate inspector, the linter, the PEM codecs, and RFC 5280 path validation.
|
package/README.md
CHANGED
|
@@ -221,7 +221,7 @@ is callable today; nothing below is a stub.
|
|
|
221
221
|
| `pki.schema.smime` | Decode S/MIME ESS signed-attribute values (RFC 5035 / RFC 8551) — `parseSigningCertificate` / `parseSigningCertificateV2` bind a signature to its signing certificate (cert hash, hash algorithm, issuer `GeneralNames` + serial), `parseSmimeCapabilities` decodes the ordered capability list, and `decodeAttribute` OID-dispatches a CMS attribute (enforcing the single-value rule, recognize-and-defer for unknown types). A companion decoder for CMS signed attributes, not an auto-routed format, fail-closed — `parseSigningCertificate`, `parseSigningCertificateV2`, `parseSmimeCapabilities`, `decodeAttribute` |
|
|
222
222
|
| `pki.schema.engine` | The declarative ASN.1 structure-schema engine every format parser composes — `walk` / `encode` / `embeddedDer` plus the schema combinators |
|
|
223
223
|
| `pki.path` | RFC 5280 §6 certification-path validation — `validate` runs the §6.1 state machine (signature chaining across RSA, ECDSA, EdDSA, ML-DSA, SLH-DSA and hybrid composite ML-DSA signatures — a composite is accepted only when **both** its post-quantum and traditional components verify; validity windows, name chaining, basic constraints and path length, key usage, name constraints, the certificate-policy tree) over an ordered path and a trust anchor, returning a structured verdict with per-check reason codes, and enforces a `pki.trust` anchor's per-purpose distrust-after dates and delegator purposes via `checkPurpose`; `crlChecker` supplies CRL-based revocation — including partitioned/sharded CRLs, whose §6.3.3 Distribution Point ↔ IDP correspondence lets a corresponding full-reason shard establish non-revocation — and `ocspChecker` supplies OCSP-based revocation (RFC 6960 — CertID binding, responder authorization, signature, currency) over the same pluggable hook. Pure and re-entrant, fail-closed — `validate`, `crlChecker`, `ocspChecker` |
|
|
224
|
-
| `pki.ct` |
|
|
224
|
+
| `pki.ct` | RFC 6962 Certificate Transparency SCTs — `parseSctList` decodes the `SignedCertificateTimestampList` a certificate or OCSP response carries in the SCT extension (a TLS-presentation-language payload inside the §3.3 double DER wrap) into per-SCT log id, exact `timestamp` (BigInt), named signature algorithm, and raw signature; `reconstructSignedData` rebuilds the exact `digitally-signed` preimage; `verifySct` verifies an SCT signature against a log's public key by reconstructing the signed data, routing an ECDSA signature through the strict DER-conformance gate, and verifying through the crypto engine — resolving true or false, and throwing a typed error on a structural fault. Structure decoded, crypto fail-closed — `parseSctList`, `reconstructSignedData`, `verifySct` |
|
|
225
225
|
| `pki.merkle` | RFC 6962 / RFC 9162 Merkle-tree proof verification — `leafHash` / `nodeHash` / `emptyRootHash` build the domain-separated (0x00 leaf / 0x01 node) SHA-256 tree hashes; `verifyInclusion` folds an audit proof back to a root and `verifyConsistency` reconstructs both the old and new root (the append-only guarantee), each constant-time-compared to a trusted checkpoint root. Fail-closed on bad geometry, sync hashing, transport-free — `leafHash`, `nodeHash`, `emptyRootHash`, `verifyInclusion`, `verifyConsistency` |
|
|
226
226
|
| `pki.trust` | Mozilla / CCADB trust-store ingestion — `parseCertdata` reads the NSS `certdata.txt` object stream and `parseCcadbCsv` the CCADB CSV export into one identical constraint-carrying anchor shape: the per-purpose trust bits (only `CKT_NSS_TRUSTED_DELEGATOR` grants) and the per-purpose distrust-after dates the bare root list omits. Certificate and trust objects pair by byte-exact issuer + serial (never adjacency) and are cross-checked against the parsed DER, so metadata can never attach to the wrong root; `anchor()` hands an entry to `pki.path.validate({ trustAnchor, checkPurpose })`. Offline, fail-closed, bounded — `parseCertdata`, `parseCcadbCsv`, `anchor` |
|
|
227
227
|
| `pki.shbs` | Stateful hash-based signature **verification** — HSS/LMS (RFC 8554), carried in X.509 by RFC 9802 and CMS by RFC 9708, profiled by NIST SP 800-208 (CNSA 2.0 firmware signing). `verify` checks an HSS signature (every level must pass) and `verifyLms` a single-tree LMS, over the raw public-key / signature blobs the parsers already surface. Pure public-input SHA-256 / SHAKE256 hashing, a data-driven typecode registry, bounds-before-slice reads; a malformed blob throws a typed `ShbsError`, a well-formed-but-wrong signature returns `false`. **Verify only by design** — stateful signing needs atomic one-time-key state that belongs in an HSM — `verify`, `verifyLms` |
|
package/lib/ct.js
CHANGED
|
@@ -43,6 +43,10 @@ var constants = require("./constants.js");
|
|
|
43
43
|
var frameworkError = require("./framework-error.js");
|
|
44
44
|
var guard = require("./guard-all.js");
|
|
45
45
|
var ByteReader = require("./byte-reader.js");
|
|
46
|
+
var oid = require("./oid.js");
|
|
47
|
+
var webcrypto = require("./webcrypto.js");
|
|
48
|
+
var validator = require("./validator-all.js");
|
|
49
|
+
var subtle = webcrypto.webcrypto.subtle;
|
|
46
50
|
|
|
47
51
|
var CtError = frameworkError.CtError;
|
|
48
52
|
var C = constants;
|
|
@@ -287,9 +291,128 @@ function reconstructSignedData(entry, sct) {
|
|
|
287
291
|
return Buffer.concat(parts);
|
|
288
292
|
}
|
|
289
293
|
|
|
294
|
+
// RFC 6962 sec. 2.1.4 constrains an SCT to SHA-256 with either ECDSA (NIST P-256) or RSA;
|
|
295
|
+
// the verifier enforces exactly that rather than accepting an off-profile hash / curve a
|
|
296
|
+
// conformant log never uses. The SCT hash name -> the WebCrypto digest.
|
|
297
|
+
var CT_HASH = { sha256: "SHA-256" };
|
|
298
|
+
// The one EC named curve an SCT log key may use (P-256), keyed by the curve OID its SPKI
|
|
299
|
+
// carries -> the WebCrypto curve name + the r/s coordinate width.
|
|
300
|
+
var CT_EC_CURVE = {};
|
|
301
|
+
CT_EC_CURVE[oid.byName("prime256v1")] = { curve: "P-256", coordLen: 32 };
|
|
302
|
+
|
|
303
|
+
// Read the SubjectPublicKeyInfo algorithm OID (+ the EC curve OID) off a log key SPKI so
|
|
304
|
+
// the verifier picks the WebCrypto import descriptor from the KEY, not just the SCT's
|
|
305
|
+
// self-declared signature type. Fail-closed: a non-SPKI shape throws a typed ct/bad-input.
|
|
306
|
+
function _spkiAlg(spki) {
|
|
307
|
+
var node;
|
|
308
|
+
try { node = asn1.decode(spki); } catch (e) { throw new CtError("ct/bad-input", "the CT log public key is not a well-formed SubjectPublicKeyInfo", e); }
|
|
309
|
+
var algId = node.children && node.children[0];
|
|
310
|
+
if (!algId || !algId.children || !algId.children.length) throw new CtError("ct/bad-input", "the CT log public key is not a SubjectPublicKeyInfo");
|
|
311
|
+
var out;
|
|
312
|
+
try { out = { algOid: asn1.read.oid(algId.children[0]) }; }
|
|
313
|
+
catch (e1) { throw new CtError("ct/bad-input", "the CT log key SPKI algorithm identifier is not an OID", e1); }
|
|
314
|
+
if (out.algOid === oid.byName("ecPublicKey")) {
|
|
315
|
+
if (!algId.children[1]) throw new CtError("ct/bad-input", "the EC log key SPKI is missing its named-curve parameters");
|
|
316
|
+
try { out.curveOid = asn1.read.oid(algId.children[1]); }
|
|
317
|
+
catch (e2) { throw new CtError("ct/bad-input", "the EC log key SPKI curve parameters are not a named-curve OID", e2); }
|
|
318
|
+
} else if (out.algOid === oid.byName("rsaEncryption")) {
|
|
319
|
+
// RFC 6962 sec. 2.1.4 requires an RSA log key of at least 2048 bits -- size it from the
|
|
320
|
+
// RSAPublicKey modulus in the subjectPublicKey BIT STRING.
|
|
321
|
+
var mod, exp;
|
|
322
|
+
try {
|
|
323
|
+
var rsaSeq = asn1.decode(asn1.read.bitString(node.children[1]).bytes);
|
|
324
|
+
mod = asn1.read.integer(rsaSeq.children[0]);
|
|
325
|
+
exp = asn1.read.integer(rsaSeq.children[1]);
|
|
326
|
+
} catch (e3) { throw new CtError("ct/bad-input", "the RSA log key SPKI is not a well-formed RSAPublicKey", e3); }
|
|
327
|
+
// A non-positive modulus is malformed -- fail closed rather than size its absolute value.
|
|
328
|
+
if (mod <= 0n) throw new CtError("ct/bad-input", "the RSA log key modulus is not a positive integer");
|
|
329
|
+
// A small or even public exponent makes RSASSA-PKCS1-v1_5 verification forgeable (with
|
|
330
|
+
// e=1 the signature is simply the encoded DigestInfo). Require an odd exponent >= 3 --
|
|
331
|
+
// critical when the log key is taken from an untrusted log list rather than a pinned key.
|
|
332
|
+
if (exp < 3n || (exp & 1n) === 0n) throw new CtError("ct/bad-input", "the RSA log key public exponent must be an odd integer >= 3");
|
|
333
|
+
out.rsaBits = mod.toString(2).length;
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @primitive pki.ct.verifySct
|
|
340
|
+
* @signature pki.ct.verifySct(entry, sct, logPublicKey) -> Promise<boolean>
|
|
341
|
+
* @since 0.2.12
|
|
342
|
+
* @status experimental
|
|
343
|
+
* @spec RFC 6962
|
|
344
|
+
* @defends sct-signature-forgery (CWE-347)
|
|
345
|
+
* @related pki.ct.parseSctList, pki.ct.reconstructSignedData
|
|
346
|
+
*
|
|
347
|
+
* Verify a Signed Certificate Timestamp's signature against a Certificate Transparency
|
|
348
|
+
* log's public key (RFC 6962 sec. 3.2). `entry` is the log entry the SCT covers
|
|
349
|
+
* (`{ entryType: 0, leafCert }` or `{ entryType: 1, tbsCertificate, issuerKeyHash }`,
|
|
350
|
+
* as for `reconstructSignedData`), `sct` a decoded v1 SCT from `parseSctList().scts[]`,
|
|
351
|
+
* and `logPublicKey` the log's SubjectPublicKeyInfo (DER `Buffer`). Reconstructs the exact
|
|
352
|
+
* signed data, imports the log key, and verifies the SCT signature -- an ECDSA signature is
|
|
353
|
+
* routed through the strict DER ECDSA-Sig-Value conformance gate before conversion to the
|
|
354
|
+
* raw r||s WebCrypto expects, an RSA signature verifies directly.
|
|
355
|
+
*
|
|
356
|
+
* Resolves `true` on a valid signature and `false` on a cryptographic mismatch (a false
|
|
357
|
+
* verdict is a verdict). Throws a typed `CtError` on structural failure -- a malformed
|
|
358
|
+
* entry/SCT, an unusable log key, or an unsupported hash/signature algorithm.
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* var sct = pki.ct.parseSctList(sctExtValue).scts[0];
|
|
362
|
+
* // Resolve the CT log's DER SubjectPublicKeyInfo from a trusted log list, keyed by log id.
|
|
363
|
+
* var logKeysByLogId = {}; // { sct.logIdHex: <SPKI Buffer>, ... }
|
|
364
|
+
* var logKey = logKeysByLogId[sct.logIdHex];
|
|
365
|
+
* var ok = await pki.ct.verifySct({ entryType: 0, leafCert: certDer }, sct, logKey);
|
|
366
|
+
*/
|
|
367
|
+
async function verifySct(entry, sct, logPublicKey) {
|
|
368
|
+
// reconstructSignedData validates the entry + SCT and rebuilds the exact preimage.
|
|
369
|
+
var message = reconstructSignedData(entry, sct);
|
|
370
|
+
var spki = _toBuffer(logPublicKey, "the CT log public key (SPKI)");
|
|
371
|
+
// RFC 6962 sec. 3.2: an SCT names its log by LogID = SHA-256(log SPKI). When the SCT
|
|
372
|
+
// carries a logId, it MUST match the key it is verified against, so an SCT is never
|
|
373
|
+
// accepted against a different log's key (a key-confusion the bare signature check misses).
|
|
374
|
+
if (sct.logId != null) {
|
|
375
|
+
var keyId = Buffer.from(await subtle.digest("SHA-256", spki));
|
|
376
|
+
if (!keyId.equals(_toBuffer(sct.logId, "sct.logId"))) {
|
|
377
|
+
throw new CtError("ct/log-id-mismatch", "the SCT logId does not match SHA-256 of the provided log key (RFC 6962 sec. 3.2)");
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
var sigInfo = sct.signatureAlgorithm || {};
|
|
381
|
+
var hashName = CT_HASH[sigInfo.hashName];
|
|
382
|
+
if (!hashName) throw new CtError("ct/unsupported-algorithm", "unsupported SCT hash algorithm " + JSON.stringify(sigInfo.hashName) + " (RFC 6962 sec. 2.1.4 mandates sha256)");
|
|
383
|
+
var alg = _spkiAlg(spki);
|
|
384
|
+
var imp, ver, sig = _toBuffer(sct.signature, "sct.signature");
|
|
385
|
+
if (sigInfo.signatureName === "ecdsa") {
|
|
386
|
+
if (alg.algOid !== oid.byName("ecPublicKey")) throw new CtError("ct/bad-input", "the SCT declares an ECDSA signature but the log key is not an EC key");
|
|
387
|
+
var ec = CT_EC_CURVE[alg.curveOid];
|
|
388
|
+
if (!ec) throw new CtError("ct/unsupported-algorithm", "unsupported SCT log EC curve (RFC 6962 sec. 2.1.4 mandates NIST P-256)");
|
|
389
|
+
imp = { name: "ECDSA", namedCurve: ec.curve };
|
|
390
|
+
ver = { name: "ECDSA", hash: hashName };
|
|
391
|
+
// The SCT signature is a DER ECDSA-Sig-Value; route it through the strict conformance
|
|
392
|
+
// gate (primitive, minimal, positive, bounded r/s) before converting to raw r||s.
|
|
393
|
+
sig = validator.sig.ecdsaSigToRaw(sig, ec.coordLen, CtError, "ct/bad-signature");
|
|
394
|
+
} else if (sigInfo.signatureName === "rsa") {
|
|
395
|
+
if (alg.algOid !== oid.byName("rsaEncryption")) throw new CtError("ct/bad-input", "the SCT declares an RSA signature but the log key is not an RSA key");
|
|
396
|
+
if (!(alg.rsaBits >= 2048)) throw new CtError("ct/unsupported-algorithm", "the SCT log RSA key is below the RFC 6962 sec. 2.1.4 minimum of 2048 bits");
|
|
397
|
+
imp = { name: "RSASSA-PKCS1-v1_5", hash: hashName };
|
|
398
|
+
ver = { name: "RSASSA-PKCS1-v1_5" };
|
|
399
|
+
} else {
|
|
400
|
+
throw new CtError("ct/unsupported-algorithm", "unsupported SCT signature algorithm " + JSON.stringify(sigInfo.signatureName) + " (RFC 6962 sec. 2.1.4 supports ecdsa/rsa)");
|
|
401
|
+
}
|
|
402
|
+
// A wrong signature resolves false from subtle.verify (a verdict); a structural failure
|
|
403
|
+
// -- an unimportable key, an algorithm/key mismatch -- is re-thrown fail-closed.
|
|
404
|
+
try {
|
|
405
|
+
var key = await subtle.importKey("spki", spki, imp, false, ["verify"]);
|
|
406
|
+
return await subtle.verify(ver, key, sig, message);
|
|
407
|
+
} catch (e) {
|
|
408
|
+
throw new CtError("ct/verify-error", "the SCT signature could not be evaluated", e);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
290
412
|
module.exports = {
|
|
291
413
|
parseSctList: parseSctList,
|
|
292
414
|
reconstructSignedData: reconstructSignedData,
|
|
415
|
+
verifySct: verifySct,
|
|
293
416
|
HASH_ALGORITHMS: HASH_ALGORITHMS,
|
|
294
417
|
SIGNATURE_ALGORITHMS: SIGNATURE_ALGORITHMS,
|
|
295
418
|
};
|
package/lib/ip-utils.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
//
|
|
5
|
+
// @internal -- no operator-facing namespace. Textual IP-literal validation, shared so a
|
|
6
|
+
// consumer (pki.lint's commonName-in-SAN check today; a future SAN iPAddress / name-
|
|
7
|
+
// constraint surface) does not hand-roll a partial regex that misses a valid IPv6 form.
|
|
8
|
+
//
|
|
9
|
+
// The IPv4 grammar is strict per-octet 0-255 (RFC 791); the IPv6 parser follows RFC 4291
|
|
10
|
+
// sec. 2.2 (8 groups of 1-4 hex, one "::" run compressing a contiguous zero run) plus the
|
|
11
|
+
// RFC 4291 sec. 2.5.5.2 / RFC 5952 sec. 5 IPv4-mapped + dual-stack "::ffff:1.2.3.4" tail.
|
|
12
|
+
// This mirrors the vetted validation in the sibling blamejs framework (lib/ip-utils.js);
|
|
13
|
+
// this toolkit keeps its own copy rather than take a runtime dependency (Hard rule #1).
|
|
14
|
+
|
|
15
|
+
// Strict RFC 791 dotted-quad: four 0-255 octets. Anchored + per-octet repeat-capped (no
|
|
16
|
+
// ReDoS on unbounded input).
|
|
17
|
+
var IPV4_RE = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
|
|
18
|
+
function isIPv4(s) { return typeof s === "string" && IPV4_RE.test(s); }
|
|
19
|
+
|
|
20
|
+
// Expand an IPv6 textual address to its 32-hex-character form, or null on any parse failure
|
|
21
|
+
// (bad hex group, group count != 8, more than one "::", an over-length group). Accepts the
|
|
22
|
+
// IPv4-mapped / dual-stack ".d.d.d.d" tail.
|
|
23
|
+
var IPV6_TEXT_MAX_LEN = 45; // 8 groups x4 hex + 7 colons + a dotted-quad tail headroom
|
|
24
|
+
function expandIpv6Hex(ip) {
|
|
25
|
+
if (typeof ip !== "string" || ip.length > IPV6_TEXT_MAX_LEN || ip.indexOf(":") === -1) return null;
|
|
26
|
+
// RFC 4291 sec. 2.5.5.2 IPv4-mapped / dual-stack: fold a trailing dotted-quad into two
|
|
27
|
+
// 16-bit hex groups before the pure-hex parse.
|
|
28
|
+
var dual = ip.match(/^(.*):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
29
|
+
if (dual) {
|
|
30
|
+
if (!isIPv4(dual[2])) return null;
|
|
31
|
+
var v4 = dual[2].split(".").map(Number);
|
|
32
|
+
ip = dual[1] + ":" + (((v4[0] << 8) | v4[1]).toString(16)) + ":" + (((v4[2] << 8) | v4[3]).toString(16));
|
|
33
|
+
}
|
|
34
|
+
var dbl = ip.split("::");
|
|
35
|
+
if (dbl.length > 2) return null;
|
|
36
|
+
var left = dbl[0] === "" ? [] : dbl[0].split(":");
|
|
37
|
+
var right = dbl.length === 2 ? (dbl[1] === "" ? [] : dbl[1].split(":")) : [];
|
|
38
|
+
if (dbl.length === 1 && left.length !== 8) return null;
|
|
39
|
+
var fill = 8 - left.length - right.length;
|
|
40
|
+
// A "::" run MUST compress at least one zero group (RFC 4291 sec. 2.2 / RFC 5952 sec.
|
|
41
|
+
// 4.2.2): a "::" with fill 0 is a full 8-group address that must not use compression.
|
|
42
|
+
if (dbl.length === 2 ? fill < 1 : fill !== 0) return null;
|
|
43
|
+
var groups = left, i;
|
|
44
|
+
for (i = 0; i < fill; i += 1) groups = groups.concat(["0"]);
|
|
45
|
+
groups = groups.concat(right);
|
|
46
|
+
if (groups.length !== 8) return null;
|
|
47
|
+
var hex = "";
|
|
48
|
+
for (i = 0; i < 8; i += 1) {
|
|
49
|
+
var g = groups[i];
|
|
50
|
+
if (g.length < 1 || g.length > 4 || !/^[0-9a-f]+$/i.test(g)) return null;
|
|
51
|
+
hex += g.toLowerCase().padStart(4, "0");
|
|
52
|
+
}
|
|
53
|
+
return hex;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Is `s` a syntactically valid IPv4 or IPv6 textual literal?
|
|
57
|
+
function isIpLiteral(s) { return isIPv4(s) || expandIpv6Hex(s) !== null; }
|
|
58
|
+
|
|
59
|
+
module.exports = { isIPv4: isIPv4, expandIpv6Hex: expandIpv6Hex, isIpLiteral: isIpLiteral, IPV4_RE: IPV4_RE };
|
package/lib/lint.js
CHANGED
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
* @card Lint a certificate against RFC 5280 + CABF TLS BR, in pure JS.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
var net = require("net");
|
|
27
26
|
var frameworkError = require("./framework-error");
|
|
28
27
|
var asn1 = require("./asn1-der");
|
|
29
28
|
var oid = require("./oid");
|
|
30
29
|
var x509 = require("./schema-x509");
|
|
31
30
|
var pkix = require("./schema-pkix");
|
|
32
31
|
var C = require("./constants");
|
|
32
|
+
var ipUtils = require("./ip-utils");
|
|
33
33
|
|
|
34
34
|
var MS_PER_DAY = C.TIME.days(1);
|
|
35
35
|
|
|
@@ -215,10 +215,11 @@ function _dnsNameProblem(s) {
|
|
|
215
215
|
return null;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
// A genuine IPv4 or IPv6 literal
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
218
|
+
// A genuine IPv4 or IPv6 literal -- a CN validated against an iPAddress SAN rather than a
|
|
219
|
+
// dNSName. Routed through the shared strict validator (no node:net, so the toolkit needs no
|
|
220
|
+
// networking module): a per-octet-bounded dotted quad, or a full RFC 4291 IPv6 form
|
|
221
|
+
// (including the IPv4-mapped / dual-stack tail). Rejects 999.999.999.999 and "api:443".
|
|
222
|
+
function _looksLikeIp(s) { return ipUtils.isIpLiteral(s); }
|
|
222
223
|
|
|
223
224
|
function _sanDnsNames(ctx) {
|
|
224
225
|
var d = ctx.decode("subjectAltName");
|
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:125643d3-4737-4dc7-ac9b-39283eea27e2",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-13T19:15:38.903Z",
|
|
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.2.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.2.13",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.2.
|
|
25
|
+
"version": "0.2.13",
|
|
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.2.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.2.13",
|
|
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.2.
|
|
57
|
+
"ref": "@blamejs/pki@0.2.13",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|