@blamejs/pki 0.6.14 → 0.6.16
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 +27 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +140 -286
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/hpke.js
CHANGED
|
@@ -28,14 +28,11 @@ var nodeCrypto = require("crypto");
|
|
|
28
28
|
var frameworkError = require("./framework-error");
|
|
29
29
|
var guard = require("./guard-all");
|
|
30
30
|
var intrinsic = require("./guard-intrinsic");
|
|
31
|
-
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
32
|
-
// replaceable properties to answer one thing, and here it decides which suite parameters are read.
|
|
33
31
|
var _hasOwn = intrinsic.hasOwn;
|
|
34
32
|
|
|
35
33
|
var HpkeError = frameworkError.HpkeError;
|
|
36
34
|
function _err(code, message, cause) { return new HpkeError(code, message, cause); }
|
|
37
35
|
|
|
38
|
-
// ---- I2OSP + byte helpers ----------------------------------------------------
|
|
39
36
|
|
|
40
37
|
function i2osp(n, len) {
|
|
41
38
|
var b = Buffer.alloc(len);
|
|
@@ -49,28 +46,17 @@ function xor(a, b) { var o = Buffer.alloc(a.length); for (var i = 0; i < a.lengt
|
|
|
49
46
|
var HPKE_V1 = Buffer.from("HPKE-v1", "ascii");
|
|
50
47
|
function L(s) { return Buffer.from(s, "ascii"); }
|
|
51
48
|
|
|
52
|
-
// ---- registries: the RFC 9180 sec. 7 data tables (Tables 2/3/5) --------------
|
|
53
49
|
|
|
54
|
-
// KDF: id -> { hash (node name), Nh }.
|
|
55
|
-
// HKDF-SHA384 (0x0002) and DHKEM(P-384) (0x0011) are registered by RFC 9180 sec.
|
|
56
|
-
// 7 but Appendix A / the cited [TestVectors] file ship no known-answer vector for
|
|
57
|
-
// them, so they are omitted here until an authoritative KAT exists. A request for
|
|
58
|
-
// either fails closed as hpke/unknown-suite, because no test vector proves the
|
|
59
|
-
// crypto it would otherwise run. They admit as a one-row addition the moment a
|
|
60
|
-
// KAT lands.
|
|
61
50
|
var KDFS = {
|
|
62
51
|
0x0001: { hash: "sha256", Nh: 32 },
|
|
63
52
|
0x0003: { hash: "sha512", Nh: 64 },
|
|
64
53
|
};
|
|
65
|
-
// AEAD: id -> { cipher (node name), Nk, Nn, Nt } or exportOnly.
|
|
66
54
|
var AEADS = {
|
|
67
55
|
0x0001: { cipher: "aes-128-gcm", Nk: 16, Nn: 12, Nt: 16 },
|
|
68
56
|
0x0002: { cipher: "aes-256-gcm", Nk: 32, Nn: 12, Nt: 16 },
|
|
69
57
|
0x0003: { cipher: "chacha20-poly1305", Nk: 32, Nn: 12, Nt: 16 },
|
|
70
58
|
0xFFFF: { exportOnly: true, Nn: 12 },
|
|
71
59
|
};
|
|
72
|
-
// KEM: id -> DHKEM group descriptor. `group` is the node keygen/import family;
|
|
73
|
-
// `kdf` is the KEM-internal KDF (its ExtractAndExpand); Nsecret = that KDF's Nh.
|
|
74
60
|
var KEMS = {
|
|
75
61
|
0x0010: { kind: "ec", curve: "P-256", nodeCurve: "prime256v1", kdf: 0x0001, Nsecret: 32, Npub: 65 },
|
|
76
62
|
0x0012: { kind: "ec", curve: "P-521", nodeCurve: "secp521r1", kdf: 0x0003, Nsecret: 64, Npub: 133 },
|
|
@@ -80,17 +66,11 @@ var KEMS = {
|
|
|
80
66
|
|
|
81
67
|
var MODE_BASE = 0x00, MODE_PSK = 0x01, MODE_AUTH = 0x02, MODE_AUTH_PSK = 0x03;
|
|
82
68
|
|
|
83
|
-
// Format a code point for an error message without assuming it is a number: a
|
|
84
|
-
// missing suiteIds member arrives as undefined, and undefined.toString(16) would
|
|
85
|
-
// crash the very error path meant to report it.
|
|
86
69
|
function _idStr(id) { return typeof id === "number" ? "0x" + id.toString(16) : String(id); }
|
|
87
70
|
function _kem(id) { var s = KEMS[id]; if (!s) throw _err("hpke/unknown-suite", "unsupported KEM id " + _idStr(id)); return s; }
|
|
88
71
|
function _kdf(id) { var s = KDFS[id]; if (!s) throw _err("hpke/unknown-suite", "unsupported KDF id " + _idStr(id)); return s; }
|
|
89
72
|
function _aead(id) { var s = AEADS[id]; if (!s) throw _err("hpke/unknown-suite", "unsupported AEAD id " + _idStr(id)); return s; }
|
|
90
73
|
|
|
91
|
-
// ---- labeled KDF (RFC 9180 sec. 4): HKDF Extract/Expand SPLIT over HMAC -------
|
|
92
|
-
// suite_id is the caller's ("KEM"||... for the KEM layer, "HPKE"||... for the
|
|
93
|
-
// rest). Extract = HMAC(salt-or-zeros, ikm); Expand = RFC 5869 sec. 2.3 feedback.
|
|
94
74
|
|
|
95
75
|
function _extract(hash, Nh, salt, ikm) {
|
|
96
76
|
var key = (salt && salt.length) ? salt : Buffer.alloc(Nh);
|
|
@@ -100,16 +80,11 @@ function _expand(hash, Nh, prk, info, len) {
|
|
|
100
80
|
if (len > 255 * Nh) throw _err("hpke/export-length", "requested length " + len + " exceeds 255*Nh");
|
|
101
81
|
var out = [], t = Buffer.alloc(0), n = Math.ceil(len / Nh);
|
|
102
82
|
for (var i = 1; i <= n; i++) {
|
|
103
|
-
// The feedback input carries the PREVIOUS output block, so each round's concatenation holds
|
|
104
|
-
// secret material and is cleared once the HMAC has absorbed it.
|
|
105
83
|
var fed = concat([t, info, Buffer.from([i])]);
|
|
106
84
|
t = nodeCrypto.createHmac(hash, prk).update(fed).digest();
|
|
107
85
|
guard.secret.zeroize(fed, HpkeError, "hpke/bad-input", "an expand feedback input");
|
|
108
86
|
out.push(t);
|
|
109
87
|
}
|
|
110
|
-
// An exact-sized owning buffer, not a VIEW over the joined blocks: this is keying material, and a
|
|
111
|
-
// caller clearing what it received must not leave the unused tail of the final HMAC block behind
|
|
112
|
-
// it. The accumulator and each block are cleared here.
|
|
113
88
|
var joined = concat(out);
|
|
114
89
|
var exact = Buffer.alloc(len);
|
|
115
90
|
joined.copy(exact, 0, 0, len);
|
|
@@ -118,8 +93,6 @@ function _expand(hash, Nh, prk, info, len) {
|
|
|
118
93
|
return exact;
|
|
119
94
|
}
|
|
120
95
|
function _labeledExtract(kdf, suiteId, salt, label, ikm) {
|
|
121
|
-
// The concatenation copies the input keying material (a DH secret or a PSK) into a fresh
|
|
122
|
-
// buffer, so that copy is cleared once extract has consumed it. The caller still owns .
|
|
123
96
|
var labeled = concat([HPKE_V1, suiteId, L(label), ikm]);
|
|
124
97
|
try { return _extract(kdf.hash, kdf.Nh, salt, labeled); }
|
|
125
98
|
finally { guard.secret.zeroize(labeled, HpkeError, "hpke/bad-input", "the labeled extract input"); }
|
|
@@ -128,14 +101,10 @@ function _labeledExpand(kdf, suiteId, prk, label, info, len) {
|
|
|
128
101
|
return _expand(kdf.hash, kdf.Nh, prk, concat([i2osp(len, 2), HPKE_V1, suiteId, L(label), info]), len);
|
|
129
102
|
}
|
|
130
103
|
|
|
131
|
-
// ---- KEM key (de)serialization + Diffie-Hellman (node:crypto) ----------------
|
|
132
104
|
|
|
133
105
|
var OKP_SPKI = { X25519: "302a300506032b656e032100", X448: "3042300506032b656f033900" };
|
|
134
106
|
var OKP_PKCS8 = { X25519: "302e020100300506032b656e04220420", X448: "3046020100300506032b656f043a0438" };
|
|
135
107
|
|
|
136
|
-
// Deserialize a serialized KEM public key (enc / pkRm / pkSm) into a node
|
|
137
|
-
// KeyObject. EC is an uncompressed 0x04 point (SerializePublicKey, sec. 7.1.1);
|
|
138
|
-
// OKP is the raw curve point. A wrong length / shape fails closed.
|
|
139
108
|
function _importPublic(kem, raw) {
|
|
140
109
|
if (!Buffer.isBuffer(raw) || raw.length !== kem.Npub) {
|
|
141
110
|
throw _err("hpke/bad-key", kem.curve + " public key must be " + kem.Npub + " bytes");
|
|
@@ -153,18 +122,13 @@ function _importPublic(kem, raw) {
|
|
|
153
122
|
throw _err("hpke/bad-key", "invalid " + kem.curve + " public key", e);
|
|
154
123
|
}
|
|
155
124
|
}
|
|
156
|
-
// Serialize a node public KeyObject back to the RFC 9180 wire encoding.
|
|
157
125
|
function _exportPublic(kem, keyObject) {
|
|
158
126
|
var jwk = keyObject.export({ format: "jwk" });
|
|
159
127
|
if (kem.kind === "ec") return concat([Buffer.from([0x04]), _b64u(jwk.x), _b64u(jwk.y)]);
|
|
160
128
|
return _b64u(jwk.x);
|
|
161
129
|
}
|
|
162
|
-
// Node's own JWK export -- decode its canonical base64url coordinates through the
|
|
163
|
-
// guard so the alphabet/canonicality check is the single shared choke point.
|
|
164
130
|
function _b64u(s) { return guard.encoding.base64url(s, null, _err, "hpke/bad-key", "JWK coordinate"); }
|
|
165
131
|
|
|
166
|
-
// Import a serialized KEM private key (skRm / skEm / skSm) into a node KeyObject,
|
|
167
|
-
// given the matching serialized public key for the EC JWK (or none for OKP).
|
|
168
132
|
function _importPrivate(kem, rawSk, rawPk) {
|
|
169
133
|
try {
|
|
170
134
|
if (kem.kind === "ec") {
|
|
@@ -177,11 +141,6 @@ function _importPrivate(kem, rawSk, rawPk) {
|
|
|
177
141
|
throw _err("hpke/bad-key", "invalid " + kem.curve + " private key", e);
|
|
178
142
|
}
|
|
179
143
|
}
|
|
180
|
-
// The single KEM Diffie-Hellman choke point. node:crypto throws a raw
|
|
181
|
-
// ERR_OSSL_FAILED_DURING_DERIVATION when the peer point is low-order / invalid
|
|
182
|
-
// (an all-zero X25519 shared secret, a point not on the curve). RFC 9180 sec.
|
|
183
|
-
// 4.1: Decap raises an error on DH failure. Surface it as a typed hpke/bad-key
|
|
184
|
-
// so no raw OpenSSL error escapes any encap / decap path.
|
|
185
144
|
function _dh(privateKey, publicKey) {
|
|
186
145
|
try {
|
|
187
146
|
return nodeCrypto.diffieHellman({ privateKey: privateKey, publicKey: publicKey });
|
|
@@ -194,34 +153,22 @@ function _generate(kem) {
|
|
|
194
153
|
return nodeCrypto.generateKeyPairSync(kem.curve.toLowerCase());
|
|
195
154
|
}
|
|
196
155
|
|
|
197
|
-
// ---- DHKEM (RFC 9180 sec. 4.1) -----------------------------------------------
|
|
198
156
|
|
|
199
157
|
function _kemSuiteId(kemId) { return concat([L("KEM"), i2osp(kemId, 2)]); }
|
|
200
158
|
function _extractAndExpand(kem, dh, kemContext) {
|
|
201
159
|
var kdf = _kdf(kem.kdf), sid = _kemSuiteId(_kemId(kem));
|
|
202
|
-
// The extract PRK is a secret intermediate this function allocated: it is as sensitive as the DH
|
|
203
|
-
// input it summarises, so it is cleared once the expand has consumed it.
|
|
204
160
|
var eaePrk = _labeledExtract(kdf, sid, Buffer.alloc(0), "eae_prk", dh);
|
|
205
161
|
try { return _labeledExpand(kdf, sid, eaePrk, "shared_secret", kemContext, kem.Nsecret); }
|
|
206
162
|
finally { guard.secret.zeroize(eaePrk, HpkeError, "hpke/bad-input", "the KEM extract PRK"); }
|
|
207
163
|
}
|
|
208
164
|
function _kemId(kem) { for (var k in KEMS) if (KEMS[k] === kem) return Number(k); return 0; }
|
|
209
165
|
|
|
210
|
-
// Encap(pkR) -> { sharedSecret, enc }. An injected ephemeral (skE, pkEnc) makes
|
|
211
|
-
// the KAT deterministic; otherwise a fresh ephemeral is generated.
|
|
212
166
|
function _ephemeral(kem, eph) {
|
|
213
167
|
if (eph) return { skE: _importPrivate(kem, eph.skm, eph.pkm), enc: _buf(eph.pkm) };
|
|
214
168
|
var kp = _generate(kem);
|
|
215
169
|
return { skE: kp.privateKey, enc: _exportPublic(kem, kp.publicKey) };
|
|
216
170
|
}
|
|
217
|
-
// The raw DH output is the KEM's shared secret before extraction: it is this module's allocation
|
|
218
|
-
// and is cleared once _extractAndExpand has consumed it, on every DHKEM arm. In the authenticated
|
|
219
|
-
// arms builds a THIRD copy holding both DH results, so the concatenation is cleared as
|
|
220
|
-
// well, since clearing only the pieces would leave the joined buffer readable.
|
|
221
171
|
function _wipeDh(dh) { guard.secret.zeroize(dh, HpkeError, "hpke/bad-input", "the KEM shared secret"); }
|
|
222
|
-
// Every agreement happens inside the protected region. In the authenticated modes the second
|
|
223
|
-
// agreement can throw on an attacker-supplied low-order sender key, and a first secret computed
|
|
224
|
-
// outside the region would be left behind on exactly that failure path.
|
|
225
172
|
function _encap(kem, pkR, pkRm, eph) {
|
|
226
173
|
var e = _ephemeral(kem, eph);
|
|
227
174
|
var dh = null;
|
|
@@ -259,7 +206,6 @@ function _authDecap(kem, enc, skR, pkRm, pkS, pkSm) {
|
|
|
259
206
|
} finally { _wipeDh(dh1); _wipeDh(dh2); _wipeDh(dh); }
|
|
260
207
|
}
|
|
261
208
|
|
|
262
|
-
// ---- key schedule (RFC 9180 sec. 5.1) ----------------------------------------
|
|
263
209
|
|
|
264
210
|
function _hpkeSuiteId(kemId, kdfId, aeadId) { return concat([L("HPKE"), i2osp(kemId, 2), i2osp(kdfId, 2), i2osp(aeadId, 2)]); }
|
|
265
211
|
|
|
@@ -276,8 +222,6 @@ function _keySchedule(suite, mode, sharedSecret, info, psk, pskId, role) {
|
|
|
276
222
|
var pskIdHash = _labeledExtract(kdf, sid, Buffer.alloc(0), "psk_id_hash", pskId);
|
|
277
223
|
var infoHash = _labeledExtract(kdf, sid, Buffer.alloc(0), "info_hash", info);
|
|
278
224
|
var ksc = concat([Buffer.from([mode]), pskIdHash, infoHash]);
|
|
279
|
-
// is the key schedule's master PRK -- every key below is derived from it, so it is the
|
|
280
|
-
// most valuable intermediate here and is cleared once the derivations are done.
|
|
281
225
|
var secret = _labeledExtract(kdf, sid, sharedSecret, "secret", psk);
|
|
282
226
|
try {
|
|
283
227
|
var exporterSecret = _labeledExpand(kdf, sid, secret, "exp", ksc, kdf.Nh);
|
|
@@ -292,18 +236,11 @@ function _keySchedule(suite, mode, sharedSecret, info, psk, pskId, role) {
|
|
|
292
236
|
}
|
|
293
237
|
}
|
|
294
238
|
|
|
295
|
-
// ---- AEAD context (RFC 9180 sec. 5.2 / 5.3) ----------------------------------
|
|
296
239
|
|
|
297
|
-
// role is "S" (sender: seal only) or "R" (recipient: open only). Sender and
|
|
298
|
-
// recipient derive the same key + base_nonce, so a wrong-direction call would
|
|
299
|
-
// reuse a nonce (RFC 9180 sec. 5.2); export is available to both.
|
|
300
240
|
function Context(suite, key, baseNonce, exporterSecret, role) {
|
|
301
241
|
this._suite = suite; this._key = key; this._baseNonce = baseNonce;
|
|
302
242
|
this._exporterSecret = exporterSecret; this._seq = 0n; this._role = role;
|
|
303
243
|
}
|
|
304
|
-
// Clear the context's key material. The single-shot entry points below build a context, use it once
|
|
305
|
-
// and drop it, so without this the AEAD key, base nonce and exporter secret stay readable for the
|
|
306
|
-
// process lifetime -- the multi-message API hands the context to the caller, who owns its lifetime.
|
|
307
244
|
Context.prototype._dispose = function () {
|
|
308
245
|
guard.secret.zeroizeAll([this._key, this._baseNonce, this._exporterSecret], HpkeError, "hpke/bad-input", "the HPKE context key material");
|
|
309
246
|
};
|
|
@@ -340,9 +277,6 @@ Context.prototype.open = function (aad, ct) {
|
|
|
340
277
|
if (aad && aad.length) d.setAAD(aad);
|
|
341
278
|
d.setAuthTag(ct.subarray(ct.length - aead.Nt));
|
|
342
279
|
var pt;
|
|
343
|
-
// Through the shared guard so the recovered plaintext is destroyed when the AEAD tag does not
|
|
344
|
-
// check out (RFC 9180 sec. 5.2 gives open one outcome on failure, and it is not a copy left in
|
|
345
|
-
// the heap), and so the success path's abandoned intermediate is cleared too.
|
|
346
280
|
try { pt = guard.secret.cipherFinish(d, ct.subarray(0, ct.length - aead.Nt), HpkeError, "hpke/bad-input", "the recovered plaintext"); }
|
|
347
281
|
catch (e) { throw _err("hpke/open-failed", "AEAD authentication failed (RFC 9180 sec. 5.2)", e); }
|
|
348
282
|
this._inc();
|
|
@@ -354,7 +288,6 @@ Context.prototype.export = function (exporterContext, len) {
|
|
|
354
288
|
};
|
|
355
289
|
function _buf(x) { return Buffer.isBuffer(x) ? x : Buffer.from(x || []); }
|
|
356
290
|
|
|
357
|
-
// ---- suite resolution + setup ------------------------------------------------
|
|
358
291
|
|
|
359
292
|
function _suite(ids) {
|
|
360
293
|
if (!ids || typeof ids !== "object") throw _err("hpke/unknown-suite", "suiteIds must be an object { kem, kdf, aead } from pki.hpke.suites");
|
|
@@ -363,14 +296,9 @@ function _suite(ids) {
|
|
|
363
296
|
return { kem: kem, kdf: kdf, aead: aead, kemId: kemId, kdfId: kdfId, aeadId: aeadId, suiteId: _hpkeSuiteId(kemId, kdfId, aeadId) };
|
|
364
297
|
}
|
|
365
298
|
|
|
366
|
-
// A KEM key argument is a node KeyObject or serialized bytes: a public key as a
|
|
367
|
-
// raw buffer (or { pkm }), a private key as { skm, pkm } (raw scalar + public
|
|
368
|
-
// point). A KAT drives the deterministic path via those serialized forms.
|
|
369
299
|
function _recipPublic(suite, pk) {
|
|
370
300
|
if (Buffer.isBuffer(pk)) return { key: _importPublic(suite.kem, pk), pkm: pk };
|
|
371
301
|
if (pk && pk.pkm) return { key: _importPublic(suite.kem, pk.pkm), pkm: pk.pkm };
|
|
372
|
-
// A node KeyObject: derive its wire form. A null/undefined/invalid value must
|
|
373
|
-
// fail closed here, so no raw error from node's export escapes upward.
|
|
374
302
|
try {
|
|
375
303
|
return { key: pk, pkm: _exportPublic(suite.kem, pk) };
|
|
376
304
|
} catch (e) {
|
|
@@ -380,10 +308,6 @@ function _recipPublic(suite, pk) {
|
|
|
380
308
|
}
|
|
381
309
|
function _recipPrivate(suite, sk) {
|
|
382
310
|
if (sk && sk.skm) return { key: _importPrivate(suite.kem, sk.skm, sk.pkm), pkm: sk.pkm };
|
|
383
|
-
// A raw scalar alone cannot be imported (an EC private key needs its public
|
|
384
|
-
// point for the JWK), so the serialized private form is { skm, pkm }; reject a
|
|
385
|
-
// bare buffer, and fail any other bad key closed; a raw createPublicKey error
|
|
386
|
-
// must not escape the setup path.
|
|
387
311
|
if (Buffer.isBuffer(sk)) throw _err("hpke/bad-key", "a serialized private key must be provided as { skm, pkm } (raw scalar + public point), not a bare buffer");
|
|
388
312
|
try {
|
|
389
313
|
var pkm = _exportPublic(suite.kem, nodeCrypto.createPublicKey(sk));
|
|
@@ -394,21 +318,6 @@ function _recipPrivate(suite, sk) {
|
|
|
394
318
|
}
|
|
395
319
|
}
|
|
396
320
|
|
|
397
|
-
// Resolve and validate the HPKE mode: RFC 9180 sec. 5.1 defines exactly base /
|
|
398
|
-
// psk / auth / auth-psk. An unknown mode must fail closed, never key-schedule
|
|
399
|
-
// with an out-of-registry mode byte.
|
|
400
|
-
// The sender and the recipient read the same option object from OPPOSITE ends, so each direction
|
|
401
|
-
// gets its own table. A shared union recognizes every name at both ends and so accepts the one that
|
|
402
|
-
// cannot do anything there: `senderPublicKey` handed to setupS, or
|
|
403
|
-
// `senderKey` handed to setupR, is silently ignored, which is the exact silence these tables exist
|
|
404
|
-
// to remove, in a wider form. An option that means nothing where it was passed is a misunderstanding
|
|
405
|
-
// worth reporting, and usually a misdirected auth-mode setup.
|
|
406
|
-
//
|
|
407
|
-
// Every one of these narrows or authenticates: a misspelled `psk` leaves a psk-mode setup with no
|
|
408
|
-
// pre-shared key and a misspelled `senderKey` leaves an auth-mode setup unauthenticated, each
|
|
409
|
-
// failing with a message about the field the caller believes they supplied. `eph` is the
|
|
410
|
-
// deterministic-KAT seam and is sender-side only; supplied by mistake it fixes the ephemeral key,
|
|
411
|
-
// which is the one option here whose silent acceptance is catastrophic.
|
|
412
321
|
var _COMMON_KEYS = { mode: 1, info: 1, psk: 1, pskId: 1 };
|
|
413
322
|
function _withCommon(extra) {
|
|
414
323
|
var out = {};
|
|
@@ -417,11 +326,11 @@ function _withCommon(extra) {
|
|
|
417
326
|
return out;
|
|
418
327
|
}
|
|
419
328
|
var _SETUP_S_KEYS = _withCommon({
|
|
420
|
-
senderKey: 1,
|
|
421
|
-
eph: 1,
|
|
329
|
+
senderKey: 1,
|
|
330
|
+
eph: 1,
|
|
422
331
|
});
|
|
423
332
|
var _SETUP_R_KEYS = _withCommon({
|
|
424
|
-
senderPublicKey: 1,
|
|
333
|
+
senderPublicKey: 1,
|
|
425
334
|
});
|
|
426
335
|
function _mode(opts, keys, who) {
|
|
427
336
|
guard.identifier.assertKnownKeys(opts, keys, _err, "hpke/bad-input", function (k) {
|
|
@@ -438,8 +347,6 @@ function _mode(opts, keys, who) {
|
|
|
438
347
|
return mode;
|
|
439
348
|
}
|
|
440
349
|
|
|
441
|
-
// pki.hpke.suites -- the RFC 9180 sec. 7 registry code points (KEM / KDF / AEAD /
|
|
442
|
-
// MODE), passed to the setup functions to select a ciphersuite (documented in the
|
|
443
350
|
// @module @intro; a data registry, not a callable primitive).
|
|
444
351
|
var suites = {
|
|
445
352
|
KEM: { DHKEM_P256_HKDF_SHA256: 0x0010, DHKEM_P521_HKDF_SHA512: 0x0012, DHKEM_X25519_HKDF_SHA256: 0x0020, DHKEM_X448_HKDF_SHA512: 0x0021 },
|
|
@@ -448,8 +355,6 @@ var suites = {
|
|
|
448
355
|
MODE: { BASE: MODE_BASE, PSK: MODE_PSK, AUTH: MODE_AUTH, AUTH_PSK: MODE_AUTH_PSK },
|
|
449
356
|
};
|
|
450
357
|
|
|
451
|
-
// setup*S(suiteIds, pkR, opts) -> { enc, context }. opts: { info, psk, pskId,
|
|
452
|
-
// senderKey (auth), eph (KAT determinism) }. setup*R(suiteIds, enc, skR, opts).
|
|
453
358
|
|
|
454
359
|
/**
|
|
455
360
|
* @primitive pki.hpke.setupS
|
|
@@ -528,8 +433,6 @@ function _setupR(ids, enc, skR, opts) {
|
|
|
528
433
|
} else {
|
|
529
434
|
ss = _decap(kem, _buf(enc), r.key, r.pkm);
|
|
530
435
|
}
|
|
531
|
-
// The recipient derives this shared secret and never hands it back (unlike setupS, whose caller
|
|
532
|
-
// receives it and owns it from then on), so it is cleared once the key schedule has consumed it.
|
|
533
436
|
try { return _keySchedule(suite, mode, ss, _buf(opts.info), _buf(opts.psk), _buf(opts.pskId), "R"); }
|
|
534
437
|
finally { guard.secret.zeroize(ss, HpkeError, "hpke/bad-input", "the KEM shared secret"); }
|
|
535
438
|
}
|
|
@@ -553,14 +456,9 @@ function _setupR(ids, enc, skR, opts) {
|
|
|
553
456
|
* var out = pki.hpke.seal(ids, pkR, {}, Buffer.from("aad"), Buffer.from("msg"));
|
|
554
457
|
*/
|
|
555
458
|
function seal(ids, pkR, opts, aad, pt) {
|
|
556
|
-
// _setupS itself can reject after allocating (an unusable AEAD, a bad psk pairing), so the whole
|
|
557
|
-
// call is inside the protected region rather than only what follows a successful setup.
|
|
558
459
|
var s = null;
|
|
559
460
|
try {
|
|
560
461
|
s = _setupS(ids, pkR, opts);
|
|
561
|
-
// The single-shot wrapper returns neither the setup result nor its shared secret, so no caller can
|
|
562
|
-
// own that buffer -- it is cleared here along with the context. setupS itself still hands both to
|
|
563
|
-
// its caller, whose lifetime they become.
|
|
564
462
|
return { enc: s.enc, ct: s.context.seal(_buf(aad), _buf(pt)) };
|
|
565
463
|
} finally {
|
|
566
464
|
if (s) {
|