@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
@@ -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.
@@ -61,14 +77,16 @@
61
77
  */
62
78
  import { Secret } from "nish:secret"
63
79
  import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
64
- import { TlsServer, TlsServerConfig, tlsSignEcdsaP256 } from "nish/net/tls"
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,57 +113,117 @@ 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. */
105
137
  peer: u8[]
138
+ /** Signs every slot's CertificateVerify in `signP256`, one for the pool, keeping nothing per signature. */
139
+ signer: TlsP256Signer
106
140
  /**
107
- * Each slot's own copy of its connection's ephemeral key, so that wiping it
108
- * 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.
109
147
  */
110
- 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
111
153
  /**
112
- * Each slot's own copy of its connection's server random, for the same
113
- * reason: `TlsServer` keeps the array it is given and writes it into the
114
- * 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.
115
159
  */
116
- 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
117
167
  /** The listening socket, which the program made with `tcpListen` and still owns. */
118
168
  listener: i32 = -1
119
169
 
120
170
  /**
121
171
  * A pool of `size` slots, at least one, serving `config` on `listener`, a
122
- * 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.
123
177
  */
124
178
  constructor(config: TlsServerConfig, listener: i32, size: i32) {
125
179
  this.config = config
126
180
  this.listener = listener
127
181
  this.connections = []
128
182
  this.fds = []
129
- this.keys = []
183
+ this.free = []
130
184
  this.randoms = []
131
- const none: u8[] = []
132
- const slots: i32 = size < 1 ? 1 : size
133
- for (let k: i32 = 0; k < slots; k++) {
134
- // Each slot's own handshake, refused by `TlsServer` for its empty
135
- // randomness, so the slot starts out failed until `accept` restarts it
136
- // for a connection; every later connection reuses its buffers.
137
- this.connections.push(new TlsRecordServer(new TlsServer(config, none, none)))
138
- this.fds.push(TLS_TCP_FREE)
139
- this.keys.push(new Array<u8>(TLS_TCP_KEY_SIZE))
140
- this.randoms.push(new Array<u8>(TLS_TCP_KEY_SIZE))
141
- }
185
+ this.slots = size < 1 ? 1 : size
186
+ this.pools = new TlsRecordPools(this.slots)
142
187
  this.scratch = new Array<u8>(TLS_MAX_RECORD)
143
188
  this.peer = new Array<u8>(TLS_TCP_ADDRESS_SIZE)
189
+ this.signer = new TlsP256Signer()
144
190
  }
145
191
 
146
- /** 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. */
147
225
  size(): i32 {
148
- return toI32(this.fds.length)
226
+ return this.slots
149
227
  }
150
228
 
151
229
  /** How many slots hold a connection. */
@@ -172,11 +250,22 @@ export class TlsTcpServer {
172
250
  /**
173
251
  * The connection in `slot`: its state, its handshake (`tls`: suite, ALPN,
174
252
  * server name) and the alerts sent and received. A free slot answers the
175
- * 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.
176
256
  */
177
257
  connection(slot: i32): TlsRecordServer {
178
- const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : 0
179
- 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
180
269
  }
181
270
 
182
271
  /**
@@ -202,20 +291,25 @@ export class TlsTcpServer {
202
291
  if (fd < 0) {
203
292
  return fd
204
293
  }
205
- let slot: i32 = -1
206
- for (let k: i32 = 0; k < toI32(this.fds.length); k++) {
207
- if (this.fds[k] < 0) {
208
- slot = k
209
- break
210
- }
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)
211
304
  }
212
- if (slot < 0) {
305
+ if (slot < 0 || slot >= toI32(this.fds.length)) {
213
306
  netClose(fd)
214
307
  return TLS_TCP_POOL_FULL
215
308
  }
309
+ const conn: TlsRecordServer =
310
+ slot < toI32(this.connections.length) ? this.connections[slot] : this.build()
216
311
  this.fds[slot] = fd
217
312
  const random: u8[] = this.randoms[slot]
218
- const key: u8[] = this.keys[slot]
219
313
  for (
220
314
  let k: i32 = 0;
221
315
  k < TLS_TCP_KEY_SIZE && k < toI32(random.length) && k < toI32(serverRandom.length);
@@ -223,16 +317,8 @@ export class TlsTcpServer {
223
317
  ) {
224
318
  random[k] = serverRandom[k]
225
319
  }
226
- for (
227
- let k: i32 = 0;
228
- k < TLS_TCP_KEY_SIZE && k < toI32(key.length) && k < toI32(ephemeralPrivate.length);
229
- k++
230
- ) {
231
- key[k] = ephemeralPrivate[k]
232
- }
233
- secureZero(ephemeralPrivate)
234
- const conn: TlsRecordServer = this.connections[slot]
235
- 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)
236
322
  conn.start(conn.tls)
237
323
  return slot
238
324
  }
@@ -277,8 +363,15 @@ export class TlsTcpServer {
277
363
  return this.flush(slot)
278
364
  }
279
365
 
280
- /** 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
+ */
281
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
+ }
282
375
  const fd: i32 = this.fds[slot]
283
376
  const conn: TlsRecordServer = this.connections[slot]
284
377
  while (conn.outputEnd > conn.outputStart) {
@@ -315,8 +408,9 @@ export class TlsTcpServer {
315
408
 
316
409
  /**
317
410
  * Signs `slot`'s CertificateVerify with the P-256 private key `key` and
318
- * hands it over, as `sign` does. The key is borrowed: the caller made the
319
- * `Secret`, and wipes it. A malformed key fails the connection with
411
+ * hands it over, as `sign` does, through the pool's `TlsP256Signer`, so a
412
+ * handshake's signature keeps nothing. The key is borrowed: the caller made
413
+ * the `Secret`, and wipes it. A malformed key fails the connection with
320
414
  * `internal_error`.
321
415
  */
322
416
  signP256(slot: i32, key: Secret<u8[]>): i32 {
@@ -324,7 +418,7 @@ export class TlsTcpServer {
324
418
  if (input === null) {
325
419
  return this.interest(slot)
326
420
  }
327
- const signature: u8[] | null = tlsSignEcdsaP256(key, input)
421
+ const signature: u8[] | null = this.signer.sign(key, input)
328
422
  if (signature === null) {
329
423
  this.connections[slot].fail(TLS_ALERT_INTERNAL_ERROR)
330
424
  return this.flush(slot)
@@ -332,6 +426,56 @@ export class TlsTcpServer {
332
426
  return this.sign(slot, signature)
333
427
  }
334
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
+
335
479
  /**
336
480
  * Application data from `slot`'s client into `buf[off .. off + len)`: the
337
481
  * count, 0 at the end of its stream, -11 when nothing is waiting, -104 once
@@ -405,6 +549,9 @@ export class TlsTcpServer {
405
549
  this.flush(slot)
406
550
  netClose(this.fds[slot])
407
551
  this.fds[slot] = TLS_TCP_FREE
552
+ this.free.push(slot)
408
553
  conn.wipeKeys()
554
+ // Whatever the last flush could not send is dropped with its buffer.
555
+ conn.release()
409
556
  }
410
557
  }