@blamejs/pki 0.5.8 → 0.5.9

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 CHANGED
@@ -4,7 +4,20 @@ 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.5.8 — 2026-08-18
7
+ ## v0.5.9 — 2026-08-18
8
+
9
+ A certificate can now carry an internationalized email address, which this toolkit could read and never write.
10
+
11
+ ### Added
12
+
13
+ - pki.x509.sign accepts an otherName entry in a subjectAltName, given as { typeId, value } where typeId is an OID string and value is a Buffer holding one DER element. It encodes RFC 5280 section 4.2.1.6's otherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }, tagged [0] IMPLICIT. The value wrapper is EXPLICIT because ANY carries no tag of its own, which is what makes the encoding unambiguous and is the shape the decoder already required. This is what an SmtpUTF8Mailbox address needs, and it is equally the carrier for any other otherName a profile defines.
14
+ - The value is validated before it is wrapped and signed, because a signer that emits a malformed encoding under a real signature has produced something strict relying parties reject. It must be exactly one element with no trailing bytes, and its contents must satisfy the rules for its type: a BOOLEAN whose octet is not 0x00 or 0xFF is refused, as is a SET whose members sit in no canonical order, and so on recursively through a constructed value. The accepted universal types are BOOLEAN, INTEGER, ENUMERATED, BIT STRING, OCTET STRING, NULL, OBJECT IDENTIFIER, UTCTime, GeneralizedTime, NumericString, and the DirectoryString family (UTF8String, PrintableString, IA5String, TeletexString, VisibleString, BMPString, UniversalString), plus SEQUENCE and SET. A universal type outside that set, such as REAL or RELATIVE-OID, has no content validator here and is refused rather than accepted on its framing alone. A context- or application-tagged value passes on its framing, since no content rule is knowable for it, and its children are still walked. One known limitation: a GeneralizedTime carrying fractional seconds, such as 20260101000000.5Z, is refused here even though X.690 section 11.7 permits it. That relaxation is scoped to the codec and to RFC 3161 timestamping on purpose, and this validator does not widen it. A constructed wrapper does not evade the rule, since the walk recurses into its children; a profile needing a fractional time must carry it under an implicit primitive context tag, which passes on framing because no content rule is knowable for it.
15
+
16
+ ### Fixed
17
+
18
+ - pki.smime.verify's sender binding is now exercised against certificates carrying an otherName. Two behaviours that previously had no conformance vector are pinned: a certificate whose subjectAltName carries an SmtpUTF8Mailbox does not let a legacy subject distinguished-name emailAddress speak for it, and an otherName unrelated to email, such as a Microsoft user principal name, neither erases a matching rfc822Name nor turns a definite non-match into an undecidable one.
19
+
20
+ ## v0.5.8 — 2026-08-17
8
21
 
9
22
  Four verdicts that answered a question nobody had asked now say what they checked, and an email domain comparison no longer folds two registrable domains into one identity.
10
23
 
package/lib/guard-all.js CHANGED
@@ -59,6 +59,7 @@ var range = require("./guard-range");
59
59
  var time = require("./guard-time");
60
60
  var name = require("./guard-name");
61
61
  var encoding = require("./guard-encoding");
62
+ var der = require("./guard-der");
62
63
  var json = require("./guard-json");
63
64
  var identifier = require("./guard-identifier");
64
65
  var header = require("./guard-header");
