@amritk/nish-x86_64-linux 0.16.0 → 0.18.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.
Files changed (58) hide show
  1. package/INSTALL.md +43 -34
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +50 -13
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +43 -24
  8. package/runtime/runtime-net.c +57 -10
  9. package/runtime/runtime-os.c +62 -20
  10. package/runtime/runtime-simd.c +203 -0
  11. package/runtime/runtime-wasm.c +12 -0
  12. package/runtime/runtime.c +66 -18
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +14 -12
  15. package/std/README.md +168 -36
  16. package/std/crypto/aes.ts +51 -8
  17. package/std/crypto/base64url.ts +25 -12
  18. package/std/crypto/chacha20poly1305.ts +43 -29
  19. package/std/crypto/ct.ts +6 -1
  20. package/std/crypto/hkdf.ts +364 -3
  21. package/std/crypto/hmac.ts +307 -7
  22. package/std/crypto/p256.ts +618 -34
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +3 -2
  25. package/std/crypto/x25519.ts +88 -36
  26. package/std/crypto/x509.ts +130 -47
  27. package/std/json.ts +297 -41
  28. package/std/net/hpack.ts +1094 -0
  29. package/std/net/http-fields.ts +604 -0
  30. package/std/net/http1-server.ts +1389 -0
  31. package/std/net/http1.ts +1492 -0
  32. package/std/net/http2-frame.ts +745 -0
  33. package/std/net/http2-tls.ts +216 -0
  34. package/std/net/http2.ts +1433 -0
  35. package/std/net/http3-frame.ts +457 -0
  36. package/std/net/http3-server.ts +911 -0
  37. package/std/net/http3.ts +2145 -0
  38. package/std/net/qpack.ts +1163 -0
  39. package/std/net/quic-conn-ack.ts +206 -0
  40. package/std/net/quic-conn-cid.ts +425 -0
  41. package/std/net/quic-conn-params.ts +462 -0
  42. package/std/net/quic-datagram.ts +130 -0
  43. package/std/net/quic-frame.ts +1206 -0
  44. package/std/net/quic-listener.ts +929 -0
  45. package/std/net/quic-packet.ts +1698 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1609 -0
  48. package/std/net/quic.ts +3578 -0
  49. package/std/net/tls/codec.ts +1089 -0
  50. package/std/net/tls/record-server.ts +1173 -0
  51. package/std/net/tls/record.ts +621 -0
  52. package/std/net/tls/schedule.ts +429 -0
  53. package/std/net/tls-tcp.ts +414 -0
  54. package/std/net/tls.ts +1437 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
  58. package/std/text.ts +77 -0
@@ -85,9 +85,18 @@ const POLY1305_HIGH_BIT: i32 = 0x1000000
85
85
  /** `x` rotated left by `n` bits, `n` in 1 to 31. */
86
86
  const chacha20Rotl = (x: u32, n: u32): u32 => (x << n) | (x >>> (32 - n))
87
87
 
88
- /** The little-endian `u32` at `buf[at]` to `buf[at + 3]`. */
89
- const chacha20Word = (buf: u8[], at: i32): u32 =>
90
- toU32(buf[at]) | (toU32(buf[at + 1]) << 8) | (toU32(buf[at + 2]) << 16) | (toU32(buf[at + 3]) << 24)
88
+ /**
89
+ * The little-endian `u32` at `buf[at]` to `buf[at + 3]`. The offsets are
90
+ * summed in `u32`, which wraps by definition, rather than as checked `i32`
91
+ * sums: every caller passes a window inside `buf`, a wrapped offset would
92
+ * index past it rather than reach another byte, and a check is a branch that
93
+ * `tests/cases/ct_asm_chacha20poly1305` refuses. `poly1305Le64`,
94
+ * `poly1305Block` and `chacha20Poly1305TagMatch` sum theirs the same way.
95
+ */
96
+ const chacha20Word = (buf: u8[], at: i32): u32 => {
97
+ const p: u32 = toU32(at)
98
+ return toU32(buf[p]) | (toU32(buf[p + 1]) << 8) | (toU32(buf[p + 2]) << 16) | (toU32(buf[p + 3]) << 24)
99
+ }
91
100
 
92
101
  /**
93
102
  * The sixteen-word state of RFC 8439 §2.3: the four constant words
@@ -346,15 +355,19 @@ export const chacha20 = (key: u8[], counter: u32, nonce: u8[], data: u8[]): u8[]
346
355
  }
347
356
 
348
357
  /** The little-endian `u64` at `buf[at]` to `buf[at + 7]`. */
