@blamejs/pki 0.8.7 → 0.8.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.
@@ -0,0 +1,244 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.schema.sec1
6
+ * @nav Schema
7
+ * @title SEC1
8
+ * @fullname SEC1 EC private keys, the EC PRIVATE KEY encoding
9
+ * @order 146
10
+ * @slug sec1
11
+ *
12
+ * @intro
13
+ * The elliptic-curve private-key encoding of RFC 5915: `ECPrivateKey`, the
14
+ * `EC PRIVATE KEY` PEM block OpenSSL writes and appliances still emit. `parse`
15
+ * reads one into its version, scalar, curve and stored public point, and
16
+ * `encode` writes it back.
17
+ *
18
+ * The curve is what fixes the scalar's width, so the parameters field is
19
+ * required here even though the ASN.1 marks it optional: without it there is no
20
+ * width to hold the scalar to, and a scalar of the wrong width is a different
21
+ * key. RFC 5915 sec. 3 states the scalar as `ceiling(log2(n)/8)` octets with its
22
+ * leading zeros intact, so a short one is refused and never padded into place.
23
+ *
24
+ * The parameters field carries a named curve and nothing else. RFC 5480
25
+ * sec. 2.1.1 says a specified curve and an implicit curve MUST NOT appear, and
26
+ * each is refused by name, so the message says which one arrived.
27
+ *
28
+ * The stored public key is surfaced as the bytes it carries. It is the
29
+ * encoding's own copy of the public half, not the key's identity: a verb that
30
+ * needs that half derives it from the scalar, so a stored point that disagrees
31
+ * cannot decide anything.
32
+ *
33
+ * @card
34
+ * Read and write the RFC 5915 `ECPrivateKey`, DER or PEM, with the scalar held
35
+ * to its curve's width and the parameters field held to a named curve.
36
+ */
37
+
38
+ var asn1 = require("./asn1-der");
39
+ var schema = require("./schema-engine");
40
+ var pkix = require("./schema-pkix");
41
+ var oid = require("./oid");
42
+ var frameworkError = require("./framework-error");
43
+ var intrinsic = require("./guard-intrinsic");
44
+
45
+ var Sec1Error = frameworkError.Sec1Error;
46
+ var PemError = frameworkError.PemError;
47
+
48
+ var NS = pkix.makeNS("sec1", Sec1Error, oid);
49
+
50
+ var LABEL = "EC PRIVATE KEY";
51
+
52
+ /** @internal The scalar width each curve fixes, `ceiling(log2(n)/8)` over the curve's order
53
+ * (RFC 5915 sec. 3). A curve with no row here is one this toolkit cannot hold a scalar to, which
54
+ * is a refusal, since guessing the width would accept a key of the wrong size. */
55
+ var CURVE_SCALAR_BYTES = intrinsic.assign(intrinsic.create(null), {
56
+ "prime256v1": 32,
57
+ "secp384r1": 48,
58
+ "secp521r1": 66,
59
+ "secp256k1": 32,
60
+ "brainpoolP256r1": 32,
61
+ "brainpoolP384r1": 48,
62
+ "brainpoolP512r1": 64,
63
+ });
64
+
65
+ /** @internal RFC 5480 sec. 2.2 writes the point's first octet as the form: 0x04 uncompressed,
66
+ * 0x02 and 0x03 compressed. The hybrid forms 0x06 and 0x07 are named there as MUST NOT, and every
67
+ * other value names no form at all. The width follows from the form and the curve. */
68
+ function _assertPoint(point, scalarBytes) {
69
+ if (point.length === 0) {
70
+ throw NS.E("sec1/bad-public-key", "the stored public key is empty (RFC 5480 sec. 2.2)");
71
+ }
72
+ var form = point[0];
73
+ if (form === 0x06 || form === 0x07) {
74
+ throw NS.E("sec1/bad-public-key", "the hybrid point forms 0x06 and 0x07 are refused (RFC 5480 sec. 2.2)");
75
+ }
76
+ if (form === 0x04) {
77
+ if (point.length !== 1 + 2 * scalarBytes) {
78
+ throw NS.E("sec1/bad-public-key", "an uncompressed point is 1 + 2 * " + scalarBytes + " octets on this curve, got " + point.length);
79
+ }
80
+ return;
81
+ }
82
+ if (form === 0x02 || form === 0x03) {
83
+ if (point.length !== 1 + scalarBytes) {
84
+ throw NS.E("sec1/bad-public-key", "a compressed point is 1 + " + scalarBytes + " octets on this curve, got " + point.length);
85
+ }
86
+ return;
87
+ }
88
+ throw NS.E("sec1/bad-public-key", "the stored public key's first octet names no point form (RFC 5480 sec. 2.2)");
89
+ }
90
+
91
+ var EC_PRIVATE_KEY = schema.seq([
92
+ schema.field("version", schema.integerLeaf()),
93
+ schema.field("privateKey", schema.octetString()),
94
+ /** @internal RFC 5915's module is DEFINITIONS EXPLICIT TAGS, so `[0]` and `[1]` wrap their
95
+ * values, and do not replace their tags. */
96
+ /** @internal Read as ANY so the two shapes RFC 5480 sec. 2.1.1 names as MUST NOT, a SEQUENCE
97
+ * (specifiedCurve) and a NULL (implicitCurve), each get a verdict naming it. The OID reader
98
+ * would answer both with "expected tag 6", which says nothing about which one arrived. */
99
+ schema.optional("parameters", schema.any(), { tag: 0, explicit: true }),
100
+ schema.optional("publicKey", schema.bitString(), { tag: 1, explicit: true }),
101
+ ], {
102
+ assert: "sequence", arity: { min: 2, max: 4 }, code: "sec1/not-an-ec-private-key", what: "ECPrivateKey",
103
+ build: function (m) {
104
+ var version = m.fields.version.value;
105
+ /** @internal RFC 5915 sec. 3 fixes the version at 1. A PKCS#8 PrivateKeyInfo opens with a
106
+ * version 0 INTEGER and an OCTET STRING too, so this is also what keeps one from reading as
107
+ * the other. */
108
+ if (version !== 1n) {
109
+ throw NS.E("sec1/bad-version", "an ECPrivateKey is version 1 (RFC 5915 sec. 3), got " + version);
110
+ }
111
+ if (!m.fields.parameters.present) {
112
+ throw NS.E("sec1/missing-curve", "an ECPrivateKey read on its own carries its [0] parameters, since the curve is what fixes the scalar's width (RFC 5915 sec. 3)");
113
+ }
114
+ var paramNode = m.fields.parameters.value;
115
+ if (paramNode.tagClass !== "universal" || paramNode.tagNumber !== asn1.TAGS.OBJECT_IDENTIFIER) {
116
+ throw NS.E("sec1/bad-curve", paramNode.tagClass === "universal" && paramNode.tagNumber === asn1.TAGS.SEQUENCE
117
+ ? "the parameters field carries a specified curve, which RFC 5480 sec. 2.1.1 refuses; name the curve instead"
118
+ : (paramNode.tagClass === "universal" && paramNode.tagNumber === asn1.TAGS.NULL
119
+ ? "the parameters field carries an implicit curve, which RFC 5480 sec. 2.1.1 refuses; name the curve instead"
120
+ : "the parameters field carries a named curve OBJECT IDENTIFIER and nothing else (RFC 5480 sec. 2.1.1)"));
121
+ }
122
+ var curveOid = asn1.read.oid(paramNode);
123
+ var curve = oid.name(curveOid);
124
+ var scalarBytes = curve === undefined ? undefined : CURVE_SCALAR_BYTES[curve];
125
+ if (scalarBytes === undefined) {
126
+ throw NS.E("sec1/unsupported-curve", "this toolkit holds no scalar width for the curve " + curveOid +
127
+ (curve === undefined ? "" : " (" + curve + ")"));
128
+ }
129
+ var scalar = m.fields.privateKey.value;
130
+ if (scalar.length !== scalarBytes) {
131
+ throw NS.E("sec1/bad-private-key", "the private key is " + scalarBytes + " octets on " + curve +
132
+ ", with its leading zeros (RFC 5915 sec. 3), got " + scalar.length);
133
+ }
134
+ var out = { version: intrinsic.Number(version), privateKey: scalar, curve: curve, curveOid: curveOid };
135
+ if (m.fields.publicKey.present) {
136
+ var bits = m.fields.publicKey.value;
137
+ if (bits.unusedBits !== 0) {
138
+ throw NS.E("sec1/bad-public-key", "the stored public key's BIT STRING holds whole octets");
139
+ }
140
+ _assertPoint(bits.bytes, scalarBytes);
141
+ out.publicKey = bits.bytes;
142
+ }
143
+ return out;
144
+ },
145
+ });
146
+
147
+ var PARSE_OPTS = intrinsic.assign(intrinsic.create(null), {
148
+ pemLabel: LABEL, PemError: PemError, ErrorClass: Sec1Error, prefix: "sec1",
149
+ what: "EC private key", topSchema: EC_PRIVATE_KEY, ns: NS,
150
+ });
151
+
152
+ /**
153
+ * @primitive pki.schema.sec1.parse
154
+ * @signature pki.schema.sec1.parse(input, opts) -> key
155
+ * @since 0.8.8
156
+ * @status stable
157
+ * @spec RFC 5915
158
+ * @related pki.schema.sec1.encode, pki.schema.pkcs8.parse
159
+ *
160
+ * Read an RFC 5915 `ECPrivateKey`, as DER bytes or an `EC PRIVATE KEY` PEM block,
161
+ * into `{ version, privateKey, curve, curveOid, publicKey }`. The scalar and the
162
+ * stored point are `Buffer`s, and `curve` is the registry name of the named curve
163
+ * the key carries.
164
+ *
165
+ * The parameters field is required, because the curve is what fixes the scalar's
166
+ * width and a scalar of the wrong width is a different key. It carries a named
167
+ * curve and nothing else: a specified curve and an implicit curve are both refused
168
+ * with `sec1/bad-curve`, which RFC 5480 sec. 2.1.1 states as a MUST NOT.
169
+ *
170
+ * The stored public key is the encoding's own copy of the public half, not the key's
171
+ * identity. A verb that needs that half derives it from the scalar.
172
+ *
173
+ * @example
174
+ * // requires: der -- an ECPrivateKey, from `openssl ecparam -genkey` or an appliance
175
+ * var key = pki.schema.sec1.parse(der);
176
+ * key.version; // 1
177
+ * key.curve; // "prime256v1"
178
+ */
179
+ var parse = pkix.makeParser(PARSE_OPTS);
180
+
181
+ /**
182
+ * @primitive pki.schema.sec1.encode
183
+ * @signature pki.schema.sec1.encode(key) -> Buffer
184
+ * @since 0.8.8
185
+ * @status stable
186
+ * @spec RFC 5915
187
+ * @related pki.schema.sec1.parse
188
+ *
189
+ * Write an `ECPrivateKey` back to DER from the shape `parse` returns, with the
190
+ * parameters field always present, which RFC 5915 sec. 3 asks of a generator. A key
191
+ * read and written again produces the bytes it came from.
192
+ *
193
+ * @example
194
+ * // requires: der -- an ECPrivateKey, from `openssl ecparam -genkey` or an appliance
195
+ * pki.schema.sec1.encode(pki.schema.sec1.parse(der)).equals(der); // true
196
+ */
197
+ function encode(key) {
198
+ if (key === null || typeof key !== "object") {
199
+ throw NS.E("sec1/bad-input", "encode takes the object parse returns");
200
+ }
201
+ var curveOid = key.curveOid;
202
+ if (typeof curveOid !== "string") {
203
+ var named = typeof key.curve === "string" ? oid.byName(key.curve) : undefined;
204
+ if (typeof named !== "string") {
205
+ throw NS.E("sec1/bad-input", "encode needs the key's named curve, as curveOid or a registered curve name");
206
+ }
207
+ curveOid = named;
208
+ }
209
+ var kids = [asn1.build.integer(intrinsic.BigInt(key.version === undefined ? 1 : key.version)),
210
+ asn1.build.octetString(key.privateKey),
211
+ asn1.build.explicit(0, asn1.build.oid(curveOid))];
212
+ if (key.publicKey !== undefined && key.publicKey !== null) {
213
+ kids[3] = asn1.build.explicit(1, asn1.build.bitString(key.publicKey, 0));
214
+ }
215
+ return asn1.build.sequence(kids);
216
+ }
217
+
218
+ function pemDecode(text) { return pkix.pemDecode(text, LABEL, PemError); }
219
+ function pemEncode(der) { return pkix.pemEncode(der, LABEL, PemError); }
220
+
221
+ /** @internal RFC 5915 sec. 3: `ECPrivateKey ::= SEQUENCE { version INTEGER, privateKey OCTET
222
+ * STRING, parameters [0] ECParameters OPTIONAL, publicKey [1] BIT STRING OPTIONAL }`. Structure
223
+ * alone does not separate this from every other SEQUENCE opening that way, which is why nothing
224
+ * routes to it from bare bytes; it answers for a caller that already has the armor label. */
225
+ function matches(root) {
226
+ var k = pkix.rootSequenceChildren(root, 2, 4);
227
+ if (!k) return false;
228
+ if (!schema.isUniversal(k[0], asn1.TAGS.INTEGER)) return false;
229
+ if (!schema.isUniversal(k[1], asn1.TAGS.OCTET_STRING)) return false;
230
+ var last = -1;
231
+ for (var i = 2; i < k.length; i++) {
232
+ if (!schema.isContextInRange(k[i], 0, 1) || k[i].tagNumber <= last) return false;
233
+ last = k[i].tagNumber;
234
+ }
235
+ return true;
236
+ }
237
+
238
+ module.exports = {
239
+ parse: parse,
240
+ matches: matches,
241
+ encode: encode,
242
+ pemDecode: pemDecode,
243
+ pemEncode: pemEncode,
244
+ };
package/lib/schema-tsp.js CHANGED
@@ -455,8 +455,10 @@ function parseToken(input, callerOpts) {
455
455
  * @related pki.schema.tsp.parse
456
456
  *
457
457
  * Extract the DER bytes from a PEM block (RFC 3161 defines no standard label, so the
458
- * first block is taken unless `label` is given). Throws `PemError` on a missing
459
- * envelope or a non-base64 body.
458
+ * a block of any label is accepted unless `label` is given). The text holds one
459
+ * object: a file of several is refused with `pem/multiple-blocks` and read with
460
+ * `pki.schema.pem.decodeBundle`. Throws `PemError` on a missing envelope or a
461
+ * non-base64 body.
460
462
  *
