@blamejs/pki 0.6.13 → 0.6.15
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 +22 -1
- package/README.md +1 -1
- package/index.js +1 -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 +59 -233
- 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/shbs.js
CHANGED
|
@@ -30,30 +30,19 @@ var constants = require("./constants");
|
|
|
30
30
|
var ShbsError = frameworkError.ShbsError;
|
|
31
31
|
function _err(code, message, cause) { return new ShbsError(code, message, cause); }
|
|
32
32
|
|
|
33
|
-
// ---- big-endian serialization (RFC 8554 sec. 3.1: network byte order) --------
|
|
34
33
|
|
|
35
34
|
function u8(x) { return Buffer.from([x & 0xff]); }
|
|
36
35
|
function u16(x) { return Buffer.from([(x >> 8) & 0xff, x & 0xff]); }
|
|
37
36
|
function u32(x) { return Buffer.from([(x >>> 24) & 0xff, (x >>> 16) & 0xff, (x >>> 8) & 0xff, x & 0xff]); }
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
var
|
|
43
|
-
var D_MESG = 0x8181; // message hash
|
|
44
|
-
var D_LEAF = 0x8282; // LMS leaf hash
|
|
45
|
-
var D_INTR = 0x8383; // LMS interior node hash
|
|
38
|
+
var D_PBLC = 0x8080;
|
|
39
|
+
var D_MESG = 0x8181;
|
|
40
|
+
var D_LEAF = 0x8282;
|
|
41
|
+
var D_INTR = 0x8383;
|
|
46
42
|
|
|
47
|
-
// ---- typecode registry (IANA Leighton-Micali Signatures; NIST SP 800-208) ----
|
|
48
|
-
// LMS 0x05..0x18 : {m, h, hashFamily}. LM-OTS 0x01..0x10 : {n, w, hashFamily}.
|
|
49
|
-
// p and ls are DERIVED from (n, w) per RFC 8554 sec. 4.1 / Appendix B so the
|
|
50
|
-
// table stays data, not a switch, and a wrong hardcoded p cannot creep in.
|
|
51
43
|
|
|
52
44
|
function _ilog2(x) { var r = 0; while (x > 1) { x = Math.floor(x / 2); r += 1; } return r; }
|
|
53
45
|
|
|
54
|
-
// RFC 8554 Appendix B: u = ceil(8n/w) hash digits; v = the checksum digit count;
|
|
55
|
-
// p = u + v total Winternitz digits; ls = the left-shift aligning the checksum's
|
|
56
|
-
// significant bits into the digit positions coef reads.
|
|
57
46
|
function _deriveWinternitz(n, w) {
|
|
58
47
|
var u = Math.ceil((8 * n) / w);
|
|
59
48
|
var v = Math.ceil((_ilog2((Math.pow(2, w) - 1) * u) + 1) / w);
|
|
@@ -63,16 +52,12 @@ function _deriveWinternitz(n, w) {
|
|
|
63
52
|
var LMS_SETS = {};
|
|
64
53
|
var LMOTS_SETS = {};
|
|
65
54
|
(function seed() {
|
|
66
|
-
var heights = [5, 10, 15, 20, 25];
|
|
67
|
-
var ws = [1, 2, 4, 8];
|
|
68
|
-
// LMS families in IANA order: SHA-256/M32 (0x05), SHA-256/M24 (0x0A),
|
|
69
|
-
// SHAKE/M32 (0x0F), SHAKE/M24 (0x14).
|
|
55
|
+
var heights = [5, 10, 15, 20, 25];
|
|
56
|
+
var ws = [1, 2, 4, 8];
|
|
70
57
|
[{ base: 0x05, m: 32, f: "sha256" }, { base: 0x0A, m: 24, f: "sha256" },
|
|
71
58
|
{ base: 0x0F, m: 32, f: "shake256" }, { base: 0x14, m: 24, f: "shake256" }].forEach(function (fam) {
|
|
72
59
|
heights.forEach(function (h, i) { LMS_SETS[fam.base + i] = { code: fam.base + i, m: fam.m, h: h, hashFamily: fam.f }; });
|
|
73
60
|
});
|
|
74
|
-
// LM-OTS families: SHA-256/N32 (0x01), SHA-256/N24 (0x05), SHAKE/N32 (0x09),
|
|
75
|
-
// SHAKE/N24 (0x0D).
|
|
76
61
|
[{ base: 0x01, n: 32, f: "sha256" }, { base: 0x05, n: 24, f: "sha256" },
|
|
77
62
|
{ base: 0x09, n: 32, f: "shake256" }, { base: 0x0D, n: 24, f: "shake256" }].forEach(function (fam) {
|
|
78
63
|
ws.forEach(function (w, i) {
|
|
@@ -93,8 +78,6 @@ function _lmotsSet(code) {
|
|
|
93
78
|
return s;
|
|
94
79
|
}
|
|
95
80
|
|
|
96
|
-
// hashFamily + output length -> the n-byte hash of the concatenated inputs.
|
|
97
|
-
// SHA-256 truncated to n (n=24 is SHA-256/192); SHAKE256 with an n-byte output.
|
|
98
81
|
function _hash(family, n, parts) {
|
|
99
82
|
var h;
|
|
100
83
|
if (family === "shake256") h = nodeCrypto.createHash("shake256", { outputLength: n });
|
|
@@ -104,9 +87,6 @@ function _hash(family, n, parts) {
|
|
|
104
87
|
return (family === "shake256" || n === 32) ? d : d.subarray(0, n);
|
|
105
88
|
}
|
|
106
89
|
|
|
107
|
-
// ---- bounded big-endian reader (bounds-before-slice; the ct.js TlsReader model
|
|
108
|
-
// for a non-DER positional wire, kept shbs-local because shbs needs u32 typecodes
|
|
109
|
-
// and fixed-n reads, not CT's length-prefixed vector helpers) ----------------
|
|
110
90
|
|
|
111
91
|
function Reader(buf, code, label) { this.buf = buf; this.pos = 0; this.code = code; this.label = label; }
|
|
112
92
|
Reader.prototype._need = function (k) {
|
|
@@ -119,10 +99,7 @@ Reader.prototype.take = function (k) { this._need(k); var b = this.buf.subarray(
|
|
|
119
99
|
Reader.prototype.remaining = function () { return this.buf.length - this.pos; };
|
|
120
100
|
Reader.prototype.atEnd = function () { return this.pos === this.buf.length; };
|
|
121
101
|
|
|
122
|
-
// ---- Winternitz digit + checksum (RFC 8554 sec. 3.1.3 / sec. 4.4) ------------
|
|
123
102
|
|
|
124
|
-
// coef(S, i, w): the i-th w-bit digit of S, big-endian WITHIN each byte (digit 0
|
|
125
|
-
// is the high-order w bits of byte 0). RFC 8554 sec. 3.1.3.
|
|
126
103
|
function _coef(S, i, w) {
|
|
127
104
|
var idx = Math.floor((i * w) / 8);
|
|
128
105
|
if (idx >= S.length) throw _err("shbs/bad-signature", "Winternitz coefficient index out of range");
|
|
@@ -130,9 +107,6 @@ function _coef(S, i, w) {
|
|
|
130
107
|
return ((1 << w) - 1) & (S[idx] >> shift);
|
|
131
108
|
}
|
|
132
109
|
|
|
133
|
-
// Cksm(Q) per RFC 8554 sec. 4.4: sum over the u message digits of (2^w-1 - digit),
|
|
134
|
-
// left-shifted by ls, as a 2-byte big-endian value. Computed over Q ALONE; the
|
|
135
|
-
// coefficient index space in the chain walk spans Q || Cksm(Q).
|
|
136
110
|
function _cksm(Q, set) {
|
|
137
111
|
var w = set.w, sum = 0, u = Math.ceil((8 * set.n) / w);
|
|
138
112
|
for (var i = 0; i < u; i++) sum += ((1 << w) - 1) - _coef(Q, i, w);
|
|
@@ -140,9 +114,6 @@ function _cksm(Q, set) {
|
|
|
140
114
|
return u16(sum);
|
|
141
115
|
}
|
|
142
116
|
|
|
143
|
-
// ---- LM-OTS public-key candidate Kc (RFC 8554 Algorithm 4b) ------------------
|
|
144
|
-
// I, q identify the LMS leaf; otsSet is resolved from the PUBLIC KEY's otstype
|
|
145
|
-
// (the authority); C + y[] come from the signature. Returns the n-byte Kc.
|
|
146
117
|
|
|
147
118
|
function _lmotsKc(otsSet, I, q, C, y, message) {
|
|
148
119
|
var n = otsSet.n, w = otsSet.w, p = otsSet.p, f = otsSet.hashFamily;
|
|
@@ -153,21 +124,14 @@ function _lmotsKc(otsSet, I, q, C, y, message) {
|
|
|
153
124
|
for (var i = 0; i < p; i++) {
|
|
154
125
|
var a = _coef(Qc, i, w);
|
|
155
126
|
var tmp = y[i];
|
|
156
|
-
// Chain to 2^w - 1 total applications; the bound is EXCLUSIVE (last j = 2^w-2),
|
|
157
|
-
// matching key generation (RFC 8554 sec. 4.5 vs Algorithm 4b step 3).
|
|
158
127
|
for (var j = a; j < (1 << w) - 1; j++) tmp = _hash(f, n, [I, qb, u16(i), u8(j), tmp]);
|
|
159
128
|
z.push(tmp);
|
|
160
129
|
}
|
|
161
130
|
return _hash(f, n, z);
|
|
162
131
|
}
|
|
163
132
|
|
|
164
|
-
// ---- LMS verification core (RFC 8554 Algorithm 6 / 6a) -----------------------
|
|
165
|
-
// pubBytes = lmstype(4) || otstype(4) || I(16) || T[1](m); sigBytes an LMS
|
|
166
|
-
// signature. Returns true iff the recomputed Merkle root equals T[1]. Throws
|
|
167
|
-
// ShbsError on any structural fault; a well-formed-but-wrong signature -> false.
|
|
168
133
|
|
|
169
134
|
function _lmsVerify(pubBytes, message, sigBytes) {
|
|
170
|
-
// -- public key (the authority for both typecodes; RFC 8554 Algorithm 6) --
|
|
171
135
|
if (pubBytes.length < 8) throw _err("shbs/bad-public-key", "LMS public key is shorter than 8 bytes");
|
|
172
136
|
var pr = new Reader(pubBytes, "shbs/bad-public-key", "LMS public key");
|
|
173
137
|
var lmsSet = _lmsSet(pr.u32());
|
|
@@ -177,81 +141,46 @@ function _lmsVerify(pubBytes, message, sigBytes) {
|
|
|
177
141
|
var I = pr.take(16);
|
|
178
142
|
var T1 = pr.take(m);
|
|
179
143
|
|
|
180
|
-
// -- signature (RFC 8554 Algorithm 6a). q first, then the LM-OTS signature,
|
|
181
|
-
// then the LMS typecode (after the LM-OTS sig), then the auth path. --
|
|
182
144
|
if (sigBytes.length < 8) throw _err("shbs/bad-signature", "LMS signature is shorter than 8 bytes");
|
|
183
145
|
var sr = new Reader(sigBytes, "shbs/bad-signature", "LMS signature");
|
|
184
146
|
var q = sr.u32();
|
|
185
147
|
var otsSigType = sr.u32();
|
|
186
|
-
// The public key -- never the attacker-controlled signature -- is the authority
|
|
187
|
-
// (downgrade defense): a signature whose OTS typecode does not equal the one the
|
|
188
|
-
// public key commits to cannot verify against it. RFC 8554 Algorithm 6a checks
|
|
189
|
-
// the typecode (step 2c, and 2g for the LMS type below) before the length (steps
|
|
190
|
-
// 2d / 2i), and returns INVALID, a verification failure (false) and not a
|
|
191
|
-
// structural error. So a mismatch is `false` at this point even for a blob too
|
|
192
|
-
// short to be a complete signature: the mismatch is decidable once q + typecode
|
|
193
|
-
// are read (the bounded reader already threw if those 8 bytes are absent). A
|
|
194
|
-
// matching typecode with a truncated body still throws below (bounds-before-
|
|
195
|
-
// slice). Re-sizing the body by the signature's own mismatched typecode, so as to
|
|
196
|
-
// "fully validate before returning false", would violate this order and reject
|
|
197
|
-
// a legitimate typecode-mutation test vector as malformed instead of INVALID.
|
|
198
148
|
if (otsSigType !== otsSet.code) return false;
|
|
199
149
|
var n = otsSet.n, p = otsSet.p;
|
|
200
150
|
var C = sr.take(n);
|
|
201
151
|
var y = [];
|
|
202
152
|
for (var yi = 0; yi < p; yi++) y.push(sr.take(n));
|
|
203
153
|
var sigLmsType = sr.u32();
|
|
204
|
-
if (sigLmsType !== lmsSet.code) return false;
|
|
154
|
+
if (sigLmsType !== lmsSet.code) return false;
|
|
205
155
|
var path = [];
|
|
206
156
|
for (var pi = 0; pi < h; pi++) path.push(sr.take(m));
|
|
207
157
|
if (!sr.atEnd()) throw _err("shbs/bad-signature", "LMS signature has " + sr.remaining() + " trailing byte(s)");
|
|
208
|
-
// RFC 8554 Algorithm 6a step 2i: a leaf index q >= 2^h is INVALID, a
|
|
209
|
-
// verification failure (false) and not a structural error. Checked here, after
|
|
210
|
-
// the exact-length validation above (a truncated or trailing blob already threw
|
|
211
|
-
// typed), against the registry height and never the blob. 2^h fits Number (h<=25).
|
|
212
158
|
if (q >= Math.pow(2, h)) return false;
|
|
213
159
|
|
|
214
|
-
// -- recompute the LM-OTS public-key candidate, then fold the Merkle path --
|
|
215
160
|
var Kc = _lmotsKc(otsSet, I, q, C, y, message);
|
|
216
161
|
var nodeNum = Math.pow(2, h) + q;
|
|
217
162
|
var tmp = _hash(lmsSet.hashFamily, m, [I, u32(nodeNum), u16(D_LEAF), Kc]);
|
|
218
163
|
for (var i2 = 0; nodeNum > 1; i2++) {
|
|
219
164
|
var parent = Math.floor(nodeNum / 2);
|
|
220
165
|
if (nodeNum % 2 === 1) {
|
|
221
|
-
// odd node_num: the current node is a RIGHT child, sibling on the LEFT.
|
|
222
166
|
tmp = _hash(lmsSet.hashFamily, m, [I, u32(parent), u16(D_INTR), path[i2], tmp]);
|
|
223
167
|
} else {
|
|
224
168
|
tmp = _hash(lmsSet.hashFamily, m, [I, u32(parent), u16(D_INTR), tmp, path[i2]]);
|
|
225
169
|
}
|
|
226
170
|
nodeNum = parent;
|
|
227
171
|
}
|
|
228
|
-
// The root comparison is over PUBLIC values (T1 is in the public key, tmp is
|
|
229
|
-
// derived from public inputs -- no secret, hence the module's "no side-channel
|
|
230
|
-
// surface" note), so constant time is not strictly required. It routes through
|
|
231
|
-
// the shared crypto guard anyway, for one length-checked comparison primitive
|
|
232
|
-
// across the hash-tree verifiers (pki.merkle folds its root the same way).
|
|
233
172
|
return guard.crypto.constantTimeEqual(tmp, T1);
|
|
234
173
|
}
|
|
235
174
|
|
|
236
|
-
// Consume exactly one LMS public key from a reader (an HSS inner key), returning
|
|
237
|
-
// its raw bytes; sized off the LMS typecode it leads with.
|
|
238
175
|
function _consumeLmsPublicKey(r) {
|
|
239
176
|
var start = r.pos;
|
|
240
|
-
var code = r.u32();
|
|
241
|
-
r.u32();
|
|
177
|
+
var code = r.u32();
|
|
178
|
+
r.u32();
|
|
242
179
|
var m = _lmsSet(code).m;
|
|
243
|
-
r.take(16 + m);
|
|
180
|
+
r.take(16 + m);
|
|
244
181
|
return r.buf.subarray(start, r.pos);
|
|
245
182
|
}
|
|
246
183
|
|
|
247
|
-
// The exact byte length (12 + n*(p+1) + m*h) of an LMS signature verified under
|
|
248
|
-
// `keyBytes` -- the AUTHORITATIVE public key for its level. The concatenated HSS
|
|
249
|
-
// blob is split by the public key's typecodes, never the attacker-controlled
|
|
250
|
-
// signature's own: so a typecode-mutated signature is sliced to the length the
|
|
251
|
-
// key expects and then _lmsVerify returns false (a verification failure, RFC 8554
|
|
252
|
-
// Algorithm 6a 2c/2g), consistent with verifyLms, instead of throwing on a
|
|
253
|
-
// mis-sized slice. Bounds-guarded (no direct read that could escape as a raw
|
|
254
|
-
// RangeError on a truncated inner key).
|
|
255
184
|
function _lmsSigLen(keyBytes) {
|
|
256
185
|
if (keyBytes.length < 8) throw _err("shbs/bad-public-key", "LMS public key is shorter than 8 bytes");
|
|
257
186
|
var lmsSet = _lmsSet(keyBytes.readUInt32BE(0));
|
|
@@ -259,41 +188,28 @@ function _lmsSigLen(keyBytes) {
|
|
|
259
188
|
return 12 + otsSet.n * (otsSet.p + 1) + lmsSet.m * lmsSet.h;
|
|
260
189
|
}
|
|
261
190
|
|
|
262
|
-
// ---- HSS verification (RFC 8554 sec. 6.3) ------------------------------------
|
|
263
191
|
|
|
264
192
|
function _hssVerify(pubBytes, message, sigBytes) {
|
|
265
193
|
if (pubBytes.length < 4) throw _err("shbs/bad-public-key", "HSS public key is shorter than 4 bytes");
|
|
266
194
|
var pr = new Reader(pubBytes, "shbs/bad-public-key", "HSS public key");
|
|
267
195
|
var L = pr.u32();
|
|
268
196
|
if (L < 1 || L > constants.LIMITS.HSS_MAX_LEVELS) throw _err("shbs/bad-public-key", "HSS level count L=" + L + " is outside 1.." + constants.LIMITS.HSS_MAX_LEVELS);
|
|
269
|
-
var topKey = pr.buf.subarray(pr.pos);
|
|
270
|
-
// structure (including an under-length blob) is validated by _lmsVerify below
|
|
271
|
-
// (the < 8 and exact-24+m checks), so no direct read here that would bypass the
|
|
272
|
-
// bounded reader and escape as a raw RangeError on a truncated key.
|
|
197
|
+
var topKey = pr.buf.subarray(pr.pos);
|
|
273
198
|
|
|
274
199
|
var sr = new Reader(sigBytes, "shbs/bad-signature", "HSS signature");
|
|
275
200
|
var Nspk = sr.u32();
|
|
276
|
-
// The level-count gate, checked before parsing any component (RFC 8554 sec. 6.3).
|
|
277
201
|
if (Nspk + 1 !== L) throw _err("shbs/bad-signature", "HSS Nspk+1 (" + (Nspk + 1) + ") does not equal the public-key level count L (" + L + ")");
|
|
278
202
|
|
|
279
203
|
var key = topKey;
|
|
280
204
|
for (var i = 0; i < Nspk; i++) {
|
|
281
|
-
// Slice this level's LMS signature by the AUTHORITATIVE key's length (so a
|
|
282
|
-
// typecode mismatch is a false verdict via _lmsVerify, not a mis-sized throw).
|
|
283
205
|
var sig = sr.take(_lmsSigLen(key));
|
|
284
206
|
var nextKey = _consumeLmsPublicKey(sr);
|
|
285
|
-
// Each level signs the SERIALIZED next-level LMS public key; that recovered
|
|
286
|
-
// key becomes the verification key for the level below (chain of trust).
|
|
287
207
|
if (!_lmsVerify(key, nextKey, sig)) return false;
|
|
288
208
|
key = nextKey;
|
|
289
209
|
}
|
|
290
|
-
// The final signature is the remainder; _lmsVerify parses it, applies the
|
|
291
|
-
// public-key-authoritative typecode check (mismatch -> false), and rejects a
|
|
292
|
-
// trailing / truncated remainder (bounds-before-slice + its own atEnd check).
|
|
293
210
|
return _lmsVerify(key, message, sr.buf.subarray(sr.pos));
|
|
294
211
|
}
|
|
295
212
|
|
|
296
|
-
// ---- public surface ----------------------------------------------------------
|
|
297
213
|
|
|
298
214
|
function _asBytes(x, label) { return guard.bytes.source(x, ShbsError, "shbs/bad-input", label); }
|
|
299
215
|
|
package/lib/sign-scheme.js
CHANGED
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- no operator-facing namespace. The signature-scheme resolver + signer shared by
|
|
6
|
-
// every producer that signs a raw preimage with a certificate's key: pki.cms.sign (SignerInfo),
|
|
7
|
-
// pki.ocsp.sign (BasicOCSPResponse), and future X.509 / CRL issuance. It owns the single key ->
|
|
8
|
-
// { signatureAlgorithm, WebCrypto import/sign params } dispatch across the whole algorithm set
|
|
9
|
-
// (RSA-PKCS1 / RSA-PSS / ECDSA P-256/384/521 / Ed25519 / Ed448 / ML-DSA / SLH-DSA / composite
|
|
10
|
-
// ML-DSA), so a new signer surface inherits the full registry, PQC included, instead of
|
|
11
|
-
// re-deriving a partial, drifting copy (Hard rule #2: registry not switch; and no second signer).
|
|
12
|
-
//
|
|
13
|
-
// Error-parameterized like the guard / validator families: the caller passes its typed error
|
|
14
|
-
// factory `E(kind, message, cause)` where kind is "bad-input" | "unsupported-algorithm", so every
|
|
15
|
-
// domain keeps its own code (cms/bad-input vs ocsp/bad-input). The digest choice (a CMS
|
|
16
|
-
// digestAlgorithm concern) is surfaced as `scheme.digest`; the caller that has a digestAlgorithm
|
|
17
|
-
// field (CMS) builds it, the caller that signs raw bytes (OCSP) ignores it.
|
|
4
|
+
// @internal
|
|
18
5
|
|
|
19
6
|
var asn1 = require("./asn1-der");
|
|
20
7
|
var oid = require("./oid");
|
|
@@ -28,10 +15,6 @@ var compositeSig = require("./composite-sig");
|
|
|
28
15
|
var b = asn1.build;
|
|
29
16
|
function O(name) { return oid.byName(name); }
|
|
30
17
|
|
|
31
|
-
// Captured at load. This module decides which algorithm a signature runs under and whether the
|
|
32
|
-
// caller's key matches the certificate it claims, so an operation read at call time is one the
|
|
33
|
-
// caller answers: a replaced case fold reports two different algorithm names equal, and a replaced
|
|
34
|
-
// `Buffer.isBuffer` admits a key that is not bytes.
|
|
35
18
|
var _BigInt = intrinsic.BigInt;
|
|
36
19
|
var _String = intrinsic.String;
|
|
37
20
|
var _filter = intrinsic.filter;
|
|
@@ -39,19 +22,12 @@ var _stringify = intrinsic.stringify;
|
|
|
39
22
|
var _toUpperCase = intrinsic.toUpperCase;
|
|
40
23
|
var _isBuffer = intrinsic.isBuffer;
|
|
41
24
|
var _bufferFrom = intrinsic.bufferFrom;
|
|
42
|
-
// Asks the value's internal slot rather than `instanceof`, which reads the `Uint8Array` global at
|
|
43
|
-
// call time and answers about whichever constructor is bound then. It also settles the two cases
|
|
44
|
-
// `instanceof` gets wrong on their own terms: a real typed array from another realm is one, and an
|
|
45
|
-
// object merely built on the prototype is not.
|
|
46
25
|
var _isU8 = intrinsic.types.isUint8Array;
|
|
47
26
|
|
|
48
27
|
var HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
|
|
49
28
|
var NODE_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512", shake128: "shake128", shake256: "shake256" };
|
|
50
29
|
var PSS_SALT = { "SHA-256": 32, "SHA-384": 48, "SHA-512": 64 };
|
|
51
30
|
var ECDSA_ALG = { sha256: "ecdsaWithSHA256", sha384: "ecdsaWithSHA384", sha512: "ecdsaWithSHA512" };
|
|
52
|
-
// RSA-PKCS1 signatures fold the digest into a combined signature OID when there is no separate
|
|
53
|
-
// digestAlgorithm field to carry it (X.509 / CRL / OCSP; RFC 5280 sec. 4.1.1.2, RFC 6960). CMS
|
|
54
|
-
// SignerInfo has that field, so it signs under the bare rsaEncryption key OID instead.
|
|
55
31
|
var RSA_PKCS1_SIG = { sha256: "sha256WithRSAEncryption", sha384: "sha384WithRSAEncryption", sha512: "sha512WithRSAEncryption" };
|
|
56
32
|
var HASH_NAME_BY_OID = {};
|
|
57
33
|
HASH_NAME_BY_OID[O("sha256")] = "sha256";
|
|
@@ -70,14 +46,7 @@ var MLDSA_SUITABLE_DIGEST = {
|
|
|
70
46
|
"ML-DSA-65": { sha384: 1, sha512: 1, shake256: 1 },
|
|
71
47
|
"ML-DSA-87": { sha512: 1, shake256: 1 },
|
|
72
48
|
};
|
|
73
|
-
// mldsaDigestSuitable(mlName, md) -- is `md` listed as meeting `mlName`'s security strength?
|
|
74
|
-
// Both levels are asked for their OWN entries. The gate rejects on a falsy lookup, so a digest name
|
|
75
|
-
// planted on Object.prototype answers for every name the row omits and a below-strength digest
|
|
76
|
-
// passes the RFC 9882 sec. 3.3 check. This is the single home for that question: the signer and the
|
|
77
|
-
// verifier both ask it, and a copy at either would let the two drift.
|
|
78
49
|
// @enforced-by behavioral -- an own-property table read has no rename-proof shape distinct from a
|
|
79
|
-
// legitimate one; the RED vectors (a below-strength digest still refused with its name planted on
|
|
80
|
-
// Object.prototype, on both the sign and verify paths) are the guard.
|
|
81
50
|
function mldsaDigestSuitable(mlName, md) {
|
|
82
51
|
if (!intrinsic.hasOwn(MLDSA_SUITABLE_DIGEST, mlName)) return false;
|
|
83
52
|
var row = MLDSA_SUITABLE_DIGEST[mlName];
|
|
@@ -90,29 +59,13 @@ var SLHDSA_BY_OID = {};
|
|
|
90
59
|
["shake-192s", "shake256"], ["shake-192f", "shake256"], ["shake-256s", "shake256"], ["shake-256f", "shake256"]
|
|
91
60
|
].forEach(function (r) { SLHDSA_BY_OID[O("id-slh-dsa-" + r[0])] = { wc: "SLH-DSA-" + r[0].toUpperCase(), digest: r[1] }; });
|
|
92
61
|
|
|
93
|
-
// An AlgorithmIdentifier { OID } (absent parameters) or { OID, NULL }.
|
|
94
62
|
function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(name)), b.nullValue()]) : b.sequence([b.oid(O(name))]); }
|
|
95
|
-
// The RSASSA-PSS AlgorithmIdentifier: explicit SHA-2 hash, MGF1 keyed to it, hash-length salt,
|
|
96
|
-
// default trailerField omitted (RFC 4055).
|
|
97
63
|
function _pssAlgId(digestName) {
|
|
98
64
|
var hashAlg = b.sequence([b.oid(O(digestName)), b.nullValue()]);
|
|
99
65
|
var mgf = b.sequence([b.oid(O("mgf1")), hashAlg]);
|
|
100
66
|
var params = b.sequence([b.explicit(0, hashAlg), b.explicit(1, mgf), b.explicit(2, b.integer(_BigInt(PSS_SALT[HASH[digestName]])))]);
|
|
101
67
|
return b.sequence([b.oid(O("rsassaPss")), params]);
|
|
102
68
|
}
|
|
103
|
-
// The hash an id-RSASSA-PSS SPKI restricts its key to, or null when it restricts none.
|
|
104
|
-
//
|
|
105
|
-
// RFC 4055 sec. 3.1 draws the line at whether the parameters are THERE: "if present, the parameters
|
|
106
|
-
// field MUST contain RSASSA-PSS-params", and "if RSASSA-PSS-params is present, the certificate user
|
|
107
|
-
// MUST perform those operations using the one-way hash function ... identified in the ...
|
|
108
|
-
// parameters". Absent parameters therefore restrict nothing, and null says so.
|
|
109
|
-
//
|
|
110
|
-
// PRESENT parameters are a restriction even where they look empty. `hashAlgorithm` is
|
|
111
|
-
// `[0] HashAlgorithm DEFAULT sha1Identifier`, so a params SEQUENCE that omits it names SHA-1 --
|
|
112
|
-
// it does not decline to name anything. Reading the omission as "no restriction" is the fail-open
|
|
113
|
-
// that matters here: it turns a key its own certificate confines to SHA-1 into one that will verify
|
|
114
|
-
// a SHA-512 signature. And parameters that are present but unreadable are a restriction this code
|
|
115
|
-
// cannot honor, which is not the same as no restriction either, so they are refused.
|
|
116
69
|
function _pssHashFromSpki(cert, E) {
|
|
117
70
|
var params = cert.subjectPublicKeyInfo.algorithm.parameters;
|
|
118
71
|
if (params == null) return null;
|
|
@@ -123,8 +76,6 @@ function _pssHashFromSpki(cert, E) {
|
|
|
123
76
|
throw E("unsupported-algorithm", "the id-RSASSA-PSS key parameters are not an RSASSA-PSS-params SEQUENCE (RFC 4055 sec. 3.1)");
|
|
124
77
|
}
|
|
125
78
|
var hashField = _filter(node.children, function (c) { return c.tagClass === "context" && c.tagNumber === 0; })[0];
|
|
126
|
-
// DEFAULT sha1Identifier -- an omitted hashAlgorithm names SHA-1, which this toolkit does not
|
|
127
|
-
// sign or verify with, so it is reported as the pin it is and refused by the caller's own table.
|
|
128
79
|
if (!hashField) return "sha1";
|
|
129
80
|
if (!hashField.children || !hashField.children[0] || !hashField.children[0].children) {
|
|
130
81
|
throw E("unsupported-algorithm", "the id-RSASSA-PSS key parameters carry a malformed hashAlgorithm");
|
|
@@ -139,28 +90,14 @@ function _pssHashFromSpki(cert, E) {
|
|
|
139
90
|
return name;
|
|
140
91
|
}
|
|
141
92
|
|
|
142
|
-
// @internal
|
|
143
|
-
// Verifiers need the same restriction the signer above honors: a key whose certificate says
|
|
144
|
-
// SHA-256 must not be handed a SHA-512 signature to check, and reading the pin in two places is
|
|
145
|
-
// how the two directions come to disagree. Throws through the caller's E on a hash this toolkit
|
|
146
|
-
// does not implement, so an unreadable restriction is never treated as no restriction.
|
|
93
|
+
// @internal
|
|
147
94
|
function pssSpkiPinnedHash(cert, E) {
|
|
148
95
|
var d = _pssHashFromSpki(cert, E);
|
|
149
96
|
if (!d) return null;
|
|
150
|
-
// A pin this toolkit has no WebCrypto hash for (SHA-1, which RSASSA-PSS-params names by DEFAULT)
|
|
151
|
-
// is still a pin. Returning undefined here would hand the caller a falsy value it reads as
|
|
152
|
-
// "unrestricted", which is the same fail-open the DEFAULT reading above exists to close, one
|
|
153
|
-
// layer up.
|
|
154
97
|
if (!HASH[d]) throw E("unsupported-algorithm", "the id-RSASSA-PSS key is restricted to " + d + ", which this toolkit does not verify with");
|
|
155
98
|
return HASH[d];
|
|
156
99
|
}
|
|
157
100
|
|
|
158
|
-
// resolveSignScheme(cert, so, noSignedAttrs, E) -> the signature scheme from the signer cert's
|
|
159
|
-
// public-key algorithm + per-signer opts (so.digestAlgorithm / so.pss / so.combinedRsaSig, the
|
|
160
|
-
// last folds the digest into a combined RSA signature OID for a caller with no digestAlgorithm
|
|
161
|
-
// field). Returns
|
|
162
|
-
// { composite?, digest, digestAlgId, sigAlgId, imp?, sign?, ecdsaDer?, coordLen? }. `digest`/
|
|
163
|
-
// `digestAlgId` matter only to a caller with a CMS digestAlgorithm field; OCSP ignores them.
|
|
164
101
|
function resolveSignScheme(cert, so, noSignedAttrs, E) {
|
|
165
102
|
so = so || {};
|
|
166
103
|
var alg = cert.subjectPublicKeyInfo.algorithm;
|
|
@@ -217,14 +154,9 @@ function resolveSignScheme(cert, so, noSignedAttrs, E) {
|
|
|
217
154
|
throw E("unsupported-algorithm", "unsupported signer key algorithm " + keyOid);
|
|
218
155
|
}
|
|
219
156
|
|
|
220
|
-
// A CryptoKey's algorithm must match the resolved scheme (name / hash / curve).
|
|
221
157
|
function _assertKeyMatchesScheme(key, imp, E) {
|
|
222
158
|
var ka = key.algorithm || {};
|
|
223
|
-
// WebCrypto algorithm names are ASCII-case-insensitive (RSASSA-PKCS1-v1_5 and RSASSA-PKCS1-V1_5 are one
|
|
224
|
-
// algorithm), so compare case-folded; a genuine mismatch (e.g. RSASSA-PKCS1-v1_5 vs ECDSA) still fails.
|
|
225
159
|
if (_toUpperCase(_String(ka.name)) !== _toUpperCase(_String(imp.name))) {
|
|
226
|
-
// A WebCrypto RSA-PSS CryptoKey is bound to PSS and cannot produce the default RSASSA-PKCS1-v1_5
|
|
227
|
-
// signature (which a raw PKCS#8 RSA key can), so name the fix rather than only the mismatch.
|
|
228
160
|
var hint = (_toUpperCase(_String(ka.name)) === "RSA-PSS" && _toUpperCase(_String(imp.name)) === "RSASSA-PKCS1-V1_5")
|
|
229
161
|
? "; this CryptoKey is bound to RSASSA-PSS -- pass opts.pss to sign with RSASSA-PSS" : "";
|
|
230
162
|
throw E("bad-input", "the signer CryptoKey algorithm (" + ka.name + ") does not match the certificate's key algorithm (" + imp.name + ")" + hint);
|
|
@@ -233,9 +165,6 @@ function _assertKeyMatchesScheme(key, imp, E) {
|
|
|
233
165
|
if (imp.namedCurve && ka.namedCurve !== imp.namedCurve) throw E("bad-input", "the signer CryptoKey curve (" + ka.namedCurve + ") does not match the certificate curve (" + imp.namedCurve + ")");
|
|
234
166
|
}
|
|
235
167
|
function _normPkcs8(k, label, E) {
|
|
236
|
-
// A caller's own Buffer is borrowed, not copied, the same rule _importKey states below. Every
|
|
237
|
-
// other form is copied, and neither copy is duplicated further, so a private key never gains a
|
|
238
|
-
// plaintext duplicate this module cannot account for.
|
|
239
168
|
if (_isBuffer(k)) return guard.bytes.view(k, E, "bad-input", label);
|
|
240
169
|
if (_isU8(k)) return guard.bytes.snapshot(k, E, "bad-input", label);
|
|
241
170
|
if (typeof k === "string") { try { return pkcs8.pemDecode(k); } catch (e) { throw E("bad-input", label + " PEM could not be decoded", e); } }
|
|
@@ -248,27 +177,13 @@ function _normCompositeKeys(key, comp, E) {
|
|
|
248
177
|
return { mldsa: _normPkcs8(key.mldsa, "the composite ML-DSA component key", E), trad: _normPkcs8(key.trad, "the composite traditional component key", E) };
|
|
249
178
|
}
|
|
250
179
|
function _importKey(key, imp, E) {
|
|
251
|
-
// A WebCrypto CryptoKey is detected structurally (a `.type` string + an `.algorithm` object, and not
|
|
252
|
-
// a byte view), never by instanceof (cross-realm / a second installed copy) or util.types.isCryptoKey
|
|
253
|
-
// (false for this engine's own keys). The gate is broader than webcrypto.isCryptoKeyLike on purpose:
|
|
254
|
-
// an incomplete CryptoKey-shaped object (e.g. one missing its `usages`) still routes here so adoptKey
|
|
255
|
-
// reports the specific reason (usages / not-exportable) rather than the generic byte-form message. It
|
|
256
|
-
// signs, so it must be a PRIVATE key: a public or secret CryptoKey cannot produce a signature and is
|
|
257
|
-
// refused precisely.
|
|
258
180
|
if (key && typeof key === "object" && !_isBuffer(key) && !_isU8(key) && typeof key.type === "string" && key.algorithm != null) {
|
|
259
181
|
if (key.type !== "private") {
|
|
260
182
|
throw E("bad-input", "the signing key is a WebCrypto CryptoKey of type " + _stringify(key.type) + "; a signing key must be a private key (type \"private\")");
|
|
261
183
|
}
|
|
262
184
|
_assertKeyMatchesScheme(key, imp, E);
|
|
263
|
-
// A CryptoKey created outside this engine signs here too -- it is re-imported through this engine,
|
|
264
|
-
// because it carries none of the key material this engine signs with. One whose material this
|
|
265
|
-
// process cannot reach is refused with that as the reason.
|
|
266
185
|
return webcrypto.adoptKey(key, imp, ["sign"], E, "bad-input");
|
|
267
186
|
}
|
|
268
|
-
// A Uint8Array is copied here and a PEM string is decoded here, so both leave a second copy of a
|
|
269
|
-
// private key that nothing else can reach, wiped once the engine has imported it, which is the
|
|
270
|
-
// only thing that reads it. A caller's own Buffer is passed through untouched: they hold a live
|
|
271
|
-
// reference and will use it again, so clearing it would destroy their key.
|
|
272
187
|
var der, owned = false;
|
|
273
188
|
if (_isBuffer(key)) der = guard.bytes.view(key, E, "bad-input", "the signer private key");
|
|
274
189
|
else if (_isU8(key)) { der = guard.bytes.snapshot(key, E, "bad-input", "the signer private key"); owned = true; }
|
|
@@ -277,13 +192,10 @@ function _importKey(key, imp, E) {
|
|
|
277
192
|
catch (e) { throw E("bad-input", "the signer PEM private key could not be decoded", e); }
|
|
278
193
|
owned = true;
|
|
279
194
|
} else if (intrinsic.types.isKeyObject(key)) {
|
|
280
|
-
// A node:crypto KeyObject carries a `.type` but not the WebCrypto surface (algorithm / usages), so
|
|
281
|
-
// it fails the CryptoKey gate above and would otherwise land on the generic message -- name it precisely.
|
|
282
195
|
throw E("bad-input", "the signing key is a node:crypto KeyObject, not a WebCrypto CryptoKey; pass a CryptoKey (e.g. from pki.key.generate) or export it to a PKCS#8 DER Buffer or PEM string");
|
|
283
196
|
} else throw E("bad-input", "a signer key must be a WebCrypto CryptoKey, a PKCS#8 DER Buffer, or a PKCS#8 PEM string");
|
|
284
197
|
var imported = subtle.importKey("pkcs8", der, imp, false, ["sign"]);
|
|
285
198
|
if (!owned) return imported;
|
|
286
|
-
// Wiped on the reject path too: a malformed key is not a way to leave the copy in memory.
|
|
287
199
|
return imported.then(function (k) {
|
|
288
200
|
guard.secret.zeroize(der, E, "bad-input", "the signer private-key copy");
|
|
289
201
|
return k;
|
|
@@ -293,9 +205,6 @@ function _importKey(key, imp, E) {
|
|
|
293
205
|
});
|
|
294
206
|
}
|
|
295
207
|
|
|
296
|
-
// signOverTbs(scheme, key, signedBytes, E) -> Promise<Buffer> the raw signature over signedBytes.
|
|
297
|
-
// The classical path imports the key + signs (re-encoding ECDSA to canonical DER); the composite
|
|
298
|
-
// path signs both component keys and returns mldsaSig || tradSig (composite-sig.js owns it).
|
|
299
208
|
function signOverTbs(scheme, key, signedBytes, E) {
|
|
300
209
|
if (scheme.composite) {
|
|
301
210
|
return compositeSig.compositeSign(scheme.composite, _normCompositeKeys(key, scheme.composite, E), signedBytes).then(function (sig) { return _bufferFrom(sig); });
|
|
@@ -309,10 +218,6 @@ function signOverTbs(scheme, key, signedBytes, E) {
|
|
|
309
218
|
});
|
|
310
219
|
}
|
|
311
220
|
|
|
312
|
-
// MLDSA_SUITABLE_DIGEST + SLHDSA_BY_OID are the RFC 9882 sec. 3.3 / RFC 9814 sec. 4 per-parameter-
|
|
313
|
-
// set digest policy, exported so the VERIFY side (cms-verify) shares the EXACT same table this
|
|
314
|
-
// resolver signs under -- a digest accepted on sign is precisely the set accepted on verify, with
|
|
315
|
-
// no drift between the two.
|
|
316
221
|
module.exports = {
|
|
317
222
|
resolveSignScheme: resolveSignScheme,
|
|
318
223
|
pssSpkiPinnedHash: pssSpkiPinnedHash,
|