@blamejs/pki 0.8.54 → 0.8.55
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 +18 -0
- package/lib/ct.js +9 -11
- package/lib/guard-identifier.js +23 -0
- package/lib/path-validate.js +65 -12
- package/lib/sigstore.js +13 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ 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.8.55 — 2026-10-06
|
|
8
|
+
|
|
9
|
+
Tell a caller when the path search stopped looking, rather than reporting the chain it had.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `pki.path.build` puts `depthLimited: true` on a `{ valid:false }` return when `opts.maxDepth` stopped an expansion that still had candidates waiting. The chain reported alongside it is the best one the search ASSEMBLED, not the best one the pool holds, so a caller treating `valid:false` as a verdict about the certificates should read the field and raise `maxDepth` instead. It is absent from a successful return and from a failing one the search explored to its end, including a chain whose last hop lands exactly on the cap with nothing left to expand: the claim is that the bound truncated the search, not that the search reached it.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `pki.sigstore.verifyBundle` reports `sigstore/chain-incomplete` where it reported `sigstore/chain-invalid`, for a bundle whose Fulcio chain the path search stopped expanding at its depth bound. A rotated CA reissues under one subject name, so a bundle can carry a short branch that assembles and fails beside a long one that reaches an anchor past the bound; the short branch's failure was being published as the verdict on the bundle. The candidate budget already answered this way, and the two bounds now agree. Nothing about a chain that was explored to its end changed: a bundle whose assembled path genuinely fails validation is still `sigstore/chain-invalid`.
|
|
18
|
+
- A reached bound stays a bound rather than becoming a refusal: a bounded depth-first search visits equal-scoring candidates in the order they arrive, so a bundle large enough to exhaust the expansion ceiling can still depend on its own ordering. That is a property of bounding the search at all. Raise `maxDepth` and `maxCandidatesConsidered` for a pool whose shape needs more of the graph walked.
|
|
19
|
+
- `depthLimited` reports a candidate the cap kept the search from expanding, and nothing weaker. A candidate already on the chain is one the cycle check would skip at any depth, so it is not counted: counting it would report a truncated search where the search was exhausted, and turn a chain this toolkit refuses into one it claims it never finished reading. Two cases it does not report, both of which leave the older behavior in place rather than overstating: a chain whose last hop lands on the cap with nothing left to match, and a cap that also suppressed an Authority Information Access fetch for the certificate at that hop. The second needs the fetch's own URL eligibility answered to be certain a fetch was available, so it is left unclaimed rather than guessed at.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `pki.ct` and `pki.path.build` take their declared options through one shared snapshot instead of two copies of the same loop. Both read every declared name once, before anything derives from one, and both keep an omitted option OMITTED rather than recording it as a name holding `undefined`, which is what lets an absent `transport` still mean "open the connection yourself, under the pinned anchors". Behavior is unchanged at both verbs; what the shared door adds is that the next caller to copy an options bag by hand is caught before it ships.
|
|
24
|
+
|
|
7
25
|
## v0.8.54 — 2026-10-06
|
|
8
26
|
|
|
9
27
|
Refuse the universal tags X.680 assigns to no type, and tell a caller which those are.
|
package/lib/ct.js
CHANGED
|
@@ -1353,18 +1353,16 @@ function _clientDoor(opts, keyName) {
|
|
|
1353
1353
|
}
|
|
1354
1354
|
|
|
1355
1355
|
/** @internal Every DECLARED option the bag can answer, read once into a record of plain values, before
|
|
1356
|
-
* anything derives from one.
|
|
1357
|
-
*
|
|
1358
|
-
* which
|
|
1359
|
-
* prototype chain exactly as the direct reads it replaces did,
|
|
1360
|
-
*
|
|
1361
|
-
*
|
|
1356
|
+
* anything derives from one. The presence-preserving snapshot is the one this module needs: absence
|
|
1357
|
+
* survives, so a name the bag cannot answer is left out rather than materialized as `undefined`,
|
|
1358
|
+
* which is what lets an absent `transport` still mean "open the connection yourself, under the
|
|
1359
|
+
* pinned anchors". It consults the prototype chain exactly as the direct reads it replaces did,
|
|
1360
|
+
* because the door accepts an option supplied through a caller's defaults object and an enumeration
|
|
1361
|
+
* omits an inherited FUNCTION member, which silently dropped a `transport` and fell through to
|
|
1362
|
+
* opening a real connection. */
|
|
1363
|
+
// @guard-via guard.identifier.snapshotPresentOptions
|
|
1362
1364
|
function _takeTopLevel(bag, known) {
|
|
1363
|
-
|
|
1364
|
-
intrinsic.forEach(intrinsic.keys(known), function (k) {
|
|
1365
|
-
if (k in bag) o[k] = bag[k];
|
|
1366
|
-
});
|
|
1367
|
-
return o;
|
|
1365
|
+
return guard.identifier.snapshotPresentOptions(bag, known);
|
|
1368
1366
|
}
|
|
1369
1367
|
|
|
1370
1368
|
/** @internal The two values a verdict rests on, made IMMUNE rather than merely taken. Holding a
|
package/lib/guard-identifier.js
CHANGED
|
@@ -101,6 +101,28 @@ function snapshotOptions(obj, known) {
|
|
|
101
101
|
return out;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/** @internal The same one-read capture, for a caller that tells an ABSENT option from one present
|
|
105
|
+
* and holding `undefined`. `snapshotOptions` materializes every declared name, which is correct for
|
|
106
|
+
* a caller testing `!== undefined` and wrong for one testing presence: an omitted option becomes an
|
|
107
|
+
* own key, and a presence-based guard then sees an option the caller never passed. Measured, a bag
|
|
108
|
+
* omitting `transport` is ACCEPTED by `assertCallableOption` and REFUSED after `snapshotOptions`,
|
|
109
|
+
* because the materialized `undefined` is a transport that is not callable.
|
|
110
|
+
*
|
|
111
|
+
* Presence consults the prototype chain, because a caller supplying options through a defaults
|
|
112
|
+
* object is supplying them, and an enumeration omits an inherited FUNCTION member: that is how a
|
|
113
|
+
* copy once dropped a `transport` and fell through to opening a real connection where the caller
|
|
114
|
+
* had passed a fake one. The VALUE is still read once per name, which is what the snapshot is for.
|
|
115
|
+
* The two live side by side rather than behind a flag so a caller chooses between them knowingly. */
|
|
116
|
+
// @enforced-by guard-shape-reinlined
|
|
117
|
+
// @guard-shape if \(\w+ in \w+\) \w+\[\w+\] = \w+\[\w+\];
|
|
118
|
+
function snapshotPresentOptions(obj, known) {
|
|
119
|
+
var out = _create(null);
|
|
120
|
+
if (obj === null || obj === undefined) return out;
|
|
121
|
+
var o = _Object(obj);
|
|
122
|
+
_forEach(_getOwnPropertyNames(known), function (k) { if (k in o) out[k] = o[k]; });
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
104
126
|
/** @internal The option names a bag carries, read the way assertKnownKeys read them: every own
|
|
105
127
|
* string key, whatever its descriptor says. Object.keys reports only the enumerable ones, so an
|
|
106
128
|
* option written with `enumerable: false` clears the door and then vanishes from the read.
|
|
@@ -656,6 +678,7 @@ module.exports = intrinsic.freeze({
|
|
|
656
678
|
optionsObject: optionsObject,
|
|
657
679
|
refuseAccessorFields: refuseAccessorFields,
|
|
658
680
|
snapshotOptions: snapshotOptions,
|
|
681
|
+
snapshotPresentOptions: snapshotPresentOptions,
|
|
659
682
|
optionNames: optionNames,
|
|
660
683
|
ownOptions: ownOptions,
|
|
661
684
|
pickSettings: pickSettings,
|
package/lib/path-validate.js
CHANGED
|
@@ -3301,7 +3301,7 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
3301
3301
|
|
|
3302
3302
|
/**
|
|
3303
3303
|
* @primitive pki.path.build
|
|
3304
|
-
* @signature pki.path.build(leaf, opts) -> Promise<{ valid, path, trustAnchor, result, candidatesConsidered, aiaFetches }>
|
|
3304
|
+
* @signature pki.path.build(leaf, opts) -> Promise<{ valid, path, trustAnchor, result, candidatesConsidered, aiaFetches, depthLimited? }>
|
|
3305
3305
|
* @since 0.3.7
|
|
3306
3306
|
* @status stable
|
|
3307
3307
|
* @spec RFC 4158, RFC 5280
|
|
@@ -3347,7 +3347,12 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
3347
3347
|
* `validate` consumes (anchor-proximal first, leaf last, the anchor excluded). Fail-closed: bad
|
|
3348
3348
|
* options throw `path/bad-input`; no chain to any anchor throws `path/no-path`; chains that
|
|
3349
3349
|
* assemble but none validate return `{ valid:false }` with the best failing `validate` result;
|
|
3350
|
-
* the search bound throws `path/build-limit`.
|
|
3350
|
+
* the search bound throws `path/build-limit`. A `{ valid:false }` return carries
|
|
3351
|
+
* `depthLimited: true` when the depth cap stopped an expansion that still had candidates waiting,
|
|
3352
|
+
* which means the chain reported is the best one ASSEMBLED rather than the best one that exists: a
|
|
3353
|
+
* caller that treats `valid:false` as a verdict about the certificates should read that field and
|
|
3354
|
+
* raise `opts.maxDepth` instead. It is absent on a successful return and on a failing one the
|
|
3355
|
+
* search explored to its end. By default `build` is OFFLINE (zero network), so supply
|
|
3351
3356
|
* intermediates in `opts.candidates`. Set `opts.fetchAia: true` to opt in to fetching a MISSING intermediate
|
|
3352
3357
|
* from a certificate's Authority Information Access `caIssuers` URL (RFC 5280 sec. 4.2.2.1) over
|
|
3353
3358
|
* `pki.transport`: the fetch triggers only on a pool miss, every fetched certificate is UNTRUSTED pool material
|
|
@@ -3511,14 +3516,24 @@ async function build(leaf, opts) {
|
|
|
3511
3516
|
|
|
3512
3517
|
var BUILD_ONLY_OPT = { candidates: 1, intermediates: 1, trustAnchors: 1, maxDepth: 1, maxCandidatesConsidered: 1, validate: 1,
|
|
3513
3518
|
fetchAia: 1, transport: 1, tls: 1, maxAiaFetches: 1, maxAiaPerCert: 1, aiaTimeout: 1, maxResponseBytes: 1 };
|
|
3514
|
-
|
|
3519
|
+
/** @internal The names this door forwards by VALUE: every validate option that is not consumed by
|
|
3520
|
+
* the builder and not one of the switches typed above. Absence has to survive the copy, so the
|
|
3521
|
+
* presence-preserving snapshot is the one to take: an option the caller omitted stays omitted
|
|
3522
|
+
* rather than arriving at `validate` as a name holding `undefined`.
|
|
3523
|
+
* @guard-via guard.identifier.snapshotPresentOptions */
|
|
3524
|
+
var FORWARD_BY_VALUE = intrinsic.create(null);
|
|
3515
3525
|
intrinsic.forEach(intrinsic.keys(_VALIDATE_OPTS), function (k) {
|
|
3516
|
-
if (intrinsic.hasOwn(BUILD_ONLY_OPT, k)) return;
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3526
|
+
if (intrinsic.hasOwn(BUILD_ONLY_OPT, k) || intrinsic.hasOwn(forwardedSwitches, k)) return;
|
|
3527
|
+
FORWARD_BY_VALUE[k] = 1;
|
|
3528
|
+
});
|
|
3529
|
+
var forwarded = guard.identifier.snapshotPresentOptions(opts, FORWARD_BY_VALUE);
|
|
3530
|
+
/** @internal A switch this door typed is forwarded as the value it typed, overlaid after the copy.
|
|
3531
|
+
* Reading it off the options object again would ask a second time, and normalizing an anchor
|
|
3532
|
+
* between the two runs caller accessors, so the value `validate` is handed need not be the one that
|
|
3533
|
+
* passed here. All four are validate options and none is build-only, so the overlay adds no name
|
|
3534
|
+
* the loop above would have skipped. */
|
|
3535
|
+
intrinsic.forEach(intrinsic.keys(forwardedSwitches), function (k) {
|
|
3536
|
+
forwarded[k] = forwardedSwitches[k];
|
|
3522
3537
|
});
|
|
3523
3538
|
if (buildPermRaw != null) forwarded.initialPermittedSubtrees = buildPermSeeds;
|
|
3524
3539
|
if (buildExclRaw != null) forwarded.initialExcludedSubtrees = buildExclSeeds;
|
|
@@ -3533,6 +3548,10 @@ async function build(leaf, opts) {
|
|
|
3533
3548
|
var considered = 0;
|
|
3534
3549
|
var anyChainAssembled = false;
|
|
3535
3550
|
var bestFail = null;
|
|
3551
|
+
/** @internal Whether the depth bound stopped an expansion that had candidates waiting. A reached
|
|
3552
|
+
* bound is "stopped looking", not "the chain is bad", and the two left this verb returning the
|
|
3553
|
+
* same `valid:false`. */
|
|
3554
|
+
var depthStopped = false;
|
|
3536
3555
|
var success = null;
|
|
3537
3556
|
_assertUserInitialPolicySet(opts.userInitialPolicySet, "build");
|
|
3538
3557
|
var uipsDistinct = null, selectByPolicy = false;
|
|
@@ -3570,7 +3589,6 @@ async function build(leaf, opts) {
|
|
|
3570
3589
|
guard.list.append(accepted, racc);
|
|
3571
3590
|
} else if (!bestFail) bestFail = { path: rpath, trustAnchor: rframe.anchor, result: rres };
|
|
3572
3591
|
}
|
|
3573
|
-
if (rframe.hop >= maxDepth) continue;
|
|
3574
3592
|
var rscored = [];
|
|
3575
3593
|
for (var rpi = 0; rpi < pool.length; rpi++) {
|
|
3576
3594
|
if (nameMatchSoft(pool[rpi].subject.rdns, rframe.anchor.name.rdns) &&
|
|
@@ -3579,6 +3597,13 @@ async function build(leaf, opts) {
|
|
|
3579
3597
|
guard.list.append(rscored, { cand: pool[rpi], score: scoreCandidateReverse(pool[rpi], frontierCert, leafCert, opts.time) });
|
|
3580
3598
|
}
|
|
3581
3599
|
}
|
|
3600
|
+
/** @internal The anchor-first search answers the same question the forward one does, for the
|
|
3601
|
+
* same reason: candidates scored before the bound is consulted, so reaching it can say whether
|
|
3602
|
+
* it stopped anything. */
|
|
3603
|
+
if (rframe.hop >= maxDepth) {
|
|
3604
|
+
if (_anyExpandable(rscored, rframe)) depthStopped = true;
|
|
3605
|
+
continue;
|
|
3606
|
+
}
|
|
3582
3607
|
considered += _pushCandidates(rframe, rscored, rstack, counter, true);
|
|
3583
3608
|
}
|
|
3584
3609
|
} else {
|
|
@@ -3635,7 +3660,7 @@ async function build(leaf, opts) {
|
|
|
3635
3660
|
guard.list.append(accepted, acc);
|
|
3636
3661
|
} else if (!bestFail) bestFail = { path: frame.chain.slice(), trustAnchor: anchors[ai], result: res };
|
|
3637
3662
|
}
|
|
3638
|
-
if (success
|
|
3663
|
+
if (success) continue;
|
|
3639
3664
|
|
|
3640
3665
|
var childAki = childAkiKeyId(current);
|
|
3641
3666
|
var scored = [];
|
|
@@ -3645,6 +3670,16 @@ async function build(leaf, opts) {
|
|
|
3645
3670
|
ineligible: _forwardPathLenIneligible(pool[pi], frame.chain) });
|
|
3646
3671
|
}
|
|
3647
3672
|
}
|
|
3673
|
+
/** @internal The candidates are scored BEFORE the depth bound is consulted, so reaching the
|
|
3674
|
+
* bound can say whether it stopped anything. A chain whose last hop lands on the bound with
|
|
3675
|
+
* nothing left to expand was explored to its end, and a chain with candidates waiting was
|
|
3676
|
+
* TRUNCATED: the two produce the same `valid:false` otherwise, and a caller reading that cannot
|
|
3677
|
+
* tell a refused chain from one the search never assembled. Nothing is pushed either way, so the
|
|
3678
|
+
* reorder costs one pool pass on a frame at the bound and changes no outcome. */
|
|
3679
|
+
if (frame.hop >= maxDepth) {
|
|
3680
|
+
if (_anyExpandable(scored, frame)) depthStopped = true;
|
|
3681
|
+
continue;
|
|
3682
|
+
}
|
|
3648
3683
|
if (aiaCtx && frame.hop < maxDepth && aiaCtx.fetches < aiaCtx.maxFetches) {
|
|
3649
3684
|
guard.list.append(deferredAia, { chain: frame.chain, hop: frame.hop, keys: frame.keys, poolMark: pool.length, seq: deferSeq, fetched: false });
|
|
3650
3685
|
deferSeq += 1;
|
|
@@ -3666,11 +3701,29 @@ async function build(leaf, opts) {
|
|
|
3666
3701
|
return guard.verdict.of({ path: success.path, trustAnchor: success.trustAnchor, candidatesConsidered: considered, aiaFetches: aiaFetches });
|
|
3667
3702
|
}
|
|
3668
3703
|
if (anyChainAssembled) {
|
|
3669
|
-
|
|
3704
|
+
/** @internal `depthLimited` is carried only on the FAILING return, and only when the bound
|
|
3705
|
+
* stopped an expansion. On a success the bound hid nothing that mattered, and a caller reading
|
|
3706
|
+
* the field on every verdict would have to ask what it meant for a valid path. */
|
|
3707
|
+
var failed = { valid: false, path: bestFail.path, trustAnchor: bestFail.trustAnchor, result: bestFail.result, candidatesConsidered: considered, aiaFetches: aiaFetches };
|
|
3708
|
+
if (depthStopped) failed.depthLimited = true;
|
|
3709
|
+
return guard.verdict.of(failed);
|
|
3670
3710
|
}
|
|
3671
3711
|
throw E("path/no-path", "build: no certification path from the leaf to any configured trust anchor could be assembled");
|
|
3672
3712
|
}
|
|
3673
3713
|
|
|
3714
|
+
/** @internal Whether raising the depth cap could reach anything through these candidates. A
|
|
3715
|
+
* candidate already on the chain is skipped by `_pushCandidates` on the identity key, so a deeper
|
|
3716
|
+
* search would skip it too: counting it reports a truncated search where the search is exhausted,
|
|
3717
|
+
* which turns a chain this toolkit refuses into one it says it never finished reading. The identity
|
|
3718
|
+
* key and the frame's visited set are the same two the push uses, so the two cannot disagree about
|
|
3719
|
+
* which candidate is reachable. */
|
|
3720
|
+
function _anyExpandable(scored, frame) {
|
|
3721
|
+
for (var i = 0; i < scored.length; i++) {
|
|
3722
|
+
if (!intrinsic.setHas(frame.keys, identityKey(scored[i].cand))) return true;
|
|
3723
|
+
}
|
|
3724
|
+
return false;
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3674
3727
|
/** @internal The fetching checker composes the two gates below, so it loads after this module
|
|
3675
3728
|
* rather than beside it. The require sits in the call so the cycle resolves at first use, which is
|
|
3676
3729
|
* the documented reason an inline require is permitted. */
|
package/lib/sigstore.js
CHANGED
|
@@ -1417,8 +1417,11 @@ async function _verifyChain(leaf, chainDers, roots, timeMs) {
|
|
|
1417
1417
|
* reported `chain-invalid`. `build` ranks candidates by the RFC 4158 sec. 3.5 heuristics, the key
|
|
1418
1418
|
* identifier among them, EXCLUDING none, and bounds the search by total candidate expansions rather
|
|
1419
1419
|
* than by how many paths it has collected, so a later candidate stays reachable and an exhausted
|
|
1420
|
-
* budget is reported as `path/build-limit` rather than as a verdict about the chain
|
|
1421
|
-
*
|
|
1420
|
+
* budget is reported as `path/build-limit` rather than as a verdict about the chain, and a depth bound
|
|
1421
|
+
* that stopped an expansion as `depthLimited` on the failing return. Both reach the incomplete code
|
|
1422
|
+
* below, because a bound the search reached says nothing about the certificates it never read. Every
|
|
1423
|
+
* assembled path still flows through `pki.path.validate`, so this delegates the search and duplicates
|
|
1424
|
+
* no check.
|
|
1422
1425
|
* The anchors go into the pool as well as the trust store, because a cross-signed root appears in a
|
|
1423
1426
|
* chain as an intermediate under another anchor. */
|
|
1424
1427
|
var anchorTuples = _map(inWindow, function (r) {
|
|
@@ -1462,6 +1465,14 @@ async function _verifyChain(leaf, chainDers, roots, timeMs) {
|
|
|
1462
1465
|
throw _err("sigstore/chain-invalid", "the Fulcio certificate chain failed validation: " + e.message, e);
|
|
1463
1466
|
}
|
|
1464
1467
|
if (!built.valid) {
|
|
1468
|
+
/** @internal The third outcome that means "stopped looking". `build` reports `depthLimited` when
|
|
1469
|
+
* its depth bound stopped an expansion that had candidates waiting, so the chain it hands back
|
|
1470
|
+
* is the best one it ASSEMBLED rather than the best one that exists. Reporting that as invalid
|
|
1471
|
+
* denies a bundle whose path the search never reached, which is the same denial the candidate
|
|
1472
|
+
* bound above avoids, so it answers with the same incomplete code. */
|
|
1473
|
+
if (built.depthLimited) {
|
|
1474
|
+
throw _err("sigstore/chain-incomplete", "no chain to a caller-supplied Fulcio anchor was established before the path search reached its depth bound");
|
|
1475
|
+
}
|
|
1465
1476
|
throw _err("sigstore/chain-invalid", "the Fulcio certificate chain is not valid as of the log time");
|
|
1466
1477
|
}
|
|
1467
1478
|
/** @internal `path` is anchor-proximal first and leaf last with the anchor excluded, so the certificate
|
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:f7650379-6444-462e-bd4b-7cfbdc879bc7",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-10-
|
|
8
|
+
"timestamp": "2026-10-06T04:32:24.064Z",
|
|
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.8.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.8.55",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.55",
|
|
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.8.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.8.55",
|
|
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.8.
|
|
57
|
+
"ref": "@blamejs/pki@0.8.55",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|