@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
@@ -0,0 +1,414 @@
1
+ /**
2
+ * `nish/net/tls-tcp` — TLS 1.3 over TCP (WP34 T2): a pool of server
3
+ * connections on `nish:net`, each a `TlsRecordServer` driving a `TlsServer`,
4
+ * for a program that owns its readiness loop.
5
+ *
6
+ * const listener = tcpListen("0.0.0.0", 8443, 128);
7
+ * const tls = new TlsTcpServer(config, listener, 64);
8
+ * pollAdd(loop, listener, 1, LISTENER);
9
+ * // when the listener is readable:
10
+ * const slot: i32 = tls.accept(random32(), random32());
11
+ * if (slot >= 0) { pollAdd(loop, tls.fd(slot), 1, slot); }
12
+ * // when a connection's token comes back:
13
+ * let wants: i32 = (events & 5) !== 0 ? tls.readable(slot) : tls.writable(slot);
14
+ * if ((wants & TLS_RECORD_SIGN) !== 0) { wants = tls.signP256(slot, leafKey); }
15
+ * if ((wants & TLS_RECORD_DATA) !== 0) { tls.read(slot, buf, 0, buf.length); ... }
16
+ * if ((wants & TLS_RECORD_DONE) !== 0) { tls.close(slot); }
17
+ * else { pollModify(loop, tls.fd(slot), wants & 3, slot); }
18
+ *
19
+ * **The program is the loop.** A function is not a value, so there are no
20
+ * callbacks: every call answers what the connection wants next as the
21
+ * `TLS_RECORD_*` bits of `nish/net/tls/record-server`, and the low two bits
22
+ * are exactly the events `pollModify` takes — readable, writable — so the
23
+ * program re-arms the descriptor with them after each call. The loop stays
24
+ * level-triggered and never spins: a connection whose application data the
25
+ * program has not read stops asking to be read, and one with nothing to send
26
+ * stops asking to be written. An event with the hang-up bit (4) goes to
27
+ * `readable`, which is where a closed or failed socket is noticed.
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
36
+ * handshake nor application data allocates anything that outlives a call:
37
+ * a thousand connections through one slot leave `Arena.mark()` where the
38
+ * first left it, under every suite (TLS-3 in `docs/security/tls.md`,
39
+ * `tests/link/net_tls_memory`).
40
+ *
41
+ * **Randomness is the caller's.** `accept` takes the 32-byte server random and
42
+ * the 32-byte x25519 private key the handshake uses, as `TlsServer` does, so a
43
+ * test replays RFC 8448's trace and a server draws them with
44
+ * `crypto.getRandomValues`. `accept` copies both into the slot and wipes the
45
+ * caller's key array, so one buffer of each refilled for every `accept` is
46
+ * safe, and refuses either when it is not 32 bytes before taking a
47
+ * connection; the slot's copy of the key is wiped once the ServerHello is
48
+ * written, when the handshake fails, or when the slot is closed. **Signing is the caller's**, as `TlsServer`'s is: `signP256` signs
49
+ * the CertificateVerify with a P-256 key the caller holds as a `Secret` and
50
+ * borrows for the call, and `signatureInput` / `sign` hand the input out and
51
+ * take any signature back.
52
+ *
53
+ * `read`, `write` and `close` answer as `nish:net`'s calls do — a count, `0`
54
+ * at the end of the stream, `-11` to wait — so a program written against a
55
+ * plain socket moves over with the `slot` in place of the descriptor.
56
+ * `close` frees the slot at once; `shutdown` sends the `close_notify` as the
57
+ * socket takes it, and the slot is done when its interest says so.
58
+ *
59
+ * Written from RFC 8446 and docs/LANGUAGE.md's `nish:net`, not ported from
60
+ * another implementation.
61
+ */
62
+ import { Secret } from "nish:secret"
63
+ import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
64
+ import { TlsP256Signer, TlsServer, TlsServerConfig } from "nish/net/tls"
65
+ import { TLS_ALERT_INTERNAL_ERROR } from "nish/net/tls/codec"
66
+ import { TLS_MAX_RECORD } from "nish/net/tls/record"
67
+ import {
68
+ TLS_RECORD_AGAIN,
69
+ TLS_RECORD_DONE,
70
+ TLS_RECORD_INVALID,
71
+ TLS_RECORD_STATE_FAILED,
72
+ TlsRecordServer,
73
+ } from "nish/net/tls/record-server"
74
+
75
+ /** What `accept` answers when every slot is busy: the connection was accepted and closed. Linux's ENOBUFS. */
76
+ export const TLS_TCP_POOL_FULL: i32 = -105
77
+
78
+ /** A free slot's descriptor. */
79
+ const TLS_TCP_FREE: i32 = -1
80
+
81
+ /** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
82
+ const TLS_TCP_FROM: i32 = 0
83
+
84
+ /** The length of an x25519 private key and of a server random: what `TlsServer` takes for each. */
85
+ const TLS_TCP_KEY_SIZE: i32 = 32
86
+
87
+ /** The address form's length, from `nish:net`. */
88
+ const TLS_TCP_ADDRESS_SIZE: i32 = 18
89
+
90
+ /**
91
+ * A pool of TLS 1.3 server connections on one listening socket. `slot`s are
92
+ * the indices `accept` answers, from 0 to the pool's size; a slot is the
93
+ * program's from `accept` until it calls `close`, and its descriptor is
94
+ * `fd(slot)`.
95
+ */
96
+ export class TlsTcpServer {
97
+ config: TlsServerConfig
98
+ /** The connections, one per slot, made once. */
99
+ connections: TlsRecordServer[]
100
+ /** Each slot's descriptor, or -1 while the slot is free. */
101
+ fds: i32[]
102
+ /** Where the socket is read into before a connection takes the bytes: the largest record. */
103
+ scratch: u8[]
104
+ /** The last accepted peer's address, in `nish:net`'s 18-byte form. */
105
+ peer: u8[]
106
+ /** Signs every slot's CertificateVerify in `signP256`, one for the pool, keeping nothing per signature. */
107
+ signer: TlsP256Signer
108
+ /**
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.
111
+ */
112
+ keys: u8[][]
113
+ /**
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.
117
+ */
118
+ randoms: u8[][]
119
+ /** The listening socket, which the program made with `tcpListen` and still owns. */
120
+ listener: i32 = -1
121
+
122
+ /**
123
+ * A pool of `size` slots, at least one, serving `config` on `listener`, a
124
+ * socket `tcpListen` made. Every slot's buffers are allocated here.
125
+ */
126
+ constructor(config: TlsServerConfig, listener: i32, size: i32) {
127
+ this.config = config
128
+ this.listener = listener
129
+ this.connections = []
130
+ this.fds = []
131
+ this.keys = []
132
+ 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
+ }
144
+ this.scratch = new Array<u8>(TLS_MAX_RECORD)
145
+ this.peer = new Array<u8>(TLS_TCP_ADDRESS_SIZE)
146
+ this.signer = new TlsP256Signer()
147
+ }
148
+
149
+ /** How many slots the pool has. */
150
+ size(): i32 {
151
+ return toI32(this.fds.length)
152
+ }
153
+
154
+ /** How many slots hold a connection. */
155
+ busy(): i32 {
156
+ let n: i32 = 0
157
+ for (const fd of this.fds) {
158
+ if (fd >= 0) {
159
+ n = n + 1
160
+ }
161
+ }
162
+ return n
163
+ }
164
+
165
+ /** Whether `slot` is a slot of this pool that holds a connection. */
166
+ holds(slot: i32): boolean {
167
+ return slot >= 0 && slot < toI32(this.fds.length) && this.fds[slot] >= 0
168
+ }
169
+
170
+ /** The descriptor of `slot`, for `pollAdd`; -1 for a free or unknown slot. */
171
+ fd(slot: i32): i32 {
172
+ return this.holds(slot) ? this.fds[slot] : -1
173
+ }
174
+
175
+ /**
176
+ * The connection in `slot`: its state, its handshake (`tls`: suite, ALPN,
177
+ * server name) and the alerts sent and received. A free slot answers the
178
+ * last connection it held.
179
+ */
180
+ connection(slot: i32): TlsRecordServer {
181
+ const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : 0
182
+ return this.connections[at]
183
+ }
184
+
185
+ /**
186
+ * Accepts the next waiting connection into a free slot and starts its
187
+ * handshake with `serverRandom` and `ephemeralPrivate` (32 bytes each),
188
+ * and answers the slot. The peer's address is in `peer`. Both arrays are
189
+ * copied into the slot, which reads them only when the ClientHello
190
+ * arrives, and the key's array is then wiped, so the program may refill
191
+ * one buffer of each for every call. Answers -11 when no connection is
192
+ * waiting, `TLS_TCP_POOL_FULL` when one was but every slot is busy (it is
193
+ * closed), or `tcpAccept`'s own failure; and `TLS_RECORD_INVALID` (-22),
194
+ * before any connection is taken, for a random or key that is not 32
195
+ * bytes. On any answer but a slot both arrays are left as they were.
196
+ */
197
+ accept(serverRandom: u8[], ephemeralPrivate: u8[]): i32 {
198
+ if (
199
+ toI32(serverRandom.length) !== TLS_TCP_KEY_SIZE ||
200
+ toI32(ephemeralPrivate.length) !== TLS_TCP_KEY_SIZE
201
+ ) {
202
+ return TLS_RECORD_INVALID
203
+ }
204
+ const fd: i32 = tcpAccept(this.listener, this.peer)
205
+ if (fd < 0) {
206
+ return fd
207
+ }
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
+ }
214
+ }
215
+ if (slot < 0) {
216
+ netClose(fd)
217
+ return TLS_TCP_POOL_FULL
218
+ }
219
+ this.fds[slot] = fd
220
+ const random: u8[] = this.randoms[slot]
221
+ const key: u8[] = this.keys[slot]
222
+ for (
223
+ let k: i32 = 0;
224
+ k < TLS_TCP_KEY_SIZE && k < toI32(random.length) && k < toI32(serverRandom.length);
225
+ k++
226
+ ) {
227
+ random[k] = serverRandom[k]
228
+ }
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)
239
+ conn.start(conn.tls)
240
+ return slot
241
+ }
242
+
243
+ /**
244
+ * The socket of `slot` is readable: reads it until it would block or the
245
+ * connection has no room, handing each read to the connection, then sends
246
+ * what that produced. Answers the connection's `interest`; a free slot
247
+ * answers `TLS_RECORD_DONE`.
248
+ */
249
+ readable(slot: i32): i32 {
250
+ if (!this.holds(slot)) {
251
+ return TLS_RECORD_DONE
252
+ }
253
+ const fd: i32 = this.fds[slot]
254
+ const conn: TlsRecordServer = this.connections[slot]
255
+ while (true) {
256
+ const room: i32 = conn.space()
257
+ const want: i32 = room < toI32(this.scratch.length) ? room : toI32(this.scratch.length)
258
+ if (want <= 0 || conn.state === TLS_RECORD_STATE_FAILED || conn.peerClosed) {
259
+ break
260
+ }
261
+ const n: i32 = netRead(fd, this.scratch, TLS_TCP_FROM, want)
262
+ if (n <= 0) {
263
+ if (n === 0) {
264
+ conn.end()
265
+ } else if (n !== TLS_RECORD_AGAIN) {
266
+ conn.abort()
267
+ }
268
+ break
269
+ }
270
+ conn.receive(this.scratch, TLS_TCP_FROM, n)
271
+ }
272
+ return this.flush(slot)
273
+ }
274
+
275
+ /** The socket of `slot` is writable: sends what the connection holds. Answers its `interest`. */
276
+ writable(slot: i32): i32 {
277
+ if (!this.holds(slot)) {
278
+ return TLS_RECORD_DONE
279
+ }
280
+ return this.flush(slot)
281
+ }
282
+
283
+ /** Writes the connection's output until the socket would block or it is all sent, and answers its `interest`. */
284
+ flush(slot: i32): i32 {
285
+ const fd: i32 = this.fds[slot]
286
+ const conn: TlsRecordServer = this.connections[slot]
287
+ while (conn.outputEnd > conn.outputStart) {
288
+ const n: i32 = netWrite(fd, conn.output, conn.outputStart, conn.outputEnd - conn.outputStart)
289
+ if (n <= 0) {
290
+ if (n !== TLS_RECORD_AGAIN) {
291
+ conn.abort()
292
+ }
293
+ break
294
+ }
295
+ conn.consume(n)
296
+ }
297
+ return conn.interest()
298
+ }
299
+
300
+ /** What `slot`'s connection wants now, without touching the socket; `TLS_RECORD_DONE` for a free slot. */
301
+ interest(slot: i32): i32 {
302
+ return this.holds(slot) ? this.connections[slot].interest() : TLS_RECORD_DONE
303
+ }
304
+
305
+ /** What `slot`'s CertificateVerify signs, while its `interest` says `TLS_RECORD_SIGN`; `null` otherwise. */
306
+ signatureInput(slot: i32): u8[] | null {
307
+ return this.holds(slot) ? this.connections[slot].signatureInput() : null
308
+ }
309
+
310
+ /** Hands `slot` the signature over `signatureInput(slot)`, seals the rest of its flight and sends it. Answers its `interest`. */
311
+ sign(slot: i32, signature: u8[]): i32 {
312
+ if (!this.holds(slot)) {
313
+ return TLS_RECORD_DONE
314
+ }
315
+ this.connections[slot].sign(signature)
316
+ return this.flush(slot)
317
+ }
318
+
319
+ /**
320
+ * Signs `slot`'s CertificateVerify with the P-256 private key `key` and
321
+ * hands it over, as `sign` does, through the pool's `TlsP256Signer`, so a
322
+ * handshake's signature keeps nothing. The key is borrowed: the caller made
323
+ * the `Secret`, and wipes it. A malformed key fails the connection with
324
+ * `internal_error`.
325
+ */
326
+ signP256(slot: i32, key: Secret<u8[]>): i32 {
327
+ const input: u8[] | null = this.signatureInput(slot)
328
+ if (input === null) {
329
+ return this.interest(slot)
330
+ }
331
+ const signature: u8[] | null = this.signer.sign(key, input)
332
+ if (signature === null) {
333
+ this.connections[slot].fail(TLS_ALERT_INTERNAL_ERROR)
334
+ return this.flush(slot)
335
+ }
336
+ return this.sign(slot, signature)
337
+ }
338
+
339
+ /**
340
+ * Application data from `slot`'s client into `buf[off .. off + len)`: the
341
+ * count, 0 at the end of its stream, -11 when nothing is waiting, -104 once
342
+ * the connection has failed, as `TlsRecordServer.read` says. Reading makes
343
+ * room, and the records that were waiting for it may be handled and
344
+ * answered (a KeyUpdate), which is sent here; the connection may then want
345
+ * the socket read again, so re-arm it with `interest(slot)`.
346
+ */
347
+ read(slot: i32, buf: u8[], off: i32, len: i32): i32 {
348
+ if (!this.holds(slot)) {
349
+ return TLS_RECORD_INVALID
350
+ }
351
+ const n: i32 = this.connections[slot].read(buf, off, len)
352
+ this.flush(slot)
353
+ return n
354
+ }
355
+
356
+ /**
357
+ * Application data for `slot`'s client, `data[off .. off + len)`: seals as
358
+ * much as fits and sends what the socket takes, and answers how much was
359
+ * taken, -11 when nothing fits yet or the handshake is under way, -32 once
360
+ * the server has closed, as `TlsRecordServer.write` says.
361
+ */
362
+ write(slot: i32, data: u8[], off: i32, len: i32): i32 {
363
+ if (!this.holds(slot)) {
364
+ return TLS_RECORD_INVALID
365
+ }
366
+ const n: i32 = this.connections[slot].write(data, off, len)
367
+ this.flush(slot)
368
+ return n
369
+ }
370
+
371
+ /** Sends a KeyUpdate on `slot`, asking the client for one too when `requestPeer`; answers as `TlsRecordServer.keyUpdate`. */
372
+ keyUpdate(slot: i32, requestPeer: boolean): i32 {
373
+ if (!this.holds(slot)) {
374
+ return TLS_RECORD_INVALID
375
+ }
376
+ const r: i32 = this.connections[slot].keyUpdate(requestPeer)
377
+ this.flush(slot)
378
+ return r
379
+ }
380
+
381
+ /**
382
+ * Closes the server's side of `slot`'s connection (§6.1): a `close_notify`
383
+ * after whatever is already written, sent as the socket takes it. The slot
384
+ * stays the program's until its interest says `TLS_RECORD_DONE` — every
385
+ * byte is sent — and the program calls `close`. Answers the interest.
386
+ */
387
+ shutdown(slot: i32): i32 {
388
+ if (!this.holds(slot)) {
389
+ return TLS_RECORD_DONE
390
+ }
391
+ this.connections[slot].close()
392
+ return this.flush(slot)
393
+ }
394
+
395
+ /**
396
+ * Ends `slot`'s connection and frees the slot at once: a `close_notify`
397
+ * unless the connection already failed or closed, one attempt to send what
398
+ * is left, every key wiped, and the descriptor closed, which also takes it
399
+ * out of every loop. A program that must know every byte and the
400
+ * `close_notify` arrived calls `shutdown` instead, and `close` once the
401
+ * slot's interest says `TLS_RECORD_DONE`.
402
+ */
403
+ close(slot: i32): void {
404
+ if (!this.holds(slot)) {
405
+ return
406
+ }
407
+ const conn: TlsRecordServer = this.connections[slot]
408
+ conn.close()
409
+ this.flush(slot)
410
+ netClose(this.fds[slot])
411
+ this.fds[slot] = TLS_TCP_FREE
412
+ conn.wipeKeys()
413
+ }
414
+ }