@blamejs/pki 0.1.1
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 +32 -0
- package/LICENSE +201 -0
- package/MIGRATING.md +9 -0
- package/NOTICE +21 -0
- package/README.md +252 -0
- package/bin/pki.js +76 -0
- package/index.js +50 -0
- package/lib/asn1-der.js +576 -0
- package/lib/constants.js +115 -0
- package/lib/framework-error.js +130 -0
- package/lib/oid.js +219 -0
- package/lib/vendor/MANIFEST.json +4 -0
- package/lib/vendor/README.md +41 -0
- package/lib/webcrypto.js +542 -0
- package/lib/x509.js +320 -0
- package/package.json +74 -0
- package/sbom.cdx.json +61 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.errors
|
|
6
|
+
* @nav Core
|
|
7
|
+
* @title Errors
|
|
8
|
+
* @order 20
|
|
9
|
+
* @slug errors
|
|
10
|
+
*
|
|
11
|
+
* @intro
|
|
12
|
+
* Error taxonomy for the toolkit. Every error the library throws
|
|
13
|
+
* extends `PkiError`, so a consumer needs a single
|
|
14
|
+
* `err instanceof pki.errors.PkiError` check instead of sniffing per-
|
|
15
|
+
* module boolean flags, and every error carries a stable shape:
|
|
16
|
+
* `{ name, code, message, permanent, isPkiError: true }`.
|
|
17
|
+
*
|
|
18
|
+
* `code` is a stable, greppable `domain/reason` string
|
|
19
|
+
* (`asn1/indefinite-length`, `x509/not-a-certificate`) — safe to switch
|
|
20
|
+
* on and safe to log. Because every failure here is a deterministic
|
|
21
|
+
* verdict on the bytes in hand (a malformed length, an unknown OID
|
|
22
|
+
* shape, a truncated certificate), errors are `permanent: true` — the
|
|
23
|
+
* same input will never parse on retry.
|
|
24
|
+
*
|
|
25
|
+
* @card
|
|
26
|
+
* `PkiError` base class + `defineClass` factory + the per-domain error
|
|
27
|
+
* classes the toolkit throws.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @primitive pki.errors.PkiError
|
|
32
|
+
* @signature new PkiError(message, code)
|
|
33
|
+
* @since 0.1.0
|
|
34
|
+
* @status stable
|
|
35
|
+
*
|
|
36
|
+
* Base class every toolkit error extends. Provides the unified
|
|
37
|
+
* `instanceof` check plus the `{ name, code, isPkiError }` shape.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* try { pki.asn1.decode(bytes); }
|
|
41
|
+
* catch (e) {
|
|
42
|
+
* if (e instanceof pki.errors.PkiError) console.error(e.code);
|
|
43
|
+
* }
|
|
44
|
+
*/
|
|
45
|
+
class PkiError extends Error {
|
|
46
|
+
constructor(message, code) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "PkiError";
|
|
49
|
+
this.code = code || "pki/invalid";
|
|
50
|
+
this.isPkiError = true;
|
|
51
|
+
this.permanent = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @primitive pki.errors.defineClass
|
|
57
|
+
* @signature pki.errors.defineClass(name, opts?) -> constructor
|
|
58
|
+
* @since 0.1.0
|
|
59
|
+
* @status stable
|
|
60
|
+
*
|
|
61
|
+
* Factory that produces a `PkiError` subclass with the standard shape —
|
|
62
|
+
* eliminating the per-domain boilerplate. The returned constructor takes
|
|
63
|
+
* `(code, message)`, stamps `name`, sets an `is<Name>` flag, and exposes
|
|
64
|
+
* a `.factory` static for the common `var _err = XxxError.factory` shape.
|
|
65
|
+
*
|
|
66
|
+
* @opts
|
|
67
|
+
* withCause: boolean, // default: false — constructor becomes (code, message, cause)
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* var MyError = pki.errors.defineClass("MyError");
|
|
71
|
+
* throw new MyError("my/bad-input", "explanation");
|
|
72
|
+
*/
|
|
73
|
+
function defineClass(name, opts) {
|
|
74
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
75
|
+
throw new TypeError("defineClass: name must be a non-empty string");
|
|
76
|
+
}
|
|
77
|
+
opts = opts || {};
|
|
78
|
+
var withCause = !!opts.withCause;
|
|
79
|
+
var flagKey = "is" + name;
|
|
80
|
+
|
|
81
|
+
var GeneratedError = class extends PkiError {
|
|
82
|
+
constructor(code, message, arg3) {
|
|
83
|
+
super(message, code);
|
|
84
|
+
this.name = name;
|
|
85
|
+
this[flagKey] = true;
|
|
86
|
+
if (withCause && arg3 !== undefined) this.cause = arg3;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
Object.defineProperty(GeneratedError, "name", { value: name, configurable: true });
|
|
90
|
+
GeneratedError.factory = function (code, message, arg3) {
|
|
91
|
+
return new GeneratedError(code, message, arg3);
|
|
92
|
+
};
|
|
93
|
+
return GeneratedError;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---- Per-domain error classes ----
|
|
97
|
+
|
|
98
|
+
// ConstantsError — a bad scale argument to C.TIME.* / C.BYTES.* (an
|
|
99
|
+
// authoring bug caught at config time).
|
|
100
|
+
var ConstantsError = defineClass("ConstantsError");
|
|
101
|
+
|
|
102
|
+
// Asn1Error — malformed / non-canonical DER: truncated TLV, indefinite
|
|
103
|
+
// length, non-minimal length or integer, leftover trailing bytes, depth
|
|
104
|
+
// or size cap exceeded. DER is a canonical encoding, so anything the
|
|
105
|
+
// decoder rejects is permanently invalid.
|
|
106
|
+
var Asn1Error = defineClass("Asn1Error");
|
|
107
|
+
|
|
108
|
+
// OidError — a malformed object-identifier: fewer than two arcs, a first
|
|
109
|
+
// arc outside 0..2, a second arc >= 40 under arcs 0/1, or a non-minimal
|
|
110
|
+
// base-128 sub-identifier.
|
|
111
|
+
var OidError = defineClass("OidError");
|
|
112
|
+
|
|
113
|
+
// PemError — a malformed PEM envelope: missing / mismatched BEGIN/END
|
|
114
|
+
// markers, a bad label, or non-base64 body.
|
|
115
|
+
var PemError = defineClass("PemError");
|
|
116
|
+
|
|
117
|
+
// CertificateError — a byte sequence that is not a well-formed X.509
|
|
118
|
+
// certificate (wrong outer structure, unparseable field, unsupported
|
|
119
|
+
// version).
|
|
120
|
+
var CertificateError = defineClass("CertificateError", { withCause: true });
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
PkiError: PkiError,
|
|
124
|
+
defineClass: defineClass,
|
|
125
|
+
ConstantsError: ConstantsError,
|
|
126
|
+
Asn1Error: Asn1Error,
|
|
127
|
+
OidError: OidError,
|
|
128
|
+
PemError: PemError,
|
|
129
|
+
CertificateError: CertificateError,
|
|
130
|
+
};
|
package/lib/oid.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.oid
|
|
6
|
+
* @nav Core
|
|
7
|
+
* @title Object Identifiers
|
|
8
|
+
* @order 40
|
|
9
|
+
* @slug oid
|
|
10
|
+
*
|
|
11
|
+
* @intro
|
|
12
|
+
* The object-identifier registry: a two-way map between dotted-decimal
|
|
13
|
+
* OID strings and their human names, plus arc conversion and DER
|
|
14
|
+
* encode/decode convenience. Every algorithm, attribute type, and
|
|
15
|
+
* extension in PKI is named by an OID, and resolving them through one
|
|
16
|
+
* registry — rather than scattering magic dotted strings across the
|
|
17
|
+
* codebase — is what lets a new algorithm be a data entry instead of a
|
|
18
|
+
* code change.
|
|
19
|
+
*
|
|
20
|
+
* The seed set covers the RFC 5280 attribute types and extensions, the
|
|
21
|
+
* classical signature / public-key / digest algorithms, and the
|
|
22
|
+
* NIST-assigned post-quantum arcs (ML-DSA, ML-KEM, SLH-DSA). Operators
|
|
23
|
+
* extend it with `register`.
|
|
24
|
+
*
|
|
25
|
+
* @card
|
|
26
|
+
* Two-way OID ↔ name registry with arc conversion, seeded with the
|
|
27
|
+
* RFC 5280 and NIST post-quantum object identifiers.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
var asn1 = require("./asn1-der");
|
|
31
|
+
var frameworkError = require("./framework-error");
|
|
32
|
+
|
|
33
|
+
var OidError = frameworkError.OidError;
|
|
34
|
+
|
|
35
|
+
// SEED — [dotted, canonicalName]. Kept as a flat table so it reads as
|
|
36
|
+
// data and diffs cleanly when an arc is added.
|
|
37
|
+
var SEED = [
|
|
38
|
+
// --- RFC 5280 attribute types (2.5.4.*) ---
|
|
39
|
+
["2.5.4.3", "commonName"],
|
|
40
|
+
["2.5.4.4", "surname"],
|
|
41
|
+
["2.5.4.5", "serialNumber"],
|
|
42
|
+
["2.5.4.6", "countryName"],
|
|
43
|
+
["2.5.4.7", "localityName"],
|
|
44
|
+
["2.5.4.8", "stateOrProvinceName"],
|
|
45
|
+
["2.5.4.9", "streetAddress"],
|
|
46
|
+
["2.5.4.10", "organizationName"],
|
|
47
|
+
["2.5.4.11", "organizationalUnitName"],
|
|
48
|
+
["2.5.4.12", "title"],
|
|
49
|
+
["2.5.4.42", "givenName"],
|
|
50
|
+
["0.9.2342.19200300.100.1.25", "domainComponent"],
|
|
51
|
+
["1.2.840.113549.1.9.1", "emailAddress"],
|
|
52
|
+
|
|
53
|
+
// --- Public-key + signature algorithms ---
|
|
54
|
+
["1.2.840.113549.1.1.1", "rsaEncryption"],
|
|
55
|
+
["1.2.840.113549.1.1.10", "rsassaPss"],
|
|
56
|
+
["1.2.840.113549.1.1.11", "sha256WithRSAEncryption"],
|
|
57
|
+
["1.2.840.113549.1.1.12", "sha384WithRSAEncryption"],
|
|
58
|
+
["1.2.840.113549.1.1.13", "sha512WithRSAEncryption"],
|
|
59
|
+
["1.2.840.10045.2.1", "ecPublicKey"],
|
|
60
|
+
["1.2.840.10045.4.3.2", "ecdsaWithSHA256"],
|
|
61
|
+
["1.2.840.10045.4.3.3", "ecdsaWithSHA384"],
|
|
62
|
+
["1.2.840.10045.4.3.4", "ecdsaWithSHA512"],
|
|
63
|
+
["1.2.840.10045.3.1.7", "prime256v1"],
|
|
64
|
+
["1.3.132.0.34", "secp384r1"],
|
|
65
|
+
["1.3.132.0.35", "secp521r1"],
|
|
66
|
+
["1.3.101.110", "X25519"],
|
|
67
|
+
["1.3.101.111", "X448"],
|
|
68
|
+
["1.3.101.112", "Ed25519"],
|
|
69
|
+
["1.3.101.113", "Ed448"],
|
|
70
|
+
|
|
71
|
+
// --- Digests ---
|
|
72
|
+
["2.16.840.1.101.3.4.2.1", "sha256"],
|
|
73
|
+
["2.16.840.1.101.3.4.2.2", "sha384"],
|
|
74
|
+
["2.16.840.1.101.3.4.2.3", "sha512"],
|
|
75
|
+
["2.16.840.1.101.3.4.2.8", "sha3-256"],
|
|
76
|
+
["2.16.840.1.101.3.4.2.10", "sha3-512"],
|
|
77
|
+
["2.16.840.1.101.3.4.2.12", "shake256"],
|
|
78
|
+
|
|
79
|
+
// --- RFC 5280 certificate extensions (2.5.29.*) ---
|
|
80
|
+
["2.5.29.14", "subjectKeyIdentifier"],
|
|
81
|
+
["2.5.29.15", "keyUsage"],
|
|
82
|
+
["2.5.29.17", "subjectAltName"],
|
|
83
|
+
["2.5.29.18", "issuerAltName"],
|
|
84
|
+
["2.5.29.19", "basicConstraints"],
|
|
85
|
+
["2.5.29.30", "nameConstraints"],
|
|
86
|
+
["2.5.29.31", "cRLDistributionPoints"],
|
|
87
|
+
["2.5.29.32", "certificatePolicies"],
|
|
88
|
+
["2.5.29.35", "authorityKeyIdentifier"],
|
|
89
|
+
["2.5.29.37", "extKeyUsage"],
|
|
90
|
+
["1.3.6.1.5.5.7.1.1", "authorityInfoAccess"],
|
|
91
|
+
|
|
92
|
+
// --- NIST post-quantum algorithms ---
|
|
93
|
+
// FIPS 204 ML-DSA (signature-algorithm arc 2.16.840.1.101.3.4.3.*).
|
|
94
|
+
["2.16.840.1.101.3.4.3.17", "id-ml-dsa-44"],
|
|
95
|
+
["2.16.840.1.101.3.4.3.18", "id-ml-dsa-65"],
|
|
96
|
+
["2.16.840.1.101.3.4.3.19", "id-ml-dsa-87"],
|
|
97
|
+
// FIPS 203 ML-KEM (KEM arc 2.16.840.1.101.3.4.4.*).
|
|
98
|
+
["2.16.840.1.101.3.4.4.1", "id-ml-kem-512"],
|
|
99
|
+
["2.16.840.1.101.3.4.4.2", "id-ml-kem-768"],
|
|
100
|
+
["2.16.840.1.101.3.4.4.3", "id-ml-kem-1024"],
|
|
101
|
+
// FIPS 205 SLH-DSA (a representative slice of the SHAKE-256 family).
|
|
102
|
+
["2.16.840.1.101.3.4.3.20", "id-slh-dsa-sha2-128s"],
|
|
103
|
+
["2.16.840.1.101.3.4.3.24", "id-slh-dsa-shake-128s"],
|
|
104
|
+
["2.16.840.1.101.3.4.3.27", "id-slh-dsa-shake-256s"],
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
var _byOid = new Map();
|
|
108
|
+
var _byName = new Map();
|
|
109
|
+
|
|
110
|
+
function _index(dotted, name) {
|
|
111
|
+
_byOid.set(dotted, name);
|
|
112
|
+
// First registration of a name wins as the canonical reverse entry.
|
|
113
|
+
if (!_byName.has(name)) _byName.set(name, dotted);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (var _i = 0; _i < SEED.length; _i++) _index(SEED[_i][0], SEED[_i][1]);
|
|
117
|
+
|
|
118
|
+
function _assertDotted(dotted, who) {
|
|
119
|
+
if (typeof dotted !== "string" || !/^\d+(\.\d+)+$/.test(dotted)) {
|
|
120
|
+
throw new OidError("oid/bad-input", who + ": expected a dotted-decimal OID string");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @primitive pki.oid.name
|
|
126
|
+
* @signature pki.oid.name(dotted) -> string | undefined
|
|
127
|
+
* @since 0.1.0
|
|
128
|
+
* @status stable
|
|
129
|
+
* @related pki.oid.byName, pki.oid.register
|
|
130
|
+
*
|
|
131
|
+
* Resolve a dotted OID to its registered name. Returns `undefined` for an
|
|
132
|
+
* unregistered OID (a caller that needs the raw arc keeps the dotted
|
|
133
|
+
* string); throws `OidError` only when the argument isn't a dotted OID.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* pki.oid.name("1.2.840.113549.1.1.11"); // -> "sha256WithRSAEncryption"
|
|
137
|
+
*/
|
|
138
|
+
function name(dotted) {
|
|
139
|
+
_assertDotted(dotted, "name");
|
|
140
|
+
return _byOid.get(dotted);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function byName(n) {
|
|
144
|
+
if (typeof n !== "string" || n.length === 0) throw new OidError("oid/bad-input", "byName: expected a name string");
|
|
145
|
+
return _byName.get(n);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function has(dotted) {
|
|
149
|
+
_assertDotted(dotted, "has");
|
|
150
|
+
return _byOid.has(dotted);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @primitive pki.oid.register
|
|
155
|
+
* @signature pki.oid.register(dotted, name) -> void
|
|
156
|
+
* @since 0.1.0
|
|
157
|
+
* @status stable
|
|
158
|
+
* @related pki.oid.name
|
|
159
|
+
*
|
|
160
|
+
* Add (or override) an OID → name mapping so an operator's private or
|
|
161
|
+
* newly-standardized arc resolves through the same registry as the seed
|
|
162
|
+
* set. A later registration of the same OID replaces the forward name;
|
|
163
|
+
* the reverse (name → OID) keeps the first registration as canonical.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* pki.oid.register("1.3.6.1.4.1.99999.1", "acmeWidgetPolicy");
|
|
167
|
+
*/
|
|
168
|
+
function register(dotted, n) {
|
|
169
|
+
_assertDotted(dotted, "register");
|
|
170
|
+
if (typeof n !== "string" || n.length === 0) throw new OidError("oid/bad-input", "register: name must be a non-empty string");
|
|
171
|
+
_index(dotted, n);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function all() {
|
|
175
|
+
var out = {};
|
|
176
|
+
_byOid.forEach(function (v, k) { out[k] = v; });
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// toArcs / fromArcs — dotted <-> numeric arc array. Arcs come back as
|
|
181
|
+
// Number where safe and BigInt where an arc exceeds 2^53 (rare, but a
|
|
182
|
+
// UUID-based OID arc can), so the round-trip never loses precision.
|
|
183
|
+
function toArcs(dotted) {
|
|
184
|
+
_assertDotted(dotted, "toArcs");
|
|
185
|
+
return dotted.split(".").map(function (p) {
|
|
186
|
+
var b = BigInt(p);
|
|
187
|
+
return b <= 9007199254740991n ? Number(b) : b;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function fromArcs(arcs) {
|
|
192
|
+
if (!Array.isArray(arcs) || arcs.length < 2) throw new OidError("oid/bad-input", "fromArcs: expected an array of >= 2 arcs");
|
|
193
|
+
return arcs.map(function (a) {
|
|
194
|
+
if (typeof a === "bigint") return a.toString();
|
|
195
|
+
if (typeof a === "number" && Number.isInteger(a) && a >= 0) return String(a);
|
|
196
|
+
throw new OidError("oid/bad-arc", "fromArcs: arc " + String(a) + " is not a non-negative integer");
|
|
197
|
+
}).join(".");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// DER convenience — thin pass-throughs to the codec so callers reach for
|
|
201
|
+
// one namespace when they have a dotted string in hand.
|
|
202
|
+
function toDER(dotted) { _assertDotted(dotted, "toDER"); return asn1.build.oid(dotted); }
|
|
203
|
+
function fromDER(input) {
|
|
204
|
+
var buf = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
205
|
+
var node = asn1.decode(buf);
|
|
206
|
+
return asn1.read.oid(node);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = {
|
|
210
|
+
name: name,
|
|
211
|
+
byName: byName,
|
|
212
|
+
has: has,
|
|
213
|
+
register: register,
|
|
214
|
+
all: all,
|
|
215
|
+
toArcs: toArcs,
|
|
216
|
+
fromArcs: fromArcs,
|
|
217
|
+
toDER: toDER,
|
|
218
|
+
fromDER: fromDER,
|
|
219
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Vendored dependencies — none currently. @blamejs/pki's cryptography runs entirely on Node's built-in node:crypto: the classical algorithm set (RSA, ECDSA, EdDSA, ECDH, AES, HMAC, HKDF, PBKDF2, the SHA family) AND the FIPS 203/204/205 post-quantum algorithms (ML-KEM, ML-DSA, SLH-DSA) via the platform OpenSSL 3.5 that the Node engine floor (>=24.18) ships. A built-in ships zero bytes and is OpenSSL-interoperable by construction, so no crypto bundle is vendored. A package is added here ONLY when a specific operation is confirmed missing from the engine floor; see lib/vendor/README.md for the policy.",
|
|
3
|
+
"packages": {}
|
|
4
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Vendored dependencies
|
|
2
|
+
|
|
3
|
+
`@blamejs/pki` ships with **zero npm runtime dependencies**, and it currently
|
|
4
|
+
vendors **nothing** — this directory holds only the manifest.
|
|
5
|
+
|
|
6
|
+
## Native-first crypto
|
|
7
|
+
|
|
8
|
+
The toolkit's cryptography runs entirely on Node's built-in `node:crypto`. The
|
|
9
|
+
engine floor (Node `>=24.18`) links OpenSSL 3.5, which provides:
|
|
10
|
+
|
|
11
|
+
- the full classical set — RSA (PKCS#1 v1.5, PSS, OAEP), ECDSA, EdDSA
|
|
12
|
+
(Ed25519/Ed448), ECDH (incl. X25519/X448), AES (GCM/CBC/CTR/KW), HMAC, HKDF,
|
|
13
|
+
PBKDF2, and the SHA-1/2/3 family; and
|
|
14
|
+
- the FIPS post-quantum set — **ML-KEM** (FIPS 203), **ML-DSA** (FIPS 204), and
|
|
15
|
+
**SLH-DSA** (FIPS 205).
|
|
16
|
+
|
|
17
|
+
A platform built-in ships **zero bytes**, has **nothing to hash-pin or keep
|
|
18
|
+
current**, and is **OpenSSL/NSS-interoperable by construction** — which is what
|
|
19
|
+
the toolkit's interoperability gate needs. So there is no reason to vendor a
|
|
20
|
+
crypto library when the runtime already provides the primitive.
|
|
21
|
+
|
|
22
|
+
## When something IS vendored
|
|
23
|
+
|
|
24
|
+
A package is added here **only** when a specific operation is confirmed missing
|
|
25
|
+
from the engine floor (for example, a pure-JS fallback for a post-quantum
|
|
26
|
+
operation whose JS binding is absent on a supported Node version, or a
|
|
27
|
+
cross-check reference used only in tests). When that happens:
|
|
28
|
+
|
|
29
|
+
- `MANIFEST.json` records the package's version, SPDX license, upstream author,
|
|
30
|
+
source URL, exported surface, bundler invocation, CPE, and pinned SHA-256, so
|
|
31
|
+
any tampering is detectable and `scripts/check-vendor-currency.js` can gate
|
|
32
|
+
version drift.
|
|
33
|
+
- The top-level `NOTICE` gains the component's attribution.
|
|
34
|
+
- The reason and the re-open condition are recorded alongside the entry — a
|
|
35
|
+
vendored dependency is a deliberate, justified exception to native-first, not
|
|
36
|
+
a default.
|
|
37
|
+
|
|
38
|
+
Bundles, when present, are produced with
|
|
39
|
+
`esbuild --format=cjs --minify --platform=node` against the pinned upstream
|
|
40
|
+
version; refreshing one recomputes its SHA-256 and updates the bytes and the
|
|
41
|
+
matching `hashes.server` entry in the same change.
|