349
- const poly1305Le64 = (buf: u8[], at: i32): u64 =>
350
- toU64(buf[at]) |
351
- (toU64(buf[at + 1]) << 8) |
352
- (toU64(buf[at + 2]) << 16) |
353
- (toU64(buf[at + 3]) << 24) |
354
- (toU64(buf[at + 4]) << 32) |
355
- (toU64(buf[at + 5]) << 40) |
356
- (toU64(buf[at + 6]) << 48) |
357
- (toU64(buf[at + 7]) << 56)
358
+ const poly1305Le64 = (buf: u8[], at: i32): u64 => {
359
+ const p: u32 = toU32(at)
360
+ return (
361
+ toU64(buf[p]) |
362
+ (toU64(buf[p + 1]) << 8) |
363
+ (toU64(buf[p + 2]) << 16) |
364
+ (toU64(buf[p + 3]) << 24) |
365
+ (toU64(buf[p + 4]) << 32) |
366
+ (toU64(buf[p + 5]) << 40) |
367
+ (toU64(buf[p + 6]) << 48) |
368
+ (toU64(buf[p + 7]) << 56)
369
+ )
370
+ }
358
371
 
359
372
  /**
360
373
  * `r` from the first 16 bytes of a one-time key, clamped as RFC 8439 §2.5
@@ -397,7 +410,7 @@ const poly1305Clamp = (key: u8[], r: u64[]): void => {
397
410
  */
