@amritk/nish-x86_64-linux 0.17.0 → 0.20.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 (61) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +231 -20
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +203 -0
  14. package/runtime/runtime-wasm.c +16 -3
  15. package/runtime/runtime.c +89 -23
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +225 -20
  18. package/std/README.md +86 -55
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +1376 -42
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1412 -70
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +231 -45
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-conn-params.ts +86 -16
  45. package/std/net/quic-listener.ts +576 -219
  46. package/std/net/quic-packet.ts +443 -58
  47. package/std/net/quic-recovery.ts +361 -30
  48. package/std/net/quic-stream.ts +434 -28
  49. package/std/net/quic.ts +922 -206
  50. package/std/net/router.ts +676 -0
  51. package/std/net/tls/codec.ts +426 -27
  52. package/std/net/tls/record-server.ts +478 -132
  53. package/std/net/tls/record.ts +133 -42
  54. package/std/net/tls/schedule.ts +464 -55
  55. package/std/net/tls/ticket.ts +610 -0
  56. package/std/net/tls-tcp.ts +204 -57
  57. package/std/net/tls.ts +1132 -305
  58. package/std/net/url.ts +571 -0
  59. package/std/random.ts +94 -0
  60. package/std/text.ts +77 -0
  61. package/std/threads.ts +438 -14
@@ -11,12 +11,13 @@
11
11
  *
12
12
  * **Framing.** A record is a five-byte header — the content type, a legacy
13
13
  * version and a 16-bit length — and that many bytes. `TlsRecordReader` holds
14
- * the bytes of a stream that are not yet a whole record, in a buffer allocated
15
- * once and big enough for the largest record the RFC allows (2^14 + 256 bytes
16
- * of body), and says as soon as a header is in whether the record is one this
17
- * layer will read: a content type it does not know is `unexpected_message`,
18
- * and a length past the limit is `record_overflow`, before any of the body is
19
- * waited for. The legacy version is ignored, as §5.1 says it must be.
14
+ * the bytes of a stream that are not yet a whole record, in a buffer big
15
+ * enough for the largest record the RFC allows (2^14 + 256 bytes of body),
16
+ * borrowed from a `BufferPool` only while it holds any, and says as soon as
17
+ * a header is in whether the record is one this layer will read: a content
18
+ * type it does not know is `unexpected_message`, and a length past the limit
19
+ * is `record_overflow`, before any of the body is waited for. The legacy
20
+ * version is ignored, as §5.1 says it must be.
20
21
  *
21
22
  * **Protection.** `TlsRecordProtection` is one direction of a connection:
22
23
  * cleartext until `install` gives it a suite and a traffic secret, then
@@ -44,24 +45,32 @@
44
45
  * derived from it are held in the protection's fields, because a `Secret` may
45
46
  * not be one (NL2430), in arrays the protection reuses for every key it is
46
47
  * given, and are zeroed when it is installed again or cleared — the key and