461
463
  * @example
462
464
  * var req = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) }, {});
@@ -51,8 +51,10 @@ var PemError = frameworkError.PemError;
51
51
  * Extract the DER bytes from a PEM block (default label `CERTIFICATE`, the
52
52
  * RFC 7468 sec. 5 armor, the canonical-label default every sibling format
53
53
  * applies). Pass a `label` to enforce a different block type, or an explicit
54
- * `null` to take the first block of any type. Throws `PemError` on a
55
- * missing / mismatched envelope or a non-base64 body.
54
+ * `null` to accept a block of any type. The text holds one object: a file of
55
+ * several is refused with `pem/multiple-blocks` and read with
56
+ * `pki.schema.pem.decodeBundle`. Throws `PemError` on a missing / mismatched
57
+ * envelope or a non-base64 body.
56
58
  *
57
59
  * @example
58
60
  * var pair = await pki.key.generate("Ed25519");
package/lib/trust.js CHANGED
@@ -383,8 +383,8 @@ function _copyDistrustAfter(src) {
383
383
  function _copyName(name) {
384
384
  if (!name || !Array.isArray(name.rdns)) return name;
385
385
  var out = Object.assign({}, name);
386
- out.rdns = name.rdns.map(function (rdn) {
387
- return Array.isArray(rdn) ? rdn.map(function (atv) { return Object.assign({}, atv); }) : rdn;
386
+ out.rdns = guard.list.copyMap(name.rdns, function (rdn) {
387
+ return Array.isArray(rdn) ? guard.list.copyMap(rdn, function (atv) { return Object.assign({}, atv); }) : rdn;
388
388
  });
389
389
  out.bytes = Buffer.isBuffer(name.bytes) ? Buffer.from(name.bytes) : name.bytes;
390
390
  return out;
package/lib/tsp-sign.js CHANGED
@@ -76,7 +76,7 @@ function _certDer(c) {
76
76
  c = buf.toString("latin1");
77
77
  }
78
78
  if (typeof c !== "string") throw _err("tsp/bad-input", "the TSA certificate must be a DER BufferSource or a PEM string");
79
- var der = pkix.pemDecodeLenient(c, "CERTIFICATE");
79
+ var der = pkix.pemDecodeLenient(c, "CERTIFICATE", _err, "tsp/bad-input");
80
80
  if (der === null) throw _err("tsp/bad-input", "the TSA certificate PEM is not a CERTIFICATE block");
81
81
  return der;
82
82
  }
package/lib/webauthn.js CHANGED
@@ -350,7 +350,7 @@ var _FORMAT_SCOPED_BOOLEAN_OPTS = ["verifySafetyNetJws", "requireCtsProfileMatch
350
350
  function _cloneParsed(v, depth) {
351
351
  if (depth > 64) throw _err("webauthn/bad-input", "opts.rootCertificates[] is nested too deeply to be a parsed certificate");
352
352
  if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, _err, "webauthn/bad-input", "a byte field of opts.rootCertificates[]");
353
- if (Array.isArray(v)) return v.map(function (x) { return _cloneParsed(x, depth + 1); });
353
+ if (Array.isArray(v)) return guard.list.copyMap(v, function (x) { return _cloneParsed(x, depth + 1); });
354
354
  if (guard.time.isDate(v)) return new Date(guard.time.instantOf(v));
355
355
  if (v && typeof v === "object") {
356
356
  var out = intrinsic.create(null);
@@ -1497,9 +1497,9 @@ function _snapshotAssertion(input) {
1497
1497
  }
1498
1498
  out.credentialPublicKey = key;
1499
1499
  }
1500
- if (Array.isArray(out.allowedAlgorithms)) out.allowedAlgorithms = out.allowedAlgorithms.slice();
1501
- if (Array.isArray(out.expectedOrigin)) out.expectedOrigin = out.expectedOrigin.slice();
1502
- if (Array.isArray(out.expectedTopOrigin)) out.expectedTopOrigin = out.expectedTopOrigin.slice();
1500
+ if (Array.isArray(out.allowedAlgorithms)) out.allowedAlgorithms = guard.list.snapshot(out.allowedAlgorithms);
1501
+ if (Array.isArray(out.expectedOrigin)) out.expectedOrigin = guard.list.snapshot(out.expectedOrigin);
1502
+ if (Array.isArray(out.expectedTopOrigin)) out.expectedTopOrigin = guard.list.snapshot(out.expectedTopOrigin);
1503
1503
  return out;
1504
1504
  }
1505
1505
 
package/lib/webcrypto.js CHANGED
@@ -1148,13 +1148,24 @@ function isCryptoKeyLike(x) {
1148
1148
  }
1149
1149
 
1150
1150
  // @internal
1151
- function exportAnyKey(key, E, code) {
1152
- var format = _EXPORT_FORMAT[key && key.type];
1151
+ /** @internal `want` names the WebCrypto export format, defaulting to the DER one the key's type
1152
+ * calls for. A key from another implementation can be reached three ways, and which of them works
1153
+ * is a property of the key rather than of the format asked for, so every format is exported
1154
+ * through the same three; a format that went straight to this engine's `exportKey` would refuse a
1155
+ * key this engine did not make while the others returned it. A JWK is an object rather than
1156
+ * bytes, so only the byte formats are wrapped. */
1157
+ function exportAnyKey(key, E, code, want) {
1158
+ var format = want || _EXPORT_FORMAT[key && key.type];
1153
1159
  if (!format) throw E(code, "a CryptoKey with a private, public, or secret type is required");
1160
+ if (want === "jwk" && !_EXPORT_FORMAT[key && key.type]) {
1161
+ throw E(code, "a CryptoKey with a private, public, or secret type is required");
1162
+ }
1163
+ var asJwk = format === "jwk";
1164
+ function wrap(b) { return asJwk ? b : _bufferFrom(b); }
1154
1165
  if (key instanceof CryptoKey) {
1155
1166
  return _resolve(Promise)
1156
1167
  .then(function () { return _crypto.subtle.exportKey(format, key); })
1157
- .then(function (b) { return _bufferFrom(b); });
1168
+ .then(wrap);
1158
1169
  }
1159
1170
  if (!key.extractable) {
1160
1171
  throw E(code, "the CryptoKey comes from a different WebCrypto implementation and is not extractable, so its key material cannot be reached; import it through pki.webcrypto.subtle, or pass the key as DER");
@@ -1162,14 +1173,15 @@ function exportAnyKey(key, E, code) {
1162
1173
  var handle = key._handle;
1163
1174
  if (handle instanceof nodeCrypto.KeyObject && handle.type === key.type) {
1164
1175
  return _resolve(Promise).then(function () {
1176
+ if (asJwk) return handle.export({ format: "jwk" });
1165
1177
  return format === "raw" ? handle.export() : handle.export({ format: "der", type: format });
1166
- }).then(function (b) { return _bufferFrom(b); }, function (e) {
1178
+ }).then(wrap, function (e) {
1167
1179
  throw E(code, "the CryptoKey comes from a different copy of this WebCrypto engine and its key material could not be read; import it through pki.webcrypto.subtle, or pass the key as DER", e);
1168
1180
  });
1169
1181
  }
1170
1182
  return _resolve(Promise)
1171
1183
  .then(function () { return nodeCrypto.webcrypto.subtle.exportKey(format, key); })
1172
- .then(function (b) { return _bufferFrom(b); }, function (e) {
1184
+ .then(wrap, function (e) {
1173
1185
  throw E(code, "the CryptoKey comes from a different WebCrypto implementation and could not be exported for re-import; import it through pki.webcrypto.subtle, or pass the key as DER", e);
1174
1186
  });
1175
1187
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.8.7",
3
+ "version": "0.8.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:f6667295-f56b-4c1b-a9a8-6f8889816a2b",
5
+ "serialNumber": "urn:uuid:1d0c3725-eae5-4743-8430-5684e3e52ec7",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-09-23T18:05:34.573Z",
8
+ "timestamp": "2026-09-23T19:11:33.586Z",
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.7",
22
+ "bom-ref": "@blamejs/pki@0.8.9",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.8.7",
25
+ "version": "0.8.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.8.7",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.8.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.8.7",
57
+ "ref": "@blamejs/pki@0.8.9",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]