@blamejs/pki 0.1.16 → 0.1.18
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 +39 -0
- package/README.md +9 -7
- package/lib/asn1-der.js +122 -22
- package/lib/constants.js +16 -0
- package/lib/framework-error.js +13 -0
- package/lib/oid.js +42 -3
- package/lib/path-validate.js +10 -2
- package/lib/schema-all.js +41 -7
- package/lib/schema-cms.js +22 -3
- package/lib/schema-crl.js +2 -2
- package/lib/schema-crmf.js +487 -0
- package/lib/schema-csr.js +3 -3
- package/lib/schema-engine.js +216 -18
- package/lib/schema-ocsp.js +5 -7
- package/lib/schema-pkcs12.js +615 -0
- package/lib/schema-pkcs8.js +14 -4
- package/lib/schema-pkix.js +71 -29
- package/lib/schema-tsp.js +1 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-pkix.js
CHANGED
|
@@ -77,8 +77,17 @@ function _isPemArmor(buf) {
|
|
|
77
77
|
|
|
78
78
|
// Decode the DER root, wrapping a codec fault in the caller's <prefix>/bad-der.
|
|
79
79
|
function decodeRoot(der, opts) {
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// A format whose wire encoding is normatively BER (RFC 7292 PKCS#12) opts
|
|
81
|
+
// in to the codec's ber mode for the WHOLE decode. BER-vs-DER divergence is
|
|
82
|
+
// not always a decode-time throw — a definite-length constructed IMPLICIT
|
|
83
|
+
// OCTET STRING decodes strictly and only diverges at the typed reader — so
|
|
84
|
+
// a strict-first-retry-on-throw boundary would miss legal BER shapes. A
|
|
85
|
+
// strict-DER input decodes identically under the ber mode, every other
|
|
86
|
+
// strictness verdict included.
|
|
87
|
+
try { return asn1.decode(der, opts.ber ? { ber: true } : undefined); }
|
|
88
|
+
catch (e) {
|
|
89
|
+
throw new opts.ErrorClass(opts.prefix + "/bad-der", (opts.what || "input") + " DER did not decode: " + ((e && e.message) || String(e)), e);
|
|
90
|
+
}
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
// The shared parse entry: coerce -> decode -> walk the top schema. A format's
|
|
@@ -166,6 +175,12 @@ function attrValueToString(ns) {
|
|
|
166
175
|
}
|
|
167
176
|
return "#" + node.bytes.toString("hex");
|
|
168
177
|
}
|
|
178
|
+
}, function (value) {
|
|
179
|
+
// encode: a "#hex" escape (RFC 4514 §2.4) round-trips its raw DER verbatim; any
|
|
180
|
+
// other string encodes as UTF8String (the decode does not preserve the original
|
|
181
|
+
// string type, so this is the canonical re-encoding, not a byte-exact one).
|
|
182
|
+
if (typeof value === "string" && value.charAt(0) === "#") return Buffer.from(value.slice(1), "hex");
|
|
183
|
+
return asn1.build.utf8(value);
|
|
169
184
|
});
|
|
170
185
|
}
|
|
171
186
|
|
|
@@ -193,25 +208,34 @@ function relativeDistinguishedName(ns) {
|
|
|
193
208
|
// RelativeDistinguishedName ::= SET SIZE (1..MAX) — an empty SET {} is malformed.
|
|
194
209
|
return schema.setOf(attributeTypeAndValue(ns), { assert: "set", min: 1, code: ns.prefix + "/bad-rdn", what: "RelativeDistinguishedName" });
|
|
195
210
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
+
// opts.implicitTag (optional): read the Name as a [tag] IMPLICIT RDNSequence — the
|
|
212
|
+
// context tag REPLACES the universal SEQUENCE tag, so the node is a context-class
|
|
213
|
+
// constructed [tag] whose children ARE the RDN SETs (the CRMF CertTemplate issuer
|
|
214
|
+
// [3] / subject [5] IMPLICIT arm, RFC 4211 §5). With no opts the shape is a bare
|
|
215
|
+
// universal SEQUENCE OF, byte-identical to every existing caller.
|
|
216
|
+
function name(ns, opts) {
|
|
217
|
+
opts = opts || {};
|
|
218
|
+
function nameBuild(m) {
|
|
219
|
+
var rdns = [], parts = [];
|
|
220
|
+
m.items.forEach(function (rdnItem) {
|
|
221
|
+
var atvs = [], atvParts = [];
|
|
222
|
+
rdnItem.value.items.forEach(function (atvItem) {
|
|
223
|
+
var a = atvItem.value.result; // atvItem.value = the atv seq-match; .result = its build result
|
|
224
|
+
atvs.push(a);
|
|
225
|
+
var label = (a.name && DN_SHORT[a.name]) || a.name || a.type;
|
|
226
|
+
atvParts.push(label + "=" + _escapeDnValue(a.value));
|
|
211
227
|
});
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
228
|
+
rdns.push(atvs);
|
|
229
|
+
parts.push(atvParts.join("+"));
|
|
230
|
+
});
|
|
231
|
+
return { rdns: rdns, dn: parts.join(", ") };
|
|
232
|
+
}
|
|
233
|
+
if (opts.implicitTag != null) {
|
|
234
|
+
return schema.implicitSeqOf(opts.implicitTag, relativeDistinguishedName(ns), {
|
|
235
|
+
code: ns.prefix + "/bad-name", what: "Name", build: nameBuild });
|
|
236
|
+
}
|
|
237
|
+
return schema.seqOf(relativeDistinguishedName(ns), {
|
|
238
|
+
assert: "sequence", code: ns.prefix + "/bad-name", what: "Name", build: nameBuild });
|
|
215
239
|
}
|
|
216
240
|
|
|
217
241
|
// GeneralName ::= CHOICE (RFC 5280 §4.2.1.6). A validate-and-surface-raw leaf for a
|
|
@@ -623,12 +647,20 @@ function extension(ns) {
|
|
|
623
647
|
return { oid: extnID, name: ns.oid.name(extnID) || null, critical: critical, value: asn1.read.octetString(valueNode) };
|
|
624
648
|
});
|
|
625
649
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
650
|
+
// opts.implicitTag (optional): read Extensions as a [tag] IMPLICIT SEQUENCE OF
|
|
651
|
+
// Extension — the context tag REPLACES the universal SEQUENCE tag, for the CRMF
|
|
652
|
+
// CertTemplate extensions [9] (RFC 4211 §5). With no opts the shape is a bare
|
|
653
|
+
// universal SEQUENCE OF, byte-identical to every existing caller.
|
|
654
|
+
function extensions(ns, opts) {
|
|
655
|
+
opts = opts || {};
|
|
656
|
+
var EXT = extension(ns);
|
|
657
|
+
var extOpts = {
|
|
658
|
+
min: 1, code: ns.prefix + "/bad-extensions", what: "Extensions",
|
|
659
|
+
unique: function (it) { return it.value.oid; }, dupCode: ns.prefix + "/duplicate-extension",
|
|
630
660
|
build: function (m) { return m.items.map(function (it) { return it.value; }); },
|
|
631
|
-
}
|
|
661
|
+
};
|
|
662
|
+
if (opts.implicitTag != null) return schema.implicitSeqOf(opts.implicitTag, EXT, extOpts);
|
|
663
|
+
return schema.seqOf(EXT, Object.assign({ assert: "sequence" }, extOpts));
|
|
632
664
|
}
|
|
633
665
|
|
|
634
666
|
// SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier,
|
|
@@ -636,17 +668,26 @@ function extensions(ns) {
|
|
|
636
668
|
// universal SEQUENCE — a context-tagged or SET-tagged constructed node carrying
|
|
637
669
|
// two well-formed children is NOT a SubjectPublicKeyInfo. Shared by the
|
|
638
670
|
// certificate and CSR parsers.
|
|
639
|
-
|
|
671
|
+
// opts.implicitTag (optional): read the SubjectPublicKeyInfo as a [tag] IMPLICIT
|
|
672
|
+
// SEQUENCE (a context-class constructed node whose children are algorithm +
|
|
673
|
+
// subjectPublicKey), for the CRMF CertTemplate publicKey [6] (RFC 4211 §5). With no
|
|
674
|
+
// opts the shape is a universal SEQUENCE, byte-identical to every existing caller.
|
|
675
|
+
function spki(ns, opts) {
|
|
676
|
+
opts = opts || {};
|
|
640
677
|
return schema.seq([
|
|
641
678
|
schema.field("algorithm", algorithmIdentifier(ns)),
|
|
642
679
|
schema.field("subjectPublicKey", schema.bitString()),
|
|
643
680
|
], {
|
|
644
|
-
assert:
|
|
681
|
+
assert: opts.implicitTag != null ? "implicit" : "sequence", implicitTag: opts.implicitTag,
|
|
682
|
+
arity: { exact: 2 }, code: ns.prefix + "/bad-spki", what: "SubjectPublicKeyInfo",
|
|
645
683
|
build: function (m) {
|
|
646
684
|
return {
|
|
647
685
|
algorithm: m.fields.algorithm.value.result,
|
|
648
686
|
publicKey: { unusedBits: m.fields.subjectPublicKey.value.unusedBits, bytes: m.fields.subjectPublicKey.value.bytes },
|
|
649
|
-
bytes
|
|
687
|
+
// `bytes` is the importable SubjectPublicKeyInfo DER. In IMPLICIT [tag]
|
|
688
|
+
// mode the wire node leads with the context tag, so recover the SEQUENCE
|
|
689
|
+
// encoding a consumer imports / hashes rather than the [tag] wire form.
|
|
690
|
+
bytes: opts.implicitTag != null ? asn1.sequenceTlv(m.node) : m.node.bytes,
|
|
650
691
|
};
|
|
651
692
|
},
|
|
652
693
|
});
|
|
@@ -660,7 +701,8 @@ function spki(ns) {
|
|
|
660
701
|
function attribute(ns) {
|
|
661
702
|
return schema.seq([
|
|
662
703
|
schema.field("type", schema.oidLeaf()),
|
|
663
|
-
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: 1,
|
|
704
|
+
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: 1, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
|
|
705
|
+
code: ns.prefix + "/bad-attribute-values", what: "attribute values" })),
|
|
664
706
|
], {
|
|
665
707
|
assert: "sequence", arity: { exact: 2 }, code: ns.prefix + "/bad-attribute", what: "Attribute",
|
|
666
708
|
build: function (m, ctx) {
|
package/lib/schema-tsp.js
CHANGED
|
@@ -359,7 +359,7 @@ function parseToken(input) {
|
|
|
359
359
|
throw new TspError("tsp/multi-signer", "a TimeStampToken must contain exactly one (TSA) signerInfo (RFC 3161 §2.4.2)");
|
|
360
360
|
}
|
|
361
361
|
var tstInfo;
|
|
362
|
-
try { tstInfo = schema.
|
|
362
|
+
try { tstInfo = schema.embeddedDer(TST_INFO, encap.eContent, NS, { code: "tsp/bad-der", what: "the encapsulated TSTInfo" }); }
|
|
363
363
|
catch (e) {
|
|
364
364
|
if (e instanceof TspError) throw e;
|
|
365
365
|
throw new TspError("tsp/bad-der", "the encapsulated TSTInfo did not decode: " + ((e && e.message) || String(e)), e);
|
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:a14a33cf-8c68-40d3-92b0-6f17ef343543",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-09T03:55:56.855Z",
|
|
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.1.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.1.18",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.1.
|
|
25
|
+
"version": "0.1.18",
|
|
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.1.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.1.18",
|
|
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.1.
|
|
57
|
+
"ref": "@blamejs/pki@0.1.18",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|