@@ -76,6 +77,7 @@ module.exports = {
76
77
  time: time,
77
78
  name: name,
78
79
  encoding: encoding,
80
+ der: der,
79
81
  json: json,
80
82
  identifier: identifier,
81
83
  header: header,
@@ -0,0 +1,152 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ //
5
+ // @internal -- no operator-facing namespace. The documented surface is the consumers
6
+ // whose encoding integrity composes this guard (pki.schema.c509 reconstruct sites,
7
+ // pki.x509.sign and its sibling builders on any subjectAltName otherName value).
8
+ //
9
+ // guard-der: the single choke point for "these raw bytes are exactly one strictly-valid
10
+ // DER element", for every site that splices caller-supplied ANY bytes verbatim into
11
+ // something it then signs or reconstructs.
12
+ //
13
+ // The bug class is framing mistaken for validation. asn1.decode checks TLV framing and
14
+ // rejects trailing data, and stopping there feels sufficient. It is not: framing accepts
15
+ // a BOOLEAN whose content octet is 0x01 where DER requires 0xFF, a NumericString holding
16
+ // "@", and a SET whose members sit in no canonical order. None of those is DER. Spliced
17
+ // into a certificate and signed, the issuer emits a structure that strict relying parties
18
+ // reject, under a real signature, while the producing verb reported success (CWE-20).
19
+ //
20
+ // The rule is validate-or-refuse. A universal primitive runs through its strict content
21
+ // reader, and a universal type with no reader here is REFUSED, so the table is exhaustive
22
+ // by refusal and adding a type is a deliberate act. A non-universal element (a
23
+ // legitimately context- or application-tagged ANY) passes on its framing because no
24
+ // content rule is knowable for it, and its constructed children are still walked.
25
+ //
26
+ // Every entry point takes the CALLER's error factory and code, so a c509 splice reports in
27
+ // the c509 domain and a certificate builder in the x509 domain.
28
+ // asn1-der composes this guard family, so requiring the codec at module scope would read its
29
+ // exports mid-initialisation and see an empty object. The require and the table it builds are
30
+ // therefore both deferred to first use -- the documented circular-load exception to top-of-file
31
+ // requires. Guards sit BELOW the codec in the dependency order; this is the one place that
32
+ // order is inverted, and it is inverted lazily.
33
+ var asn1 = null;
34
+ function _asn1() { if (asn1 === null) asn1 = require("./asn1-der"); return asn1; }
35
+
36
+ // The strict content reader per universal primitive tag. A tag absent from this table has no
37
+ // validator here and is refused by `element` below -- that refusal is the point.
38
+ var VALUE_READERS = null;
39
+ function _readers() {
40
+ if (VALUE_READERS !== null) return VALUE_READERS;
41
+ var asn1 = _asn1();
42
+ var m = {}, R = asn1.read, T = asn1.TAGS;
43
+ // ENUMERATED shares INTEGER's content rules and is NOT constrained to non-negative
44
+ // values. X.680 (02/2021) sec. 20.2 requires each NamedNumber's SignedNumber to be
45
+ // distinct, and sec. 19 defines SignedNumber ::= number | "-" number; the non-negativity
46
+ // in sec. 20.3 governs only an EnumerationItem written as a bare identifier, which is
47
+ // auto-assigned. `ENUMERATED { lowPriority(-1), normal(0) }` is well-formed, so rejecting
48
+ // a negative here would refuse valid input. This guard validates the ENCODING; which
49
+ // values a particular ENUMERATED admits lives in a type definition an opaque ANY does
50
+ // not carry.
51
+ m[T.BOOLEAN] = R.boolean; m[T.INTEGER] = R.integer; m[T.ENUMERATED] = R.enumerated;
52
+ m[T.BIT_STRING] = R.bitString; m[T.OCTET_STRING] = R.octetString; m[T.NULL] = R.nullValue;
53
+ m[T.OBJECT_IDENTIFIER] = R.oid; m[T.UTC_TIME] = R.time; m[T.GENERALIZED_TIME] = R.time;
54
+ // NumericString reads through its own reader: it is not a DirectoryString type, and routing
55
+ // it through read.string would fold it into the RFC 5280 sec. 7.1 name-comparison identity
56
+ // class (see asn1-der.js).
57
+ m[T.NUMERIC_STRING] = R.numericString;
58
+ [T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING,
59
+ T.BMP_STRING, T.UNIVERSAL_STRING].forEach(function (t) { m[t] = R.string; });
60
+ VALUE_READERS = m;
61
+ return VALUE_READERS;
62
+ }
63
+
64
+ // A universal SET's required member order depends on a type the ANY does not carry: X.690
65
+ // sec. 11.6 orders a SET OF by the members' full encodings, while a structured SET is ordered
66
+ // by TAG (X.680 sec. 8.6), and the two differ whenever the constructed bit does (a SEQUENCE
67
+ // member, tag 16, sorts before a PrintableString, tag 19, by tag but after it by octets). A
68
+ // structured SET cannot repeat a tag, so a repeated tag proves SET OF and the octet rule binds;
69
+ // with all-distinct tags either reading is possible, so accept a value that satisfies EITHER
70
+ // (rejecting only what is non-canonical under both readings: sound in both directions, never a
71
+ // guess).
72
+ //
73
+ // KNOWN LIMITATION, and the two consumers do not weigh it identically. The either-reading rule
74
+ // admits a SET OF that is tag-ordered but not octet-ordered, which is not canonical DER. On a
75
+ // RECONSTRUCT path the input already exists and the alternative would refuse a valid structured
76
+ // SET, so accepting is the sound direction. On a SIGNING path the caller composes the bytes and
77
+ // could be held to octet order, so the same permissiveness lets a signer emit a non-canonical
78
+ // SET under a real signature. Tightening it for signers alone would mean a mode flag on a
79
+ // security posture, which this family avoids, and tightening it for both would reject valid
80
+ // structured SETs that c509 round-trips today. The rule is therefore unchanged and the cost is
81
+ // written down rather than discovered later.
82
+ var TAG_CLASS_RANK = { universal: 0, application: 1, context: 2, private: 3 };
83
+ function setOrderOk(kids) {
84
+ var i, dup = false, seen = {};
85
+ for (i = 0; i < kids.length; i++) {
86
+ var key = kids[i].tagClass + ":" + kids[i].tagNumber;
87
+ if (seen[key]) { dup = true; break; }
88
+ seen[key] = true;
89
+ }
90
+ var octetAsc = true, tagAsc = true;
91
+ for (i = 1; i < kids.length; i++) {
92
+ if (Buffer.compare(kids[i - 1].bytes, kids[i].bytes) > 0) octetAsc = false;
93
+ // Tag order ranks by CLASS first (universal < application < context < private, X.680
94
+ // sec. 8.6), then by tag number. Compare the class's NUMBER: the names do not sort in
95
+ // class order.
96
+ var pc = TAG_CLASS_RANK[kids[i - 1].tagClass], cc = TAG_CLASS_RANK[kids[i].tagClass];
97
+ if (pc !== cc ? pc > cc : kids[i - 1].tagNumber > kids[i].tagNumber) tagAsc = false;
98
+ }
99
+ return dup ? octetAsc : (octetAsc || tagAsc);
100
+ }
101
+
102
+ // element(node, E, code, label). Strict-validate an already-decoded DER element at ANY depth.
103
+ // Rejects the reserved EOC tag 0; runs a universal primitive through its strict content reader
104
+ // (or refuses a type with none); recurses into a constructed element's children; holds a
105
+ // universal SET to a canonical order. Only SEQUENCE and SET are accepted as universal
106
+ // CONSTRUCTED types -- an EXTERNAL / EMBEDDED PDV / CHARACTER STRING has mandatory components
107
+ // this gate cannot verify (the degenerate empty form is not a valid encoding of any of them),
108
+ // so it is refused for the same reason an unvalidatable primitive is.
109
+ //
110
+ // @enforced-by guard-shape-reinlined
111
+ // @guard-shape _ANY_VALUE_READERS\[
112
+ function element(node, E, code, label) {
113
+ if (node.tagClass === "universal" && node.tagNumber === 0) {
114
+ throw E(code, label + " must not use the reserved end-of-contents encoding (tag 0)");
115
+ }
116
+ var T = _asn1().TAGS;
117
+ if (node.constructed) {
118
+ if (node.tagClass === "universal" && node.tagNumber !== T.SEQUENCE && node.tagNumber !== T.SET) {
119
+ throw E(code, label + " of universal constructed type " + node.tagNumber + " has no strict DER structure validator here");
120
+ }
121
+ var kids = node.children; // asn1.decode always sets a (possibly empty) children array
122
+ for (var i = 0; i < kids.length; i++) element(kids[i], E, code, label);
123
+ if (node.tagClass === "universal" && node.tagNumber === T.SET && !setOrderOk(kids)) {
124
+ throw E(code, label + " has a SET whose members are in no canonical DER order (X.690 sec. 11.6 / X.680 sec. 8.6)");
125
+ }
126
+ return;
127
+ }
128
+ if (node.tagClass === "universal") {
129
+ var reader = _readers()[node.tagNumber];
130
+ if (!reader) throw E(code, label + " of universal type " + node.tagNumber + " has no strict DER content validator here");
131
+ try { reader(node); }
132
+ catch (e) { throw E(code, label + " is not a valid DER element for its type", e); }
133
+ }
134
+ }
135
+
136
+ // tlv(content, E, code, label) -> content. Raw ANY bytes about to be spliced verbatim must be
137
+ // exactly one non-empty, well-formed AND strictly-valid DER element: framing + no-trailing-data
138
+ // via asn1.decode, then content / structure / SET order via `element`. Returns the bytes so a
139
+ // call site can wrap a splice inline.
140
+ //
141
+ // @enforced-by guard-shape-reinlined
142
+ // @guard-shape must be exactly one well-formed DER element
143
+ function tlv(content, E, code, label) {
144
+ if (!content || content.length === 0) throw E(code, label + " must be a non-empty DER element");
145
+ var node;
146
+ try { node = _asn1().decode(content); }
147
+ catch (e) { throw E(code, label + " must be exactly one well-formed DER element (no trailing data)", e); }
148
+ element(node, E, code, label);
149
+ return content;
150
+ }
151
+
152
+ module.exports = { element: element, tlv: tlv };
package/lib/pki-build.js CHANGED
@@ -129,7 +129,33 @@ function makeBuilder(ctx) {
129
129
  if (!Buffer.isBuffer(ipBuf) || (ipBuf.length !== 4 && ipBuf.length !== 16)) throw E("bad-input", "iPAddress must be a 4- or 16-octet Buffer or an IPv4/IPv6 string");
130
130
  return b.contextPrimitive(7, ipBuf);
131
131
  case "directoryName": return b.explicit(4, encodeName(v)); // Name is a CHOICE -> the context tag is EXPLICIT
132
- default: throw E("bad-input", "unsupported GeneralName form " + JSON.stringify(k) + " (supported: rfc822Name, dNSName, uniformResourceIdentifier, iPAddress, directoryName)");
132
+ case "otherName":
133
+ // otherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }, tagged
134
+ // [0] IMPLICIT (RFC 5280 sec. 4.2.1.6). The value is EXPLICIT because ANY carries no tag
135
+ // of its own, so the wrapper is what makes the encoding unambiguous -- the same shape the
136
+ // decoder requires, and without it an SmtpUTF8Mailbox (RFC 8398 sec. 3) could be read but
137
+ // never written.
138
+ if (typeof v !== "object" || Buffer.isBuffer(v)) throw E("bad-input", "otherName must be an object { typeId, value }");
139
+ if (typeof v.typeId !== "string" || !v.typeId) throw E("bad-input", "otherName requires a `typeId` OID string");
140
+ if (!Buffer.isBuffer(v.value) || v.value.length === 0) {
141
+ throw E("bad-input", "otherName requires a `value` Buffer holding one DER element");
142
+ }
143
+ // The value is spliced in raw and this verb SIGNS the result, so it is strictly
144
+ // validated rather than taken on the caller's word. Framing alone is not DER: a
145
+ // BOOLEAN whose content octet is 0x01, a NumericString holding "@", or a SET in no
146
+ // canonical order all frame cleanly and would ship inside a [0] EXPLICIT wrapper
147
+ // under a real signature. guard.der.tlv is the one place that rule lives.
148
+ guard.der.tlv(v.value, E, "bad-input", "otherName `value`");
149
+ // [0] IMPLICIT on a SEQUENCE replaces the SEQUENCE tag, so the content is the two
150
+ // members concatenated -- build.contextConstructed takes content bytes, not elements.
151
+ // The type-id encode is wrapped so a malformed OID reports in the CALLER's namespace
152
+ // (x509/bad-input, csr/bad-input, ...) like every other raw-OID path here, rather than
153
+ // surfacing the codec's own oid/* code from a shared builder the caller never named.
154
+ var typeIdDer;
155
+ try { typeIdDer = b.oid(v.typeId); }
156
+ catch (e) { throw E("bad-input", "invalid otherName type-id OID " + JSON.stringify(v.typeId) + " (violates the X.660 arc bounds)", e); }
157
+ return b.contextConstructed(0, Buffer.concat([typeIdDer, b.explicit(0, v.value)]));
158
+ default: throw E("bad-input", "unsupported GeneralName form " + JSON.stringify(k) + " (supported: rfc822Name, dNSName, uniformResourceIdentifier, iPAddress, directoryName, otherName)");
133
159
  }
134
160
  }
