@amritk/nish-x86_64-linux 0.16.0 → 0.17.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 (55) 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 +30 -7
  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-wasm.c +1 -0
  11. package/runtime/runtime.c +46 -8
  12. package/runtime/shim.mjs +114 -5
  13. package/scripts/build.sh +1 -3
  14. package/std/README.md +166 -35
  15. package/std/crypto/aes.ts +51 -8
  16. package/std/crypto/base64url.ts +25 -12
  17. package/std/crypto/chacha20poly1305.ts +43 -29
  18. package/std/crypto/ct.ts +6 -1
  19. package/std/crypto/hkdf.ts +364 -3
  20. package/std/crypto/hmac.ts +307 -7
  21. package/std/crypto/p256.ts +148 -27
  22. package/std/crypto/sha1.ts +147 -0
  23. package/std/crypto/sha256.ts +3 -2
  24. package/std/crypto/x25519.ts +88 -36
  25. package/std/crypto/x509.ts +130 -47
  26. package/std/net/hpack.ts +1094 -0
  27. package/std/net/http-fields.ts +604 -0
  28. package/std/net/http1-server.ts +1389 -0
  29. package/std/net/http1.ts +1492 -0
  30. package/std/net/http2-frame.ts +745 -0
  31. package/std/net/http2-tls.ts +216 -0
  32. package/std/net/http2.ts +1433 -0
  33. package/std/net/http3-frame.ts +457 -0
  34. package/std/net/http3-server.ts +905 -0
  35. package/std/net/http3.ts +2145 -0
  36. package/std/net/qpack.ts +1163 -0
  37. package/std/net/quic-conn-ack.ts +206 -0
  38. package/std/net/quic-conn-cid.ts +425 -0
  39. package/std/net/quic-conn-params.ts +392 -0
  40. package/std/net/quic-datagram.ts +130 -0
  41. package/std/net/quic-frame.ts +1206 -0
  42. package/std/net/quic-listener.ts +653 -0
  43. package/std/net/quic-packet.ts +1474 -0
  44. package/std/net/quic-recovery.ts +849 -0
  45. package/std/net/quic-stream.ts +1603 -0
  46. package/std/net/quic.ts +3214 -0
  47. package/std/net/tls/codec.ts +1089 -0
  48. package/std/net/tls/record-server.ts +1173 -0
  49. package/std/net/tls/record.ts +621 -0
  50. package/std/net/tls/schedule.ts +429 -0
  51. package/std/net/tls-tcp.ts +410 -0
  52. package/std/net/tls.ts +1368 -0
  53. package/std/net/websocket.ts +823 -0
  54. package/std/net/webtransport.ts +1334 -0
  55. package/std/secret.ts +62 -0
