@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.
Files changed (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
package/lib/ip-utils.js CHANGED
@@ -1,24 +1,8 @@
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. Textual IP-literal validation, shared so a
6
- // consumer (pki.lint's commonName-in-SAN check today; the SAN iPAddress GeneralName shorthand
7
- // and name-constraint surfaces) does not hand-roll a partial regex that misses a valid IPv6 form.
8
- //
9
- // The IPv4 grammar is strict per-octet 0-255 (RFC 791); the IPv6 parser follows RFC 4291
10
- // sec. 2.2 (8 groups of 1-4 hex, one "::" run compressing a contiguous zero run) plus the
11
- // RFC 4291 sec. 2.5.5.2 / RFC 5952 sec. 5 IPv4-mapped + dual-stack "::ffff:1.2.3.4" tail.
12
- // This mirrors the vetted validation in the sibling blamejs framework (lib/ip-utils.js);
13
- // this toolkit keeps its own copy, since Hard rule #1 rules out a runtime dependency.
4
+ // @internal
14
5
 
15
- // This parser uses no regular expression. packIpLiteral gates a GeneralName form decision (iPAddress
16
- // vs dNSName), and a regex is matched through a live, replaceable protocol -- RegExp.prototype.exec /
17
- // test and String.prototype.match / split consult the regex's own `exec` and the `[Symbol.match]` /
18
- // `[Symbol.split]` hooks, and RegExp.prototype.compile can mutate a pattern in place -- so co-resident
19
- // code could steer the decision after load. Instead every byte is read directly through captured string
20
- // primitives, and separation on a literal is done by scanning with captured indexOf/slice, so nothing the
21
- // decision rests on is re-dispatchable.
22
6
  var intrinsic = require("./guard-intrinsic");
23
7
  var _slice = intrinsic.uncurry(String.prototype.slice);
24
8
  var _padStart = intrinsic.uncurry(String.prototype.padStart);
@@ -31,15 +15,7 @@ var _bufferFrom = intrinsic.bufferFrom;
31
15
  var _bufferAlloc = intrinsic.bufferAlloc;
32
16
  var _defineProperty = intrinsic.defineProperty;
33
17
  var _Number = intrinsic.Number;
34
- // Every array here is built and read as own elements only. Appending through a captured Object.defineProperty
35
- // (not `arr[i] = v`) creates an own element, so a co-resident Array.prototype numeric setter is never invoked
36
- // and the own element shadows any inherited getter on read; and no Array.prototype.map / concat runs, so
37
- // Array[Symbol.species] is never consulted. Both are routes a hostile prototype could otherwise use to
38
- // rewrite a packed octet and make a certificate carry an attacker-chosen address. The emitted octets are
39
- // written straight into a Buffer, whose integer-indexed store bypasses the prototype entirely.
40
18
  function _push(arr, v) { _defineProperty(arr, arr.length, { value: v, writable: true, enumerable: true, configurable: true }); }
41
- // Split on a literal string separator without String.prototype.split, which consults the separator's
42
- // `[Symbol.split]`; this scans with captured indexOf/slice only.
43
19
  function _splitOn(str, sep) {
44
20
  var out = [], from = 0, at;
45
21
  while ((at = _indexOf(str, sep, from)) !== -1) { _push(out, _slice(str, from, at)); from = at + sep.length; }
@@ -47,19 +23,17 @@ function _splitOn(str, sep) {
47
23
  return out;
48
24
  }
49
25
 
50
- function _isDigitCode(c) { return c >= 48 && c <= 57; } // 0-9
26
+ function _isDigitCode(c) { return c >= 48 && c <= 57; }
51
27
  function _isHexCode(c) { return (c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70); }
52
28
 
53
- // A strict RFC 791 dotted-quad octet: 1-3 digits, value 0-255, no leading zero beyond "0" itself.
54
29
  function _isOctet(p) {
55
30
  var n = p.length, i, c;
56
31
  if (n < 1 || n > 3) return false;
57
32
  for (i = 0; i < n; i += 1) { c = _charCodeAt(p, i); if (!_isDigitCode(c)) return false; }
58
- if (n > 1 && _charCodeAt(p, 0) === 48) return false; // a leading zero is not the canonical form
33
+ if (n > 1 && _charCodeAt(p, 0) === 48) return false;
59
34
  return _Number(p) <= 255;
60
35
  }
61
36
 
62
- // Strict RFC 791 dotted quad: four 0-255 octets, nothing else.
63
37
  function isIPv4(s) {
64
38
  if (typeof s !== "string") return false;
65
39
  var parts = _splitOn(s, ".");
@@ -68,7 +42,6 @@ function isIPv4(s) {
68
42
  return true;
69
43
  }
70
44
 
71
- // A single 1-4 hex-digit IPv6 group.
72
45
  function _isHexGroup(g) {
73
46
  var n = g.length, i;
74
47
  if (n < 1 || n > 4) return false;
@@ -76,8 +49,6 @@ function _isHexGroup(g) {
76
49
  return true;
77
50
  }
78
51
 
79
- // The RFC 4291 sec. 2.5.5.2 dual-stack tail SHAPE: exactly four 1-3 digit groups joined by dots. Validity
80
- // (0-255 per octet) is a separate isIPv4 check, matching the historical two-step (shape then value).
81
52
  function _isDottedQuadShape(t) {
82
53
  var q = _splitOn(t, "."), i, p, j;
83
54
  if (q.length !== 4) return false;
@@ -89,14 +60,9 @@ function _isDottedQuadShape(t) {
89
60
  return true;
90
61
  }
91
62
 
92
- // Expand an IPv6 textual address to its 32-hex-character form, or null on any parse failure
93
- // (bad hex group, group count != 8, more than one "::", an over-length group). Accepts the
94
- // IPv4-mapped / dual-stack ".d.d.d.d" tail.
95
- var IPV6_TEXT_MAX_LEN = 45; // 8 groups x4 hex + 7 colons + a dotted-quad tail headroom
63
+ var IPV6_TEXT_MAX_LEN = 45;
96
64
  function expandIpv6Hex(ip) {
97
65
  if (typeof ip !== "string" || ip.length > IPV6_TEXT_MAX_LEN || _indexOf(ip, ":") === -1) return null;
98
- // RFC 4291 sec. 2.5.5.2 IPv4-mapped / dual-stack: fold a trailing dotted-quad (the suffix after the
99
- // last colon, when it carries a dot) into two 16-bit hex groups before the pure-hex parse.
100
66
  var lastColon = _lastIndexOf(ip, ":");
101
67
  var tail = _slice(ip, lastColon + 1);
102
68
  if (_indexOf(tail, ".") !== -1) {
@@ -110,10 +76,8 @@ function expandIpv6Hex(ip) {
110
76
  var right = dbl.length === 2 ? (dbl[1] === "" ? [] : _splitOn(dbl[1], ":")) : [];
111
77
  if (dbl.length === 1 && left.length !== 8) return null;
112
78
  var fill = 8 - left.length - right.length;
113
- // A "::" run MUST compress at least one zero group (RFC 4291 sec. 2.2 / RFC 5952 sec.
114
- // 4.2.2): a "::" with fill 0 is a full 8-group address that must not use compression.
115
79
  if (dbl.length === 2 ? fill < 1 : fill !== 0) return null;
116
- var groups = left, i; // `left` is a fresh own-element array; append through _push (no inherited setter)
80
+ var groups = left, i;
117
81
  for (i = 0; i < fill; i += 1) _push(groups, "0");
118
82
  for (i = 0; i < right.length; i += 1) _push(groups, right[i]);
119
83
  if (groups.length !== 8) return null;
@@ -126,17 +90,12 @@ function expandIpv6Hex(ip) {
126
90
  return hex;
127
91
  }
128
92
 
129
- // Is `s` a syntactically valid IPv4 or IPv6 textual literal?
130
93
  function isIpLiteral(s) { return isIPv4(s) || expandIpv6Hex(s) !== null; }
131
94
 
132
- // Pack an IPv4/IPv6 textual literal to its network-order octets (4 for IPv4, 16 for IPv6),
133
- // or null when `s` is not a valid literal. The binary inverse of the textual forms isIPv4 /
134
- // expandIpv6Hex validate, so a SAN/GeneralName iPAddress (RFC 5280 sec. 4.2.1.6, always a bare
135
- // host address of 4 or 16 octets) can be given as a string instead of a pre-packed Buffer.
136
95
  function packIpLiteral(s) {
137
96
  if (isIPv4(s)) {
138
97
  var parts = _splitOn(s, "."), buf = _bufferAlloc(4), i;
139
- for (i = 0; i < 4; i += 1) buf[i] = _Number(parts[i]); // Buffer integer-index store; each octet range-checked by _isOctet
98
+ for (i = 0; i < 4; i += 1) buf[i] = _Number(parts[i]);
140
99
  return buf;
141
100
  }
142
101
  var hex = expandIpv6Hex(s);
package/lib/jose.js CHANGED
@@ -42,12 +42,6 @@ var webcrypto = wcEngine.webcrypto;
42
42
  var frameworkError = require("./framework-error");
43
43
  var guard = require("./guard-all");
44
44
  var edwardsPoint = require("./edwards-point");
45
- // What this module reads from the runtime while it is deciding something, taken at load. A JWS is
46
- // signed and verified against a header the caller owns, and the reads below are the ones that decide
47
- // what that header IS: whether a value is a list, whether a name is the caller's own, which
48
- // conversion turns a claim into the string compared against a profile. A caller who receives control
49
- // afterwards -- through an accessor on the header, or a `toJSON` while it is being serialized -- can
50
- // replace any of them, and every check around them still runs and still passes. See guard-intrinsic.
51
45
  var intrinsic = require("./guard-intrinsic");
52
46
  var _isArray = intrinsic.isArray;
53
47
  var _hasOwn = intrinsic.hasOwn;
@@ -66,7 +60,6 @@ function E(code, message, cause) { return new JoseError(code, message, cause); }
66
60
 
67
61
  var LIMITS = constants.LIMITS;
68
62
 
69
- // ---- strict base64url codec (RFC 7515 sec. 2 / RFC 4648 sec. 5) ----------
70
63
 
71
64
  /**
72
65
  * @primitive pki.jose.base64url.encode
@@ -84,8 +77,6 @@ var LIMITS = constants.LIMITS;
84
77
  */
85
78
  function b64uEncode(bytes) {
86
79
  if (!_isBuffer(bytes)) throw E("jose/bad-input", "base64url.encode requires a Buffer");
87
- // Re-view through the byte guard: a detached-backed Buffer reads as zero-length,
88
- // so a bare .toString would silently encode "" -- fail closed instead.
89
80
  bytes = guard.bytes.view(bytes, JoseError, "jose/bad-input", "base64url.encode input");
90
81
  return _bufToString(bytes, "base64url");
91
82
  }
@@ -107,13 +98,9 @@ function b64uEncode(bytes) {
107
98
  * pki.jose.base64url.decode("AQID"); // -> <Buffer 01 02 03>
108
99
  */
109
100
  function b64uDecode(text) {
110
- // The strict alphabet + impossible-length + canonical-round-trip discipline
111
- // lives once in the shared encoding guard (RFC 4648 sec. 5 / RFC 8555 sec. 6.1);
112
- // the frozen jose/bad-base64url code is threaded through.
113
101
  return guard.encoding.base64url(text, null, E, "jose/bad-base64url", "base64url value");
114
102
  }
115
103
 
116
- // ---- strict bounded JSON reader ------------------------------------------
117
104
 
118
105
  /**
119
106
  * @primitive pki.jose.parseJson
@@ -134,9 +121,6 @@ function b64uDecode(text) {
134
121
  * pki.jose.parseJson('{"a":1}'); // -> { a: 1 }
135
122
  */
136
123
  function parseJson(input) {
137
- // The strict bounded reader (byte + depth caps, duplicate-member reject at every
138
- // depth, __proto__-safe own-property assignment, fatal UTF-8, RFC 8259 grammar)
139
- // lives once in the shared JSON guard; the frozen jose/* codes are threaded through.
140
124
  return guard.json.parse(input, E, {
141
125
  maxBytes: LIMITS.JSON_MAX_BYTES, maxDepth: LIMITS.JSON_MAX_DEPTH,
142
126
  badJson: "jose/bad-json", tooDeep: "jose/too-deep", duplicateMember: "jose/duplicate-member",
@@ -144,15 +128,7 @@ function parseJson(input) {
144
128
  });
145
129
  }
146
130
 
147
- // ---- JOSE algorithm registry (RFC 7518 / 8037 / 9964) --------------------
148
131
 
149
- // One data row per JWS `alg`, keyed for O(1) resolution -- never a switch. Each
150
- // row binds the alg to its JWK key type (and, for EC/OKP, the exact curve), the
151
- // hash, and the WebCrypto import/verify parameters, plus the exact signature
152
- // byte length (fixed for EC / EdDSA / ML-DSA; the RSA modulus length for RS*/PS*,
153
- // resolved from the key). Binding alg->kty in DATA is what kills the RS256->HS256
154
- // key-confusion class: an ES256 header with an RSA key is rejected here, before
155
- // any crypto call. `HS*`/`none` are absent from this table by construction.
156
132
  var SIG_ALGS = {
157
133
  ES256: { kty: "EC", crv: "P-256", hash: "SHA-256", subtle: "ECDSA", sigBytes: 64 },
158
134
  ES384: { kty: "EC", crv: "P-384", hash: "SHA-384", subtle: "ECDSA", sigBytes: 96 },
@@ -169,66 +145,41 @@ var SIG_ALGS = {
169
145
  "ML-DSA-87": { kty: "AKP", subtle: "ML-DSA-87", sigBytes: 4627 },
170
146
  };
171
147
 
172
- // The MAC table exists ONLY for the RFC 8555 sec. 7.3.4 externalAccountBinding
173
- // inner JWS -- HS* appears nowhere in the outer/signature profiles, so an
174
- // `alg:"HS256"` on an ACME request rejects (the RS256->HS256 downgrade class).
175
148
  var MAC_ALGS = {
176
149
  HS256: { kty: "oct", hash: "SHA-256", sigBytes: 32 },
177
150
  HS384: { kty: "oct", hash: "SHA-384", sigBytes: 48 },
178
151
  HS512: { kty: "oct", hash: "SHA-512", sigBytes: 64 },
179
152
  };
180
153
 
181
- // The header parameter names this implementation understands. A `crit` naming
182
- // anything outside this set is unprocessable -> the JWS is invalid (RFC 7515
183
- // sec. 4.1.11); ACME defines no extension params, so any `crit` entry rejects.
184
154
  var UNDERSTOOD_HEADER = { alg: 1, nonce: 1, url: 1, jwk: 1, kid: 1, crit: 1 };
185
155
 
186
- // ---- JWS profiles (declarative -- one table drives sign AND verify) ------
187
156
 
188
- // Each profile is DATA: the algorithm table it permits, the required params,
189
- // whether `nonce` is required/forbidden, and the key-identification rule
190
- // (exactly-one-of jwk/kid, or one specifically). The profile carries the rule,
191
- // not the code path -- so the EAB-inner table ACCEPTS HS256 where the ACME-outer
192
- // table REJECTS it, from the same walker.
193
157
  var PROFILES = {
194
158
  "acme-outer": { algs: SIG_ALGS, nonce: "required", keyId: "one-of", requireUrl: true },
195
159
  "eab-inner": { algs: MAC_ALGS, nonce: "forbidden", keyId: "kid", requireUrl: true },
196
160
  "keychange-inner": { algs: SIG_ALGS, nonce: "forbidden", keyId: "jwk", requireUrl: true },
197
161
  };
198
162
 
199
- // Validate a decoded protected header against a profile. Returns the resolved
200
- // { alg, algRow, header }; throws a typed jose/* fault on any violation.
201
163
  function _checkHeader(header, profileName) {
202
164
  var profile = PROFILES[profileName];
203
165
  if (!profile) throw E("jose/bad-input", "unknown JWS profile " + _stringify(profileName));
204
166
  if (!header || typeof header !== "object" || _isArray(header)) throw E("jose/bad-header", "the protected header must be a JSON object");
205
- // Every field is read as an OWN property. JSON.stringify (used by sign) omits
206
- // inherited members, so an alg/nonce/url living on the prototype (a polluted or
207
- // Object.create(...) header) would pass here yet serialize to a header MISSING
208
- // them -- so require own-ness, matching what the serialized JWS will actually carry.
209
167
  var own = function (k) { return _hasOwn(header, k); };
210
- // b64 (RFC 7797) MUST NOT be present at all -- the unencoded-payload downgrade.
211
168
  if (own("b64")) throw E("jose/bad-jws", "the RFC 7797 b64 header parameter is forbidden");
212
169
  var alg = header.alg;
213
170
  if (!own("alg") || typeof alg !== "string") throw E("jose/bad-header", "the protected header must carry a string alg");
214
171
  var algRow = profile.algs[alg];
215
172
  if (!algRow) throw E("jose/bad-alg", "unsupported or forbidden algorithm " + _stringify(alg) + " for this JWS profile");
216
173
  if (profile.requireUrl && (!own("url") || typeof header.url !== "string" || header.url.length === 0)) throw E("jose/bad-header", "the protected header must carry a non-empty string url");
217
- // nonce discipline.
218
174
  var hasNonce = own("nonce");
219
175
  if (profile.nonce === "required") {
220
- // A Replay-Nonce is 1*base64url -- NON-empty (RFC 8555 sec. 6.5.2); "" decodes as
221
- // canonical base64url but no server issues it, so reject it locally.
222
176
  if (!hasNonce || typeof header.nonce !== "string" || header.nonce.length === 0) throw E("jose/bad-header", "the protected header must carry a non-empty nonce");
223
- // a nonce that is not valid base64url is malformed (RFC 8555 sec. 6.5.2).
224
177
  try { b64uDecode(header.nonce); } catch (e) { throw E("jose/bad-header", "the nonce is not valid base64url", e); }
225
178
  } else if (profile.nonce === "forbidden" && hasNonce) {
226
179
  throw E("jose/bad-header", "a nonce is forbidden in this inner JWS");
227
180
  }
228
- // key identification.
229
181
  var hasJwk = own("jwk");
230
182
  var hasKid = own("kid");
231
- // A kid is a non-empty account URL string.
232
183
  if (hasKid && (typeof header.kid !== "string" || header.kid.length === 0)) throw E("jose/bad-header", "kid must be a non-empty string");
233
184
  if (profile.keyId === "one-of") {
234
185
  if (hasJwk === hasKid) throw E("jose/bad-header", "the protected header must carry EXACTLY ONE of jwk / kid");
@@ -238,7 +189,6 @@ function _checkHeader(header, profileName) {
238
189
  if (!hasJwk || hasKid) throw E("jose/bad-header", "this JWS must carry an embedded jwk, not kid");
239
190
  }
240
191
  if (hasJwk && (!header.jwk || typeof header.jwk !== "object" || _isArray(header.jwk))) throw E("jose/bad-header", "jwk must be a JSON object");
241
- // crit: protected-only, non-empty, no standard names, no duplicates, all understood.
242
192
  if (_hasOwn(header, "crit")) {
243
193
  var crit = header.crit;
244
194
  if (!_isArray(crit) || crit.length === 0) throw E("jose/bad-crit", "crit must be a non-empty array");
@@ -249,72 +199,53 @@ function _checkHeader(header, profileName) {
249
199
  if (_hasOwn(seen, name)) throw E("jose/bad-crit", "duplicate crit entry " + _stringify(name));
250
200
  seen[name] = 1;
251
201
  if (UNDERSTOOD_HEADER[name]) throw E("jose/bad-crit", "crit must not name a standard header parameter " + _stringify(name));
252
- // Anything else is an extension parameter this implementation does not process.
253
202
  throw E("jose/bad-crit", "unprocessed critical header parameter " + _stringify(name));
254
203
  }
255
204
  }
256
205
  return { alg: alg, algRow: algRow, header: header };
257
206
  }
258
207
 
259
- // ---- flattened-JWS verify (RFC 7515 sec. 5.2) ----------------------------
260
208
 
261
- // Resolve the WebCrypto import parameters for a JWK under an alg row.
262
209
  function _importParams(algRow, jwk) {
263
210
  if (algRow.kty === "EC") return { name: "ECDSA", namedCurve: algRow.crv };
264
211
  if (algRow.kty === "RSA") return { name: algRow.subtle, hash: algRow.hash };
265
- if (algRow.kty === "OKP") return { name: jwk.crv }; // Ed25519 / Ed448
266
- if (algRow.kty === "AKP") return { name: algRow.subtle }; // ML-DSA-44/65/87
267
- if (algRow.kty === "oct") return { name: "HMAC", hash: algRow.hash }; // EAB inner (HS*)
212
+ if (algRow.kty === "OKP") return { name: jwk.crv };
213
+ if (algRow.kty === "AKP") return { name: algRow.subtle };
214
+ if (algRow.kty === "oct") return { name: "HMAC", hash: algRow.hash };
268
215
  throw E("jose/bad-alg", "unsupported key type");
269
216
  }
270
217
 
271
- // The WebCrypto verify/sign algorithm for an alg row + JWK.
272
218
  function _cryptoAlg(algRow, jwk, key) {
273
219
  if (algRow.kty === "EC") return { name: "ECDSA", hash: algRow.hash };
274
220
  if (algRow.kty === "RSA") return algRow.subtle === "RSA-PSS" ? { name: "RSA-PSS", saltLength: algRow.saltLength } : { name: "RSASSA-PKCS1-V1_5" };
275
- // EdDSA carries the curve (Ed25519 / Ed448) as the WebCrypto algorithm name. In
276
- // a kid-signed request there is no header/opts jwk to read it from, so fall back
277
- // to the signing key's own algorithm -- an Ed25519 account key must stay usable
278
- // after account creation, not only in the embedded-jwk (newAccount) case.
279
221
  if (algRow.kty === "OKP") return { name: (jwk && jwk.crv) || (key && key.algorithm && key.algorithm.name) };
280
222
  if (algRow.kty === "AKP") return { name: algRow.subtle };
281
223
  if (algRow.kty === "oct") return { name: "HMAC" };
282
224
  throw E("jose/bad-alg", "unsupported key type");
283
225
  }
284
226
 
285
- // The key type the JWK MUST have for this alg (fail closed on confusion).
286
227
  function _assertKeyType(algRow, jwk) {
287
228
  if (!jwk || typeof jwk !== "object" || typeof jwk.kty !== "string") throw E("jose/bad-key", "a JWK object with a kty is required");
288
229
  if (jwk.kty !== algRow.kty) throw E("jose/bad-alg", "the key type " + _stringify(jwk.kty) + " does not match the algorithm");
289
230
  if (algRow.kty === "EC" && jwk.crv !== algRow.crv) throw E("jose/bad-alg", "the EC curve does not match the algorithm");
290
231
  if (algRow.kty === "OKP" && jwk.crv !== "Ed25519" && jwk.crv !== "Ed448") throw E("jose/bad-alg", "unsupported OKP curve " + _stringify(jwk.crv));
291
- // node/OpenSSL import a low-order (e.g. identity) OKP point without complaint and it verifies a
292
- // forged signature; reject a non-full-order Edwards point through the shared gate, the same check
293
- // every EdDSA verify path in the toolkit applies.
294
232
  if (algRow.kty === "OKP" && typeof jwk.x === "string" && !edwardsPoint.validate(b64uDecode(jwk.x), jwk.crv === "Ed25519" ? 6 : 7)) {
295
233
  throw E("jose/bad-key", "the OKP public key is not a valid, full-order Edwards point");
296
234
  }
297
- // An AKP (ML-DSA) JWK carries its parameter set in `alg` (RFC 9964); it MUST match
298
- // the algorithm's set, or an ML-DSA-44 key could embed an ML-DSA-65 public JWK.
299
235
  if (algRow.kty === "AKP" && jwk.alg !== algRow.subtle) throw E("jose/bad-alg", "the AKP parameter set " + _stringify(jwk.alg) + " does not match the algorithm " + algRow.subtle);
300
236
  }
301
237
 
302
- // The exact signature byte length this alg + key must produce.
303
238
  function _expectedSigBytes(algRow, jwk, key) {
304
239
  if (algRow.sigBytes != null) return algRow.sigBytes;
305
- // EdDSA: the curve fixes the length. On the verify side it is read from the JWK;
306
- // on the sign side (kid mode, no JWK) it comes from the signing key's algorithm.
307
240
  if (algRow.sigBytesByCrv) return algRow.sigBytesByCrv[(jwk && jwk.crv) || (key && key.algorithm && key.algorithm.name)] || null;
308
241
  if (algRow.kty === "RSA") {
309
- if (jwk && typeof jwk.n === "string") return b64uDecode(jwk.n).length; // verify: modulus from the JWK
310
- if (key && key.algorithm && key.algorithm.modulusLength) return key.algorithm.modulusLength / 8; // sign: from the key
242
+ if (jwk && typeof jwk.n === "string") return b64uDecode(jwk.n).length;
243
+ if (key && key.algorithm && key.algorithm.modulusLength) return key.algorithm.modulusLength / 8;
311
244
  return null;
312
245
  }
313
246
  return null;
314
247
  }
315
248
 
316
- // The JWK members that carry PRIVATE key material across every key type -- EC/OKP
317
- // `d`, the RSA CRT set, the symmetric `k`, and the AKP `priv`.
318
249
  var PRIVATE_JWK_MEMBERS = ["d", "p", "q", "dp", "dq", "qi", "k", "priv"];
319
250
 
320
251
  /**
@@ -384,10 +315,6 @@ function assertPublicJwk(jwk) {
384
315
  * var v = await pki.jose.verify(jws, { profile: "acme-outer", key: accountJwk });
385
316
  * v.header.alg; // -> "ES256"
386
317
  */
387
- // `key` NAMES the key this message must be signed under and `profile` selects which header rules
388
- // apply -- both narrow what verifies, so a misspelling of either silently widens it back to the
389
- // default. That is the shape this check exists for: the caller asked for something stricter and got
390
- // the looser behavior, with nothing said.
391
318
  var _VERIFY_KEYS = { key: 1, profile: 1 };
392
319
  async function verify(jws, opts) {
393
320
  opts = opts || {};
@@ -400,17 +327,6 @@ async function verify(jws, opts) {
400
327
  var header = parseJson(b64uDecode(jws.protected));
401
328
  var profileName = opts.profile || "acme-outer";
402
329
  var checked = _checkHeader(header, profileName);
403
- // A caller supplying opts.key is NAMING the key this message must be signed under. A profile
404
- // that also permits an embedded jwk lets the message carry one, and preferring that would let
405
- // the sender choose which key verifies it -- exactly the question opts.key was asked to settle.
406
- // So when both are present they must be the SAME key, compared as RFC 7638 thumbprints: that
407
- // canonicalizes member order and ignores members outside the key itself, so two spellings of
408
- // one key agree and two different keys cannot.
409
- // A SUPPLIED key that cannot be used is refused, never quietly treated as absent. Falling back
410
- // to the embedded jwk there would drop the caller's intent to pin a signer at the moment it
411
- // matters most -- a `key` that came back null from a lookup would verify against whatever the
412
- // message carried, and the verdict would report the embedded key as though none was named.
413
- // `undefined` alone means "not supplied", so spreading an options object stays safe.
414
330
  if (opts.key !== undefined && (typeof opts.key !== "object" || opts.key === null || _isArray(opts.key))) {
415
331
  throw E("jose/bad-key", "opts.key was supplied but is not a JWK object, so the key this message must be signed under cannot be established");
416
332
  }
@@ -436,20 +352,15 @@ async function verify(jws, opts) {
436
352
  var sig = b64uDecode(jws.signature);
437
353
  var want = _expectedSigBytes(checked.algRow, jwk);
438
354
  if (want != null && sig.length !== want) throw E("jose/bad-signature", "the signature length " + sig.length + " is not the " + want + " bytes " + checked.alg + " requires");
439
- // Signing input from the RECEIVED base64url strings verbatim (never a re-serialize).
440
355
  var signingInput = _bufferFrom(jws.protected + "." + jws.payload, "ascii");
441
356
  var key;
442
357
  try { key = await webcrypto.subtle.importKey("jwk", jwk, _importParams(checked.algRow, jwk), false, ["verify"]); }
443
358
  catch (e) { throw E("jose/bad-key", "the JWK could not be imported for verification", e); }
444
359
  var ok = await webcrypto.subtle.verify(_cryptoAlg(checked.algRow, jwk), key, sig, signingInput);
445
360
  if (!ok) throw E("jose/verify-failed", "the JWS signature did not verify");
446
- // WHICH key answered. A caller auditing the decision needs "checked against the key I named"
447
- // to be distinguishable from "checked against the one the message brought with it"; the two
448
- // are different claims and only the first says anything about who the signer is.
449
361
  return { header: header, payload: b64uDecode(jws.payload), keySource: keySource };
450
362
  }
451
363
 
452
- // ---- flattened-JWS sign (RFC 7515 sec. 5.1) ------------------------------
453
364
 
454
365
  /**
455
366
  * @primitive pki.jose.sign
@@ -484,31 +395,16 @@ async function verify(jws, opts) {
484
395
  * nonce: "oFvnlFP1wIhRlYS2jTaXbA", url: "https://ca.example/acme/new-acct" };
485
396
  * var jws = await pki.jose.sign({ protected: hdr, payload: Buffer.from("{}"), key: priv });
486
397
  */
487
- // The public JWK has two sources and the embedded one wins, so opts.jwk is read only in kid mode.
488
- // Supplied alongside a header that embeds its own, it selected nothing: the signature parameters
489
- // came from the header's key while the caller had named a different one.
490
398
  var _SIGN_KEYS = { protected: 1, payload: 1, key: 1, jwk: 1, profile: 1 };
491
399
  var _SIGN_KEYS_EMBEDDED_JWK = { protected: 1, payload: 1, key: 1, profile: 1 };
492
400
  async function sign(opts) {
493
401
  opts = opts || {};
494
- // The options are checked BEFORE the header is touched. Serializing it runs the caller's own code
495
- // -- a toJSON, an accessor -- and that code can delete an option from the very bag being checked,
496
- // so a misspelling reached the door already removed and the message went out signed.
497
402
  guard.identifier.assertKnownKeys(opts, _SIGN_KEYS, E, "jose/bad-input", "unknown pki.jose.sign option ");
498
- // The header is then serialized ONCE, and every read below comes from a snapshot of those exact
499
- // bytes: the header validated, the header embedded in the JWS and the key confirmed against the
500
- // produced signature are then all the same values. Read live, a caller getter answers each of
501
- // those questions separately -- assertPublicJwk sees a public JWK while the bytes that ship carry
502
- // the private one, and the door sees a jwk-mode header while the signer sees a kid-mode one.
503
403
  var headerJson;
504
404
  try { headerJson = _stringify(opts.protected); }
505
405
  catch (e) { throw E("jose/bad-header", "the protected header must be JSON-serializable", e); }
506
- // Read back through the same bounded reader a received JWS goes through, so a header this side
507
- // produces is held to the caps and the duplicate-member rule the other side enforces.
508
406
  var headerBytes = headerJson === undefined ? null : _bufferFrom(headerJson, "utf8");
509
407
  var header = headerBytes === null ? opts.protected : parseJson(headerBytes);
510
- // Which of the two key sources is in force is known only once the header has been read, so the
511
- // narrower table runs here. It re-walks the bag, which also catches a name that code added.
512
408
  var embedsJwk = !!(header && typeof header === "object" && header.jwk);
513
409
  if (embedsJwk) {
514
410
  guard.identifier.assertKnownKeys(opts, _SIGN_KEYS_EMBEDDED_JWK, E, "jose/bad-input", function (k) {
@@ -518,47 +414,22 @@ async function sign(opts) {
518
414
  }
519
415
  var payload = opts.payload;
520
416
  if (!_isBuffer(payload)) throw E("jose/bad-input", "payload must be a Buffer (empty Buffer for POST-as-GET)");
521
- // Re-view before the length===0 fast-path: a DETACHED payload reads as zero-length
522
- // and would be silently signed as an empty POST-as-GET body -- fail closed instead.
523
417
  payload = guard.bytes.view(payload, JoseError, "jose/bad-input", "payload");
524
- // A non-CryptoKey (a raw Buffer, a string, a JWK object) would reach subtle.sign
525
- // and throw a bare TypeError; require a CryptoKey (it carries an `algorithm`) so
526
- // every caller -- and every acme builder that composes sign -- fails closed.
527
418
  if (!wcEngine.isCryptoKeyLike(opts.key)) throw E("jose/bad-input", "a private or secret CryptoKey (opts.key) is required");
528
419
  var checked = _checkHeader(header, opts.profile || "acme-outer");
529
- // An RSA / HMAC key binds its hash at import; the signature-length pin below cannot
530
- // see a hash mismatch (the modulus / MAC size is unchanged), so a SHA-512 key under
531
- // RS256 would sign here yet fail verification. Reject the hash disagreement up front.
532
420
  if ((checked.algRow.kty === "RSA" || checked.algRow.kty === "oct") &&
533
421
  (!opts.key.algorithm.hash || opts.key.algorithm.hash.name !== checked.algRow.hash)) {
534
422
  throw E("jose/bad-key", "the signing key's hash does not match the algorithm " + checked.alg);
535
423
  }
536
- // An embedded jwk (jwk mode / keychange-inner) is the public key a verifier will
537
- // use, so it MUST match the alg exactly as verify enforces -- otherwise the header
538
- // advertises a key that cannot verify this signature. (No jwk in kid mode.) It is
539
- // also PUBLISHED to the server, so it must be public-only: an accidentally exported
540
- // private JWK must never be serialized into the protected header.
541
424
  if (header.jwk) { _assertKeyType(checked.algRow, header.jwk); assertPublicJwk(header.jwk); }
542
- var protectedB64 = b64uEncode(headerBytes); // the exact bytes every check above read
425
+ var protectedB64 = b64uEncode(headerBytes);
543
426
  var payloadB64 = payload.length === 0 ? "" : b64uEncode(payload);
544
427
  var signingInput = _bufferFrom(protectedB64 + "." + payloadB64, "ascii");
545
428
  var jwk = header.jwk || opts.jwk || {};
546
- // opts.key is documented as a CryptoKey, so one created outside this engine signs here too: it
547
- // carries none of the material this engine signs with, and is re-imported through it rather than
548
- // reaching subtle.sign as a key it cannot read. A key whose material this process cannot reach at
549
- // all -- non-extractable, or held behind another implementation's interface -- is refused there.
550
429
  var signKey = await wcEngine.adoptKey(opts.key, null, ["sign"], E, "jose/bad-input");
551
430
  var sigBuf = _bufferFrom(await webcrypto.subtle.sign(_cryptoAlg(checked.algRow, jwk, signKey), signKey, signingInput));
552
- // Pin the produced signature length the same way verify does. A key whose curve /
553
- // hash / modulus does not match the header alg (a P-384 key under ES256, an
554
- // HS512 key under HS256) still signs, but emits the wrong length -- caught here
555
- // so the builder fails locally instead of shipping a JWS the server will reject.
556
431
  var want = _expectedSigBytes(checked.algRow, jwk, opts.key);
557
432
  if (want != null && sigBuf.length !== want) throw E("jose/bad-key", "the signing key produced a " + sigBuf.length + "-byte signature but " + checked.alg + " requires " + want + " (the key does not match the algorithm)");
558
- // In jwk mode the EMBEDDED public jwk is what verifiers use. Confirm it actually
559
- // verifies this signature -- i.e. it is the signing key's public half -- so a
560
- // key-pair mix-up (a jwk from a different key of the same type) fails locally
561
- // instead of producing a JWS the CA rejects. Same-type/curve is not enough.
562
433
  if (header.jwk) {
563
434
  var pubKey;
564
435
  try { pubKey = await webcrypto.subtle.importKey("jwk", header.jwk, _importParams(checked.algRow, header.jwk), false, ["verify"]); }
@@ -570,17 +441,13 @@ async function sign(opts) {
570
441
  return { protected: protectedB64, payload: payloadB64, signature: b64uEncode(sigBuf) };
571
442
  }
572
443
 
573
- // ---- JWK thumbprint (RFC 7638 + RFC 8037 + RFC 9964) ---------------------
574
444
 
575
- // The required-member templates per key type (RFC 7638 sec. 3.2 / RFC 8037 sec. 2
576
- // / RFC 9964). ONLY these members, in lexicographic order, no whitespace, feed
577
- // the SHA-256; optional members (alg/use/kid) never enter the hash.
578
445
  var THUMBPRINT_MEMBERS = {
579
446
  EC: ["crv", "kty", "x", "y"],
580
447
  RSA: ["e", "kty", "n"],
581
448
  oct: ["k", "kty"],
582
449
  OKP: ["crv", "kty", "x"],
583
- AKP: ["alg", "kty", "pub"], // RFC 9964 sec. 6: alg, kty, pub in lexicographic order
450
+ AKP: ["alg", "kty", "pub"],
584
451
  };
585
452
 
586
453
  /**
@@ -613,8 +480,6 @@ async function thumbprint(jwk) {
613
480
  for (var i = 0; i < members.length; i++) {
614
481
  var m = members[i];
615
482
  if (typeof jwk[m] !== "string") throw E("jose/bad-key", "the JWK is missing the required member " + _stringify(m));
616
- // The canonical form IS the thumbprint preimage (RFC 7638 sec. 3.3), so the serializer that
617
- // builds it is the captured one: a replacement changes the identity every consumer compares.
618
483
  _push(parts, _stringify(m) + ":" + _stringify(jwk[m]));
619
484
  }
620
485
  var canonical = "{" + _join(parts, ",") + "}";