@amritk/nish-x86_64-linux 0.18.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 (59) 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 +215 -18
  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 +4 -4
  14. package/runtime/runtime-wasm.c +5 -3
  15. package/runtime/runtime.c +69 -13
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +223 -22
  18. package/std/README.md +81 -51
  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 +932 -61
  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 +1120 -34
  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 +211 -31
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-listener.ts +88 -7
  45. package/std/net/quic-packet.ts +243 -82
  46. package/std/net/quic-recovery.ts +361 -30
  47. package/std/net/quic-stream.ts +424 -24
  48. package/std/net/quic.ts +519 -167
  49. package/std/net/router.ts +676 -0
  50. package/std/net/tls/codec.ts +426 -27
  51. package/std/net/tls/record-server.ts +478 -132
  52. package/std/net/tls/record.ts +133 -42
  53. package/std/net/tls/schedule.ts +464 -55
  54. package/std/net/tls/ticket.ts +610 -0
  55. package/std/net/tls-tcp.ts +196 -53
  56. package/std/net/tls.ts +1018 -260
  57. package/std/net/url.ts +571 -0
  58. package/std/random.ts +94 -0
  59. package/std/threads.ts +438 -14
@@ -26,13 +26,20 @@
26
26
  * stops asking to be written. An event with the hang-up bit (4) goes to
27
27
  * `readable`, which is where a closed or failed socket is noticed.
28
28
  *
29
- * **Slots, sized at start-up.** The constructor allocates every connection's
30
- * buffers — about 136 KB of resident memory each, the largest record twice
31
- * over in each direction and the handshake's own state (TLS-3) — and `accept` hands the next connection a free slot and
32
- * reuses it once the program `close`s it (WP34 N9). When every slot is busy
33
- * a new connection is accepted and closed at once, so the listener does not
34
- * stay readable; the program sees `TLS_TCP_POOL_FULL`. Each slot keeps its
35
- * own `TlsServer`, which `accept` restarts for the connection, so neither the
29
+ * **Slots, built on first use; record buffers, borrowed.** The pool's size
30
+ * is a cap: the constructor builds no slot, and the first `accept` into a
31
+ * slot builds its handshake state and keys (TLS-3) and keeps them, so a pool
32
+ * sized for its peak costs at start-up only what does not depend on the
33
+ * size. `accept` hands the next connection the slot closed last, or failing
34
+ * that the next never used, and reuses it once the program `close`s it
35
+ * (WP34 N9). The record buffers — the bytes to send, the plaintext waiting
36
+ * for the program, the record being opened and the record being read — are
37
+ * borrowed from pools this server owns while a connection uses the slot, and
38
+ * given back zeroed (`BufferPool.giveSecret`) when it ends, so a free slot
39
+ * holds none of them ("What moves between connections" in
40
+ * `docs/security/tls.md`). When every slot is busy a new connection is
41
+ * accepted and closed at once, so the listener does not stay readable; the
42
+ * program sees `TLS_TCP_POOL_FULL`. Each slot keeps its own `TlsServer`, which `accept` restarts for the connection, so neither the
36
43
  * handshake nor application data allocates anything that outlives a call:
37
44
  * a thousand connections through one slot leave `Arena.mark()` where the