47
- * IV with `secureZero`. The key and IV are derived straight into place, in an
48
- * HKDF scratch that is wiped after each derivation, and the schedule is
49
- * expanded into place by `aesKeyInto`. The schedule is `u64` words, which
50
- * `secureZero` does not take, and are zeroed by ordinary stores that stand
51
- * because it stays reachable (TLS-2 in `docs/security/tls.md`).
48
+ * IV with `secureZero`, the schedule with `aesKeyWipe`, the volatile `wipe`
49
+ * of its round keys (the kernels' powers of H included) and of H. The key and
50
+ * IV are derived straight into place, in an HKDF scratch that is wiped after
51
+ * each derivation, and the schedule is expanded into place by `aesKeyInto`
52
+ * (TLS-2 in `docs/security/tls.md`). A caller that holds the traffic secret
53
+ * as a `Secret` installs it with `installHeld` and ratchets it in place with
54
+ * `advanceHeld`, so the secret itself is never plain outside `exposeWith`.
52
55
  *
53
56
  * The record layer is sans-IO: `nish/net/tls/record-server` runs it over a
54
57
  * stream for `TlsServer`, and `nish/net/tls-tcp` puts that on a socket.
55
58
  * Written from RFC 8446 §5, not ported from another implementation.
56
59
  */
57
- import { AesKey, aesGcmOpen, aesGcmSeal, aesKeyInto } from "nish/crypto/aes"
60
+ import { BufferPool } from "nish/net/buffer-pool"
61
+ import { AesKey, aesGcmOpen, aesGcmSeal, aesKeyInto, aesKeyWipe } from "nish/crypto/aes"
58
62
  import { chacha20Poly1305Open, chacha20Poly1305Seal } from "nish/crypto/chacha20poly1305"
59
63
  import {
60
64
  TLS_ALERT_INTERNAL_ERROR,
61
65
  TLS_ALERT_UNEXPECTED_MESSAGE,
62
66
  TLS_LEGACY_VERSION,
63
67
  } from "nish/net/tls/codec"
64
- import { HkdfScratch, hkdfExpandLabelInto } from "nish/crypto/hkdf"
68
+ import {
69
+ HkdfScratch,
70
+ hkdfExpandLabelInto,
71
+ hkdfExpandLabelSha256Held,
72
+ hkdfExpandLabelSha384Held,
73
+ } from "nish/crypto/hkdf"
65
74
  import { SHA384_SIZE } from "nish/crypto/sha512"
66
75
  import {
67
76
  TLS_CHACHA20_POLY1305_SHA256,
@@ -69,8 +78,10 @@ import {
69
78
  tlsExpandLabel,
70
79
  tlsSuiteHashLength,
71
80
  tlsSuiteKeyLength,
81
+ tlsTrafficKeysFromHeld,
72
82
  tlsTrafficKeysInto,
73
83
  } from "nish/net/tls/schedule"
84
+ import { Secret, expose } from "nish:secret"
74
85
 
75
86
  // ---- The wire's constants ------------------------------------------------------
76
87
 
@@ -134,11 +145,12 @@ const TLS_RECORD_FROM: i32 = 0
134
145
  /**
135
146
  * The traffic secret a KeyUpdate moves one direction to (RFC 8446 §7.2):
136
147
  * `HKDF-Expand-Label(secret, "traffic upd", "", Hash.length)` over the hash
137
- * `hashLength` names. The old secret is the caller's to wipe.
148
+ * `hashLength` names, held as a `Secret`. The old secret is the caller's to
149
+ * wipe.
138
150
  */
139
- export const tlsNextTrafficSecret = (hashLength: i32, secret: u8[]): u8[] => {
151
+ export const tlsNextTrafficSecret = (hashLength: i32, trafficSecret: Secret<u8[]>): Secret<u8[]> => {
140
152
  const none: u8[] = []
141
- return tlsExpandLabel(hashLength, secret, "traffic upd", none, hashLength)
153
+ return tlsExpandLabel(hashLength, trafficSecret, "traffic upd", none, hashLength)
142
154
  }
143
155
 
144
156
  /** Whether `[off, off + len)` is a window inside an array of `length` elements: every caller's range check. */
@@ -182,44 +194,65 @@ export const tlsRecordLength = (buf: u8[], off: i32, avail: i32): i32 => {
182
194
  return TLS_RECORD_HEADER_SIZE + length
183
195
  }
184
196
 
185
- /**
186
- * Zeroes an AES key schedule's round keys and H with ordinary stores, which
187
- * stand because the schedule stays reachable for the next key (TLS-2).
188
- */
189
- const tlsClearAesKey = (aes: AesKey): void => {
190
- for (let k: i32 = 0; k < toI32(aes.roundKeys.length); k++) {
191
- aes.roundKeys[k] = toU64(0)
192
- }
193
- aes.hHi = toU64(0)
194
- aes.hLo = toU64(0)
195
- }
196
-
197
197
  /**
198
198
  * The bytes of a stream that are not yet a whole record. The buffer is
199
- * allocated once, `TLS_MAX_RECORD` bytes, so a reader never grows: `push`
200
- * takes what fits, and the bytes of a record stay until `consume` lets them
201
- * go. The caller reads `buffer[start .. start + next())` for the record.
199
+ * `TLS_MAX_RECORD` bytes, borrowed from `pool` by the first `push` that brings
200
+ * a byte, kept between records, and given back, zeroed with `giveSecret`, by
201
+ * `reset`, as its connection ends: a reader that has not been pushed to since
202
+ * holds no buffer at all. `push` takes what fits, and the
203
+ * bytes of a record stay until `consume` lets them go. The caller reads
204
+ * `buffer[start .. start + next())` for the record, while `next()` is above 0.
205
+ *
206
+ * A reader starts with a pool of its own, one buffer deep; `borrowFrom` points
207
+ * it at a pool a server shares between its slots.
202
208
  */
203
209
  export class TlsRecordReader {
210
+ // References, then 32-bit fields: the order the layout packs without padding.
211
+ /** The bytes held, or `empty` while the reader holds none. */
204
212
  buffer: u8[]
213
+ /** The zero-length array `buffer` is while no buffer is borrowed. */
214
+ empty: u8[]
215
+ /** Where `buffer` comes from and goes back to. */
216
+ pool: BufferPool
205
217
  /** The first byte not yet consumed. */
206
218
  start: i32 = 0
207
219
  /** One past the last byte pushed. */
208
220
  end: i32 = 0
209
221
 
210
222
  constructor() {
211
- this.buffer = new Array<u8>(TLS_MAX_RECORD)
223
+ this.empty = []
224
+ this.buffer = this.empty
225
+ this.pool = new BufferPool("TlsRecordReader.buffer", TLS_MAX_RECORD, 1)
226
+ }
227
+
228
+ /**
229
+ * Borrows from `pool` from now on, giving back first, zeroed, any buffer
230
+ * held from the old one. Every byte held is forgotten, as `reset` does.
231
+ */
232
+ borrowFrom(pool: BufferPool): void {
233
+ this.reset()
234
+ this.pool = pool
212
235
  }
213
236
 
214
- /** Forgets every byte, for a connection that starts again in the same slot. */
237
+ /** Forgets every byte, for a connection that starts again in the same slot, and gives the buffer back. */
215
238
  reset(): void {
216
239
  this.start = 0
217
240
  this.end = 0
241
+ this.giveBack()
242
+ }
243
+
244
+ /** Gives the buffer back, zeroed, if one is held. */
245
+ giveBack(): void {
246
+ if (this.buffer.length > 0) {
247
+ // Every byte of it may have held a record: zero all of it.
248
+ this.pool.giveSecret(this.buffer, TLS_MAX_RECORD)
249
+ this.buffer = this.empty
250
+ }
218
251
  }
219
252
 
220
253
  /** How many bytes `push` would take now. */
221
254
  space(): i32 {
222
- return toI32(this.buffer.length) - (this.end - this.start)
255
+ return TLS_MAX_RECORD - (this.end - this.start)
223
256
  }
224
257
 
225
258
  /**
@@ -231,16 +264,22 @@ export class TlsRecordReader {
231
264
  if (!tlsRecordWindowFits(toI32(data.length), off, len)) {
232
265
  return -TLS_ALERT_INTERNAL_ERROR
233
266
  }
267
+ if (len === 0) {
268
+ return 0
269
+ }
270
+ if (this.buffer.length === 0) {
271
+ this.buffer = this.pool.take()
272
+ }
234
273
  const held: i32 = this.end - this.start
235
274
  // The held bytes move to the front only when the new ones would not fit after them.
236
- if (this.start > 0 && toI32(this.buffer.length) - this.end < len) {
275
+ if (this.start > 0 && TLS_MAX_RECORD - this.end < len) {
237
276
  for (let k: i32 = 0; k < held; k++) {
238
277
  this.buffer[k] = this.buffer[this.start + k]
239
278
  }
240
279
  this.start = 0
241
280
  this.end = held
242
281
  }
243
- const room: i32 = toI32(this.buffer.length) - this.end
282
+ const room: i32 = TLS_MAX_RECORD - this.end
244
283
  const take: i32 = len < room ? len : room
245
284
  for (let k: i32 = 0; k < take; k++) {
246
285
  this.buffer[this.end + k] = data[off + k]
@@ -260,7 +299,11 @@ export class TlsRecordReader {
260
299
  return length > avail ? 0 : length
261
300
  }
262
301
 
263
- /** Lets the first `n` held bytes go: the record `next` answered, once it is handled. */
302
+ /**
303
+ * Lets the first `n` held bytes go: the record `next` answered, once it is
304
+ * handled. When none are left the next record starts at the front; the
305
+ * buffer stays until `reset`.
306
+ */
264
307
  consume(n: i32): void {
265
308
  const held: i32 = this.end - this.start
266
309
  let take: i32 = n < held ? n : held
@@ -334,16 +377,16 @@ export class TlsRecordProtection {
334
377
 
335
378
  /**
336
379
  * Back to cleartext, with the sequence number at 0 and both keys, the IV
337
- * and both AES key schedules zeroed: the keys and IV with `secureZero`,
338
- * and the schedules' `u64` words with ordinary stores, which stand because
339
- * the schedules stay reachable for the next `install` to fill (TLS-2).
380
+ * and both AES key schedules wiped: the keys and IV with `secureZero`, and
381
+ * each schedule with `aesKeyWipe`, its whole `roundKeys` and its H, which
382
+ * the next `install` fills again (TLS-2).
340
383
  */
341
384
  clear(): void {
342
385
  secureZero(this.key16)
343
386
  secureZero(this.key32)
344
387
  secureZero(this.iv)
345
- tlsClearAesKey(this.aes128)
346
- tlsClearAesKey(this.aes256)
388
+ aesKeyWipe(this.aes128)
389
+ aesKeyWipe(this.aes256)
347
390
  this.suite = 0
348
391
  this.sequence = 0
349
392
  this.contentType = 0
@@ -443,6 +486,54 @@ export class TlsRecordProtection {
443
486
  return this.install(suite, secret)
444
487
  }
445
488
 
489
+ /**
490
+ * `install` from a traffic secret the caller holds as a `Secret`: the key
491
+ * and IV are derived from it straight into this direction's arrays, the
492
+ * plain output of each derivation wiped, and the AES key schedule expanded
493
+ * into place inside an arena block, as `derive`'s is. `false`, leaving the
494
+ * direction cleartext, where `install` answers it.
495
+ */
496
+ installHeld(suite: i32, secret: Secret<u8[]>): boolean {
497
+ this.clear()
498
+ const hashLength: i32 = tlsSuiteHashLength(suite)
499
+ if (hashLength === 0 || expose(secret, (bytes: u8[]): i32 => toI32(bytes.length)) !== hashLength) {
500
+ return false
501
+ }
502
+ this.reserve(suite)
503
+ tlsTrafficKeysFromHeld(suite, secret, this.key, this.iv)
504
+ const aes: AesKey | null = this.aes
505
+ if (aes !== null && suite !== TLS_CHACHA20_POLY1305_SHA256) {
506
+ using _scope = arena()
507
+ aesKeyInto(this.key, aes)
508
+ }
509
+ this.suite = suite
510
+ return true
511
+ }
512
+
513
+ /**
514
+ * `advance` on a traffic secret the caller holds as a `Secret`: the next
515
+ * traffic secret (§7.2) is written over `secret`'s bytes in place, a
516
+ * ratchet that leaves no copy of either, and the keys are installed from
517
+ * it with `installHeld`. False, as `advance` answers, when the keys do not
518
+ * install; a secret that is not its hash's length is left as it was.
519
+ */
520
+ advanceHeld(secret: Secret<u8[]>): boolean {
521
+ const suite: i32 = this.suite
522
+ const hashLength: i32 = tlsSuiteHashLength(suite)
523
+ if (hashLength === 0) {
524
+ return false
525
+ }
526
+ if (expose(secret, (bytes: u8[]): i32 => toI32(bytes.length)) === hashLength) {
527
+ const none: u8[] = []
528
+ if (hashLength === SHA384_SIZE) {
529
+ hkdfExpandLabelSha384Held(secret, secret, "traffic upd", none)
530
+ } else {
531
+ hkdfExpandLabelSha256Held(secret, secret, "traffic upd", none)
532
+ }
533
+ }
534
+ return this.installHeld(suite, secret)
535
+ }
536
+
446
537
  /** The per-record nonce (§5.3): the IV with the sequence number XORed into its last eight bytes, big-endian. */
447
538
  nonceInto(nonce: u8[]): void {
448
539
  for (let k: i32 = 0; k < TLS_IV_SIZE && k < toI32(nonce.length) && k < toI32(this.iv.length); k++) {