@blamejs/pki 0.8.26 → 0.8.28
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 +46 -0
- package/README.md +2 -2
- package/index.js +14 -1
- package/lib/composite-sig.js +5 -0
- package/lib/crl-sign.js +22 -3
- package/lib/csr-sign.js +17 -2
- package/lib/http-retry-after.js +28 -7
- package/lib/inspect.js +3 -2
- package/lib/lint.js +942 -41
- package/lib/rfc3339.js +12 -5
- package/lib/schema-crl.js +12 -7
- package/lib/x509-sign.js +20 -1
- package/package.json +2 -2
- package/sbom.cdx.json +6 -6
package/lib/rfc3339.js
CHANGED
|
@@ -6,11 +6,18 @@
|
|
|
6
6
|
* lib/rfc3339.js: RFC 3339 date-time parsing + validation, a fail-closed engine primitive shared by
|
|
7
7
|
* pki.acme (a boolean field-validator over server-supplied JSON) and pki.ct (parse a CT log-list
|
|
8
8
|
* timestamp / temporal-interval bound to a comparable Date). Beyond the grammar it enforces CALENDAR
|
|
9
|
-
* validity: a syntactically well-formed but impossible instant
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* validity: a syntactically well-formed but impossible instant is rejected, so a downstream expiry or
|
|
10
|
+
* window comparison never runs on a value JS `Date` reads as a different instant.
|
|
11
|
+
* `2026-02-30T00:00:00Z` is the case that earns the check its keep: `new Date` reads it as
|
|
12
|
+
* `2026-03-02T00:00:00.000Z`, two days forward, and returns that instant without complaint. The
|
|
13
|
+
* runtime refuses month 13, hour 25 and a `+25:00` offset on its own, so the check agrees with it
|
|
14
|
+
* there and adds nothing.
|
|
15
|
+
* A `:60` second is well-formed: RFC 3339 sec. 5.7 permits `time-second` to be 60 at the end of a
|
|
16
|
+
* month in which a leap second occurs. Refusing it here is a decision, taken because JS cannot
|
|
17
|
+
* represent a leap second, so a caller that accepted one would hold either an invalid Date or an
|
|
18
|
+
* instant one second off; a typed error says so instead. Like the byte-reader / guard family, `parse`
|
|
19
|
+
* takes the caller's `(code, message) -> error` factory so every consumer keeps its own domain fault
|
|
20
|
+
* code.
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
23
|
function _digitsAt(v, start, count) {
|
package/lib/schema-crl.js
CHANGED
|
@@ -168,7 +168,8 @@ var CERTIFICATE_LIST = pkix.signedEnvelope(NS, TBS_CERTLIST, {
|
|
|
168
168
|
* var caCert = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
169
169
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
|
|
170
170
|
* extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: key });
|
|
171
|
-
* var der = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
171
|
+
* var der = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
172
|
+
* nextUpdate: new Date("2026-02-01T00:00:00Z"), crlNumber: 1n,
|
|
172
173
|
* revoked: [{ serialNumber: 0x0a3fn, revocationDate: new Date("2026-01-15T00:00:00Z") }] },
|
|
173
174
|
* { cert: caCert, key: key });
|
|
174
175
|
* var crl = pki.schema.crl.parse(der);
|
|
@@ -197,8 +198,9 @@ var parse = pkix.makeRecordingParser({ pemLabel: "X509 CRL", PemError: PemError,
|
|
|
197
198
|
* var key = await pki.key.export(pair.privateKey);
|
|
198
199
|
* var caCert = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
199
200
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
|
|
200
|
-
* extensions: { basicConstraints: { cA: true }, keyUsage: ["cRLSign"] } }, { key: key });
|
|
201
|
-
* var pemText = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
201
|
+
* extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: key });
|
|
202
|
+
* var pemText = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
203
|
+
* nextUpdate: new Date("2026-02-01T00:00:00Z"), crlNumber: 1n, revoked: [] },
|
|
202
204
|
* { cert: caCert, key: key }, { pem: true });
|
|
203
205
|
* var der = pki.schema.crl.pemDecode(pemText);
|
|
204
206
|
*/
|
|
@@ -220,8 +222,9 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "X509 CRL
|
|
|
220
222
|
* var key = await pki.key.export(pair.privateKey);
|
|
221
223
|
* var caCert = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
222
224
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
|
|
223
|
-
* extensions: { basicConstraints: { cA: true }, keyUsage: ["cRLSign"] } }, { key: key });
|
|
224
|
-
* var der = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
225
|
+
* extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: key });
|
|
226
|
+
* var der = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
227
|
+
* nextUpdate: new Date("2026-02-01T00:00:00Z"), crlNumber: 1n, revoked: [] },
|
|
225
228
|
* { cert: caCert, key: key });
|
|
226
229
|
* var pem = pki.schema.crl.pemEncode(der);
|
|
227
230
|
*/
|
|
@@ -329,7 +332,8 @@ var DOOR = pkix.extensionDoor({
|
|
|
329
332
|
* var caCert = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
330
333
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
|
|
331
334
|
* extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: key });
|
|
332
|
-
* var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
335
|
+
* var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
336
|
+
* nextUpdate: new Date("2026-02-01T00:00:00Z"), crlNumber: 7n,
|
|
333
337
|
* revoked: [{ serialNumber: 0x0a3fn, revocationDate: new Date("2026-01-15T00:00:00Z"), reason: "keyCompromise" }] },
|
|
334
338
|
* { cert: caCert, key: key });
|
|
335
339
|
* var rows = pki.schema.crl.decodeExtensions(crlDer);
|
|
@@ -362,7 +366,8 @@ var decodeExtensions = DOOR.decodeExtensions;
|
|
|
362
366
|
* var caCert = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
363
367
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
|
|
364
368
|
* extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: key });
|
|
365
|
-
* var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
369
|
+
* var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"),
|
|
370
|
+
* nextUpdate: new Date("2026-02-01T00:00:00Z"), crlNumber: 7n,
|
|
366
371
|
* revoked: [{ serialNumber: 0x0a3fn, revocationDate: new Date("2026-01-15T00:00:00Z"), reason: "keyCompromise" }] },
|
|
367
372
|
* { cert: caCert, key: key });
|
|
368
373
|
* var e = pki.schema.crl.parse(crlDer).revokedCertificates[0].crlEntryExtensions[0];
|
package/lib/x509-sign.js
CHANGED
|
@@ -31,6 +31,7 @@ var schema = require("./schema-engine");
|
|
|
31
31
|
var pkix = require("./schema-pkix");
|
|
32
32
|
var pkiBuild = require("./pki-build");
|
|
33
33
|
var ct = require("./ct");
|
|
34
|
+
var lint = require("./lint");
|
|
34
35
|
|
|
35
36
|
var CertificateError = frameworkError.CertificateError;
|
|
36
37
|
var NS = pkix.makeNS("x509", CertificateError, oid);
|
|
@@ -47,7 +48,12 @@ var KNOWN_SPEC_KEYS = intrinsic.assign(intrinsic.create(null), {
|
|
|
47
48
|
});
|
|
48
49
|
var KNOWN_ISSUER_CERT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, cert: 1 });
|
|
49
50
|
var KNOWN_ISSUER_EXPLICIT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, name: 1, publicKey: 1 });
|
|
50
|
-
var KNOWN_SIGN_OPTS = intrinsic.assign(intrinsic.create(null), { digestAlgorithm: 1, pem: 1, pss: 1 });
|
|
51
|
+
var KNOWN_SIGN_OPTS = intrinsic.assign(intrinsic.create(null), { digestAlgorithm: 1, pem: 1, pss: 1, profile: 1 });
|
|
52
|
+
|
|
53
|
+
/** @internal The signature the build-time gate lints against, standing in for one that does not exist
|
|
54
|
+
* until the key is used. It is non-empty so the gate is not answering a rule about its own placeholder,
|
|
55
|
+
* and the rules that read the signature VALUE are exempt in lint.js for the same reason. */
|
|
56
|
+
var GATE_PLACEHOLDER_SIG = intrinsic.bufferAlloc(64, 0xff);
|
|
51
57
|
|
|
52
58
|
var KNOWN_EXT_KEYS = intrinsic.assign(intrinsic.create(null), {
|
|
53
59
|
subjectKeyIdentifier: 1, authorityKeyIdentifier: 1, keyUsage: 1, keyUsageCritical: 1,
|
|
@@ -493,10 +499,19 @@ function _hasCriticalSan(extSpec) {
|
|
|
493
499
|
* marks it critical. The two name opposite ends of one exchange, so a spec naming both is refused. A
|
|
494
500
|
* client recovers the signed entry from the issued certificate with `pki.ct.x509CertEntry`.
|
|
495
501
|
*
|
|
502
|
+
* Before the key is used, the certificate the spec describes is linted against the RFC 5280 profile
|
|
503
|
+
* and refused with `x509/profile-violation` if any rule grades it `error`, naming the rule and its
|
|
504
|
+
* clause. Two rules are passed over: one reading the not-yet-written signature, and
|
|
505
|
+
* `lint/rfc5280/unknown-critical-extension`, whose requirement RFC 5280 sec. 4.2 places on a
|
|
506
|
+
* certificate-using system rather than on the issuer.
|
|
507
|
+
*
|
|
496
508
|
* @opts
|
|
497
509
|
* - `pem` (boolean) -- return a PEM `CERTIFICATE` string instead of DER.
|
|
498
510
|
* - `pss` (boolean) -- sign an RSA key with RSASSA-PSS instead of PKCS#1 v1.5.
|
|
499
511
|
* - `digestAlgorithm` (string) -- override the message digest where the algorithm permits a choice.
|
|
512
|
+
* - `profile` (string) -- a second lint profile to hold the certificate to beside RFC 5280, named
|
|
513
|
+
* from `pki.lint.profiles()`. `"none"` runs no rules at all and emits what the spec describes,
|
|
514
|
+
* which is how a deliberately non-conforming certificate is produced.
|
|
500
515
|
* @example
|
|
501
516
|
* var pair = await pki.key.generate("Ed25519");
|
|
502
517
|
* var signerSpki = await pki.key.export(pair.publicKey);
|
|
@@ -530,6 +545,7 @@ function _sign(spec, issuer, opts) {
|
|
|
530
545
|
: "explicit form; the issuer takes { key, name, publicKey }, or { key, cert } to take both from a certificate");
|
|
531
546
|
});
|
|
532
547
|
guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "x509/bad-input", "pki.x509.sign has an unknown option ");
|
|
548
|
+
lint.assertKnownBuildProfile("certificate", opts.profile, _err, "x509/bad-input");
|
|
533
549
|
if (issuer.key == null) throw _err("x509/bad-input", "a signing key (issuer.key, a WebCrypto CryptoKey or a PKCS#8 private key DER/PEM) is required");
|
|
534
550
|
|
|
535
551
|
var spki = _reqDer(spec.subjectPublicKey, "spec.subjectPublicKey (the SPKI DER of the certified key)");
|
|
@@ -596,6 +612,9 @@ function _sign(spec, issuer, opts) {
|
|
|
596
612
|
tbsChildren.push(b.raw(spki));
|
|
597
613
|
if (exts.length) tbsChildren.push(b.explicit(3, b.sequence(exts)));
|
|
598
614
|
var tbsDer = b.sequence(tbsChildren);
|
|
615
|
+
lint.assertBuildClean("certificate",
|
|
616
|
+
b.sequence([tbsDer, scheme.sigAlgId, b.bitString(GATE_PLACEHOLDER_SIG, 0)]),
|
|
617
|
+
opts.profile, _err, "x509/profile-violation");
|
|
599
618
|
|
|
600
619
|
return signScheme.signOverTbs(scheme, issuer.key, tbsDer, _signE).then(function (sig) {
|
|
601
620
|
return Promise.resolve(_assertCertVerifies(tbsDer, sig, issuerSpki, scheme)).then(function () {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/pki",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.28",
|
|
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",
|
|
@@ -86,6 +86,6 @@
|
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"c8": "12.0.0",
|
|
88
88
|
"esbuild": "0.28.2",
|
|
89
|
-
"eslint": "10.
|
|
89
|
+
"eslint": "10.11.0"
|
|
90
90
|
}
|
|
91
91
|
}
|
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:3eb9063d-93f7-4a44-bdff-52a992c9a1e7",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-28T21:19:37.849Z",
|
|
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.28",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.28",
|
|
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.28",
|
|
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.28",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|