38
45
  * first left it, under every suite (TLS-3 in `docs/security/tls.md`,
@@ -50,6 +57,15 @@
50
57
  * borrows for the call, and `signatureInput` / `sign` hand the input out and
51
58
  * take any signature back.
52
59
  *
60
+ * **Resumption is opt-in, and its keys are the caller's.** After
61
+ * `enableTickets`, a slot whose interest says `TLS_RECORD_TICKET` waits on
62
+ * the program: in the handshake, `resume` reads the ClientHello's tickets
63
+ * under the current and previous ticket keys and a strike register the
64
+ * program lends for the call, and answers the hello resumed or in full; once
65
+ * the connection is open, `issue` seals it one ticket. The pool keeps no
66
+ * ticket key and no register of its own (`docs/security/tls.md`,
67
+ * "Resumption").
68
+ *
53
69
  * `read`, `write` and `close` answer as `nish:net`'s calls do — a count, `0`
54
70
  * at the end of the stream, `-11` to wait — so a program written against a
55
71
  * plain socket moves over with the `slot` in place of the descriptor.
@@ -64,11 +80,13 @@ import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
64
80
  import { TlsP256Signer, TlsServer, TlsServerConfig } from "nish/net/tls"
65
81
  import { TLS_ALERT_INTERNAL_ERROR } from "nish/net/tls/codec"
66
82
  import { TLS_MAX_RECORD } from "nish/net/tls/record"
83
+ import { TlsTicketKeyInfo, TlsTicketStrikes } from "nish/net/tls/ticket"
67
84
  import {
68
85
  TLS_RECORD_AGAIN,
69
86
  TLS_RECORD_DONE,
70
87
  TLS_RECORD_INVALID,
71
88
  TLS_RECORD_STATE_FAILED,
89
+ TlsRecordPools,
72
90
  TlsRecordServer,
73
91
  } from "nish/net/tls/record-server"
74
92
 
@@ -95,10 +113,24 @@ const TLS_TCP_ADDRESS_SIZE: i32 = 18
95
113
  */
96
114
  export class TlsTcpServer {
97
115
  config: TlsServerConfig
98
- /** The connections, one per slot, made once. */
116
+ /**
117
+ * The connections built so far, slot `k`'s at `k`: one per slot ever
118
+ * accepted into. `connection` never adds to it; a slot never built reads
119
+ * `vacant` instead.
120
+ */
99
121
  connections: TlsRecordServer[]
100
- /** Each slot's descriptor, or -1 while the slot is free. */
122
+ /**
123
+ * Each slot's descriptor, or -1 while the slot is free, for the slots
124
+ * accepted into so far: its length is the high-water mark, and a slot past
125
+ * it has never been used.
126
+ */
101
127
  fds: i32[]
128
+ /**
129
+ * The slots closed since they were used, a stack whose top `accept` takes
130
+ * before it builds a slot past the high-water mark, so finding one costs
131
+ * the same with every slot free as with all but one busy.
132
+ */
133
+ free: i32[]
102
134
  /** Where the socket is read into before a connection takes the bytes: the largest record. */
103
135
  scratch: u8[]
104
136
  /** The last accepted peer's address, in `nish:net`'s 18-byte form. */
@@ -106,49 +138,92 @@ export class TlsTcpServer {
106
138
  /** Signs every slot's CertificateVerify in `signP256`, one for the pool, keeping nothing per signature. */
107
139
  signer: TlsP256Signer
108
140
  /**
109
- * Each slot's own copy of its connection's ephemeral key, so that wiping it
110
- * can never reach an array the program has refilled for the next one.
141
+ * Each slot's own copy of its connection's server random: `TlsServer`
142
+ * keeps the array it is given and writes it into the ServerHello only once
143
+ * the ClientHello arrives, so the program's array may be refilled for the
144
+ * next one. The ephemeral key needs no copy here: `TlsServer.restart`
145
+ * copies it into the server's own `Secret` and wipes the array it was
146
+ * handed.
111
147
  */
112
- keys: u8[][]
148
+ randoms: u8[][]
149
+ /** How many slots the pool may hold, which `size` answers. */
150
+ slots: i32 = 1
151
+ /** Whether `enableTickets` has run, so a slot built after it resumes too. */
152
+ tickets: boolean = false
113
153
  /**
114
- * Each slot's own copy of its connection's server random, for the same
115
- * reason: `TlsServer` keeps the array it is given and writes it into the
116
- * ServerHello only once the ClientHello arrives.
154
+ * What `connection` answers for every slot never built: one failed
155
+ * connection no slot holds and every such slot shares, so the slot reads
156
+ * as a free one does (no ALPN, not open) and never as another slot's live
157
+ * session. Its first read allocates it, once per server; it has no
158
+ * tickets, and a caller must not change it.
117
159
  */
118
- randoms: u8[][]
160
+ vacant: TlsRecordServer | null = null
161
+ /**
162
+ * The pools every slot borrows its record buffers from, each with a
163
+ * ceiling of one per slot: `lent()` is 0 once every connection has ended,
164
+ * and `made()` is the most connections that were ever open at once.
165
+ */
166
+ pools: TlsRecordPools
119
167
  /** The listening socket, which the program made with `tcpListen` and still owns. */
120
168
  listener: i32 = -1
121
169
 
122
170
  /**
123
171
  * A pool of `size` slots, at least one, serving `config` on `listener`, a
124
- * socket `tcpListen` made. Every slot's buffers are allocated here.
172
+ * socket `tcpListen` made. No slot is built here: a slot's handshake state
173
+ * and its server random are allocated by the first `accept` into it and
174
+ * kept, and its record buffers are borrowed from `pools` while a
175
+ * connection uses them, so a pool sized for a peak costs at start-up only
176
+ * the read scratch, the signer and the empty pools.
125
177
  */
126
178
  constructor(config: TlsServerConfig, listener: i32, size: i32) {
127
179
  this.config = config
128
180
  this.listener = listener
129
181
  this.connections = []
130
182
  this.fds = []
131
- this.keys = []
183
+ this.free = []
132
184
  this.randoms = []
133
- const none: u8[] = []
134
- const slots: i32 = size < 1 ? 1 : size
135
- for (let k: i32 = 0; k < slots; k++) {
136
- // Each slot's own handshake, refused by `TlsServer` for its empty
137
- // randomness, so the slot starts out failed until `accept` restarts it
138
- // for a connection; every later connection reuses its buffers.
139
- this.connections.push(new TlsRecordServer(new TlsServer(config, none, none)))
140
- this.fds.push(TLS_TCP_FREE)
141
- this.keys.push(new Array<u8>(TLS_TCP_KEY_SIZE))
142
- this.randoms.push(new Array<u8>(TLS_TCP_KEY_SIZE))
143
- }
185
+ this.slots = size < 1 ? 1 : size
186
+ this.pools = new TlsRecordPools(this.slots)
144
187
  this.scratch = new Array<u8>(TLS_MAX_RECORD)
145
188
  this.peer = new Array<u8>(TLS_TCP_ADDRESS_SIZE)
146
189
  this.signer = new TlsP256Signer()
147
190
  }
148
191
 
149
- /** How many slots the pool has. */
192
+ /**
193
+ * The next slot's connection and server random, for the slot built next.
194
+ * Called by `accept` alone, as the pool's high-water mark rises: slot `k`'s
195
+ * entry must sit at index `k` of `connections` and `randoms`. Its handshake
196
+ * is never given a ClientHello: `accept` restarts it for a connection with
197
+ * real randomness, and every later connection reuses its state.
198
+ */
199
+ build(): TlsRecordServer {
200
+ const random: u8[] = new Array<u8>(TLS_TCP_KEY_SIZE)
201
+ this.randoms.push(random)
202
+ const conn: TlsRecordServer = this.make(random)
203
+ if (this.tickets) {
204
+ conn.tls.enableTickets()
205
+ }
206
+ this.connections.push(conn)
207
+ return conn
208
+ }
209
+
210
+ /**
211
+ * A connection over `random` that is marked failed without sealing an
212
+ * alert, so a stray call on it fails closed, and that borrows no buffer
213
+ * until a connection moves bytes through it.
214
+ */
215
+ make(random: u8[]): TlsRecordServer {
216
+ // A placeholder key for a first handshake, which never runs.
217
+ const zeros: u8[] = new Array<u8>(TLS_TCP_KEY_SIZE)
218
+ const conn: TlsRecordServer = new TlsRecordServer(new TlsServer(this.config, random, zeros))
219
+ conn.borrowFrom(this.pools)
220
+ conn.state = TLS_RECORD_STATE_FAILED
221
+ return conn
222
+ }
223
+
224
+ /** How many slots the pool has, built or not. */
150
225
  size(): i32 {
151
- return toI32(this.fds.length)
226
+ return this.slots
152
227
  }
153
228
 
154
229
  /** How many slots hold a connection. */
@@ -175,11 +250,22 @@ export class TlsTcpServer {
175
250
  /**
176
251
  * The connection in `slot`: its state, its handshake (`tls`: suite, ALPN,
177
252
  * server name) and the alerts sent and received. A free slot answers the
178
- * last connection it held.
253
+ * last connection it held. A slot never accepted into answers `vacant`,
254
+ * failed, as a free slot does, and builds no slot; a slot out of range
255
+ * names slot 0's.
179
256
  */
180
257
  connection(slot: i32): TlsRecordServer {
181
- const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : 0
182
- return this.connections[at]
258
+ const index: i32 = slot >= 0 && slot < this.slots ? slot : 0
259
+ if (index >= 0 && index < toI32(this.connections.length)) {
260
+ return this.connections[index]
261
+ }
262
+ const vacant: TlsRecordServer | null = this.vacant
263
+ if (vacant !== null) {
264
+ return vacant
265
+ }
266
+ const made: TlsRecordServer = this.make(new Array<u8>(TLS_TCP_KEY_SIZE))
267
+ this.vacant = made
268
+ return made
183
269
  }
184
270
 
185
271
  /**
@@ -205,20 +291,25 @@ export class TlsTcpServer {
205
291
  if (fd < 0) {
206
292
  return fd
207
293
  }
208
- let slot: i32 = -1
209
- for (let k: i32 = 0; k < toI32(this.fds.length); k++) {
210
- if (this.fds[k] < 0) {
211
- slot = k
212
- break
213
- }
294
+ // A slot closed before is taken first, then the next never used, so the
295
+ // first accepts take slots 0, 1, 2 ... and a pool that never fills never
296
+ // builds its last slots.
297
+ let slot: i32 = TLS_TCP_POOL_FULL
298
+ if (this.free.length > 0) {
299
+ slot = this.free[this.free.length - 1]
300
+ this.free.pop()
301
+ } else if (toI32(this.fds.length) < this.slots) {
302
+ slot = toI32(this.fds.length)
303
+ this.fds.push(TLS_TCP_FREE)
214
304
  }
215
- if (slot < 0) {
305
+ if (slot < 0 || slot >= toI32(this.fds.length)) {
216
306
  netClose(fd)
217
307
  return TLS_TCP_POOL_FULL
218
308
  }
309
+ const conn: TlsRecordServer =
310
+ slot < toI32(this.connections.length) ? this.connections[slot] : this.build()
219
311
  this.fds[slot] = fd
220
312
  const random: u8[] = this.randoms[slot]
221
- const key: u8[] = this.keys[slot]
222
313
  for (
223
314
  let k: i32 = 0;
224
315
  k < TLS_TCP_KEY_SIZE && k < toI32(random.length) && k < toI32(serverRandom.length);
@@ -226,16 +317,8 @@ export class TlsTcpServer {
226
317
  ) {
227
318
  random[k] = serverRandom[k]
228
319
  }
229
- for (
230
- let k: i32 = 0;
231
- k < TLS_TCP_KEY_SIZE && k < toI32(key.length) && k < toI32(ephemeralPrivate.length);
232
- k++
233
- ) {
234
- key[k] = ephemeralPrivate[k]
235
- }
236
- secureZero(ephemeralPrivate)
237
- const conn: TlsRecordServer = this.connections[slot]
238
- conn.tls.restart(random, key)
320
+ // `restart` copies the key into the server's own `Secret` and wipes this array.
321
+ conn.tls.restart(random, ephemeralPrivate)
239
322
  conn.start(conn.tls)
240
323
  return slot
241
324
  }
@@ -280,8 +363,15 @@ export class TlsTcpServer {
280
363
  return this.flush(slot)
281
364
  }
282
365
 
283
- /** Writes the connection's output until the socket would block or it is all sent, and answers its `interest`. */
366
+ /**
367
+ * Writes the connection's output until the socket would block or it is all
368
+ * sent, and answers its `interest`; a slot never built answers
369
+ * `TLS_RECORD_DONE`.
370
+ */
284
371
  flush(slot: i32): i32 {
372
+ if (slot < 0 || slot >= toI32(this.fds.length) || slot >= toI32(this.connections.length)) {
373
+ return TLS_RECORD_DONE
374
+ }
285
375
  const fd: i32 = this.fds[slot]
286
376
  const conn: TlsRecordServer = this.connections[slot]
287
377
  while (conn.outputEnd > conn.outputStart) {
@@ -336,6 +426,56 @@ export class TlsTcpServer {
336
426
  return this.sign(slot, signature)
337
427
  }
338
428
 
429
+ /**
430
+ * Turns session resumption on for every slot (`TlsServer.enableTickets`):
431
+ * from the next `accept`, a connection offering a ticket, or one that may
432
+ * be issued one, says `TLS_RECORD_TICKET` in its interest. A slot built
433
+ * later is built with them on.
434
+ */
435
+ enableTickets(): void {
436
+ this.tickets = true
437
+ for (let k: i32 = 0; k < toI32(this.connections.length); k++) {
438
+ this.connections[k].tls.enableTickets()
439
+ }
440
+ }
441
+
442
+ /**
443
+ * While `slot`'s interest says `TLS_RECORD_TICKET` in the handshake: reads
444
+ * its ClientHello's tickets under `current` and `previous`, with what each
445
+ * `info` says of them, records the one it resumes in `strikes`, and sends
446
+ * the flight that answers the hello, resumed or in full
447
+ * (`TlsRecordServer.resume`). The keys and the register are borrowed for
448
+ * the call. Answers the slot's interest.
449
+ */
450
+ resume(
451
+ slot: i32,
452
+ current: Secret<u8[]>,
453
+ currentInfo: TlsTicketKeyInfo,
454
+ previous: Secret<u8[]>,
455
+ previousInfo: TlsTicketKeyInfo,
456
+ strikes: TlsTicketStrikes,
457
+ now: i64
458
+ ): i32 {
459
+ if (!this.holds(slot)) {
460
+ return TLS_RECORD_DONE
461
+ }
462
+ this.connections[slot].resume(current, currentInfo, previous, previousInfo, strikes, now)
463
+ return this.flush(slot)
464
+ }
465
+
466
+ /**
467
+ * While `slot`'s interest says `TLS_RECORD_TICKET` on an open connection:
468
+ * seals one ticket under the borrowed `key` and sends its
469
+ * NewSessionTicket (`TlsRecordServer.issue`). Answers the slot's interest.
470
+ */
471
+ issue(slot: i32, key: Secret<u8[]>, info: TlsTicketKeyInfo, now: i64): i32 {
472
+ if (!this.holds(slot)) {
473
+ return TLS_RECORD_DONE
474
+ }
475
+ this.connections[slot].issue(key, info, now)
476
+ return this.flush(slot)
477
+ }
478
+
339
479
  /**
340
480
  * Application data from `slot`'s client into `buf[off .. off + len)`: the
341
481
  * count, 0 at the end of its stream, -11 when nothing is waiting, -104 once
@@ -409,6 +549,9 @@ export class TlsTcpServer {
409
549
  this.flush(slot)
410
550
  netClose(this.fds[slot])
411
551
  this.fds[slot] = TLS_TCP_FREE
552
+ this.free.push(slot)
412
553
  conn.wipeKeys()
554
+ // Whatever the last flush could not send is dropped with its buffer.
555
+ conn.release()
413
556
  }
414
557
  }