@@ -0,0 +1,410 @@
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 { TlsServer, TlsServerConfig, tlsSignEcdsaP256 } 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
+ /**
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.
109
+ */
110
+ keys: u8[][]
111
+ /**
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.
115
+ */
116
+ randoms: u8[][]
117
+ /** The listening socket, which the program made with `tcpListen` and still owns. */
118
+ listener: i32 = -1
119
+
120
+ /**
121
+ * A pool of `size` slots, at least one, serving `config` on `listener`, a
122
+ * socket `tcpListen` made. Every slot's buffers are allocated here.
123
+ */
124
+ constructor(config: TlsServerConfig, listener: i32, size: i32) {
125
+ this.config = config
126
+ this.listener = listener
127
+ this.connections = []
128
+ this.fds = []
129
+ this.keys = []
130
+ 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
+ }
142
+ this.scratch = new Array<u8>(TLS_MAX_RECORD)
143
+ this.peer = new Array<u8>(TLS_TCP_ADDRESS_SIZE)
144
+ }
145
+
146
+ /** How many slots the pool has. */
147
+ size(): i32 {
148
+ return toI32(this.fds.length)
149
+ }
150
+
151
+ /** How many slots hold a connection. */
152
+ busy(): i32 {
153
+ let n: i32 = 0
154
+ for (const fd of this.fds) {
155
+ if (fd >= 0) {
156
+ n = n + 1
157
+ }
158
+ }
159
+ return n
160
+ }
161
+
162
+ /** Whether `slot` is a slot of this pool that holds a connection. */
163
+ holds(slot: i32): boolean {
164
+ return slot >= 0 && slot < toI32(this.fds.length) && this.fds[slot] >= 0
165
+ }
166
+
167
+ /** The descriptor of `slot`, for `pollAdd`; -1 for a free or unknown slot. */
168
+ fd(slot: i32): i32 {
169
+ return this.holds(slot) ? this.fds[slot] : -1
170
+ }
171
+
172
+ /**
173
+ * The connection in `slot`: its state, its handshake (`tls`: suite, ALPN,
174
+ * server name) and the alerts sent and received. A free slot answers the
175
+ * last connection it held.
176
+ */
177
+ connection(slot: i32): TlsRecordServer {
178
+ const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : 0
179
+ return this.connections[at]
180
+ }
181
+
182
+ /**
183
+ * Accepts the next waiting connection into a free slot and starts its
184
+ * handshake with `serverRandom` and `ephemeralPrivate` (32 bytes each),
185
+ * and answers the slot. The peer's address is in `peer`. Both arrays are
186
+ * copied into the slot, which reads them only when the ClientHello
187
+ * arrives, and the key's array is then wiped, so the program may refill
188
+ * one buffer of each for every call. Answers -11 when no connection is
189
+ * waiting, `TLS_TCP_POOL_FULL` when one was but every slot is busy (it is
190
+ * closed), or `tcpAccept`'s own failure; and `TLS_RECORD_INVALID` (-22),
191
+ * before any connection is taken, for a random or key that is not 32
192
+ * bytes. On any answer but a slot both arrays are left as they were.
193
+ */
194
+ accept(serverRandom: u8[], ephemeralPrivate: u8[]): i32 {
195
+ if (
196
+ toI32(serverRandom.length) !== TLS_TCP_KEY_SIZE ||
197
+ toI32(ephemeralPrivate.length) !== TLS_TCP_KEY_SIZE
198
+ ) {
199
+ return TLS_RECORD_INVALID
200
+ }
201
+ const fd: i32 = tcpAccept(this.listener, this.peer)
202
+ if (fd < 0) {
203
+ return fd
204
+ }
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
+ }
211
+ }
212
+ if (slot < 0) {
213
+ netClose(fd)
214
+ return TLS_TCP_POOL_FULL
215
+ }
216
+ this.fds[slot] = fd
217
+ const random: u8[] = this.randoms[slot]
218
+ const key: u8[] = this.keys[slot]
219
+ for (
220
+ let k: i32 = 0;
221
+ k < TLS_TCP_KEY_SIZE && k < toI32(random.length) && k < toI32(serverRandom.length);
222
+ k++
223
+ ) {
224
+ random[k] = serverRandom[k]
225
+ }
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)
236
+ conn.start(conn.tls)
237
+ return slot
238
+ }
239
+
240
+ /**
241
+ * The socket of `slot` is readable: reads it until it would block or the
242
+ * connection has no room, handing each read to the connection, then sends
243
+ * what that produced. Answers the connection's `interest`; a free slot
244
+ * answers `TLS_RECORD_DONE`.
245
+ */
246
+ readable(slot: i32): i32 {
247
+ if (!this.holds(slot)) {
248
+ return TLS_RECORD_DONE
249
+ }
250
+ const fd: i32 = this.fds[slot]
251
+ const conn: TlsRecordServer = this.connections[slot]
252
+ while (true) {
253
+ const room: i32 = conn.space()
254
+ const want: i32 = room < toI32(this.scratch.length) ? room : toI32(this.scratch.length)
255
+ if (want <= 0 || conn.state === TLS_RECORD_STATE_FAILED || conn.peerClosed) {
256
+ break
257
+ }
258
+ const n: i32 = netRead(fd, this.scratch, TLS_TCP_FROM, want)
259
+ if (n <= 0) {
260
+ if (n === 0) {
261
+ conn.end()
262
+ } else if (n !== TLS_RECORD_AGAIN) {
263
+ conn.abort()
264
+ }
265
+ break
266
+ }
267
+ conn.receive(this.scratch, TLS_TCP_FROM, n)
268
+ }
269
+ return this.flush(slot)
270
+ }
271
+
272
+ /** The socket of `slot` is writable: sends what the connection holds. Answers its `interest`. */
273
+ writable(slot: i32): i32 {
274
+ if (!this.holds(slot)) {
275
+ return TLS_RECORD_DONE
276
+ }
277
+ return this.flush(slot)
278
+ }
279
+
280
+ /** Writes the connection's output until the socket would block or it is all sent, and answers its `interest`. */
281
+ flush(slot: i32): i32 {
282
+ const fd: i32 = this.fds[slot]
283
+ const conn: TlsRecordServer = this.connections[slot]
284
+ while (conn.outputEnd > conn.outputStart) {
285
+ const n: i32 = netWrite(fd, conn.output, conn.outputStart, conn.outputEnd - conn.outputStart)
286
+ if (n <= 0) {
287
+ if (n !== TLS_RECORD_AGAIN) {
288
+ conn.abort()
289
+ }
290
+ break
291
+ }
292
+ conn.consume(n)
293
+ }
294
+ return conn.interest()
295
+ }
296
+
297
+ /** What `slot`'s connection wants now, without touching the socket; `TLS_RECORD_DONE` for a free slot. */
298
+ interest(slot: i32): i32 {
299
+ return this.holds(slot) ? this.connections[slot].interest() : TLS_RECORD_DONE
300
+ }
301
+
302
+ /** What `slot`'s CertificateVerify signs, while its `interest` says `TLS_RECORD_SIGN`; `null` otherwise. */
303
+ signatureInput(slot: i32): u8[] | null {
304
+ return this.holds(slot) ? this.connections[slot].signatureInput() : null
305
+ }
306
+
307
+ /** Hands `slot` the signature over `signatureInput(slot)`, seals the rest of its flight and sends it. Answers its `interest`. */
308
+ sign(slot: i32, signature: u8[]): i32 {
309
+ if (!this.holds(slot)) {
310
+ return TLS_RECORD_DONE
311
+ }
312
+ this.connections[slot].sign(signature)
313
+ return this.flush(slot)
314
+ }
315
+
316
+ /**
317
+ * 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
320
+ * `internal_error`.
321
+ */
322
+ signP256(slot: i32, key: Secret<u8[]>): i32 {
323
+ const input: u8[] | null = this.signatureInput(slot)
324
+ if (input === null) {
325
+ return this.interest(slot)
326
+ }
327
+ const signature: u8[] | null = tlsSignEcdsaP256(key, input)
328
+ if (signature === null) {
329
+ this.connections[slot].fail(TLS_ALERT_INTERNAL_ERROR)
330
+ return this.flush(slot)
331
+ }
332
+ return this.sign(slot, signature)
333
+ }
334
+
335
+ /**
336
+ * Application data from `slot`'s client into `buf[off .. off + len)`: the
337
+ * count, 0 at the end of its stream, -11 when nothing is waiting, -104 once
338
+ * the connection has failed, as `TlsRecordServer.read` says. Reading makes
339
+ * room, and the records that were waiting for it may be handled and
340
+ * answered (a KeyUpdate), which is sent here; the connection may then want
341
+ * the socket read again, so re-arm it with `interest(slot)`.
342
+ */
343
+ read(slot: i32, buf: u8[], off: i32, len: i32): i32 {
344
+ if (!this.holds(slot)) {
345
+ return TLS_RECORD_INVALID
346
+ }
347
+ const n: i32 = this.connections[slot].read(buf, off, len)
348
+ this.flush(slot)
349
+ return n
350
+ }
351
+
352
+ /**
353
+ * Application data for `slot`'s client, `data[off .. off + len)`: seals as
354
+ * much as fits and sends what the socket takes, and answers how much was
355
+ * taken, -11 when nothing fits yet or the handshake is under way, -32 once
356
+ * the server has closed, as `TlsRecordServer.write` says.
357
+ */
358
+ write(slot: i32, data: u8[], off: i32, len: i32): i32 {
359
+ if (!this.holds(slot)) {
360
+ return TLS_RECORD_INVALID
361
+ }
362
+ const n: i32 = this.connections[slot].write(data, off, len)
363
+ this.flush(slot)
364
+ return n
365
+ }
366
+
367
+ /** Sends a KeyUpdate on `slot`, asking the client for one too when `requestPeer`; answers as `TlsRecordServer.keyUpdate`. */
368
+ keyUpdate(slot: i32, requestPeer: boolean): i32 {
369
+ if (!this.holds(slot)) {
370
+ return TLS_RECORD_INVALID
371
+ }
372
+ const r: i32 = this.connections[slot].keyUpdate(requestPeer)
373
+ this.flush(slot)
374
+ return r
375
+ }
376
+
377
+ /**
378
+ * Closes the server's side of `slot`'s connection (§6.1): a `close_notify`
379
+ * after whatever is already written, sent as the socket takes it. The slot
380
+ * stays the program's until its interest says `TLS_RECORD_DONE` — every
381
+ * byte is sent — and the program calls `close`. Answers the interest.
382
+ */
383
+ shutdown(slot: i32): i32 {
384
+ if (!this.holds(slot)) {
385
+ return TLS_RECORD_DONE
386
+ }
387
+ this.connections[slot].close()
388
+ return this.flush(slot)
389
+ }
390
+
391
+ /**
392
+ * Ends `slot`'s connection and frees the slot at once: a `close_notify`
393
+ * unless the connection already failed or closed, one attempt to send what
394
+ * is left, every key wiped, and the descriptor closed, which also takes it
395
+ * out of every loop. A program that must know every byte and the
396
+ * `close_notify` arrived calls `shutdown` instead, and `close` once the
397
+ * slot's interest says `TLS_RECORD_DONE`.
398
+ */
399
+ close(slot: i32): void {
400
+ if (!this.holds(slot)) {
401
+ return
402
+ }
403
+ const conn: TlsRecordServer = this.connections[slot]
404
+ conn.close()
405
+ this.flush(slot)
406
+ netClose(this.fds[slot])
407
+ this.fds[slot] = TLS_TCP_FREE
408
+ conn.wipeKeys()
409
+ }
410
+ }