@amritk/nish-x86_64-linux 0.28.0 → 0.29.0

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.
@@ -0,0 +1,1661 @@
1
+ /**
2
+ * `nish/crypto/jwt` — JSON Web Tokens in the JWS compact serialization: verify
3
+ * one and check its claims, or sign one. RFC 7519 (JWT), RFC 7515 (JWS),
4
+ * RFC 7518 (the HS, RS and ES algorithms), RFC 8037 (EdDSA) and the RFC 8725
5
+ * best current practice.
6
+ *
7
+ * import { JWT_ALG_HS256, JWT_MAX_TOKEN, JWT_OK, JwtVerifier, jwtHmacKey, jwtSignString } from "nish/crypto/jwt";
8
+ *
9
+ * const key = jwtHmacKey(JWT_ALG_HS256, secretKey); // once; null for a short key
10
+ * const v = new JwtVerifier(JWT_MAX_TOKEN); // once, per thread
11
+ * v.issuer = "https://issuer.example";
12
+ * v.audience = "api";
13
+ * v.leeway = toI64(30);
14
+ * if (key !== null && v.verifyString(key, token, now) === JWT_OK) {
15
+ * // v.claims[0 .. v.claimsLen) is the payload's JSON; v.exp, v.nbf, v.iat are read
16
+ * }
17
+ * const minted: string | null = key === null ? null : jwtSignString(key, "{\"sub\":\"ana\"}");
18
+ *
19
+ * **The key decides the algorithm, never the token.** A `JwtKey` is made for
20
+ * one algorithm, and a token verifies only when its header's `alg` is that
21
+ * algorithm's name, spelled exactly. `none` names no key, so a token that
22
+ * says it is unsigned is refused like any other mismatch (`JWT_ERR_ALG`); an
23
+ * `HS256` header in front of an RSA key is refused before any MAC is taken,
24
+ * so an RSA public key can never be used as an HMAC secret (RFC 8725 §2.1,
25
+ * §3.1). The header must hold `alg` exactly once, as a string; `typ` and `kid`
26
+ * at most once, as strings; and no `crit` and no `b64` (RFC 7515 §4.1.11,
27
+ * RFC 7797), since this verifier understands no extension and a header that
28
+ * names one must be refused. Other header members are skipped.
29
+ *
30
+ * **Nothing in the payload is read before the signature holds.** The verifier
31
+ * reads the header, decodes the signature and checks it, and only then decodes
32
+ * and parses the claims. The claims are then checked against the clock and the
33
+ * verifier's settings:
34
+ *
35
+ * - `exp` (RFC 7519 §4.1.4): the token is good while `now - leeway < exp`.
36
+ * A token with no `exp` is refused unless `requireExp` is cleared.
37
+ * - `nbf` (§4.1.5): good once `nbf <= now + leeway`.
38
+ * - `iat` (§4.1.6): refused when it is later than `now + leeway`.
39
+ * - `iss`, `sub` (§4.1.1, §4.1.2): when the verifier names one, the token must
40
+ * carry it, spelled exactly.
41
+ * - `aud` (§4.1.3): one string or an array of strings. When the verifier names
42
+ * an audience, one of them must be it. A token that carries `aud` is refused
43
+ * by a verifier that names none, as §4.1.3 requires.
44
+ *
45
+ * Each registered claim may appear once; the time claims are integers
46
+ * (seconds since 1970). `now` is the caller's, so a test passes its own clock.
47
+ *
48
+ * **Bounded once made.** A `JwtVerifier` holds its decode buffers, its
49
+ * tokenizer and its results, sized once from the largest token it takes
50
+ * (`JWT_MAX_TOKEN` by default, 8 KiB); a longer token is refused before a
51
+ * byte is decoded. An HS token is checked against `HmacKey`'s pads, keyed
52
+ * once when the key was made, through `hmacKeyedVerify`, and the tag is
53
+ * compared in constant time: an HS verify allocates nothing past its arena
54
+ * block. An RS, ES or EdDSA verify allocates the signed input's copy and
55
+ * whatever the public-key check itself allocates (bignums, points), which
56
+ * the caller's scope releases. One verifier is one thread's: it is not safe
57
+ * to share while a `verify` runs.
58
+ *
59
+ * **Base64url is strict**, as `nish/crypto/base64url`: no padding, no
60
+ * whitespace, no character outside `A-Z a-z 0-9 - _`, and no stray bits in the
61
+ * last character, so each token has one spelling. The token itself is public,
62
+ * so the decoder here branches freely.
63
+ *
64
+ * **Signing** covers HS256/384/512, ES256 and EdDSA, the algorithms the
65
+ * standard library holds a signer for; `jwtSign` answers `null` for an RS or
66
+ * ES384 key, which can verify only. The header written is
67
+ * `{"alg":"…","typ":"JWT"}`, with a `kid` when one is given.
68
+ *
69
+ * **Secrets.** An HMAC key and a private key are `Secret<u8[]>`
70
+ * (`nish:secret`). A `JwtKey` copies what it is handed and the caller still
71
+ * owns, and wipes, its own; `JwtKey.wipe()` zeroes the key's copy and its HMAC
72
+ * pads. The threat model and what was checked are in
73
+ * `docs/security/crypto-jwt.md`.
74
+ *
75
+ * Private names carry the `jwt` prefix because a `std/` module's private
76
+ * functions share the importing program's flat symbol namespace
77
+ * (`docs/wp26-stdlib.md` §3e).
78
+ */
79
+ import {
80
+ HMAC_SHA256,
81
+ HMAC_SHA384,
82
+ HMAC_SHA512,
83
+ HmacKey,
84
+ hmacKeyedInto,
85
+ hmacKeyedVerify,
86
+ } from "nish/crypto/hmac"
87
+ import {
88
+ RSA_HASH_SHA256,
89
+ RSA_HASH_SHA384,
90
+ RSA_HASH_SHA512,
91
+ RsaPublicKey,
92
+ rsaParsePublicKey,
93
+ rsaVerifyPkcs1v15,
94
+ } from "nish/crypto/rsa"
95
+ import {
96
+ P256_POINT_SIZE,
97
+ P256_SIGNATURE_SIZE,
98
+ p256PublicKey,
99
+ p256SignSha256,
100
+ p256VerifySha256,
101
+ } from "nish/crypto/p256"
102
+ import { P384_POINT_SIZE, P384_SIGNATURE_SIZE, p384VerifySha384 } from "nish/crypto/p384"
103
+ import { ed25519PublicKey, ed25519Sign, ed25519Verify } from "nish/crypto/ed25519"
104
+ import { base64urlEncode } from "nish/crypto/base64url"
105
+ import { Secret, expose, secret, wipe } from "nish:secret"
106
+
107
+ /** HMAC with SHA-256 (RFC 7518 §3.2). */
108
+ export const JWT_ALG_HS256: i32 = 1
109
+ /** HMAC with SHA-384. */
110
+ export const JWT_ALG_HS384: i32 = 2
111
+ /** HMAC with SHA-512. */
112
+ export const JWT_ALG_HS512: i32 = 3
113
+ /** RSASSA-PKCS1-v1_5 with SHA-256 (RFC 7518 §3.3). */
114
+ export const JWT_ALG_RS256: i32 = 4
115
+ /** RSASSA-PKCS1-v1_5 with SHA-384. */
116
+ export const JWT_ALG_RS384: i32 = 5
117
+ /** RSASSA-PKCS1-v1_5 with SHA-512. */
118
+ export const JWT_ALG_RS512: i32 = 6
119
+ /** ECDSA on P-256 with SHA-256 (RFC 7518 §3.4). */
120
+ export const JWT_ALG_ES256: i32 = 7
121
+ /** ECDSA on P-384 with SHA-384. */
122
+ export const JWT_ALG_ES384: i32 = 8
123
+ /** Ed25519 (RFC 8037 §3.1). */
124
+ export const JWT_ALG_EDDSA: i32 = 9
125
+
126
+ /** The token verified and every claim check passed. */
127
+ export const JWT_OK: i32 = 0
128
+ /** The token is longer than the verifier takes, or its window is outside its array. */
129
+ export const JWT_ERR_SIZE: i32 = -1
130
+ /** Not three base64url parts joined by two dots. */
131
+ export const JWT_ERR_FORMAT: i32 = -2
132
+ /** The header is not a JSON object, repeats a member, or names `crit` or `b64`. */
133
+ export const JWT_ERR_HEADER: i32 = -3
134
+ /** The header's `alg` is missing, is not a string, or is not the key's algorithm (`none` included). */
135
+ export const JWT_ERR_ALG: i32 = -4
136
+ /** The signature is the wrong length or does not verify. */
137
+ export const JWT_ERR_SIGNATURE: i32 = -5
138
+ /** The payload is not a JSON object, repeats or mistypes a registered claim, or lacks `exp`. */
139
+ export const JWT_ERR_CLAIMS: i32 = -6
140
+ /** `exp` has passed. */
141
+ export const JWT_ERR_EXPIRED: i32 = -7
142
+ /** `nbf` has not come. */
143
+ export const JWT_ERR_NOT_YET: i32 = -8
144
+ /** `iat` is in the future. */
145
+ export const JWT_ERR_IAT: i32 = -9
146
+ /** `iss` is missing or not the verifier's issuer. */
147
+ export const JWT_ERR_ISSUER: i32 = -10
148
+ /** `aud` does not name the verifier's audience, or is present when the verifier names none. */
149
+ export const JWT_ERR_AUDIENCE: i32 = -11
150
+ /** `sub` is missing or not the verifier's subject. */
151
+ export const JWT_ERR_SUBJECT: i32 = -12
152
+ /** The header's `typ` is not the verifier's `typ`. */
153
+ export const JWT_ERR_TYPE: i32 = -13
154
+
155
+ /** The longest token a default `JwtVerifier` takes, in bytes. */
156
+ export const JWT_MAX_TOKEN: i32 = 8192
157
+ /** The longest token any `JwtVerifier` takes: 1 MiB. */
158
+ export const JWT_MAX_TOKEN_LIMIT: i32 = 1048576
159
+
160
+ /** The longest signature: an 8192-bit RSA modulus. */
161
+ const JWT_MAX_SIGNATURE: i32 = 1024
162
+ /** How deep a header or a payload may nest. */
163
+ const JWT_MAX_DEPTH: i32 = 32
164
+ /** The largest leeway, in seconds: about 68 years, which `now ± leeway` cannot overflow. */
165
+ const JWT_MAX_LEEWAY: i64 = 2147483647
166
+ /** The latest `now`: 2^62 seconds, so `now + leeway` stays inside `i64`. */
167
+ const JWT_MAX_NOW: i64 = 1073741824 * 1073741824 * 4
168
+ const JWT_DOT: i32 = 46
169
+
170
+ /** The `alg` header value for `alg`, `""` for no algorithm this module knows. */
171
+ export const jwtAlgName = (alg: i32): string => {
172
+ if (alg === JWT_ALG_HS256) {
173
+ return "HS256"
174
+ }
175
+ if (alg === JWT_ALG_HS384) {
176
+ return "HS384"
177
+ }
178
+ if (alg === JWT_ALG_HS512) {
179
+ return "HS512"
180
+ }
181
+ if (alg === JWT_ALG_RS256) {
182
+ return "RS256"
183
+ }
184
+ if (alg === JWT_ALG_RS384) {
185
+ return "RS384"
186
+ }
187
+ if (alg === JWT_ALG_RS512) {
188
+ return "RS512"
189
+ }
190
+ if (alg === JWT_ALG_ES256) {
191
+ return "ES256"
192
+ }
193
+ if (alg === JWT_ALG_ES384) {
194
+ return "ES384"
195
+ }
196
+ if (alg === JWT_ALG_EDDSA) {
197
+ return "EdDSA"
198
+ }
199
+ return ""
200
+ }
201
+
202
+ /** Whether `alg` is one of the HS algorithms, checked with an HMAC key. */
203
+ const jwtIsHmac = (alg: i32): boolean =>
204
+ alg === JWT_ALG_HS256 || alg === JWT_ALG_HS384 || alg === JWT_ALG_HS512
205
+
206
+ /** The signature length `alg` gives under `key`, in bytes. */
207
+ const jwtSignatureSize = (key: JwtKey): i32 => {
208
+ const alg: i32 = key.alg
209
+ const hmac: HmacKey | null = key.hmac
210
+ if (hmac !== null) {
211
+ return hmac.size()
212
+ }
213
+ const rsa: RsaPublicKey | null = key.rsa
214
+ if (rsa !== null) {
215
+ return (rsa.bits + 7) >> 3
216
+ }
217
+ if (alg === JWT_ALG_ES256) {
218
+ return P256_SIGNATURE_SIZE
219
+ }
220
+ if (alg === JWT_ALG_ES384) {
221
+ return P384_SIGNATURE_SIZE
222
+ }
223
+ return 64
224
+ }
225
+
226
+ const jwtCopy = (a: u8[]): u8[] => jwtSlice(a, toI32(0), toI32(a.length))
227
+
228
+ const jwtLength = (a: u8[]): i32 => toI32(a.length)
229
+
230
+ /**
231
+ * A key for one algorithm: what verifies its tokens and, for a key that can,
232
+ * signs them. Make one with `jwtHmacKey`, `jwtRsaKey`, `jwtRsaKeyParts`,
233
+ * `jwtEcKey`, `jwtEd25519Key`, `jwtEs256SigningKey` or
234
+ * `jwtEd25519SigningKey`; `wipe()` it when it is done with. An HS key holds
235
+ * its HMAC work hashers, so a key is one thread's, like a `JwtVerifier`: give
236
+ * each worker its own.
237
+ */
238
+ export class JwtKey {
239
+ /** An HS key's pads, keyed once; `null` for any other. */
240
+ readonly hmac: HmacKey | null
241
+ /** An RS key; `null` for any other. */
242
+ readonly rsa: RsaPublicKey | null
243
+ /** An ES key's uncompressed point or an EdDSA key's 32 bytes; empty for HS and RS. */
244
+ readonly pub: u8[]
245
+ /** An ES256 or EdDSA signing key's private scalar or seed; empty for any other. */
246
+ readonly priv: Secret<u8[]>
247
+ /** The one algorithm this key verifies, `JWT_ALG_*`. */
248
+ readonly alg: i32
249
+ /** Whether `jwtSign` signs with this key. */
250
+ readonly canSign: boolean
251
+ /** Whether the key is in use: `wipe()` clears it, and a wiped key signs and verifies nothing. */
252
+ live: boolean
253
+
254
+ constructor(
255
+ alg: i32,
256
+ hmac: HmacKey | null,
257
+ rsa: RsaPublicKey | null,
258
+ pub: u8[],
259
+ priv: Secret<u8[]>,
260
+ canSign: boolean
261
+ ) {
262
+ this.alg = alg
263
+ this.hmac = hmac
264
+ this.rsa = rsa
265
+ this.pub = pub
266
+ this.priv = secret(expose(priv, jwtCopy))
267
+ this.canSign = canSign
268
+ this.live = true
269
+ }
270
+
271
+ /**
272
+ * Zero the key's private copy and its HMAC pads. It neither signs nor
273
+ * verifies after: its zeroed secret is one anyone could hold.
274
+ */
275
+ wipe(): void {
276
+ this.live = false
277
+ wipe(this.priv)
278
+ const hmac: HmacKey | null = this.hmac
279
+ if (hmac !== null) {
280
+ hmac.wipe()
281
+ }
282
+ }
283
+ }
284
+
285
+ /** A key with no private half: the private slot is an empty array, made and wiped here. */
286
+ const jwtPublicKey = (
287
+ alg: i32,
288
+ hmac: HmacKey | null,
289
+ rsa: RsaPublicKey | null,
290
+ pub: u8[],
291
+ canSign: boolean
292
+ ): JwtKey => {
293
+ const none: Secret<u8[]> = secret(new Array<u8>(0))
294
+ const key = new JwtKey(alg, hmac, rsa, pub, none, canSign)
295
+ wipe(none)
296
+ return key
297
+ }
298
+
299
+ /**
300
+ * An HS256, HS384 or HS512 key that verifies and signs. RFC 7518 §3.2 asks for
301
+ * a key at least as long as the hash, so a shorter one answers `null`, as does
302
+ * an `alg` that is not HS. The pads are keyed once, here; `key` stays the
303
+ * caller's to wipe.
304
+ */
305
+ export const jwtHmacKey = (alg: i32, key: Secret<u8[]>): JwtKey | null => {
306
+ let hash: i32 = 0
307
+ let size: i32 = 0
308
+ if (alg === JWT_ALG_HS256) {
309
+ hash = HMAC_SHA256
310
+ size = 32
311
+ } else if (alg === JWT_ALG_HS384) {
312
+ hash = HMAC_SHA384
313
+ size = 48
314
+ } else if (alg === JWT_ALG_HS512) {
315
+ hash = HMAC_SHA512
316
+ size = 64
317
+ } else {
318
+ return null
319
+ }
320
+ if (expose(key, jwtLength) < size) {
321
+ return null
322
+ }
323
+ return jwtPublicKey(alg, new HmacKey(hash, key), null, new Array<u8>(0), true)
324
+ }
325
+
326
+ /**
327
+ * An RS256, RS384 or RS512 key from the DER of a PKCS#1 `RSAPublicKey`
328
+ * (`rsaParsePublicKey`'s input). A key that does not parse, a modulus below
329
+ * 2048 bits (RFC 7518 §3.3) or an `alg` that is not RS answers `null`.
330
+ */
331
+ export const jwtRsaKey = (alg: i32, der: u8[]): JwtKey | null => {
332
+ if (alg !== JWT_ALG_RS256 && alg !== JWT_ALG_RS384 && alg !== JWT_ALG_RS512) {
333
+ return null
334
+ }
335
+ const rsa: RsaPublicKey | null = rsaParsePublicKey(der)
336
+ if (rsa === null || rsa.bits < 2048) {
337
+ return null
338
+ }
339
+ return jwtPublicKey(alg, null, rsa, new Array<u8>(0), false)
340
+ }
341
+
342
+ /** Appends a DER length. */
343
+ const jwtDerLength = (out: u8[], n: i32): void => {
344
+ if (n < 128) {
345
+ out.push(toU8(n))
346
+ } else if (n < 256) {
347
+ out.push(toU8(0x81))
348
+ out.push(toU8(n))
349
+ } else {
350
+ out.push(toU8(0x82))
351
+ out.push(toU8(n >> 8))
352
+ out.push(toU8(n & 255))
353
+ }
354
+ }
355
+
356
+ /** Appends `v` as a DER INTEGER: leading zero bytes dropped, one put back when the top bit is set. */
357
+ const jwtDerInteger = (out: u8[], v: u8[]): void => {
358
+ let start: i32 = 0
359
+ const last: i32 = toI32(v.length) - 1
360
+ for (let i: i32 = 0; i < last && i < toI32(v.length); i += 1) {
361
+ if (toI32(v[i]) !== 0) {
362
+ break
363
+ }
364
+ start = i + 1
365
+ }
366
+ const pad: boolean = start < toI32(v.length) && (toI32(v[start]) & 0x80) !== 0
367
+ out.push(toU8(2))
368
+ jwtDerLength(out, toI32(v.length) - start + (pad ? 1 : 0))
369
+ if (pad) {
370
+ out.push(toU8(0))
371
+ }
372
+ for (let i: i32 = start; i >= 0 && i < toI32(v.length); i += 1) {
373
+ out.push(v[i])
374
+ }
375
+ }
376
+
377
+ /**
378
+ * An RS key from its big-endian modulus and exponent, as a JWK's `n` and `e`
379
+ * spell them once base64url-decoded (RFC 7518 §6.3.1). The same checks as
380
+ * `jwtRsaKey`; parts too long for any key this module takes answer `null`.
381
+ */
382
+ export const jwtRsaKeyParts = (alg: i32, n: u8[], e: u8[]): JwtKey | null => {
383
+ if (toI32(n.length) > 2048 || toI32(e.length) > 8 || toI32(n.length) === 0 || toI32(e.length) === 0) {
384
+ return null
385
+ }
386
+ const body: u8[] = []
387
+ jwtDerInteger(body, n)
388
+ jwtDerInteger(body, e)
389
+ const der: u8[] = [toU8(0x30)]
390
+ jwtDerLength(der, toI32(body.length))
391
+ for (let i: i32 = 0; i < toI32(body.length); i += 1) {
392
+ der.push(body[i])
393
+ }
394
+ return jwtRsaKey(alg, der)
395
+ }
396
+
397
+ /**
398
+ * An ES256 or ES384 verifying key from its uncompressed point, `04 ‖ x ‖ y`
399
+ * (65 or 97 bytes). A point of the wrong length or form answers `null`; one
400
+ * off the curve makes every verification fail.
401
+ */
402
+ export const jwtEcKey = (alg: i32, point: u8[]): JwtKey | null => {
403
+ let size: i32 = 0
404
+ if (alg === JWT_ALG_ES256) {
405
+ size = P256_POINT_SIZE
406
+ } else if (alg === JWT_ALG_ES384) {
407
+ size = P384_POINT_SIZE
408
+ }
409
+ if (size === 0 || toI32(point.length) !== size || toI32(point[0]) !== 4) {
410
+ return null
411
+ }
412
+ return jwtPublicKey(alg, null, null, jwtCopy(point), false)
413
+ }
414
+
415
+ /** An EdDSA (Ed25519) verifying key from its 32 bytes (RFC 8037 §2's `x`); another length answers `null`. */
416
+ export const jwtEd25519Key = (pub: u8[]): JwtKey | null => {
417
+ if (toI32(pub.length) !== 32) {
418
+ return null
419
+ }
420
+ return jwtPublicKey(JWT_ALG_EDDSA, null, null, jwtCopy(pub), false)
421
+ }
422
+
423
+ /**
424
+ * An ES256 key that signs and verifies, from its 32-byte private scalar. A
425
+ * scalar `p256PublicKey` refuses answers `null`. The key keeps a copy;
426
+ * `priv` stays the caller's to wipe.
427
+ */
428
+ export const jwtEs256SigningKey = (priv: Secret<u8[]>): JwtKey | null => {
429
+ const pub: u8[] | null = p256PublicKey(priv)
430
+ if (pub === null) {
431
+ return null
432
+ }
433
+ return new JwtKey(JWT_ALG_ES256, null, null, pub, priv, true)
434
+ }
435
+
436
+ /**
437
+ * An EdDSA key that signs and verifies, from its 32-byte seed (RFC 8037 §2's
438
+ * `d`). A seed of another length answers `null`. The key keeps a copy; `seed`
439
+ * stays the caller's to wipe.
440
+ */
441
+ export const jwtEd25519SigningKey = (seed: Secret<u8[]>): JwtKey | null => {
442
+ const pub: u8[] | null = ed25519PublicKey(seed)
443
+ if (pub === null) {
444
+ return null
445
+ }
446
+ return new JwtKey(JWT_ALG_EDDSA, null, null, pub, seed, true)
447
+ }
448
+
449
+ // ---- base64url --------------------------------------------------------------
450
+
451
+ /** The sextet `c` spells, or -1. */
452
+ const jwtSextet = (c: i32): i32 => {
453
+ if (c >= 65 && c <= 90) {
454
+ return c - 65
455
+ }
456
+ if (c >= 97 && c <= 122) {
457
+ return c - 71
458
+ }
459
+ if (c >= 48 && c <= 57) {
460
+ return c + 4
461
+ }
462
+ if (c === 45) {
463
+ return 62
464
+ }
465
+ if (c === 95) {
466
+ return 63
467
+ }
468
+ return -1
469
+ }
470
+
471
+ /**
472
+ * Decodes the unpadded base64url `src[off .. off + len)` into `out` from 0 and
473
+ * answers the byte count, or -1 for a length of 1 mod 4, a character outside
474
+ * the alphabet, stray low bits in the last character, or more bytes than
475
+ * `out` holds.
476
+ */
477
+ const jwtDecode = (src: u8[], off: i32, len: i32, out: u8[]): i32 => {
478
+ const tail: i32 = len & 3
479
+ if (tail === 1) {
480
+ return -1
481
+ }
482
+ const size: i32 = (len >> 2) * 3 + (tail === 0 ? 0 : tail - 1)
483
+ if (size > toI32(out.length)) {
484
+ return -1
485
+ }
486
+ let acc: i32 = 0
487
+ let bits: i32 = 0
488
+ let at: i32 = 0
489
+ for (let i: i32 = 0; i < len; i += 1) {
490
+ const v: i32 = jwtSextet(toI32(src[off + i]))
491
+ if (v < 0) {
492
+ return -1
493
+ }
494
+ acc = ((acc << 6) | v) & 0xffffff
495
+ bits += 6
496
+ if (bits >= 8 && at >= 0 && at < toI32(out.length)) {
497
+ bits -= 8
498
+ out[at] = toU8((acc >> bits) & 255)
499
+ at += 1
500
+ }
501
+ }
502
+ if ((acc & ((1 << bits) - 1)) !== 0) {
503
+ return -1
504
+ }
505
+ return at
506
+ }
507
+
508
+ // ---- JSON -------------------------------------------------------------------
509
+ //
510
+ // The header and the claims are read by `JwtJson`, a pull reader of RFC 8259
511
+ // written for this module, rather than by `nish/json-tokenizer`: that one
512
+ // steps over blanks and string runs with `nish/bytes`'s scan kernels, which
513
+ // `std/crypto` keeps out of (docs/security/bytes.md, BYTES-2). This one reads
514
+ // a byte at a time. It answers the token kinds `jwt.ts` asks about and one
515
+ // error, `JWT_JSON_ERR`; it has no string or number limits of its own, since
516
+ // the window it reads is a decoded token part of at most `maxToken` bytes.
517
+
518
+ /** The top-level value is complete and only blanks follow it. */
519
+ const JWT_JSON_END: i32 = 0
520
+ const JWT_JSON_OBJECT_BEGIN: i32 = 1
521
+ const JWT_JSON_OBJECT_END: i32 = 2
522
+ const JWT_JSON_ARRAY_BEGIN: i32 = 3
523
+ const JWT_JSON_ARRAY_END: i32 = 4
524
+ /** A member's name and its colon; the value is the next token. */
525
+ const JWT_JSON_KEY: i32 = 5
526
+ const JWT_JSON_STRING: i32 = 6
527
+ const JWT_JSON_NUMBER: i32 = 7
528
+ /** `true`, `false` or `null`. */
529
+ const JWT_JSON_LITERAL: i32 = 8
530
+ /** Anything RFC 8259 refuses, the window ending inside a value, or a container past the depth. */
531
+ const JWT_JSON_ERR: i32 = -1
532
+
533
+ /** What `next()` reads at the cursor. */
534
+ const JWT_JSON_WANT_VALUE: i32 = 0
535
+ const JWT_JSON_WANT_VALUE_OR_CLOSE: i32 = 1
536
+ const JWT_JSON_WANT_KEY: i32 = 2
537
+ const JWT_JSON_WANT_KEY_OR_CLOSE: i32 = 3
538
+ const JWT_JSON_WANT_COMMA_OR_CLOSE: i32 = 4
539
+ const JWT_JSON_WANT_NOTHING: i32 = 5
540
+
541
+ /** What the depth stack holds for an open container. */
542
+ const JWT_JSON_IN_OBJECT: i32 = 1
543
+ const JWT_JSON_IN_ARRAY: i32 = 2
544
+
545
+ const JWT_CH_TAB: i32 = 9
546
+ const JWT_CH_NEWLINE: i32 = 10
547
+ const JWT_CH_RETURN: i32 = 13
548
+ const JWT_CH_SPACE: i32 = 32
549
+ const JWT_CH_QUOTE: i32 = 34
550
+ const JWT_CH_PLUS: i32 = 43
551
+ const JWT_CH_COMMA: i32 = 44
552
+ const JWT_CH_MINUS: i32 = 45
553
+ const JWT_CH_DOT: i32 = 46
554
+ const JWT_CH_0: i32 = 48
555
+ const JWT_CH_9: i32 = 57
556
+ const JWT_CH_COLON: i32 = 58
557
+ const JWT_CH_OPEN_BRACKET: i32 = 91
558
+ const JWT_CH_BACKSLASH: i32 = 92
559
+ const JWT_CH_CLOSE_BRACKET: i32 = 93
560
+ const JWT_CH_OPEN_BRACE: i32 = 123
561
+ const JWT_CH_CLOSE_BRACE: i32 = 125
562
+
563
+ /** The value of hex digit `b`, or `-1`. */
564
+ const jwtHexValue = (b: i32): i32 => {
565
+ if (b >= 48 && b <= 57) {
566
+ return b - 48
567
+ }
568
+ if (b >= 65 && b <= 70) {
569
+ return b - 55
570
+ }
571
+ if (b >= 97 && b <= 102) {
572
+ return b - 87
573
+ }
574
+ return -1
575
+ }
576
+
577
+ /** How many bytes code point `cp` takes in UTF-8. */
578
+ const jwtUtf8Width = (cp: i32): i32 => {
579
+ if (cp < 0x80) {
580
+ return 1
581
+ }
582
+ if (cp < 0x800) {
583
+ return 2
584
+ }
585
+ if (cp < 0x10000) {
586
+ return 3
587
+ }
588
+ return 4
589
+ }
590
+
591
+ /** Byte `k` of the `width`-byte UTF-8 form of `cp`. */
592
+ const jwtUtf8Byte = (cp: i32, width: i32, k: i32): i32 => {
593
+ if (width === 1) {
594
+ return cp
595
+ }
596
+ let lead: i32 = 0xf0
597
+ if (width === 2) {
598
+ lead = 0xc0
599
+ } else if (width === 3) {
600
+ lead = 0xe0
601
+ }
602
+ const shift: i32 = (width - 1 - k) * 6
603
+ const tail: i32 = 0x80
604
+ return k === 0 ? lead | (cp >> shift) : tail | ((cp >> shift) & 0x3f)
605
+ }
606
+
607
+ /**
608
+ * A pull reader of one JSON value in `src[off, off + len)`, a token per
609
+ * `next()`, as strict as RFC 8259: UTF-8 checked, every escape and surrogate
610
+ * pair checked, no control byte in a string, no trailing bytes. Keys and
611
+ * strings compare decoded (`keyEquals`), so an escaped name is the name.
612
+ */
613
+ class JwtJson {
614
+ /** The current integer's value, negated, when `integral` holds. */
615
+ value: i64
616
+ src: u8[]
617
+ readonly stack: u8[]
618
+ pos: i32
619
+ end: i32
620
+ depth: i32
621
+ want: i32
622
+ kind: i32
623
+ failed: i32
624
+ /** The current token's bytes, `src[tokStart, tokEnd)`; a string's and a key's include their quotes. */
625
+ tokStart: i32
626
+ tokEnd: i32
627
+ /** The current key's or string's length once decoded. */
628
+ textBytes: i32
629
+ /** Whether the current number has no fraction or exponent and fits an `i64`. */
630
+ integral: boolean
631
+
632
+ /** A reader for values nested at most `maxDepth` containers deep; `reset` gives it a window. */
633
+ constructor(src: u8[], maxDepth: i32) {
634
+ this.value = toI64(0)
635
+ this.src = src
636
+ this.stack = new Array<u8>(maxDepth)
637
+ this.pos = 0
638
+ this.end = 0
639
+ this.depth = 0
640
+ this.want = JWT_JSON_WANT_VALUE
641
+ this.kind = JWT_JSON_ERR
642
+ this.failed = JWT_JSON_ERR
643
+ this.tokStart = 0
644
+ this.tokEnd = 0
645
+ this.textBytes = 0
646
+ this.integral = false
647
+ }
648
+
649
+ /** Starts over on `src[off, off + len)`; a window outside `src` fails the first `next()`. */
650
+ reset(src: u8[], off: i32, len: i32): void {
651
+ this.src = src
652
+ this.pos = off
653
+ this.end = off + len
654
+ this.depth = 0
655
+ this.want = JWT_JSON_WANT_VALUE
656
+ this.kind = JWT_JSON_ERR
657
+ this.failed = off < 0 || len < 0 || len > toI32(src.length) - off ? JWT_JSON_ERR : 0
658
+ this.integral = false
659
+ }
660
+
661
+ /** The byte at `i`, or `-1` past the window. */
662
+ at(i: i32): i32 {
663
+ return i >= 0 && i < this.end && i < toI32(this.src.length) ? toI32(this.src[i]) : -1
664
+ }
665
+
666
+ /** Fails the reader for good. */
667
+ fail(): i32 {
668
+ this.failed = JWT_JSON_ERR
669
+ this.kind = JWT_JSON_ERR
670
+ return JWT_JSON_ERR
671
+ }
672
+
673
+ /** Steps over the four blanks RFC 8259 §2 allows. */
674
+ skipBlanks(): void {
675
+ let b: i32 = this.at(this.pos)
676
+ while (b === JWT_CH_SPACE || b === JWT_CH_TAB || b === JWT_CH_NEWLINE || b === JWT_CH_RETURN) {
677
+ this.pos += 1
678
+ b = this.at(this.pos)
679
+ }
680
+ }
681
+
682
+ /** The token after the current one, or `JWT_JSON_ERR`, answered again on every later call. */
683
+ next(): i32 {
684
+ if (this.failed !== 0) {
685
+ return this.failed
686
+ }
687
+ this.skipBlanks()
688
+ const want: i32 = this.want
689
+ const b: i32 = this.at(this.pos)
690
+ if (want === JWT_JSON_WANT_NOTHING) {
691
+ if (b >= 0) {
692
+ return this.fail()
693
+ }
694
+ this.kind = JWT_JSON_END
695
+ return JWT_JSON_END
696
+ }
697
+ if (want === JWT_JSON_WANT_COMMA_OR_CLOSE) {
698
+ if (b === JWT_CH_COMMA) {
699
+ this.pos += 1
700
+ this.skipBlanks()
701
+ return toI32(this.stack[this.depth - 1]) === JWT_JSON_IN_OBJECT ? this.readKey() : this.readValue()
702
+ }
703
+ return this.readClose(b)
704
+ }
705
+ if (want === JWT_JSON_WANT_KEY_OR_CLOSE) {
706
+ return b === JWT_CH_CLOSE_BRACE ? this.readClose(b) : this.readKey()
707
+ }
708
+ if (want === JWT_JSON_WANT_KEY) {
709
+ return this.readKey()
710
+ }
711
+ if (want === JWT_JSON_WANT_VALUE_OR_CLOSE && b === JWT_CH_CLOSE_BRACKET) {
712
+ return this.readClose(b)
713
+ }
714
+ return this.readValue()
715
+ }
716
+
717
+ /** `b`, the closer of the innermost container, popped. */
718
+ readClose(b: i32): i32 {
719
+ if (this.depth === 0) {
720
+ return this.fail()
721
+ }
722
+ const inObject: boolean = toI32(this.stack[this.depth - 1]) === JWT_JSON_IN_OBJECT
723
+ if (b !== (inObject ? JWT_CH_CLOSE_BRACE : JWT_CH_CLOSE_BRACKET)) {
724
+ return this.fail()
725
+ }
726
+ this.tokStart = this.pos
727
+ this.pos += 1
728
+ this.tokEnd = this.pos
729
+ this.depth -= 1
730
+ this.afterValue()
731
+ this.kind = inObject ? JWT_JSON_OBJECT_END : JWT_JSON_ARRAY_END
732
+ return this.kind
733
+ }
734
+
735
+ /** What follows a complete value: nothing at the top, else a comma or a closer. */
736
+ afterValue(): void {
737
+ this.want = this.depth === 0 ? JWT_JSON_WANT_NOTHING : JWT_JSON_WANT_COMMA_OR_CLOSE
738
+ }
739
+
740
+ /** A member's name, its blanks and its colon. */
741
+ readKey(): i32 {
742
+ if (this.at(this.pos) !== JWT_CH_QUOTE || !this.scanString()) {
743
+ return this.fail()
744
+ }
745
+ this.skipBlanks()
746
+ if (this.at(this.pos) !== JWT_CH_COLON) {
747
+ return this.fail()
748
+ }
749
+ this.pos += 1
750
+ this.want = JWT_JSON_WANT_VALUE
751
+ this.kind = JWT_JSON_KEY
752
+ return JWT_JSON_KEY
753
+ }
754
+
755
+ /** A value at the cursor: an opener, a string, a number or a literal. */
756
+ readValue(): i32 {
757
+ const b: i32 = this.at(this.pos)
758
+ if (b === JWT_CH_OPEN_BRACE || b === JWT_CH_OPEN_BRACKET) {
759
+ if (this.depth >= toI32(this.stack.length)) {
760
+ return this.fail()
761
+ }
762
+ const inObject: boolean = b === JWT_CH_OPEN_BRACE
763
+ this.stack[this.depth] = toU8(inObject ? JWT_JSON_IN_OBJECT : JWT_JSON_IN_ARRAY)
764
+ this.depth += 1
765
+ this.tokStart = this.pos
766
+ this.pos += 1
767
+ this.tokEnd = this.pos
768
+ this.want = inObject ? JWT_JSON_WANT_KEY_OR_CLOSE : JWT_JSON_WANT_VALUE_OR_CLOSE
769
+ this.kind = inObject ? JWT_JSON_OBJECT_BEGIN : JWT_JSON_ARRAY_BEGIN
770
+ return this.kind
771
+ }
772
+ let k: i32 = JWT_JSON_ERR
773
+ if (b === JWT_CH_QUOTE) {
774
+ k = this.scanString() ? JWT_JSON_STRING : JWT_JSON_ERR
775
+ } else if (b === JWT_CH_MINUS || (b >= JWT_CH_0 && b <= JWT_CH_9)) {
776
+ k = this.scanNumber() ? JWT_JSON_NUMBER : JWT_JSON_ERR
777
+ } else if (this.scanWord("true") || this.scanWord("false") || this.scanWord("null")) {
778
+ k = JWT_JSON_LITERAL
779
+ }
780
+ if (k < 0) {
781
+ return this.fail()
782
+ }
783
+ this.afterValue()
784
+ this.kind = k
785
+ return k
786
+ }
787
+
788
+ /** Whether `word` is at the cursor, stepped over if so. */
789
+ scanWord(word: string): boolean {
790
+ const n: i32 = toI32(word.length)
791
+ for (let i: i32 = 0; i < n; i += 1) {
792
+ if (this.at(this.pos + i) !== toI32(word.charCodeAt(i))) {
793
+ return false
794
+ }
795
+ }
796
+ this.tokStart = this.pos
797
+ this.pos += n
798
+ this.tokEnd = this.pos
799
+ return true
800
+ }
801
+
802
+ /** The digits at the cursor, accumulated negated into `value`; how many, and `integral` cleared on overflow. */
803
+ scanDigits(bound: i64): i32 {
804
+ const ten: i64 = toI64(10)
805
+ let n: i32 = 0
806
+ let b: i32 = this.at(this.pos)
807
+ while (b >= JWT_CH_0 && b <= JWT_CH_9) {
808
+ const digit: i64 = toI64(b - JWT_CH_0)
809
+ if (this.integral) {
810
+ if (this.value < (bound + digit) / ten) {
811
+ this.integral = false
812
+ } else {
813
+ this.value = this.value * ten - digit
814
+ }
815
+ }
816
+ n += 1
817
+ this.pos += 1
818
+ b = this.at(this.pos)
819
+ }
820
+ return n
821
+ }
822
+
823
+ /**
824
+ * A number of RFC 8259 §6: `-? (0 | [1-9][0-9]*) (. [0-9]+)? ([eE] [+-]? [0-9]+)?`.
825
+ * `integral` says whether it has neither fraction nor exponent and fits an
826
+ * `i64` for its sign; accumulating negated reaches -2^63, where a positive
827
+ * number stops at 2^63 - 1.
828
+ */
829
+ scanNumber(): boolean {
830
+ this.tokStart = this.pos
831
+ const negative: boolean = this.at(this.pos) === JWT_CH_MINUS
832
+ if (negative) {
833
+ this.pos += 1
834
+ }
835
+ // -2^63 has no literal (NL2055): -2^31 shifted up 32 bits. A bare literal
836
+ // would be a double under Node, where an `i64` is a BigInt.
837
+ const min: i64 = toI64(-2147483648) << toI64(32)
838
+ const bound: i64 = negative ? min : min + toI64(1)
839
+ this.value = toI64(0)
840
+ this.integral = true
841
+ const first: i32 = this.at(this.pos)
842
+ if (first === JWT_CH_0) {
843
+ this.pos += 1
844
+ } else if (this.scanDigits(bound) === 0) {
845
+ return false
846
+ }
847
+ if (this.at(this.pos) === JWT_CH_DOT) {
848
+ this.pos += 1
849
+ this.integral = false
850
+ if (this.scanDigits(bound) === 0) {
851
+ return false
852
+ }
853
+ }
854
+ const e: i32 = this.at(this.pos)
855
+ if (e === 101 || e === 69) {
856
+ this.pos += 1
857
+ this.integral = false
858
+ const sign: i32 = this.at(this.pos)
859
+ if (sign === JWT_CH_PLUS || sign === JWT_CH_MINUS) {
860
+ this.pos += 1
861
+ }
862
+ if (this.scanDigits(bound) === 0) {
863
+ return false
864
+ }
865
+ }
866
+ this.tokEnd = this.pos
867
+ return true
868
+ }
869
+
870
+ /**
871
+ * The code point of the escape at `i` (a backslash), a surrogate pair
872
+ * joined, or `-1` for one RFC 8259 §7 refuses or a lone surrogate.
873
+ */
874
+ escapeCodePoint(i: i32): i32 {
875
+ const c: i32 = this.at(i + 1)
876
+ if (c === JWT_CH_QUOTE || c === JWT_CH_BACKSLASH || c === 47) {
877
+ return c
878
+ }
879
+ if (c === 98) {
880
+ return 8
881
+ }
882
+ if (c === 102) {
883
+ return 12
884
+ }
885
+ if (c === 110) {
886
+ return JWT_CH_NEWLINE
887
+ }
888
+ if (c === 114) {
889
+ return JWT_CH_RETURN
890
+ }
891
+ if (c === 116) {
892
+ return JWT_CH_TAB
893
+ }
894
+ if (c !== 117) {
895
+ return -1
896
+ }
897
+ const hi: i32 = this.hex4(i + 2)
898
+ if (hi < 0 || (hi >= 0xdc00 && hi <= 0xdfff)) {
899
+ return -1
900
+ }
901
+ if (hi < 0xd800 || hi > 0xdbff) {
902
+ return hi
903
+ }
904
+ if (this.at(i + 6) !== JWT_CH_BACKSLASH || this.at(i + 7) !== 117) {
905
+ return -1
906
+ }
907
+ const lo: i32 = this.hex4(i + 8)
908
+ if (lo < 0xdc00 || lo > 0xdfff) {
909
+ return -1
910
+ }
911
+ const plane: i32 = 0x10000
912
+ return plane + ((hi - 0xd800) << 10) + (lo - 0xdc00)
913
+ }
914
+
915
+ /** The four hex digits at `i` as a number, or `-1`. */
916
+ hex4(i: i32): i32 {
917
+ let v: i32 = 0
918
+ for (let k: i32 = 0; k < 4; k += 1) {
919
+ const d: i32 = jwtHexValue(this.at(i + k))
920
+ if (d < 0) {
921
+ return -1
922
+ }
923
+ v = (v << 4) | d
924
+ }
925
+ return v
926
+ }
927
+
928
+ /** How many bytes the escape at `i` takes, given the code point it decodes to. */
929
+ escapeLength(i: i32, cp: i32): i32 {
930
+ if (this.at(i + 1) !== 117) {
931
+ return 2
932
+ }
933
+ return cp >= 0x10000 ? 12 : 6
934
+ }
935
+
936
+ /**
937
+ * How many continuation bytes the lead byte at `i` takes, checked against
938
+ * RFC 3629 §4 (no overlong form, no surrogate, nothing past U+10FFFF), or
939
+ * `-1`.
940
+ */
941
+ utf8Tail(i: i32): i32 {
942
+ const b: i32 = this.at(i)
943
+ let n: i32 = 0
944
+ let lo: i32 = 0x80
945
+ let hi: i32 = 0xbf
946
+ if (b >= 0xc2 && b <= 0xdf) {
947
+ n = 1
948
+ } else if (b >= 0xe0 && b <= 0xef) {
949
+ n = 2
950
+ lo = b === 0xe0 ? 0xa0 : 0x80
951
+ hi = b === 0xed ? 0x9f : 0xbf
952
+ } else if (b >= 0xf0 && b <= 0xf4) {
953
+ n = 3
954
+ lo = b === 0xf0 ? 0x90 : 0x80
955
+ hi = b === 0xf4 ? 0x8f : 0xbf
956
+ } else {
957
+ return -1
958
+ }
959
+ const second: i32 = this.at(i + 1)
960
+ if (second < lo || second > hi) {
961
+ return -1
962
+ }
963
+ for (let k: i32 = 2; k <= n; k += 1) {
964
+ const c: i32 = this.at(i + k)
965
+ if (c < 0x80 || c > 0xbf) {
966
+ return -1
967
+ }
968
+ }
969
+ return n
970
+ }
971
+
972
+ /** The string at the cursor, checked, stepped over, its decoded length in `textBytes`. */
973
+ scanString(): boolean {
974
+ this.tokStart = this.pos
975
+ let i: i32 = this.pos + 1
976
+ let decoded: i32 = 0
977
+ while (true) {
978
+ const b: i32 = this.at(i)
979
+ if (b < 0x20) {
980
+ // A control byte, or `-1`: the window ended inside the string.
981
+ return false
982
+ }
983
+ if (b === JWT_CH_QUOTE) {
984
+ break
985
+ }
986
+ if (b === JWT_CH_BACKSLASH) {
987
+ const cp: i32 = this.escapeCodePoint(i)
988
+ if (cp < 0) {
989
+ return false
990
+ }
991
+ decoded += jwtUtf8Width(cp)
992
+ i += this.escapeLength(i, cp)
993
+ } else if (b < 0x80) {
994
+ decoded += 1
995
+ i += 1
996
+ } else {
997
+ const tail: i32 = this.utf8Tail(i)
998
+ if (tail < 0) {
999
+ return false
1000
+ }
1001
+ decoded += tail + 1
1002
+ i += tail + 1
1003
+ }
1004
+ }
1005
+ this.pos = i + 1
1006
+ this.tokEnd = this.pos
1007
+ this.textBytes = decoded
1008
+ return true
1009
+ }
1010
+
1011
+ /** Whether the current token is a key or a string. */
1012
+ isText(): boolean {
1013
+ return this.kind === JWT_JSON_KEY || this.kind === JWT_JSON_STRING
1014
+ }
1015
+
1016
+ /** The current key's or string's length once decoded, or `-1`. */
1017
+ stringLength(): i32 {
1018
+ return this.isText() ? this.textBytes : -1
1019
+ }
1020
+
1021
+ /**
1022
+ * Whether the current key or string, decoded, is exactly `name`'s bytes;
1023
+ * `false` when the token is neither. Under Node, `charCodeAt` is a UTF-16
1024
+ * code unit, so only an ASCII name answers as it does natively, as with
1025
+ * `nish/json-tokenizer`'s `keyEquals`.
1026
+ */
1027
+ keyEquals(name: string): boolean {
1028
+ const n: i32 = toI32(name.length)
1029
+ if (!this.isText() || n !== this.textBytes) {
1030
+ return false
1031
+ }
1032
+ const end: i32 = this.tokEnd - 1
1033
+ let i: i32 = this.tokStart + 1
1034
+ let o: i32 = 0
1035
+ while (i < end) {
1036
+ const b: i32 = this.at(i)
1037
+ if (b !== JWT_CH_BACKSLASH) {
1038
+ if (o >= n || b !== toI32(name.charCodeAt(o))) {
1039
+ return false
1040
+ }
1041
+ o += 1
1042
+ i += 1
1043
+ continue
1044
+ }
1045
+ const cp: i32 = this.escapeCodePoint(i)
1046
+ const width: i32 = jwtUtf8Width(cp)
1047
+ for (let k: i32 = 0; k < width; k += 1) {
1048
+ if (o >= n || jwtUtf8Byte(cp, width, k) !== toI32(name.charCodeAt(o))) {
1049
+ return false
1050
+ }
1051
+ o += 1
1052
+ }
1053
+ i += this.escapeLength(i, cp)
1054
+ }
1055
+ return o === n
1056
+ }
1057
+
1058
+ /**
1059
+ * Writes the current key's or string's decoded bytes to `out` from `at` and
1060
+ * answers how many; `-1`, writing nothing, when the token is neither or
1061
+ * `out` has too little room.
1062
+ */
1063
+ stringInto(out: u8[], at: i32): i32 {
1064
+ if (!this.isText() || at < 0 || at > toI32(out.length) - this.textBytes) {
1065
+ return -1
1066
+ }
1067
+ const end: i32 = this.tokEnd - 1
1068
+ let i: i32 = this.tokStart + 1
1069
+ let o: i32 = at
1070
+ while (i < end) {
1071
+ const b: i32 = this.at(i)
1072
+ if (b !== JWT_CH_BACKSLASH) {
1073
+ if (o >= 0 && o < toI32(out.length)) {
1074
+ out[o] = toU8(b)
1075
+ }
1076
+ o += 1
1077
+ i += 1
1078
+ continue
1079
+ }
1080
+ const cp: i32 = this.escapeCodePoint(i)
1081
+ const width: i32 = jwtUtf8Width(cp)
1082
+ for (let k: i32 = 0; k < width; k += 1) {
1083
+ if (o >= 0 && o < toI32(out.length)) {
1084
+ out[o] = toU8(jwtUtf8Byte(cp, width, k))
1085
+ }
1086
+ o += 1
1087
+ }
1088
+ i += this.escapeLength(i, cp)
1089
+ }
1090
+ return o - at
1091
+ }
1092
+
1093
+ /** Whether the current token is a number without fraction or exponent that fits an `i64`. */
1094
+ isInteger(): boolean {
1095
+ return this.kind === JWT_JSON_NUMBER && this.integral
1096
+ }
1097
+
1098
+ /** The current number as an `i64` when `isInteger()` holds, else `0`. */
1099
+ numberI64(): i64 {
1100
+ if (!this.isInteger()) {
1101
+ return toI64(0)
1102
+ }
1103
+ return this.at(this.tokStart) === JWT_CH_MINUS ? this.value : -this.value
1104
+ }
1105
+
1106
+ /**
1107
+ * Steps over the value the current token starts: after a key, its value;
1108
+ * after an opener, everything through its closer; after a scalar or a
1109
+ * closer, nothing. Answers the kind of the last token read, or `JWT_JSON_ERR`.
1110
+ */
1111
+ skipValue(): i32 {
1112
+ if (this.failed !== 0) {
1113
+ return this.failed
1114
+ }
1115
+ let k: i32 = this.kind
1116
+ if (k === JWT_JSON_KEY) {
1117
+ k = this.next()
1118
+ }
1119
+ if (k !== JWT_JSON_OBJECT_BEGIN && k !== JWT_JSON_ARRAY_BEGIN) {
1120
+ return k
1121
+ }
1122
+ const outer: i32 = this.depth - 1
1123
+ while (true) {
1124
+ k = this.next()
1125
+ if (k < 0 || this.depth === outer) {
1126
+ return k
1127
+ }
1128
+ }
1129
+ }
1130
+ }
1131
+
1132
+ // ---- verify -----------------------------------------------------------------
1133
+
1134
+ /** `v` held to `[lo, hi]`. */
1135
+ const jwtClamp = (v: i64, lo: i64, hi: i64): i64 => {
1136
+ if (v < lo) {
1137
+ return lo
1138
+ }
1139
+ return v > hi ? hi : v
1140
+ }
1141
+
1142
+ /** The bytes `[off, off + len)` of `src` as a fresh array. */
1143
+ const jwtSlice = (src: u8[], off: i32, len: i32): u8[] => {
1144
+ const out: u8[] = new Array<u8>(len)
1145
+ for (let i: i32 = 0; i < len && i < toI32(out.length); i += 1) {
1146
+ out[i] = src[off + i]
1147
+ }
1148
+ return out
1149
+ }
1150
+
1151
+ /**
1152
+ * Whether `sig[0 .. sigLen)` is `key`'s public-key signature over
1153
+ * `src[off .. off + len)`. The verifiers take whole arrays, so the message and
1154
+ * the signature are copied, and the RSA, P-256, P-384 and Ed25519 verifiers
1155
+ * store allocations of their own, which no arena block may release around
1156
+ * (NL2424): a public-key verify allocates, and the collector takes it back.
1157
+ * Only HMAC verifies allocation-free (`hmacKeyedVerify`).
1158
+ */
1159
+ const jwtVerifyPublic = (key: JwtKey, src: u8[], off: i32, len: i32, sig: u8[], sigLen: i32): boolean => {
1160
+ const msg: u8[] = jwtSlice(src, off, len)
1161
+ const s: u8[] = jwtSlice(sig, 0, sigLen)
1162
+ const alg: i32 = key.alg
1163
+ const rsa: RsaPublicKey | null = key.rsa
1164
+ if (rsa !== null) {
1165
+ let hash: i32 = RSA_HASH_SHA512
1166
+ if (alg === JWT_ALG_RS256) {
1167
+ hash = RSA_HASH_SHA256
1168
+ } else if (alg === JWT_ALG_RS384) {
1169
+ hash = RSA_HASH_SHA384
1170
+ }
1171
+ return rsaVerifyPkcs1v15(rsa, hash, msg, s)
1172
+ }
1173
+ if (alg === JWT_ALG_ES256) {
1174
+ return p256VerifySha256(key.pub, msg, s)
1175
+ }
1176
+ if (alg === JWT_ALG_ES384) {
1177
+ return p384VerifySha384(key.pub, msg, s)
1178
+ }
1179
+ if (alg === JWT_ALG_EDDSA) {
1180
+ return ed25519Verify(key.pub, msg, s)
1181
+ }
1182
+ return false
1183
+ }
1184
+
1185
+ /**
1186
+ * Verifies compact JWS tokens and checks their claims, reusing its buffers:
1187
+ * make one per thread and call `verify` per token. The settings are public
1188
+ * fields, read on every `verify`. After `JWT_OK`, `claims[0 .. claimsLen)`
1189
+ * is the payload's JSON for the caller's own claims, and `exp`, `nbf` and
1190
+ * `iat` hold the time claims when `hasExp`, `hasNbf` and `hasIat` say so.
1191
+ */
1192
+ export class JwtVerifier {
1193
+ /** Seconds of clock skew allowed to `exp`, `nbf` and `iat`; held to `[0, 2^31 - 1]`. */
1194
+ leeway: i64
1195
+ /** The `iss` a token must carry, or `null` for any. */
1196
+ issuer: string | null
1197
+ /** The audience a token's `aud` must name, or `null` to refuse a token with any `aud`. */
1198
+ audience: string | null
1199
+ /** The `sub` a token must carry, or `null` for any. */
1200
+ subject: string | null
1201
+ /** The header `typ` a token must carry (RFC 8725 §3.11), or `null` for any. */
1202
+ typ: string | null
1203
+ /** The decoded payload of the last token, `claimsLen` bytes of it valid after `JWT_OK`. */
1204
+ readonly claims: u8[]
1205
+ exp: i64
1206
+ nbf: i64
1207
+ iat: i64
1208
+ readonly part: u8[]
1209
+ readonly sig: u8[]
1210
+ readonly text: u8[]
1211
+ readonly tok: JwtJson
1212
+ /** The largest token taken, in bytes. */
1213
+ readonly maxToken: i32
1214
+ claimsLen: i32
1215
+ /** Whether a token without `exp` is refused; on by default. */
1216
+ requireExp: boolean
1217
+ hasExp: boolean
1218
+ hasNbf: boolean
1219
+ hasIat: boolean
1220
+
1221
+ /** A verifier for tokens of up to `maxToken` bytes; outside `[16, JWT_MAX_TOKEN_LIMIT]` panics. */
1222
+ constructor(maxToken: i32) {
1223
+ if (maxToken < 16 || maxToken > JWT_MAX_TOKEN_LIMIT) {
1224
+ panic("JwtVerifier: maxToken outside [16, JWT_MAX_TOKEN_LIMIT]")
1225
+ }
1226
+ this.leeway = toI64(0)
1227
+ this.issuer = null
1228
+ this.audience = null
1229
+ this.subject = null
1230
+ this.typ = null
1231
+ this.requireExp = true
1232
+ this.maxToken = maxToken
1233
+ const decoded: i32 = (maxToken >> 2) * 3 + 2
1234
+ this.claims = new Array<u8>(decoded)
1235
+ this.claimsLen = 0
1236
+ this.exp = toI64(0)
1237
+ this.nbf = toI64(0)
1238
+ this.iat = toI64(0)
1239
+ this.hasExp = false
1240
+ this.hasNbf = false
1241
+ this.hasIat = false
1242
+ this.part = new Array<u8>(decoded)
1243
+ this.sig = new Array<u8>(JWT_MAX_SIGNATURE)
1244
+ this.text = new Array<u8>(maxToken)
1245
+ this.tok = new JwtJson(this.part, JWT_MAX_DEPTH)
1246
+ }
1247
+
1248
+ /**
1249
+ * `verify` on the bytes of `token`, copied into the verifier's own buffer
1250
+ * first (a string's UTF-8 bytes); a token longer than `maxToken` is refused
1251
+ * before the copy.
1252
+ */
1253
+ verifyString(key: JwtKey, token: string, now: i64): i32 {
1254
+ if (toI32(token.length) > this.maxToken) {
1255
+ return JWT_ERR_SIZE
1256
+ }
1257
+ const n: i32 = toI32(token.length)
1258
+ const text: u8[] = this.text
1259
+ for (let i: i32 = 0; i < n && i < toI32(text.length); i += 1) {
1260
+ const c: i32 = toI32(token.charCodeAt(i))
1261
+ // No token character is above 0x7F; where a string holds UTF-16 code
1262
+ // units, cutting one to its low byte could make it a base64url one.
1263
+ if (c > 0x7f) {
1264
+ return JWT_ERR_FORMAT
1265
+ }
1266
+ text[i] = toU8(c)
1267
+ }
1268
+ return this.verify(key, text, toI32(0), n, now)
1269
+ }
1270
+
1271
+ /**
1272
+ * Verifies the token `src[off .. off + len)` under `key` and checks its
1273
+ * claims at `now` (seconds since 1970): `JWT_OK`, or the first
1274
+ * `JWT_ERR_*` that applies, in the order the module comment lists.
1275
+ */
1276
+ verify(key: JwtKey, src: u8[], off: i32, len: i32, now: i64): i32 {
1277
+ this.claimsLen = 0
1278
+ this.hasExp = false
1279
+ this.hasNbf = false
1280
+ this.hasIat = false
1281
+ if (len < 0 || len > this.maxToken || off < 0 || off > toI32(src.length) - len) {
1282
+ return JWT_ERR_SIZE
1283
+ }
1284
+ let d1: i32 = -1
1285
+ let d2: i32 = -1
1286
+ for (let i: i32 = off; i < off + len && i < toI32(src.length); i += 1) {
1287
+ if (toI32(src[i]) === JWT_DOT) {
1288
+ if (d1 < 0) {
1289
+ d1 = i
1290
+ } else if (d2 < 0) {
1291
+ d2 = i
1292
+ } else {
1293
+ return JWT_ERR_FORMAT
1294
+ }
1295
+ }
1296
+ }
1297
+ if (d2 < 0) {
1298
+ return JWT_ERR_FORMAT
1299
+ }
1300
+ const part: u8[] = this.part
1301
+ const headerLen: i32 = jwtDecode(src, off, d1 - off, part)
1302
+ if (headerLen <= 0) {
1303
+ return JWT_ERR_FORMAT
1304
+ }
1305
+ const header: i32 = this.checkHeader(key, headerLen)
1306
+ if (header !== JWT_OK) {
1307
+ return header
1308
+ }
1309
+ const sigText: i32 = off + len - d2 - 1
1310
+ const sigSize: i32 = jwtSignatureSize(key)
1311
+ if (sigText > ((JWT_MAX_SIGNATURE + 2) / 3) * 4) {
1312
+ return JWT_ERR_SIGNATURE
1313
+ }
1314
+ const sig: u8[] = this.sig
1315
+ const sigLen: i32 = jwtDecode(src, d2 + 1, sigText, sig)
1316
+ if (sigLen < 0) {
1317
+ return JWT_ERR_FORMAT
1318
+ }
1319
+ if (sigLen !== sigSize) {
1320
+ return JWT_ERR_SIGNATURE
1321
+ }
1322
+ const hmac: HmacKey | null = key.hmac
1323
+ // The check follows the algorithm: an HS name with an HMAC key, any other name without one.
1324
+ if (!key.live || jwtIsHmac(key.alg) !== (hmac !== null)) {
1325
+ return JWT_ERR_SIGNATURE
1326
+ }
1327
+ const good: boolean =
1328
+ hmac !== null
1329
+ ? hmacKeyedVerify(hmac, src, off, d2 - off, sig, 0)
1330
+ : jwtVerifyPublic(key, src, off, d2 - off, sig, sigLen)
1331
+ if (!good) {
1332
+ return JWT_ERR_SIGNATURE
1333
+ }
1334
+ const claims: u8[] = this.claims
1335
+ const claimsLen: i32 = jwtDecode(src, d1 + 1, d2 - d1 - 1, claims)
1336
+ if (claimsLen < 0) {
1337
+ return JWT_ERR_FORMAT
1338
+ }
1339
+ return this.checkClaims(claimsLen, jwtClamp(now, toI64(0), toI64(JWT_MAX_NOW)))
1340
+ }
1341
+
1342
+ /** The header in `part[0 .. len)` checked against `key`'s algorithm and the verifier's `typ`. */
1343
+ checkHeader(key: JwtKey, len: i32): i32 {
1344
+ const tok: JwtJson = this.tok
1345
+ tok.reset(this.part, toI32(0), len)
1346
+ if (tok.next() !== JWT_JSON_OBJECT_BEGIN) {
1347
+ return JWT_ERR_HEADER
1348
+ }
1349
+ let seenAlg: boolean = false
1350
+ let seenTyp: boolean = false
1351
+ let seenKid: boolean = false
1352
+ let typOk: boolean = false
1353
+ const name: string = jwtAlgName(key.alg)
1354
+ const typ: string | null = this.typ
1355
+ while (true) {
1356
+ const k: i32 = tok.next()
1357
+ if (k === JWT_JSON_OBJECT_END) {
1358
+ break
1359
+ }
1360
+ if (k !== JWT_JSON_KEY) {
1361
+ return JWT_ERR_HEADER
1362
+ }
1363
+ if (tok.keyEquals("alg")) {
1364
+ if (seenAlg) {
1365
+ return JWT_ERR_HEADER
1366
+ }
1367
+ seenAlg = true
1368
+ if (tok.next() !== JWT_JSON_STRING || !tok.keyEquals(name)) {
1369
+ return JWT_ERR_ALG
1370
+ }
1371
+ } else if (tok.keyEquals("typ") || tok.keyEquals("kid")) {
1372
+ const isTyp: boolean = tok.keyEquals("typ")
1373
+ if ((isTyp && seenTyp) || (!isTyp && seenKid)) {
1374
+ return JWT_ERR_HEADER
1375
+ }
1376
+ if (tok.next() !== JWT_JSON_STRING) {
1377
+ return JWT_ERR_HEADER
1378
+ }
1379
+ if (isTyp) {
1380
+ seenTyp = true
1381
+ typOk = typ !== null && tok.keyEquals(typ)
1382
+ } else {
1383
+ seenKid = true
1384
+ }
1385
+ } else if (tok.keyEquals("crit") || tok.keyEquals("b64")) {
1386
+ return JWT_ERR_HEADER
1387
+ } else if (tok.skipValue() < 0) {
1388
+ return JWT_ERR_HEADER
1389
+ }
1390
+ }
1391
+ if (tok.next() !== JWT_JSON_END) {
1392
+ return JWT_ERR_HEADER
1393
+ }
1394
+ if (!seenAlg) {
1395
+ return JWT_ERR_ALG
1396
+ }
1397
+ if (typ !== null && !typOk) {
1398
+ return JWT_ERR_TYPE
1399
+ }
1400
+ return JWT_OK
1401
+ }
1402
+
1403
+ /** The payload in `claims[0 .. len)` parsed and checked at `now`. */
1404
+ checkClaims(len: i32, now: i64): i32 {
1405
+ const tok: JwtJson = this.tok
1406
+ tok.reset(this.claims, toI32(0), len)
1407
+ if (tok.next() !== JWT_JSON_OBJECT_BEGIN) {
1408
+ return JWT_ERR_CLAIMS
1409
+ }
1410
+ const issuer: string | null = this.issuer
1411
+ const audience: string | null = this.audience
1412
+ const subject: string | null = this.subject
1413
+ let hasIss: boolean = false
1414
+ let hasSub: boolean = false
1415
+ let hasAud: boolean = false
1416
+ let issOk: boolean = false
1417
+ let subOk: boolean = false
1418
+ let audOk: boolean = false
1419
+ while (true) {
1420
+ const k: i32 = tok.next()
1421
+ if (k === JWT_JSON_OBJECT_END) {
1422
+ break
1423
+ }
1424
+ if (k !== JWT_JSON_KEY) {
1425
+ return JWT_ERR_CLAIMS
1426
+ }
1427
+ if (tok.keyEquals("exp") || tok.keyEquals("nbf") || tok.keyEquals("iat")) {
1428
+ let which: i32 = 2
1429
+ let seen: boolean = this.hasIat
1430
+ if (tok.keyEquals("exp")) {
1431
+ which = 0
1432
+ seen = this.hasExp
1433
+ } else if (tok.keyEquals("nbf")) {
1434
+ which = 1
1435
+ seen = this.hasNbf
1436
+ }
1437
+ if (seen || tok.next() !== JWT_JSON_NUMBER || !tok.isInteger()) {
1438
+ return JWT_ERR_CLAIMS
1439
+ }
1440
+ const v: i64 = tok.numberI64()
1441
+ if (which === 0) {
1442
+ this.exp = v
1443
+ this.hasExp = true
1444
+ } else if (which === 1) {
1445
+ this.nbf = v
1446
+ this.hasNbf = true
1447
+ } else {
1448
+ this.iat = v
1449
+ this.hasIat = true
1450
+ }
1451
+ } else if (tok.keyEquals("iss") || tok.keyEquals("sub")) {
1452
+ const isIss: boolean = tok.keyEquals("iss")
1453
+ if ((isIss && hasIss) || (!isIss && hasSub) || tok.next() !== JWT_JSON_STRING) {
1454
+ return JWT_ERR_CLAIMS
1455
+ }
1456
+ if (isIss) {
1457
+ hasIss = true
1458
+ issOk = issuer !== null && tok.keyEquals(issuer)
1459
+ } else {
1460
+ hasSub = true
1461
+ subOk = subject !== null && tok.keyEquals(subject)
1462
+ }
1463
+ } else if (tok.keyEquals("aud")) {
1464
+ if (hasAud) {
1465
+ return JWT_ERR_CLAIMS
1466
+ }
1467
+ hasAud = true
1468
+ const v: i32 = tok.next()
1469
+ if (v === JWT_JSON_STRING) {
1470
+ audOk = audience !== null && tok.keyEquals(audience)
1471
+ } else if (v === JWT_JSON_ARRAY_BEGIN) {
1472
+ while (true) {
1473
+ const e: i32 = tok.next()
1474
+ if (e === JWT_JSON_ARRAY_END) {
1475
+ break
1476
+ }
1477
+ if (e !== JWT_JSON_STRING) {
1478
+ return JWT_ERR_CLAIMS
1479
+ }
1480
+ if (audience !== null && tok.keyEquals(audience)) {
1481
+ audOk = true
1482
+ }
1483
+ }
1484
+ } else {
1485
+ return JWT_ERR_CLAIMS
1486
+ }
1487
+ } else if (tok.skipValue() < 0) {
1488
+ return JWT_ERR_CLAIMS
1489
+ }
1490
+ }
1491
+ if (tok.next() !== JWT_JSON_END) {
1492
+ return JWT_ERR_CLAIMS
1493
+ }
1494
+ if (this.requireExp && !this.hasExp) {
1495
+ return JWT_ERR_CLAIMS
1496
+ }
1497
+ const leeway: i64 = jwtClamp(this.leeway, toI64(0), toI64(JWT_MAX_LEEWAY))
1498
+ if (this.hasExp && !(now - leeway < this.exp)) {
1499
+ return JWT_ERR_EXPIRED
1500
+ }
1501
+ if (this.hasNbf && this.nbf > now + leeway) {
1502
+ return JWT_ERR_NOT_YET
1503
+ }
1504
+ if (this.hasIat && this.iat > now + leeway) {
1505
+ return JWT_ERR_IAT
1506
+ }
1507
+ if (issuer !== null && !issOk) {
1508
+ return JWT_ERR_ISSUER
1509
+ }
1510
+ if (hasAud ? !audOk : audience !== null) {
1511
+ return JWT_ERR_AUDIENCE
1512
+ }
1513
+ if (subject !== null && !subOk) {
1514
+ return JWT_ERR_SUBJECT
1515
+ }
1516
+ this.claimsLen = len
1517
+ return JWT_OK
1518
+ }
1519
+
1520
+ /**
1521
+ * The header's `kid` of the token `src[off .. off + len)`, written at
1522
+ * `out[0 ..)`: its length, or -1 when the token is malformed, has no `kid`
1523
+ * or its `kid` does not fit `out`. This reads the header **before** any
1524
+ * signature is checked, so it only chooses which key to verify with; the
1525
+ * `kid` proves nothing until `verify` answers `JWT_OK` under that key.
1526
+ */
1527
+ peekKid(src: u8[], off: i32, len: i32, out: u8[]): i32 {
1528
+ if (len < 0 || len > this.maxToken || off < 0 || off > toI32(src.length) - len) {
1529
+ return -1
1530
+ }
1531
+ let d1: i32 = -1
1532
+ for (let i: i32 = off; i < off + len && i < toI32(src.length) && d1 < 0; i += 1) {
1533
+ if (toI32(src[i]) === JWT_DOT) {
1534
+ d1 = i
1535
+ }
1536
+ }
1537
+ if (d1 < 0) {
1538
+ return -1
1539
+ }
1540
+ const n: i32 = jwtDecode(src, off, d1 - off, this.part)
1541
+ if (n <= 0) {
1542
+ return -1
1543
+ }
1544
+ const tok: JwtJson = this.tok
1545
+ tok.reset(this.part, toI32(0), n)
1546
+ if (tok.next() !== JWT_JSON_OBJECT_BEGIN) {
1547
+ return -1
1548
+ }
1549
+ while (true) {
1550
+ const k: i32 = tok.next()
1551
+ if (k !== JWT_JSON_KEY) {
1552
+ return -1
1553
+ }
1554
+ if (tok.keyEquals("kid")) {
1555
+ if (tok.next() !== JWT_JSON_STRING || tok.stringLength() > toI32(out.length)) {
1556
+ return -1
1557
+ }
1558
+ return tok.stringInto(out, toI32(0))
1559
+ }
1560
+ if (tok.skipValue() < 0) {
1561
+ return -1
1562
+ }
1563
+ }
1564
+ }
1565
+ }
1566
+
1567
+ // ---- sign -------------------------------------------------------------------
1568
+
1569
+ /** Whether `src[0 .. len)` is one JSON object and nothing more. */
1570
+ const jwtIsObject = (src: u8[]): boolean => {
1571
+ const tok = new JwtJson(src, JWT_MAX_DEPTH)
1572
+ tok.reset(src, toI32(0), toI32(src.length))
1573
+ if (tok.next() !== JWT_JSON_OBJECT_BEGIN || tok.skipValue() !== JWT_JSON_OBJECT_END) {
1574
+ return false
1575
+ }
1576
+ return tok.next() === JWT_JSON_END
1577
+ }
1578
+
1579
+ /** Whether `kid` is 1 to 256 printable ASCII characters without `"` or `\`, so it needs no JSON escape. */
1580
+ const jwtKidFits = (kid: string): boolean => {
1581
+ if (kid.length === 0 || kid.length > 256) {
1582
+ return false
1583
+ }
1584
+ for (let i: i32 = 0; i < toI32(kid.length); i += 1) {
1585
+ const c: i32 = toI32(kid.charCodeAt(i))
1586
+ if (c < 0x20 || c > 0x7e || c === 34 || c === 92) {
1587
+ return false
1588
+ }
1589
+ }
1590
+ return true
1591
+ }
1592
+
1593
+ /** Whether every code unit of `text` is ASCII, so `jwtBytesOf` keeps it whole. */
1594
+ const jwtIsAscii = (text: string): boolean => {
1595
+ for (let i: i32 = 0; i < toI32(text.length); i += 1) {
1596
+ if (toI32(text.charCodeAt(i)) > 0x7f) {
1597
+ return false
1598
+ }
1599
+ }
1600
+ return true
1601
+ }
1602
+
1603
+ /** The code units of `text` as bytes; the caller has checked they are ASCII. */
1604
+ const jwtBytesOf = (text: string): u8[] => {
1605
+ const n: i32 = toI32(text.length)
1606
+ const out: u8[] = new Array<u8>(n)
1607
+ for (let i: i32 = 0; i < n && i < toI32(out.length); i += 1) {
1608
+ out[i] = toU8(text.charCodeAt(i))
1609
+ }
1610
+ return out
1611
+ }
1612
+
1613
+ /**
1614
+ * A compact JWS over the JSON object `claims`, signed with `key`, with a
1615
+ * `kid` header member when `kid` is not `null`. Answers `null` when `key`
1616
+ * cannot sign (RS, ES384 or a verify-only key), when `claims` is not one JSON
1617
+ * object, or when `kid` is empty, longer than 256 bytes, or holds a byte that
1618
+ * would need a JSON escape. The claims are signed as given: this function
1619
+ * neither adds nor checks any claim.
1620
+ */
1621
+ export const jwtSignKid = (key: JwtKey, kid: string | null, claims: u8[]): string | null => {
1622
+ if (!key.canSign || !key.live || !jwtIsObject(claims)) {
1623
+ return null
1624
+ }
1625
+ let header: string = '{"alg":"' + jwtAlgName(key.alg) + '"'
1626
+ if (kid !== null) {
1627
+ if (!jwtKidFits(kid)) {
1628
+ return null
1629
+ }
1630
+ header = header + ',"kid":"' + kid + '"'
1631
+ }
1632
+ header = header + ',"typ":"JWT"}'
1633
+ const input: string = base64urlEncode(jwtBytesOf(header)) + "." + base64urlEncode(claims)
1634
+ const msg: u8[] = jwtBytesOf(input)
1635
+ const hmac: HmacKey | null = key.hmac
1636
+ let sig: u8[] | null = null
1637
+ if (hmac !== null) {
1638
+ const tag: u8[] = new Array<u8>(hmac.size())
1639
+ hmacKeyedInto(hmac, msg, 0, toI32(msg.length), tag, 0)
1640
+ sig = tag
1641
+ } else if (key.alg === JWT_ALG_ES256) {
1642
+ sig = p256SignSha256(key.priv, msg)
1643
+ } else if (key.alg === JWT_ALG_EDDSA) {
1644
+ sig = ed25519Sign(key.priv, msg)
1645
+ }
1646
+ if (sig === null) {
1647
+ return null
1648
+ }
1649
+ return input + "." + base64urlEncode(sig)
1650
+ }
1651
+
1652
+ /** `jwtSignKid` with no `kid`. */
1653
+ export const jwtSign = (key: JwtKey, claims: u8[]): string | null => jwtSignKid(key, null, claims)
1654
+
1655
+ /**
1656
+ * `jwtSign` over the bytes of the string `claims`. Answers `null` for claims
1657
+ * outside ASCII, as `verifyString` refuses them: escape such text (`\u00e9`)
1658
+ * or pass its UTF-8 bytes to `jwtSign`.
1659
+ */
1660
+ export const jwtSignString = (key: JwtKey, claims: string): string | null =>
1661
+ jwtIsAscii(claims) ? jwtSignKid(key, null, jwtBytesOf(claims)) : null