398
411
  const poly1305Block = (h: u64[], r: u64[], m: u8[], at: i32, high: u64): void => {
399
412
  const lo: u64 = poly1305Le64(m, at)
400
- const hi: u64 = poly1305Le64(m, at + 8)
413
+ const hi: u64 = poly1305Le64(m, toI32(toU32(at) + 8))
401
414
  const h0: u64 = h[0] + (lo & 0x3ffffff)
402
415
  const h1: u64 = h[1] + ((lo >>> 26) & 0x3ffffff)
403
416
  const h2: u64 = h[2] + (((lo >>> 52) | (hi << 12)) & 0x3ffffff)
@@ -602,23 +615,24 @@ const chacha20Poly1305Tag = (polyKey: u8[], aad: u8[], ct: u8[], at: i32, len: i
602
615
  * disassembly check; the caller has checked that `sealed` holds the window.
603
616
  */
604
617
  const chacha20Poly1305TagMatch = (tag: u8[], sealed: u8[], at: i32): u32 => {
618
+ const p: u32 = toU32(at)
605
619
  const diff: u32 =
606
- toU32(tag[0] ^ sealed[at]) |
607
- toU32(tag[1] ^ sealed[at + 1]) |
608
- toU32(tag[2] ^ sealed[at + 2]) |
609
- toU32(tag[3] ^ sealed[at + 3]) |
610
- toU32(tag[4] ^ sealed[at + 4]) |
611
- toU32(tag[5] ^ sealed[at + 5]) |
612
- toU32(tag[6] ^ sealed[at + 6]) |
613
- toU32(tag[7] ^ sealed[at + 7]) |
614
- toU32(tag[8] ^ sealed[at + 8]) |
615
- toU32(tag[9] ^ sealed[at + 9]) |
616
- toU32(tag[10] ^ sealed[at + 10]) |
617
- toU32(tag[11] ^ sealed[at + 11]) |
618
- toU32(tag[12] ^ sealed[at + 12]) |
619
- toU32(tag[13] ^ sealed[at + 13]) |
620
- toU32(tag[14] ^ sealed[at + 14]) |
621
- toU32(tag[15] ^ sealed[at + 15])
620
+ toU32(tag[0] ^ sealed[p]) |
621
+ toU32(tag[1] ^ sealed[p + 1]) |
622
+ toU32(tag[2] ^ sealed[p + 2]) |
623
+ toU32(tag[3] ^ sealed[p + 3]) |
624
+ toU32(tag[4] ^ sealed[p + 4]) |
625
+ toU32(tag[5] ^ sealed[p + 5]) |
626
+ toU32(tag[6] ^ sealed[p + 6]) |
627
+ toU32(tag[7] ^ sealed[p + 7]) |
628
+ toU32(tag[8] ^ sealed[p + 8]) |
629
+ toU32(tag[9] ^ sealed[p + 9]) |
630
+ toU32(tag[10] ^ sealed[p + 10]) |
631
+ toU32(tag[11] ^ sealed[p + 11]) |
632
+ toU32(tag[12] ^ sealed[p + 12]) |
633
+ toU32(tag[13] ^ sealed[p + 13]) |
634
+ toU32(tag[14] ^ sealed[p + 14]) |
635
+ toU32(tag[15] ^ sealed[p + 15])
622
636
  return ctEq(diff, 0)
623
637
  }
624
638
 
package/std/crypto/ct.ts CHANGED
@@ -62,9 +62,14 @@ export const timingSafeEqualAt = (a: u8[], aOff: i32, b: u8[], bOff: i32, len: i
62
62
  if (aOff < 0 || bOff < 0 || len < 0 || aOff > aLen - len || bOff > bLen - len) {
63
63
  return false
64
64
  }
65
+ // The offsets are summed in `u32`, which wraps by definition, rather than
66
+ // as checked `i32` sums: the test above already put every `off + k` inside
67
+ // its array, so neither can wrap, and a check would only add a branch.
68
+ const aAt: u32 = toU32(aOff)
69
+ const bAt: u32 = toU32(bOff)
65
70
  let diff: i32 = 0
66
71
  for (let k: i32 = 0; k < len; k++) {
67
- diff = diff | toI32(a[aOff + k] ^ b[bOff + k])
72
+ diff = diff | toI32(a[aAt + toU32(k)] ^ b[bAt + toU32(k)])
68
73
  }
69
74
  return diff === 0
70
75
  }
@@ -22,10 +22,60 @@
22
22
  * shorter than HashLen, which §2.3 does not allow: an empty one is a key
23
23
  * anybody can compute with.
24
24
  *
25
- * HKDF-Expand-Label, TLS 1.3's wrapper around `expand`, belongs with TLS and is
26
- * not here. Written from RFC 5869, not ported from another implementation.
25
+ * HKDF-Expand-Label is TLS 1.3's wrapper around `expand` (RFC 8446 §7.1), and
26
+ * QUIC derives its packet keys with it too (RFC 9001 §5.1), so it is here once
27
+ * for both rather than in each:
28
+ *
29
+ * HKDF-Expand-Label(Secret, Label, Context, Length) =
30
+ * HKDF-Expand(Secret, HkdfLabel, Length)
31
+ * struct {
32
+ * uint16 length = Length;
33
+ * opaque label<7..255> = "tls13 " + Label;
34
+ * opaque context<0..255> = Context;
35
+ * } HkdfLabel;
36
+ *
37
+ * const key: u8[] = hkdfExpandLabelSha256(secret, "quic key", [], 16);
38
+ *
39
+ * `label` is given without the `"tls13 "` prefix, which the function adds. Its
40
+ * arguments come from the program rather than from the peer — a label is a
41
+ * literal, a length a key or hash size, a context a transcript hash — so one
42
+ * out of range is a bug in the caller, and it panics rather than answering
43
+ * `null`: a length below 0 or above 255, a label empty or longer than 249
44
+ * bytes (the struct's 7 to 255 less the prefix), a context longer than 255
45
+ * bytes, and a secret shorter than HashLen.
46
+ *
47
+ * **Caller-owned scratch.** The functions above make their HMAC objects and
48
+ * their output fresh on every call, which stores allocations no arena scope
49
+ * may release around them. `hkdfExtractInto`, `hkdfExpandInto` and
50
+ * `hkdfExpandLabelInto` compute the same bytes in an `HkdfScratch` the caller
51
+ * allocates once, over the hash `hashLength` names (32 for SHA-256, 48 for
52
+ * SHA-384), and write them into the caller's array, so that a loop of key
53
+ * derivations inside one `using a = arena()` block leaves the arena where it
54
+ * was. Each wipes the scratch before it returns, its last block and the
55
+ * keyed hashers of the hash it ran over, so no key material outlives the
56
+ * call there:
57
+ *
58
+ * const kdf = new HkdfScratch(); // once
59
+ * hkdfExtractInto(kdf, 32, salt, ikm, prk, 0); // 32 bytes at prk[0]
60
+ * hkdfExpandLabelInto(kdf, 32, prk, "key", context, 0, 0, key, 0, 16);
61
+ *
62
+ * They refuse what the functions above refuse, the same way: `hkdfExpandInto`
63
+ * answers `false` where `hkdfExpandSha256` answers `null`, and
64
+ * `hkdfExpandLabelInto` panics where `hkdfExpandLabelSha256` does. A hash
65
+ * length other than 32 or 48, or an output window outside its array, is the
66
+ * caller's bug and panics.
67
+ *
68
+ * Written from RFC 5869 and RFC 8446 §7.1, not ported from another
69
+ * implementation.
27
70
  */
28
- import { HmacSha256, HmacSha384, hmacSha256, hmacSha384 } from "nish/crypto/hmac"
71
+ import {
72
+ HmacSha256,
73
+ HmacSha256Scratch,
74
+ HmacSha384,
75
+ HmacSha384Scratch,
76
+ hmacSha256,
77
+ hmacSha384,
78
+ } from "nish/crypto/hmac"
29
79
  import { SHA256_SIZE } from "nish/crypto/sha256"
30
80
  import { SHA384_SIZE } from "nish/crypto/sha512"
31
81
 
@@ -35,6 +85,9 @@ const HKDF_MAX_BLOCKS: i32 = 255
35
85
  /** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
36
86
  const HKDF_FROM: i32 = 0
37
87
 
88
+ /** What HKDF-Expand-Label puts before every label (RFC 8446 §7.1). */
89
+ const HKDF_LABEL_PREFIX: string = "tls13 "
90
+
38
91
  /**
39
92
  * The salt `extract` keys HMAC with: `salt` itself, or HashLen zero bytes when
40
93
  * it is empty (RFC 5869 §2.2, "if not provided"). HMAC zero-pads a short key to
@@ -132,3 +185,311 @@ export const hkdfExpandSha384 = (prk: u8[], info: u8[], length: i32): u8[] | nul
132
185
  }
133
186
  return out
134
187
  }
188
+
189
+ /**
190
+ * The `HkdfLabel` struct of RFC 8446 §7.1, the `info` HKDF-Expand-Label hands
191
+ * to `expand`, or a panic naming `caller` when `length`, `label` or `context`
192
+ * is outside the range the module docs give: 249 is `label<7..255>`'s 255 less
193
+ * the prefix's 6 bytes. The lengths of `label` and `context` are compared as
194
+ * the `number`s they are, before any `toI32`, which would saturate them under
195
+ * `--number-mode f64`; so the bounds are literals rather than `i32` constants.
196
+ */
197
+ const hkdfLabel = (caller: string, label: string, context: u8[], length: i32): u8[] => {
198
+ if (length < 0 || length > 255) {
199
+ panic(`${caller}: a length of ${length} bytes, outside 0 to 255`)
200
+ }
201
+ if (label.length === 0 || label.length > 249) {
202
+ panic(`${caller}: a label of ${toI32(label.length)} bytes, outside 1 to 249`)
203
+ }
204
+ if (context.length > 255) {
205
+ panic(`${caller}: a context of more than 255 bytes`)
206
+ }
207
+ const full: string = `${HKDF_LABEL_PREFIX}${label}`
208
+ const fullLength: i32 = toI32(full.length)
209
+ const contextLength: i32 = toI32(context.length)
210
+ const out: u8[] = new Array<u8>(4 + fullLength + contextLength)
211
+ out[0] = toU8(length >> 8)
212
+ out[1] = toU8(length & 0xff)
213
+ out[2] = toU8(fullLength)
214
+ for (let k: i32 = 0; k < fullLength; k += 1) {
215
+ out[3 + k] = toU8(toI32(full.charCodeAt(k)))
216
+ }
217
+ out[3 + fullLength] = toU8(contextLength)
218
+ hkdfAppend(out, 4 + fullLength, context)
219
+ return out
220
+ }
221
+
222
+ /**
223
+ * HKDF-Expand-Label with HMAC-SHA-256 (RFC 8446 §7.1): `length` bytes from
224
+ * `secret`, `"tls13 " + label` and `context`. Panics on an argument out of the
225
+ * range the module docs give, a secret shorter than 32 bytes among them.
226
+ */
227
+ export const hkdfExpandLabelSha256 = (secret: u8[], label: string, context: u8[], length: i32): u8[] => {
228
+ const okm: u8[] | null = hkdfExpandSha256(
229
+ secret,
230
+ hkdfLabel("hkdfExpandLabelSha256", label, context, length),
231
+ length
232
+ )
233
+ if (okm === null) {
234
+ panic("hkdfExpandLabelSha256: a secret shorter than 32 bytes")
235
+ }
236
+ return okm
237
+ }
238
+
239
+ /**
240
+ * HKDF-Expand-Label with HMAC-SHA-384 (RFC 8446 §7.1): `length` bytes from
241
+ * `secret`, `"tls13 " + label` and `context`. Panics on an argument out of the
242
+ * range the module docs give, a secret shorter than 48 bytes among them.
243
+ */
244
+ export const hkdfExpandLabelSha384 = (secret: u8[], label: string, context: u8[], length: i32): u8[] => {
245
+ const okm: u8[] | null = hkdfExpandSha384(
246
+ secret,
247
+ hkdfLabel("hkdfExpandLabelSha384", label, context, length),
248
+ length
249
+ )
250
+ if (okm === null) {
251
+ panic("hkdfExpandLabelSha384: a secret shorter than 48 bytes")
252
+ }
253
+ return okm
254
+ }
255
+
256
+ // ---- Caller-owned scratch ----------------------------------------------------
257
+
258
+ /** The longest `HkdfLabel`: two length bytes, `label<7..255>` and `context<0..255>`, each with its length byte. */
259
+ const HKDF_LABEL_MAX: i32 = 514
260
+
261
+ /**
262
+ * What the `*Into` functions compute in: an HMAC scratch for each hash, the
263
+ * previous output block T(i - 1), and the `HkdfLabel` HKDF-Expand-Label
264
+ * builds. Allocate one per long-lived owner — a connection slot, say — and
265
+ * hand it to every derivation; it carries nothing from one call to the next.
266
+ */
267
+ export class HkdfScratch {
268
+ sha256: HmacSha256Scratch
269
+ sha384: HmacSha384Scratch
270
+ /** T(i - 1), HashLen bytes of it; wiped before every function returns. */
271
+ previous: u8[]
272
+ /** The one-byte block counter `i`. */
273
+ counter: u8[]
274
+ /** HKDF-Expand-Label's `info`, built here: public, so it is not wiped. */
275
+ label: u8[]
276
+
277
+ constructor() {
278
+ this.sha256 = new HmacSha256Scratch()
279
+ this.sha384 = new HmacSha384Scratch()
280
+ this.previous = new Array<u8>(SHA384_SIZE)
281
+ this.counter = new Array<u8>(1)
282
+ this.label = new Array<u8>(HKDF_LABEL_MAX)
283
+ }
284
+
285
+ /** Zeroes the key material the last derivation left: T(i - 1) and both keyed hashers. */
286
+ wipe(): void {
287
+ secureZero(this.previous)
288
+ this.sha256.wipe()
289
+ this.sha384.wipe()
290
+ }
291
+ }
292
+
293
+ /** Panics unless `hashLength` is 32 or 48, the two hashes HKDF runs over here. */
294
+ const hkdfCheckHash = (caller: string, hashLength: i32): void => {
295
+ if (hashLength !== SHA256_SIZE && hashLength !== SHA384_SIZE) {
296
+ panic(`${caller}: a hash length of ${hashLength}, not 32 or 48`)
297
+ }
298
+ }
299
+
300
+ /** Panics unless `out[at .. at + length)` is a window inside `out`. */
301
+ const hkdfCheckOutput = (caller: string, out: u8[], at: i32, length: i32): void => {
302
+ if (at < 0 || length < 0 || at > toI32(out.length) - length) {
303
+ panic(`${caller}: the output window is outside its array`)
304
+ }
305
+ }
306
+
307
+ /** Zeroes what the last derivation over `hashLength` left in `s`: T(i - 1) and that hash's keyed hashers. */
308
+ const hkdfWipe = (s: HkdfScratch, hashLength: i32): void => {
309
+ secureZero(s.previous)
310
+ if (hashLength === SHA384_SIZE) {
311
+ s.sha384.wipe()
312
+ } else {
313
+ s.sha256.wipe()
314
+ }
315
+ }
316
+
317
+ /** Keys the scratch's HMAC for `hashLength` with `key[off .. off + len)`. */
318
+ const hkdfMacBegin = (s: HkdfScratch, hashLength: i32, key: u8[], off: i32, len: i32): void => {
319
+ if (hashLength === SHA384_SIZE) {
320
+ s.sha384.begin(key, off, len)
321
+ } else {
322
+ s.sha256.begin(key, off, len)
323
+ }
324
+ }
325
+
326
+ /** Feeds `data[off .. off + len)` to the scratch's HMAC for `hashLength`. */
327
+ const hkdfMacUpdate = (s: HkdfScratch, hashLength: i32, data: u8[], off: i32, len: i32): void => {
328
+ if (hashLength === SHA384_SIZE) {
329
+ s.sha384.update(data, off, len)
330
+ } else {
331
+ s.sha256.update(data, off, len)
332
+ }
333
+ }
334
+
335
+ /** Writes the HMAC for `hashLength`'s tag at `out[at]`. */
336
+ const hkdfMacFinish = (s: HkdfScratch, hashLength: i32, out: u8[], at: i32): void => {
337
+ if (hashLength === SHA384_SIZE) {
338
+ s.sha384.finishInto(out, at)
339
+ } else {
340
+ s.sha256.finishInto(out, at)
341
+ }
342
+ }
343
+
344
+ /**
345
+ * HKDF-Extract (RFC 5869 §2.2) over the hash `hashLength` names, the PRK's
346
+ * HashLen bytes written at `out[at]`. An empty `salt` keys HMAC with no bytes,
347
+ * which HMAC pads to the block with zeros, exactly as the HashLen zeros §2.2
348
+ * asks for would be. An `ikm` longer than 2^31 - 1 bytes panics, as
349
+ * `hmacSha256` does.
350
+ */
351
+ export const hkdfExtractInto = (
352
+ s: HkdfScratch,
353
+ hashLength: i32,
354
+ salt: u8[],
355
+ ikm: u8[],
356
+ out: u8[],
357
+ at: i32
358
+ ): void => {
359
+ hkdfCheckHash("hkdfExtractInto", hashLength)
360
+ hkdfCheckOutput("hkdfExtractInto", out, at, hashLength)
361
+ if (ikm.length > 2147483647) {
362
+ panic("hkdfExtractInto: input keying material longer than 2^31 - 1 bytes")
363
+ }
364
+ hkdfMacBegin(s, hashLength, salt, HKDF_FROM, toI32(salt.length))
365
+ hkdfMacUpdate(s, hashLength, ikm, HKDF_FROM, toI32(ikm.length))
366
+ hkdfMacFinish(s, hashLength, out, at)
367
+ hkdfWipe(s, hashLength)
368
+ }
369
+
370
+ /**
371
+ * The T(1) | T(2) | … loop of RFC 5869 §2.3, `length` bytes of it at
372
+ * `out[at]`, with `info` the first `infoLen` bytes of `info`.
373
+ * The callers have checked every bound.
374
+ */
375
+ const hkdfExpandWindow = (
376
+ s: HkdfScratch,
377
+ hashLength: i32,
378
+ prk: u8[],
379
+ info: u8[],
380
+ infoLen: i32,
381
+ out: u8[],
382
+ at: i32,
383
+ length: i32
384
+ ): void => {
385
+ const counterLength: i32 = 1
386
+ let done: i32 = 0
387
+ let i: i32 = 1
388
+ while (done < length) {
389
+ hkdfMacBegin(s, hashLength, prk, HKDF_FROM, toI32(prk.length))
390
+ if (i > 1) {
391
+ hkdfMacUpdate(s, hashLength, s.previous, HKDF_FROM, hashLength)
392
+ }
393
+ hkdfMacUpdate(s, hashLength, info, HKDF_FROM, infoLen)
394
+ s.counter[0] = toU8(i)
395
+ hkdfMacUpdate(s, hashLength, s.counter, HKDF_FROM, counterLength)
396
+ hkdfMacFinish(s, hashLength, s.previous, HKDF_FROM)
397
+ for (let k: i32 = 0; k < hashLength && done < length && k < toI32(s.previous.length); k += 1) {
398
+ out[at + done] = s.previous[k]
399
+ done += 1
400
+ }
401
+ i += 1
402
+ }
403
+ hkdfWipe(s, hashLength)
404
+ }
405
+
406
+ /**
407
+ * HKDF-Expand (RFC 5869 §2.3) over the hash `hashLength` names: `length`
408
+ * bytes from `prk` and `info`, written at `out[at]`. `false`, writing nothing,
409
+ * where `hkdfExpandSha256` answers `null`: a `length` negative or past
410
+ * 255 × HashLen, an `info` longer than 2^31 - 1 bytes, or a `prk` shorter than
411
+ * HashLen.
412
+ */
413
+ export const hkdfExpandInto = (
414
+ s: HkdfScratch,
415
+ hashLength: i32,
416
+ prk: u8[],
417
+ info: u8[],
418
+ out: u8[],
419
+ at: i32,
420
+ length: i32
421
+ ): boolean => {
422
+ hkdfCheckHash("hkdfExpandInto", hashLength)
423
+ if (
424
+ length < 0 ||
425
+ length > HKDF_MAX_BLOCKS * hashLength ||
426
+ info.length > 2147483647 ||
427
+ toI32(prk.length) < hashLength
428
+ ) {
429
+ return false
430
+ }
431
+ hkdfCheckOutput("hkdfExpandInto", out, at, length)
432
+ hkdfExpandWindow(s, hashLength, prk, info, toI32(info.length), out, at, length)
433
+ return true
434
+ }
435
+
436
+ /**
437
+ * HKDF-Expand-Label (RFC 8446 §7.1) over the hash `hashLength` names: `length`
438
+ * bytes from `secret`, `"tls13 " + label` and the context
439
+ * `context[contextOff .. contextOff + contextLen)`, written at `out[at]`. It
440
+ * panics where `hkdfExpandLabelSha256` does — a length outside 0 to 255, a
441
+ * label empty or past 249 bytes, a context past 255, a secret shorter than
442
+ * HashLen — and on a context window outside its array.
443
+ */
444
+ export const hkdfExpandLabelInto = (
445
+ s: HkdfScratch,
446
+ hashLength: i32,
447
+ secret: u8[],
448
+ label: string,
449
+ context: u8[],
450
+ contextOff: i32,
451
+ contextLen: i32,
452
+ out: u8[],
453
+ at: i32,
454
+ length: i32
455
+ ): void => {
456
+ hkdfCheckHash("hkdfExpandLabelInto", hashLength)
457
+ if (length < 0 || length > 255) {
458
+ panic(`hkdfExpandLabelInto: a length of ${length} bytes, outside 0 to 255`)
459
+ }
460
+ if (label.length === 0 || label.length > 249) {
461
+ panic(`hkdfExpandLabelInto: a label of ${toI32(label.length)} bytes, outside 1 to 249`)
462
+ }
463
+ if (
464
+ contextOff < 0 ||
465
+ contextLen < 0 ||
466
+ contextOff > toI32(context.length) - contextLen ||
467
+ contextLen > 255
468
+ ) {
469
+ panic("hkdfExpandLabelInto: the context window is outside its array or longer than 255 bytes")
470
+ }
471
+ if (toI32(secret.length) < hashLength) {
472
+ panic("hkdfExpandLabelInto: a secret shorter than HashLen")
473
+ }
474
+ hkdfCheckOutput("hkdfExpandLabelInto", out, at, length)
475
+ // The `HkdfLabel` struct, as `hkdfLabel` builds it, into the scratch.
476
+ const prefixLength: i32 = toI32(HKDF_LABEL_PREFIX.length)
477
+ const labelLength: i32 = toI32(label.length)
478
+ const fullLength: i32 = prefixLength + labelLength
479
+ const info: u8[] = s.label
480
+ info[0] = toU8(length >> 8)
481
+ info[1] = toU8(length & 0xff)
482
+ info[2] = toU8(fullLength)
483
+ for (let k: i32 = 0; k < prefixLength && 3 + k < toI32(info.length); k += 1) {
484
+ info[3 + k] = toU8(toI32(HKDF_LABEL_PREFIX.charCodeAt(k)))
485
+ }
486
+ for (let k: i32 = 0; k < labelLength && 3 + prefixLength + k < toI32(info.length); k += 1) {
487
+ info[3 + prefixLength + k] = toU8(toI32(label.charCodeAt(k)))
488
+ }
489
+ const contextAt: i32 = 3 + fullLength
490
+ info[contextAt] = toU8(contextLen)
491
+ for (let k: i32 = 0; k < contextLen && contextAt + 1 + k < toI32(info.length); k += 1) {
492
+ info[contextAt + 1 + k] = context[contextOff + k]
493
+ }
494
+ hkdfExpandWindow(s, hashLength, secret, info, contextAt + 1 + contextLen, out, at, length)
495
+ }