135
161
 
@@ -1594,17 +1594,9 @@ function _extValueFromDer(name, der) {
1594
1594
  // and RFC 3161 timestamping, and must not creep into a third consumer). On the ENCODE path such a value simply
1595
1595
  // degrades to the byte-exact ~oid + byte-string form, losing nothing; on the DECODE path it is a fail-closed
1596
1596
  // verdict.
1597
- var _ANY_VALUE_READERS = (function () {
1598
- var m = {}, R = asn1.read, T = asn1.TAGS;
1599
- m[T.BOOLEAN] = R.boolean; m[T.INTEGER] = R.integer; m[T.ENUMERATED] = R.enumerated;
1600
- m[T.BIT_STRING] = R.bitString; m[T.OCTET_STRING] = R.octetString; m[T.NULL] = R.nullValue;
1601
- m[T.OBJECT_IDENTIFIER] = R.oid; m[T.UTC_TIME] = R.time; m[T.GENERALIZED_TIME] = R.time;
1602
- // NumericString reads through its own reader: it is not a DirectoryString type, and routing it through
1603
- // read.string would fold it into the RFC 5280 sec. 7.1 name-comparison identity class (see asn1-der.js).
1604
- m[T.NUMERIC_STRING] = R.numericString;
1605
- [T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING, T.BMP_STRING, T.UNIVERSAL_STRING].forEach(function (t) { m[t] = R.string; });
1606
- return m;
1607
- })();
1597
+ // The per-type strict content readers, the SET canonical-order rule and the recursive element
1598
+ // walk all moved to guard.der, so this module and pki-build cannot drift on what "one strictly
1599
+ // valid DER element" means.
1608
1600
 
1609
1601
  // A universal SET's required member order depends on a type the ANY does not carry: X.690 sec. 11.6 orders a
1610
1602
  // SET OF by the members' full encodings, while a structured SET is ordered by TAG (X.680 sec. 8.6), and the two
@@ -1612,24 +1604,6 @@ var _ANY_VALUE_READERS = (function () {
1612
1604
  // tag but after it by octets). A structured SET cannot repeat a tag, so a repeated tag proves SET OF and the
1613
1605
  // octet rule binds; with all-distinct tags either reading is possible, so accept a value that satisfies EITHER
1614
1606
  // (rejecting only what is non-canonical under both readings: sound in both directions, never a guess).
1615
- var _TAG_CLASS_RANK = { universal: 0, application: 1, context: 2, private: 3 };
1616
- function _setOrderOk(kids) {
1617
- var i, dup = false, seen = {};
1618
- for (i = 0; i < kids.length; i++) {
1619
- var key = kids[i].tagClass + ":" + kids[i].tagNumber;
1620
- if (seen[key]) { dup = true; break; }
1621
- seen[key] = true;
1622
- }
1623
- var octetAsc = true, tagAsc = true;
1624
- for (i = 1; i < kids.length; i++) {
1625
- if (Buffer.compare(kids[i - 1].bytes, kids[i].bytes) > 0) octetAsc = false;
1626
- // Tag order ranks by CLASS first (universal < application < context < private, X.680 sec. 8.6), then by tag
1627
- // number. Compare the class's NUMBER: the names do not sort in class order.
1628
- var pc = _TAG_CLASS_RANK[kids[i - 1].tagClass], cc = _TAG_CLASS_RANK[kids[i].tagClass];
1629
- if (pc !== cc ? pc > cc : kids[i - 1].tagNumber > kids[i].tagNumber) tagAsc = false;
1630
- }
1631
- return dup ? octetAsc : (octetAsc || tagAsc);
1632
- }
1633
1607
 
1634
1608
  // Strict-validate a decoded DER element at ANY depth, in the caller's error domain. Rejects the reserved EOC tag
1635
1609
  // 0; runs a universal primitive through its strict content reader (or rejects a type with none); recurses into a
@@ -1638,37 +1612,13 @@ function _setOrderOk(kids) {
1638
1612
  // components this gate cannot verify (the degenerate empty form is not a valid encoding of any of them), so it is
1639
1613
  // refused for the same reason an unvalidatable primitive is. A NON-universal element (a legitimately context- or
1640
1614
  // application-tagged ANY) passes on its framing, but its constructed children are still walked.
1641
- function _strictDerElement(node, code, label) {
1642
- if (node.tagClass === "universal" && node.tagNumber === 0) throw _err(code, label + " must not use the reserved end-of-contents encoding (tag 0)");
1643
- if (node.constructed) {
1644
- if (node.tagClass === "universal" && node.tagNumber !== asn1.TAGS.SEQUENCE && node.tagNumber !== asn1.TAGS.SET) {
1645
- throw _err(code, label + " of universal constructed type " + node.tagNumber + " has no strict DER structure validator here");
1646
- }
1647
- var kids = node.children; // asn1.decode always sets a (possibly empty) children array on a constructed node
1648
- for (var i = 0; i < kids.length; i++) _strictDerElement(kids[i], code, label);
1649
- if (node.tagClass === "universal" && node.tagNumber === asn1.TAGS.SET && !_setOrderOk(kids)) {
1650
- throw _err(code, label + " has a SET whose members are in no canonical DER order (X.690 sec. 11.6 / X.680 sec. 8.6)");
1651
- }
1652
- return;
1653
- }
1654
- if (node.tagClass === "universal") {
1655
- // Validate-or-reject: a universal primitive with no strict content reader is never accepted on framing alone
1656
- // (asn1.decode frames a malformed NumericString "12 01 40" happily), so the map is exhaustive by refusal.
1657
- var reader = _ANY_VALUE_READERS[node.tagNumber];
1658
- if (!reader) throw _err(code, label + " of universal type " + node.tagNumber + " has no strict DER content validator here");
1659
- try { reader(node); } catch (e) { throw _err(code, label + " is not a valid DER element for its type", e); }
1660
- }
1661
- }
1662
1615
  // Raw ANY bytes about to be spliced verbatim must be exactly one non-empty, well-formed AND strictly-valid DER
1663
- // element: framing + no-trailing-data via asn1.decode, then content / structure / SET order via
1664
- // _strictDerElement, both reported in the CALLER's error domain. Returns the bytes so a call site can wrap a
1665
- // splice inline. Used by every reconstruct site that emits caller-supplied ANY bytes verbatim.
1616
+ // element. The rule itself lives in guard.der -- framing + no-trailing-data, then content / structure / SET
1617
+ // order -- so this module and the certificate builder cannot drift apart on what "one DER element" means. This
1618
+ // stays as a named local because every reconstruct site here reads better naming the intent than the guard.
1619
+
1666
1620
  function _requireStrictDerTlv(content, code, label) {
1667
- if (content.length === 0) throw _err(code, label + " must be a non-empty DER element");
1668
- var node;
1669
- try { node = asn1.decode(content); } catch (e) { throw _err(code, label + " must be exactly one well-formed DER element (no trailing data)", e); }
1670
- _strictDerElement(node, code, label);
1671
- return content;
1621
+ return guard.der.tlv(content, _err, code, label);
1672
1622
  }
1673
1623
 
1674
1624
  // asn1.build.set SORTS its members, so handing it a non-canonically-ordered values list would silently rewrite
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
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:7e436b38-9f18-4222-a517-c69211740ba0",
5
+ "serialNumber": "urn:uuid:0541b2cd-7339-4ee1-9122-38999da30d35",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-18T02:52:33.662Z",
8
+ "timestamp": "2026-08-18T06:52:40.138Z",
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.5.8",
22
+ "bom-ref": "@blamejs/pki@0.5.9",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.5.8",
25
+ "version": "0.5.9",
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.5.8",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.5.9",
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.5.8",
57
+ "ref": "@blamejs/pki@0.5.9",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]