@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
package/std/net/quic.ts CHANGED
@@ -66,13 +66,23 @@
66
66
  * holds back everything ack-eliciting but a probe; an ACK always goes.
67
67
  * PATH_RESPONSE and DATAGRAM are never sent again (RFC 9000 §13.3, RFC 9221
68
68
  * §5.2). Pacing, and GSO batching, are the carrier's:
69
- * `nish/net/quic-listener`.
69
+ * `nish/net/quic-listener`. Once the 1-RTT keys are in, one Application
70
+ * Data packet number in every 128 to 1024 is skipped, at a distance drawn
71
+ * from the ID seed, and the last `QUIC_CONN_SKIP_RING` are remembered: an
72
+ * ACK of one acknowledges a packet never sent, an optimistic ACK meant to
73
+ * widen the congestion window, and is PROTOCOL_VIOLATION (RFC 9000 §21.4).
70
74
  *
71
75
  * **Nothing allocated per packet** (QUIC-3). A connection is a slot: every
72
- * buffer it uses — the packet record, the stream buffers, the CRYPTO
73
- * reassembly, the connection-ID table, the datagram rings — is made by the
74
- * constructor and reused, and `reset` makes the slot a new connection for
75
- * the next peer. A packet is opened (`receiveWindow`) and a datagram built
76
+ * buffer it uses — the packet record, the CRYPTO reassembly, the
77
+ * connection-ID table, the datagram rings — is made by the constructor and
78
+ * reused, and `reset` makes the slot a new connection for the next peer. The
79
+ * stream buffers are borrowed instead (`QuicStreamPools` in
80
+ * `nish/net/quic-stream`): a receive side takes one on its first byte and a
81
+ * send side on its first write, each goes back zeroed once the stream is done
82
+ * with it, and a server's connections share one set of pools, so idle slots
83
+ * hold none. A standalone connection makes private pools as large as all its
84
+ * slots at its first packet. The pools are primed before a packet's arena
85
+ * block and never touched inside it. A packet is opened (`receiveWindow`) and a datagram built
76
86
  * and sealed (`takeDatagramInto`) inside a `using a = arena()` block, so the
77
87
  * AEAD's and the header protection's temporaries go when the block ends, and
78
88
  * the compiler refuses the block if anything in it could store one. The
@@ -87,7 +97,7 @@
87
97
  *
88
98
  * **Time.** The connection has no clock: `receive`, `takeDatagram` and
89
99
  * `handleTimer` take the caller's monotonic time in milliseconds, and
90
- * `deadline()` says when `handleTimer` is next due. Three things run on it.
100
+ * `deadline()` says when `handleTimer` is next due. Four things run on it.
91
101
  * The idle timeout (RFC 9000 §10.1) is the smaller of the two sides'
92
102
  * `max_idle_timeout`, never under `QUIC_CONN_IDLE_FLOOR` or three probe
93
103
  * timeouts; once it passes with nothing received, the connection closes
@@ -95,7 +105,13 @@
95
105
  * timer declares packets lost by the time threshold or fires the probe
96
106
  * timeout. And after a key update the previous read keys are kept for a
97
107
  * probe timeout (`recovery.probeTimeout()`), for packets the network
98
- * reordered, before the next generation's are derived (RFC 9001 §6.5).
108
+ * reordered, before the next generation's are derived (RFC 9001 §6.5). Once
109
+ * the connection has closed, by either side, its keys are kept through the
110
+ * closing or draining period, three probe timeouts (RFC 9000 §10.2), and
111
+ * wiped at its end; `deadline()` is that end, then -1. A close this side
112
+ * owes starts the period when `takeDatagram` sends it, not when `close` or
113
+ * `fail` is called, so that however long the caller waits, the close still
114
+ * goes out.
99
115
  *
100
116
  * **Key update** (RFC 9001 §6), both ways. A 1-RTT packet whose Key Phase
101
117
  * bit differs from the current one is opened with the next generation's read
@@ -108,6 +124,12 @@
108
124
  * update keeps nothing in the arena; each still costs two key derivations of
109
125
  * work, so a connection takes at most `QUIC_CONN_MAX_KEY_UPDATES` updates the
110
126
  * client starts and closes on the next with KEY_UPDATE_ERROR (QUIC-4).
127
+ * The AEAD limits are kept (RFC 9001 §6.6): from three quarters of AES-GCM's
128
+ * confidentiality limit, 2^23 packets under one write key, the server starts
129
+ * an update itself, and closes with AEAD_LIMIT_REACHED, sealing nothing more,
130
+ * if it reaches the limit with none possible; past the integrity limit of
131
+ * packets that fail to authenticate over the connection, 2^52 for AES-GCM
132
+ * and 2^36 for ChaCha20-Poly1305, it closes with AEAD_LIMIT_REACHED too.
111
133
  *
112
134
  * **Sans-IO and deterministic.** No socket, no clock and no random device:
113
135
  * every random choice (the TLS server random and ephemeral key, the server's
@@ -132,24 +154,28 @@
132
154
  * packet numbers by `QUIC_ACK_MAX_RANGES`, and datagrams by their ring.
133
155
  *
134
156
  * **Secrets.** The packet keys of each level live in this connection's key
135
- * slots as long as the level does, and in `TlsServer`'s (TLS-1); so do the
136
- * 1-RTT secrets the next key generation is derived from, and the next
137
- * generation's read keys. A `Secret` may not be a field (NL2430), so they are
138
- * plain bytes; `secureZero` wipes each level's key, IV and header-protection
139
- * key, and its AES schedules with ordinary stores, when the level is
140
- * discarded, each generation's key, IV and secret when a key update
141
- * replaces it, and `release()` (and `reset`) the rest — the 1-RTT keys, the
142
- * key-update secrets, the traffic secrets `TlsServer` holds, its ephemeral
143
- * key and the connection-ID seed. What no wipe reaches (`aesKeyInto`'s
144
- * working copy of a schedule) is recorded as QUIC-2 in
145
- * `docs/security/quic.md`, with #430, the follow-up that moves these structs
146
- * onto `nish:secret`.
157
+ * slots as long as the level does; so do the 1-RTT secrets the next key
158
+ * generation is derived from, and the next generation's read keys. The
159
+ * traffic secrets and the ephemeral key `TlsServer` holds are `Secret`
160
+ * fields its `wipe()` reaches (TLS-1): `afterTls` takes each level's secrets
161
+ * out of them once, into this connection's plain slots, and zeroes them
162
+ * through `wipeSecret`. The slots are plain bytes, wiped with stores no
163
+ * optimiser removes (`secureZero`, and `aesKeyWipe` for an AES schedule):
164
+ * each level's key, IV, header-protection key and schedules when the level
165
+ * is discarded; each generation's key, IV, schedule and secret when a key
166
+ * update replaces it; and the rest — the 1-RTT keys, the key-update secrets,
167
+ * whatever `TlsServer` still holds and the connection-ID seed — by
168
+ * `release()`, `reset`, the idle timeout, and the end of the closing or
169
+ * draining period (RFC 9000 §10.2), three probe timeouts after this side's
170
+ * CONNECTION_CLOSE went out or the client's arrived.
171
+ * `docs/security/quic.md` records what is wiped where as QUIC-2.
147
172
  *
148
173
  * Written from RFC 9000, RFC 9001 and RFC 9221, in this module's own
149
174
  * structure; nothing here is ported from another implementation. Private
150
175
  * names carry the `quicConn` prefix (`docs/wp26-stdlib.md` §3e).
151
176
  */
152
177
  import { timingSafeEqualAt } from "nish/crypto/ct"
178
+ import { AesKey, aesKeyWipe } from "nish/crypto/aes"
153
179
  import { HkdfScratch } from "nish/crypto/hkdf"
154
180
  import { HmacSha256Scratch, hmacSha256 } from "nish/crypto/hmac"
155
181
  import {
@@ -164,6 +190,7 @@ import {
164
190
  QUIC_PACKET_INITIAL,
165
191
  QUIC_PACKET_OK,
166
192
  QUIC_PACKET_SHORT,
193
+ QUIC_PACKET_ZERO_RTT,
167
194
  QuicHeader,
168
195
  QuicKeys,
169
196
  QuicKeysSlot,
@@ -181,6 +208,7 @@ import {
181
208
  quicUnprotectHeader,
182
209
  } from "nish/net/quic-packet"
183
210
  import {
211
+ QUIC_ERROR_AEAD_LIMIT_REACHED,
184
212
  QUIC_ERROR_APPLICATION,
185
213
  QUIC_ERROR_CRYPTO,
186
214
  QUIC_ERROR_CRYPTO_BUFFER_EXCEEDED,
@@ -260,13 +288,22 @@ import {
260
288
  TLS_LEVEL_APPLICATION,
261
289
  TLS_LEVEL_HANDSHAKE,
262
290
  TLS_LEVEL_INITIAL,
291
+ TLS_SECRET_CLIENT_APPLICATION,
292
+ TLS_SECRET_CLIENT_HANDSHAKE,
293
+ TLS_SECRET_SERVER_APPLICATION,
294
+ TLS_SECRET_SERVER_HANDSHAKE,
263
295
  TLS_STATE_CONNECTED,
264
296
  TLS_STATE_WAIT_CLIENT_HELLO,
265
297
  TLS_STATE_WAIT_SIGNATURE,
298
+ TlsSecretPool,
266
299
  TlsServer,
267
300
  TlsServerConfig,
301
+ tlsHeldClone,
302
+ tlsHeldCopyIn,
303
+ tlsHeldCopyOver,
268
304
  } from "nish/net/tls"
269
305
  import { TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256 } from "nish/net/tls/schedule"
306
+ import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
270
307
 
271
308
  // ---- Sizes ----------------------------------------------------------------------
272
309
 
@@ -299,10 +336,11 @@ export const QUIC_CONN_MAX_STREAM_DATA: i64 = 1048576
299
336
  /** The most streams of one kind a configuration may let the client open at once, or open itself. */
300
337
  export const QUIC_CONN_MAX_STREAMS: i64 = 1024
301
338
  /**
302
- * The most bytes of stream buffer a configuration may make each connection
339
+ * The most bytes of stream buffer a configuration may let each connection
303
340
  * hold: every stream slot (`maxStreamsBidi + maxStreamsUni + localStreams`)
304
- * has `maxStreamData` bytes each way, made when the connection is, so a
305
- * configuration past 64 MiB of them is refused rather than allocated.
341
+ * may borrow `maxStreamData` bytes each way, and the pools are sized so all
342
+ * of them can at once, so a configuration past 64 MiB of them is refused
343
+ * rather than allowed to allocate it.
306
344
  */
307
345
  export const QUIC_CONN_MAX_STREAM_BUFFERS: i64 = 67108864
308
346
  /** The length of the configuration's static keys: the stateless reset key and the Retry token key. */
@@ -337,6 +375,37 @@ export const QUIC_CONN_PACKET_CONTROL: i32 = 4
337
375
  * a probe timeout between two of them.
338
376
  */
339
377
  export const QUIC_CONN_MAX_KEY_UPDATES: i32 = 64
378
+ /**
379
+ * AES-GCM's confidentiality limit (RFC 9001 §6.6): how many packets one
380
+ * 1-RTT write key seals. The server starts a key update at three quarters of
381
+ * it, and closes the connection with AEAD_LIMIT_REACHED, sending nothing more
382
+ * under the key, if it reaches it with no update possible. ChaCha20-Poly1305
383
+ * has no such limit.
384
+ */
385
+ export const QUIC_CONN_CONFIDENTIALITY_LIMIT: i64 = 8388608
386
+ /**
387
+ * The integrity limits (RFC 9001 §6.6): how many packets may fail to
388
+ * authenticate over a connection's life, across every key, before it closes
389
+ * with AEAD_LIMIT_REACHED. AES-GCM's is 2^52, ChaCha20-Poly1305's 2^36.
390
+ */
391
+ export const QUIC_CONN_INTEGRITY_LIMIT_GCM: i64 = 4503599627370496
392
+ export const QUIC_CONN_INTEGRITY_LIMIT_CHACHA: i64 = 68719476736
393
+ /**
394
+ * How many skipped packet numbers the server remembers (RFC 9000 §21.4): an
395
+ * ACK of any of the last this many closes the connection as an optimistic
396
+ * acknowledgement. One is skipped every `QUIC_CONN_SKIP_MIN` to
397
+ * `QUIC_CONN_SKIP_MIN + QUIC_CONN_SKIP_SPREAD - 1` packets, so the ring
398
+ * reaches back at least 7 × 129 = 903 packets.
399
+ */
400
+ export const QUIC_CONN_SKIP_RING: i32 = 8
401
+ export const QUIC_CONN_SKIP_MIN: i64 = 128
402
+ export const QUIC_CONN_SKIP_SPREAD: i64 = 897
403
+ /**
404
+ * The HMAC counter the skip distances are drawn under, past any connection
405
+ * ID's sequence number (`QUIC_CONN_MAX_ISSUED_CIDS`), so the same seed gives
406
+ * both without either telling anything about the other.
407
+ */
408
+ const QUIC_CONN_SKIP_SEQUENCE: i64 = QUIC_MAX_VARINT + 1
340
409
  /**
341
410
  * How many ack-eliciting datagrams a probe timeout sends in the Initial and
342
411
  * Handshake spaces (RFC 9002 §6.2.4 allows up to two). The server's first
@@ -923,7 +992,14 @@ const quicConnAead = (suite: i32): i32 => {
923
992
  return QUIC_AEAD_AES_128_GCM
924
993
  }
925
994
 
926
- /** Wipes a level's key, IV and header-protection key; the expanded AES schedules are out of reach (QUIC-2). */
995
+ /** Empties a ring of skipped packet numbers. */
996
+ const quicConnForgetSkips = (skipped: i64[]): void => {
997
+ for (let k: i32 = 0; k < toI32(skipped.length); k += 1) {
998
+ skipped[k] = -1
999
+ }
1000
+ }
1001
+
1002
+ /** Wipes a level's key, IV and header-protection key; `QuicKeysSlot.wipe` takes the header-protection schedule. */
927
1003
  const quicConnWipeKeys = (keys: QuicKeys | null): void => {
928
1004
  quicConnWipePacketKey(keys)
929
1005
  if (keys !== null) {
@@ -932,14 +1008,19 @@ const quicConnWipeKeys = (keys: QuicKeys | null): void => {
932
1008
  }
933
1009
 
934
1010
  /**
935
- * Wipes a key generation's packet key and IV that a key update replaced. The
936
- * header-protection key is left alone: every generation shares it (RFC 9001
937
- * §6), so it goes only when the level does.
1011
+ * Wipes a key generation's packet key, IV and the packet key's AES schedule,
1012
+ * which a key update replaced. The header-protection key and its schedule are
1013
+ * left alone: every generation shares them (RFC 9001 §6), so they go only
1014
+ * when the level does.
938
1015
  */
939
1016
  const quicConnWipePacketKey = (keys: QuicKeys | null): void => {
940
1017
  if (keys !== null) {
941
1018
  secureZero(keys.key)
942
1019
  secureZero(keys.iv)
1020
+ const packetAes: AesKey | null = keys.packetAes
1021
+ if (packetAes !== null) {
1022
+ aesKeyWipe(packetAes)
1023
+ }
943
1024
  }
944
1025
  }
945
1026
 
@@ -999,26 +1080,17 @@ const quicConnRefill = (to: u8[], from: u8[], at: i32, length: i32): void => {
999
1080
  }
1000
1081
  }
1001
1082
 
1002
- /**
1003
- * Secret `index` (0 or 1) of a direction's pool, of the length `aead`'s hash
1004
- * gives: what a key update derives the next traffic secret into.
1005
- */
1006
- const quicConnSecretAt = (pool: u8[][], aead: i32, index: i32): u8[] => {
1007
- let wanted: i32 = index === 1 ? 1 : 0
1008
- if (aead === QUIC_AEAD_AES_256_GCM) {
1009
- wanted = wanted + 2
1010
- }
1011
- const at: i32 = wanted < toI32(pool.length) ? wanted : 0
1012
- return pool[at]
1083
+ /** Zeroes the bytes a `Secret` field holds, for `exposeWith` to run outside the connection's `wipe()`; answers `answer`. */
1084
+ const quicConnZeroHeld = (held: u8[], answer: i32): i32 => {
1085
+ wipe(held)
1086
+ return answer
1013
1087
  }
1014
1088
 
1015
- /** Two secrets of SHA-256's length and two of SHA-384's, for one direction's key updates. */
1016
- const quicConnSecretPool = (): u8[][] => [
1017
- new Array<u8>(32),
1018
- new Array<u8>(32),
1019
- new Array<u8>(48),
1020
- new Array<u8>(48),
1021
- ]
1089
+ /** Copies the x25519 private key, `entropy[32 .. 64)`, into the held key, for `exposeWith`; answers 0. */
1090
+ const quicConnEphemeralIn = (held: u8[], entropy: u8[]): i32 => {
1091
+ quicCidCopy(held, entropy, 32, 32)
1092
+ return 0
1093
+ }
1022
1094
 
1023
1095
  /** Whether the configuration's limits are ones this module can honour. */
1024
1096
  const quicConnConfigFits = (config: QuicServerConfig): boolean =>
@@ -1063,7 +1135,8 @@ export class QuicConnection {
1063
1135
  /** For a transport error: the type of the frame that caused it, or 0. */
1064
1136
  errorFrameType: i64 = 0
1065
1137
  serverRandom: u8[]
1066
- ephemeralPrivate: u8[]
1138
+ /** The x25519 private key `begin` takes from the entropy, until the handshake hands it to `TlsServer`. */
1139
+ readonly ephemeral: Secret<u8[]>
1067
1140
  cidSeed: u8[]
1068
1141
  /** The DCID of the client's first Initial, which the Initial keys come from: its first `originalDcidLength` bytes. */
1069
1142
  originalDcid: u8[]
@@ -1087,12 +1160,19 @@ export class QuicConnection {
1087
1160
  initialClient: u8[]
1088
1161
  initialServer: u8[]
1089
1162
  /**
1090
- * The 1-RTT secrets a key update derives, two of each hash's length a
1091
- * direction (SHA-256's first, then SHA-384's), used in turn: the next
1092
- * generation's goes in the one the current generation's is not in.
1093
- */
1094
- writeSecrets: u8[][]
1095
- readSecrets: u8[][]
1163
+ * The 1-RTT traffic secrets (RFC 9001 §6.1), one set of SHA-256's length
1164
+ * and one of SHA-384's, the one the suite's hash picks in use: the
1165
+ * current read secret, the next generation's, and the current write
1166
+ * secret, which a key update ratchets in place. The write side keeps no
1167
+ * next secret, because the server derives its next write keys only when
1168
+ * it moves to them.
1169
+ */
1170
+ readonly readSecret256: Secret<u8[]>
1171
+ readonly nextReadSecret256: Secret<u8[]>
1172
+ readonly writeSecret256: Secret<u8[]>
1173
+ readonly readSecret384: Secret<u8[]>
1174
+ readonly nextReadSecret384: Secret<u8[]>
1175
+ readonly writeSecret384: Secret<u8[]>
1096
1176
  /** A connection ID being issued and its stateless reset token, before the table copies them. */
1097
1177
  cidScratch: u8[]
1098
1178
  tokenScratch: u8[]
@@ -1136,14 +1216,10 @@ export class QuicConnection {
1136
1216
  /** When the idle timer last restarted (RFC 9000 §10.1), or -1 before the first packet. */
1137
1217
  idleSince: i64 = -1
1138
1218
  /**
1139
- * The 1-RTT secrets of the current generation, the next generation's read
1140
- * secret, and its read keys (RFC 9001 §6.1, §6.3). `otherReadKeys` holds
1141
- * the next generation's while `otherIsNext`, and for a probe timeout after
1142
- * an update the previous one's, for reordered packets (§6.5).
1219
+ * The other read keys of the Application Data space (RFC 9001 §6.3):
1220
+ * the next generation's while `otherIsNext`, and for a probe timeout
1221
+ * after an update the previous one's, for reordered packets (§6.5).
1143
1222
  */
1144
- appReadSecret: u8[]
1145
- appWriteSecret: u8[]
1146
- nextReadSecret: u8[]
1147
1223
  otherReadKeys: QuicKeys | null = null
1148
1224
  /** The lowest and highest packet numbers opened with the current read keys, or -1. */
1149
1225
  readPhaseLowest: i64 = -1
@@ -1152,10 +1228,49 @@ export class QuicConnection {
1152
1228
  writePhaseFirst: i64 = 0
1153
1229
  /** When the previous read keys go and the next are derived, or -1. */
1154
1230
  keyRetainUntil: i64 = -1
1231
+ /**
1232
+ * When a closed connection's closing or draining period ends (RFC 9000
1233
+ * §10.2), and `runClocks` wipes every key it kept for it, or -1: before
1234
+ * the close, while a close this side owes has not gone out, and once the
1235
+ * keys are wiped.
1236
+ */
1237
+ closingUntil: i64 = -1
1155
1238
  /** The packet number of a client key update read but not applied yet, or -1. */
1156
1239
  phasePending: i64 = -1
1157
1240
  /** The largest DATAGRAM frame the client takes (its `max_datagram_frame_size`), 0 for none. */
1158
1241
  peerMaxDatagramFrame: i64 = 0
1242
+ /**
1243
+ * The AEAD limits this connection keeps (RFC 9001 §6.6): packets one 1-RTT
1244
+ * write key seals, and packets that fail to authenticate over the
1245
+ * connection. `afterTls` sets them from the suite when the 1-RTT keys
1246
+ * arrive; until then they are AES-GCM's. They are fields so that a test can
1247
+ * lower them after the handshake; nothing else should set them.
1248
+ */
1249
+ confidentialityLimit: i64 = 8388608
1250
+ integrityLimit: i64 = 4503599627370496
1251
+ /** Packets sealed under the current 1-RTT write key, and packets that failed to authenticate, against those limits. */
1252
+ sealedUnderKey: i64 = 0
1253
+ authFailures: i64 = 0
1254
+ /**
1255
+ * ECN (RFC 9000 §13.4): `ecnMarking` is the carrier's to set when it sends
1256
+ * this connection's datagrams with the ECN bits of `datagramEcn`, which
1257
+ * each `takeDatagramInto` sets for the datagram it wrote: ECT(0) while
1258
+ * loss recovery's `ecnCodepoint()` asks for it, 0 otherwise and always
1259
+ * while `ecnMarking` is false. Every packet of a marked datagram is counted
1260
+ * as marked, and an ACK_ECN's counts are validated against them. `reset`
1261
+ * keeps `ecnMarking`, which is the carrier's, and clears the rest.
1262
+ */
1263
+ ecnMarking: boolean = false
1264
+ datagramEcn: i32 = 0
1265
+ /**
1266
+ * The next Application Data packet number skipped (RFC 9000 §21.4), or -1
1267
+ * before the 1-RTT keys; a test may set it to force one. `skipped` holds
1268
+ * the last `QUIC_CONN_SKIP_RING` skipped, -1 where none, `skipHead` where
1269
+ * the next goes, and `skipsDrawn` how many distances were drawn.
1270
+ */
1271
+ nextSkip: i64 = -1
1272
+ skipped: i64[]
1273
+ skipsDrawn: i64 = 0
1159
1274
  originalDcidLength: i32 = 0
1160
1275
  peerScidLength: i32 = 0
1161
1276
  pathHead: i32 = 0
@@ -1165,12 +1280,11 @@ export class QuicConnection {
1165
1280
  dropped: i32 = 0
1166
1281
  /** Key updates the client started, against `QUIC_CONN_MAX_KEY_UPDATES`. */
1167
1282
  keyUpdates: i32 = 0
1283
+ /** Where the next skipped packet number goes in `skipped`. */
1284
+ skipHead: i32 = 0
1168
1285
  /** Which of the Application Data space's two read slots, and two write slots, hold the current keys. */
1169
1286
  appReadSlot: i32 = 0
1170
1287
  appWriteSlot: i32 = 0
1171
- /** Which of `readSecrets` and `writeSecrets` (0 or 1) holds the current secret, or -1 for `TlsServer`'s own. */
1172
- readSecretCurrent: i32 = -1
1173
- writeSecretCurrent: i32 = -1
1174
1288
  /** Handshake data sent again early, against `QUIC_CONN_EARLY_RESENDS` (RFC 9002 §6.2.3). */
1175
1289
  earlyResends: i32 = 0
1176
1290
  /** The level at which the packet just read showed the client lacks the server's handshake data, or -1. */
@@ -1184,6 +1298,12 @@ export class QuicConnection {
1184
1298
  /** Whether the handshake is complete, which for a server is also confirmed (RFC 9001 §4.1.2). */
1185
1299
  handshakeComplete: boolean = false
1186
1300
  handshakeDonePending: boolean = false
1301
+ /**
1302
+ * Whether `afterTls` has taken the application traffic secrets out of
1303
+ * `TlsServer`. It takes them once: `wipeSecret` leaves them known, so a
1304
+ * second take would derive the 1-RTT keys from the zeros it left.
1305
+ */
1306
+ applicationTaken: boolean = false
1187
1307
  /** Whether the connection was set up by `acceptRetry`. */
1188
1308
  retried: boolean = false
1189
1309
  /** Whether an ack-eliciting packet went out since the last packet was received. */
@@ -1208,7 +1328,7 @@ export class QuicConnection {
1208
1328
  constructor(config: QuicServerConfig, entropy: u8[]) {
1209
1329
  this.config = config
1210
1330
  this.serverRandom = new Array<u8>(32)
1211
- this.ephemeralPrivate = new Array<u8>(32)
1331
+ this.ephemeral = secret(new Array<u8>(32))
1212
1332
  this.localScid = new Array<u8>(QUIC_CONN_CID_LENGTH)
1213
1333
  this.cidSeed = new Array<u8>(32)
1214
1334
  this.originalDcid = new Array<u8>(QUIC_MAX_CID_LENGTH)
@@ -1263,13 +1383,16 @@ export class QuicConnection {
1263
1383
  this.kdf = new HkdfScratch()
1264
1384
  this.initialClient = new Array<u8>(32)
1265
1385
  this.initialServer = new Array<u8>(32)
1266
- this.writeSecrets = quicConnSecretPool()
1267
- this.readSecrets = quicConnSecretPool()
1386
+ this.readSecret256 = secret(new Array<u8>(32))
1387
+ this.nextReadSecret256 = secret(new Array<u8>(32))
1388
+ this.writeSecret256 = secret(new Array<u8>(32))
1389
+ this.readSecret384 = secret(new Array<u8>(48))
1390
+ this.nextReadSecret384 = secret(new Array<u8>(48))
1391
+ this.writeSecret384 = secret(new Array<u8>(48))
1268
1392
  this.cidScratch = new Array<u8>(QUIC_CONN_CID_LENGTH)
1269
1393
  this.tokenScratch = new Array<u8>(QUIC_RESET_TOKEN_SIZE)
1270
- this.appReadSecret = this.none
1271
- this.appWriteSecret = this.none
1272
- this.nextReadSecret = this.none
1394
+ this.skipped = new Array<i64>(QUIC_CONN_SKIP_RING)
1395
+ quicConnForgetSkips(this.skipped)
1273
1396
  this.begin(entropy)
1274
1397
  }
1275
1398
 
@@ -1304,14 +1427,9 @@ export class QuicConnection {
1304
1427
  this.serverName = ""
1305
1428
  quicConnRefill(this.retryOriginalDcid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
1306
1429
  quicConnRefill(this.retryScid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
1307
- this.appReadSecret = this.none
1308
- this.appWriteSecret = this.none
1309
- this.nextReadSecret = this.none
1310
1430
  this.otherReadKeys = null
1311
1431
  this.appReadSlot = 0
1312
1432
  this.appWriteSlot = 0
1313
- this.readSecretCurrent = -1
1314
- this.writeSecretCurrent = -1
1315
1433
  this.earlyResends = 0
1316
1434
  this.resendLevel = -1
1317
1435
  this.now = 0
@@ -1320,6 +1438,7 @@ export class QuicConnection {
1320
1438
  this.readPhaseHighest = -1
1321
1439
  this.writePhaseFirst = 0
1322
1440
  this.keyRetainUntil = -1
1441
+ this.closingUntil = -1
1323
1442
  this.phasePending = -1
1324
1443
  this.peerMaxDatagramFrame = 0
1325
1444
  this.originalDcidLength = 0
@@ -1329,10 +1448,20 @@ export class QuicConnection {
1329
1448
  this.state = QUIC_STATE_WAIT_INITIAL
1330
1449
  this.dropped = 0
1331
1450
  this.keyUpdates = 0
1451
+ this.confidentialityLimit = QUIC_CONN_CONFIDENTIALITY_LIMIT
1452
+ this.integrityLimit = QUIC_CONN_INTEGRITY_LIMIT_GCM
1453
+ this.sealedUnderKey = 0
1454
+ this.authFailures = 0
1455
+ this.datagramEcn = 0
1456
+ this.nextSkip = -1
1457
+ quicConnForgetSkips(this.skipped)
1458
+ this.skipsDrawn = 0
1459
+ this.skipHead = 0
1332
1460
  this.errorIsApplication = false
1333
1461
  this.closeSent = false
1334
1462
  this.addressValidated = false
1335
1463
  this.handshakeComplete = false
1464
+ this.applicationTaken = false
1336
1465
  this.handshakeDonePending = false
1337
1466
  this.retried = false
1338
1467
  this.elicitingSent = false
@@ -1347,7 +1476,7 @@ export class QuicConnection {
1347
1476
  /** Takes `entropy` (and wipes it), or closes with INTERNAL_ERROR for entropy or a configuration that does not fit. */
1348
1477
  begin(entropy: u8[]): void {
1349
1478
  quicCidCopy(this.serverRandom, entropy, 0, 32)
1350
- quicCidCopy(this.ephemeralPrivate, entropy, 32, 32)
1479
+ exposeWith(this.ephemeral, entropy, quicConnEphemeralIn)
1351
1480
  quicCidCopy(this.localScid, entropy, 64, QUIC_CONN_CID_LENGTH)
1352
1481
  quicCidCopy(this.cidSeed, entropy, 72, 32)
1353
1482
  const fits: boolean = toI32(entropy.length) === QUIC_CONN_ENTROPY_SIZE && quicConnConfigFits(this.config)
@@ -1356,6 +1485,7 @@ export class QuicConnection {
1356
1485
  this.state = QUIC_STATE_CLOSING
1357
1486
  this.error = QUIC_ERROR_INTERNAL
1358
1487
  this.closeSent = true
1488
+ this.startClosingPeriod()
1359
1489
  }
1360
1490
  }
1361
1491
 
@@ -1426,6 +1556,19 @@ export class QuicConnection {
1426
1556
  this.errorIsApplication = true
1427
1557
  }
1428
1558
 
1559
+ /**
1560
+ * Starts the closing or draining period, three probe timeouts long (RFC
1561
+ * 9000 §10.2), once nothing more is owed: when the client's
1562
+ * CONNECTION_CLOSE arrives, when `takeCloseInto` sends this side's, or when
1563
+ * `begin` closes before anything could be sent. The keys stay through it,
1564
+ * as the period's state, and `runClocks` wipes them all at its end, which
1565
+ * `deadline()` names, so that a connection nobody `release()`s does not
1566
+ * keep them.
1567
+ */
1568
+ startClosingPeriod(): void {
1569
+ this.closingUntil = this.now + this.recovery.probeTimeout() * 3
1570
+ }
1571
+
1429
1572
  /** The space of packets of `type`, or `null` for a 0-RTT or Retry packet. */
1430
1573
  spaceOf(type: i32): QuicConnSpace | null {
1431
1574
  if (type === QUIC_PACKET_INITIAL) {
@@ -1646,15 +1789,24 @@ export class QuicConnection {
1646
1789
  quicEncodeTransportParametersInto(params, this.localEncoded)
1647
1790
  // One `TlsServer` a slot, made by its first handshake (so its flight is
1648
1791
  // sized for these parameters) and restarted, wiped, by each after it.
1792
+ // The ephemeral key goes from the connection's `Secret` to the
1793
+ // server's: a restart takes a plain copy, which it zeroes, made in an
1794
+ // arena block; a new server takes a zero key that ours is copied over.
1795
+ // Either way ours is zeroed once it is handed on.
1649
1796
  const tls: TlsServer | null = this.tlsSlot
1650
1797
  if (tls !== null) {
1651
- tls.restart(this.serverRandom, this.ephemeralPrivate)
1798
+ {
1799
+ using _scope = arena()
1800
+ tls.restart(this.serverRandom, expose(this.ephemeral, tlsHeldClone))
1801
+ }
1652
1802
  this.tls = tls
1653
1803
  } else {
1654
- const made: TlsServer = new TlsServer(this.tlsConfig, this.serverRandom, this.ephemeralPrivate)
1804
+ const made: TlsServer = new TlsServer(this.tlsConfig, this.serverRandom, new Array<u8>(32))
1805
+ exposeWith(this.ephemeral, made.ephemeral, tlsHeldCopyOver)
1655
1806
  this.tlsSlot = made
1656
1807
  this.tls = made
1657
1808
  }
1809
+ exposeWith(this.ephemeral, QUIC_CONN_FROM, quicConnZeroHeld)
1658
1810
  }
1659
1811
 
1660
1812
  /**
@@ -1713,9 +1865,20 @@ export class QuicConnection {
1713
1865
  this.phasePending = -1
1714
1866
  this.resendLevel = -1
1715
1867
  let used: boolean = false
1868
+ // A STREAM frame's first byte borrows a receive buffer, and no pool may
1869
+ // take one inside the scope, so the reserve is primed before it with what
1870
+ // this packet could want. Only 0-RTT and 1-RTT packets carry STREAM
1871
+ // frames (RFC 9000 §12.4); any other still makes a standalone
1872
+ // connection's private pools, so the handshake, not the first 1-RTT
1873
+ // packet, pays for them.
1874
+ if (type === QUIC_PACKET_SHORT || type === QUIC_PACKET_ZERO_RTT) {
1875
+ this.streams.prime(header.end - header.pnOffset)
1876
+ } else {
1877
+ this.streams.ownPools()
1878
+ }
1716
1879
  {
1717
1880
  using _scope = arena()
1718
- used = this.openScoped(buf, header, space, keys)
1881
+ used = this.openScoped(buf, header, space, keys, first)
1719
1882
  }
1720
1883
  if (!used) {
1721
1884
  return false
@@ -1755,7 +1918,7 @@ export class QuicConnection {
1755
1918
  * an allocation, which is what lets the block release the packet's
1756
1919
  * temporaries. Answers whether the packet was used rather than dropped.
1757
1920
  */
1758
- openScoped(buf: u8[], header: QuicHeader, space: QuicConnSpace, keys: QuicKeys): boolean {
1921
+ openScoped(buf: u8[], header: QuicHeader, space: QuicConnSpace, keys: QuicKeys, first: boolean): boolean {
1759
1922
  const packet: QuicPacket = this.packet
1760
1923
  // Every key generation shares the header-protection key (RFC 9001 §6),
1761
1924
  // so the current keys take it off whatever the Key Phase bit says.
@@ -1770,6 +1933,15 @@ export class QuicConnection {
1770
1933
  const payload: u8[] | null = quicDecryptPayload(open, buf, header, clear, packet)
1771
1934
  if (payload === null) {
1772
1935
  this.drop()
1936
+ // RFC 9001 §6.6: past the integrity limit, the connection closes and
1937
+ // reads nothing more. A first Initial that fails is not counted: the
1938
+ // connection is not started, and the next one starts afresh.
1939
+ if (!first) {
1940
+ this.authFailures = this.authFailures + 1
1941
+ if (this.authFailures > this.integrityLimit) {
1942
+ this.fail(QUIC_ERROR_AEAD_LIMIT_REACHED, QUIC_CONN_NONE)
1943
+ }
1944
+ }
1773
1945
  return false
1774
1946
  }
1775
1947
  // §17.2, §17.3.1: reserved bits set under a valid tag close the connection.
@@ -1859,10 +2031,13 @@ export class QuicConnection {
1859
2031
  this.otherReadKeys = previous
1860
2032
  this.otherIsNext = false
1861
2033
  this.appReadSlot = 1 - this.appReadSlot
1862
- secureZero(this.appReadSecret)
1863
- this.appReadSecret = this.nextReadSecret
1864
- this.readSecretCurrent = this.readSecretCurrent === 0 ? 1 : 0
1865
- this.nextReadSecret = this.none
2034
+ // The next read secret becomes the current one, over the secret it
2035
+ // replaces, and is zeroed where it was. Both widths move: the one the
2036
+ // suite does not use holds zeros.
2037
+ exposeWith(this.nextReadSecret256, this.readSecret256, tlsHeldCopyOver)
2038
+ exposeWith(this.nextReadSecret384, this.readSecret384, tlsHeldCopyOver)
2039
+ exposeWith(this.nextReadSecret256, QUIC_CONN_FROM, quicConnZeroHeld)
2040
+ exposeWith(this.nextReadSecret384, QUIC_CONN_FROM, quicConnZeroHeld)
1866
2041
  this.readPhase = !this.readPhase
1867
2042
  this.readPhaseLowest = pn
1868
2043
  this.readPhaseHighest = pn
@@ -1880,28 +2055,68 @@ export class QuicConnection {
1880
2055
  if (keys === null) {
1881
2056
  return false
1882
2057
  }
1883
- const target: i32 = this.writeSecretCurrent === 0 ? 1 : 0
1884
- const secret: u8[] = quicConnSecretAt(this.writeSecrets, keys.aead, target)
1885
- if (!quicKeyUpdateSecretInto(this.kdf, keys.aead, this.appWriteSecret, secret)) {
1886
- return false
1887
- }
1888
2058
  const slot: QuicKeysSlot = this.application.keySlot(QUIC_CONN_APP_WRITE_SLOTS + 1 - this.appWriteSlot)
1889
- const next: QuicKeys | null = quicKeysUpdateInto(this.kdf, slot, keys, secret)
2059
+ const next: QuicKeys | null =
2060
+ keys.aead === QUIC_AEAD_AES_256_GCM
2061
+ ? this.nextKeys(slot, keys, this.writeSecret384, this.writeSecret384)
2062
+ : this.nextKeys(slot, keys, this.writeSecret256, this.writeSecret256)
1890
2063
  if (next === null) {
1891
- secureZero(secret)
1892
2064
  return false
1893
2065
  }
1894
2066
  quicConnWipePacketKey(keys)
1895
- secureZero(this.appWriteSecret)
1896
2067
  this.application.writeKeys = next
1897
- this.appWriteSecret = secret
1898
- this.writeSecretCurrent = target
1899
2068
  this.appWriteSlot = 1 - this.appWriteSlot
1900
2069
  this.writePhase = !this.writePhase
1901
2070
  this.writePhaseFirst = this.application.nextPn
2071
+ this.sealedUnderKey = 0
1902
2072
  return true
1903
2073
  }
1904
2074
 
2075
+ /**
2076
+ * A level's keys from a held traffic secret, derived into `slot` through
2077
+ * the connection's own HKDF scratch, as `quicKeys` derives them: the
2078
+ * derivation reads a plain copy made in an arena block, which is zeroed
2079
+ * before it ends, so the call keeps nothing in the arena. Answers the
2080
+ * slot's keys, or `null` for a secret of the wrong length.
2081
+ */
2082
+ keysFrom(slot: QuicKeysSlot, aead: i32, held: Secret<u8[]>): QuicKeys | null {
2083
+ let made: boolean = false
2084
+ {
2085
+ using _scope = arena()
2086
+ const plain: u8[] = expose(held, tlsHeldClone)
2087
+ made = quicKeysInto(this.kdf, slot, aead, plain) !== null
2088
+ secureZero(plain)
2089
+ }
2090
+ return made ? slot.keys : null
2091
+ }
2092
+
2093
+ /**
2094
+ * The next generation of `keys` into `slot` (RFC 9001 §6.1): the next
2095
+ * traffic secret, `quic ku` of `current`, and its packet key and IV, with
2096
+ * the header-protection key and its schedule shared, as every generation
2097
+ * shares them. The secret is written into `next`, which may be `current`
2098
+ * and then ratchets it. The derivation reads plain copies made in an arena
2099
+ * block, which are zeroed before it ends. Answers the slot's keys, or
2100
+ * `null`, leaving `next` as it was, for keys `quicKeys` did not make.
2101
+ */
2102
+ nextKeys(slot: QuicKeysSlot, keys: QuicKeys, current: Secret<u8[]>, next: Secret<u8[]>): QuicKeys | null {
2103
+ let made: boolean = false
2104
+ {
2105
+ using _scope = arena()
2106
+ const plain: u8[] = expose(current, tlsHeldClone)
2107
+ const out: u8[] = new Array<u8>(plain.length)
2108
+ made =
2109
+ quicKeyUpdateSecretInto(this.kdf, keys.aead, plain, out) &&
2110
+ quicKeysUpdateInto(this.kdf, slot, keys, out) !== null
2111
+ if (made) {
2112
+ exposeWith(next, out, tlsHeldCopyIn)
2113
+ }
2114
+ secureZero(plain)
2115
+ secureZero(out)
2116
+ }
2117
+ return made ? slot.keys : null
2118
+ }
2119
+
1905
2120
  /**
1906
2121
  * Derives the next generation's read secret and keys from the current
1907
2122
  * ones, into `otherReadKeys`, wiping the previous generation's key and IV
@@ -1918,17 +2133,11 @@ export class QuicConnection {
1918
2133
  if (keys === null) {
1919
2134
  return
1920
2135
  }
1921
- const secret: u8[] = quicConnSecretAt(this.readSecrets, keys.aead, this.readSecretCurrent === 0 ? 1 : 0)
1922
- if (!quicKeyUpdateSecretInto(this.kdf, keys.aead, this.appReadSecret, secret)) {
1923
- return
1924
- }
1925
- this.nextReadSecret = secret
1926
- this.otherReadKeys = quicKeysUpdateInto(
1927
- this.kdf,
1928
- this.application.keySlot(1 - this.appReadSlot),
1929
- keys,
1930
- secret
1931
- )
2136
+ const slot: QuicKeysSlot = this.application.keySlot(1 - this.appReadSlot)
2137
+ this.otherReadKeys =
2138
+ keys.aead === QUIC_AEAD_AES_256_GCM
2139
+ ? this.nextKeys(slot, keys, this.readSecret384, this.nextReadSecret384)
2140
+ : this.nextKeys(slot, keys, this.readSecret256, this.nextReadSecret256)
1932
2141
  this.otherIsNext = this.otherReadKeys !== null
1933
2142
  }
1934
2143
 
@@ -2123,6 +2332,7 @@ export class QuicConnection {
2123
2332
  this.error = code
2124
2333
  this.errorIsApplication = application
2125
2334
  this.closeSent = true
2335
+ this.startClosingPeriod()
2126
2336
  }
2127
2337
 
2128
2338
  /**
@@ -2132,7 +2342,7 @@ export class QuicConnection {
2132
2342
  * carried, and each it shows lost has its frames queued again.
2133
2343
  */
2134
2344
  receiveAck(space: QuicConnSpace, frame: QuicFrame): void {
2135
- if (frame.largest >= space.nextPn) {
2345
+ if (frame.largest >= space.nextPn || (space.level === TLS_LEVEL_APPLICATION && this.acksSkipped(frame))) {
2136
2346
  this.fail(QUIC_ERROR_PROTOCOL_VIOLATION, toI64(frame.type))
2137
2347
  return
2138
2348
  }
@@ -2141,9 +2351,24 @@ export class QuicConnection {
2141
2351
  }
2142
2352
  const sent: QuicSentPackets | null = this.recovery.space(space.level)
2143
2353
  const delay: i64 = this.ackDelayOf(frame.ackDelay)
2354
+ // ACK_ECN's counts (RFC 9002 §7.1), or -1 each for a plain ACK.
2355
+ const ecn: boolean = frame.type === QUIC_FRAME_ACK_ECN
2356
+ const none: i64 = -1
2357
+ const ect0: i64 = ecn ? frame.ect0 : none
2358
+ const ect1: i64 = ecn ? frame.ect1 : none
2359
+ const ce: i64 = ecn ? frame.ce : none
2144
2360
  if (
2145
2361
  sent === null ||
2146
- this.recovery.onAck(space.level, frame.ackRanges, frame.ackRangeCount, delay, this.now) <= 0
2362
+ this.recovery.onAckEcn(
2363
+ space.level,
2364
+ frame.ackRanges,
2365
+ frame.ackRangeCount,
2366
+ delay,
2367
+ this.now,
2368
+ ect0,
2369
+ ect1,
2370
+ ce
2371
+ ) <= 0
2147
2372
  ) {
2148
2373
  return
2149
2374
  }
@@ -2153,6 +2378,26 @@ export class QuicConnection {
2153
2378
  this.packetsLost(space, sent)
2154
2379
  }
2155
2380
 
2381
+ /**
2382
+ * Whether `frame` acknowledges a packet number the server skipped and
2383
+ * still remembers (RFC 9000 §21.4): a client that does is acknowledging
2384
+ * what it never received. Every range the frame stored is read; one past
2385
+ * `QUIC_FRAME_MAX_ACK_RANGES` is not, and loss recovery does not act on
2386
+ * it either.
2387
+ */
2388
+ acksSkipped(frame: QuicFrame): boolean {
2389
+ const ranges: i64[] = frame.ackRanges
2390
+ for (let k: i32 = 0; k < toI32(this.skipped.length); k += 1) {
2391
+ const pn: i64 = this.skipped[k]
2392
+ for (let r: i32 = 0; pn >= 0 && r < frame.ackRangeCount && r * 2 + 1 < toI32(ranges.length); r += 1) {
2393
+ if (pn >= ranges[r * 2] && pn <= ranges[r * 2 + 1]) {
2394
+ return true
2395
+ }
2396
+ }
2397
+ }
2398
+ return false
2399
+ }
2400
+
2156
2401
  /**
2157
2402
  * An ACK frame's ACK Delay in milliseconds: the field scaled by the
2158
2403
  * client's `ack_delay_exponent` (RFC 9000 §19.3, §18.2). A value too large
@@ -2440,35 +2685,59 @@ export class QuicConnection {
2440
2685
  }
2441
2686
  this.alpn = tls.alpn
2442
2687
  this.serverName = tls.serverName
2443
- this.handshake.readKeys = this.keysFor(
2444
- this.handshake.keySlot(QUIC_CONN_READ_SLOT),
2445
- aead,
2446
- tls.readSecret(TLS_LEVEL_HANDSHAKE)
2447
- )
2448
- this.handshake.writeKeys = this.keysFor(
2449
- this.handshake.keySlot(QUIC_CONN_WRITE_SLOT),
2450
- aead,
2451
- tls.writeSecret(TLS_LEVEL_HANDSHAKE)
2452
- )
2688
+ if (tls.readKnown(TLS_LEVEL_HANDSHAKE)) {
2689
+ this.handshake.readKeys = this.keysFrom(
2690
+ this.handshake.keySlot(QUIC_CONN_READ_SLOT),
2691
+ aead,
2692
+ tls.secrets().clientHandshake
2693
+ )
2694
+ }
2695
+ if (tls.writeKnown(TLS_LEVEL_HANDSHAKE)) {
2696
+ this.handshake.writeKeys = this.keysFrom(
2697
+ this.handshake.keySlot(QUIC_CONN_WRITE_SLOT),
2698
+ aead,
2699
+ tls.secrets().serverHandshake
2700
+ )
2701
+ }
2453
2702
  }
2454
2703
  if (
2704
+ !this.applicationTaken &&
2455
2705
  !this.application.discarded &&
2456
2706
  this.application.writeKeys === null &&
2457
2707
  tls.state !== TLS_STATE_WAIT_SIGNATURE
2458
2708
  ) {
2459
- const write: u8[] | null = tls.writeSecret(TLS_LEVEL_APPLICATION)
2460
- const read: u8[] | null = tls.readSecret(TLS_LEVEL_APPLICATION)
2461
- if (write !== null && read !== null) {
2462
- this.application.writeKeys = this.keysFor(
2463
- this.application.keySlot(QUIC_CONN_APP_WRITE_SLOTS + this.appWriteSlot),
2464
- aead,
2465
- write
2709
+ if (tls.writeKnown(TLS_LEVEL_APPLICATION) && tls.readKnown(TLS_LEVEL_APPLICATION)) {
2710
+ // The key updates derive from the connection's own `Secret`
2711
+ // fields, so the application secrets are copied into those of the
2712
+ // suite's width and wiped in `TlsServer`, leaving one copy of each.
2713
+ const pool: TlsSecretPool = tls.secrets()
2714
+ const writeSlot: QuicKeysSlot = this.application.keySlot(
2715
+ QUIC_CONN_APP_WRITE_SLOTS + this.appWriteSlot
2466
2716
  )
2467
- this.application.readKeys = this.keysFor(this.application.keySlot(this.appReadSlot), aead, read)
2468
- // The key update secrets start from `TlsServer`'s own arrays, so the
2469
- // first update wipes those too, rather than leaving a copy behind.
2470
- this.appWriteSecret = write
2471
- this.appReadSecret = read
2717
+ const readSlot: QuicKeysSlot = this.application.keySlot(this.appReadSlot)
2718
+ if (aead === QUIC_AEAD_AES_256_GCM) {
2719
+ exposeWith(pool.serverApplication, this.writeSecret384, tlsHeldCopyOver)
2720
+ exposeWith(pool.clientApplication, this.readSecret384, tlsHeldCopyOver)
2721
+ } else {
2722
+ exposeWith(pool.serverApplication, this.writeSecret256, tlsHeldCopyOver)
2723
+ exposeWith(pool.clientApplication, this.readSecret256, tlsHeldCopyOver)
2724
+ }
2725
+ this.applicationTaken = true
2726
+ // ChaCha20-Poly1305 has no confidentiality limit, and no packet
2727
+ // number, so no count of packets, reaches `QUIC_MAX_VARINT`.
2728
+ const chacha: boolean = aead === QUIC_AEAD_CHACHA20_POLY1305
2729
+ this.integrityLimit = chacha ? QUIC_CONN_INTEGRITY_LIMIT_CHACHA : QUIC_CONN_INTEGRITY_LIMIT_GCM
2730
+ this.confidentialityLimit = chacha ? QUIC_MAX_VARINT : QUIC_CONN_CONFIDENTIALITY_LIMIT
2731
+ this.scheduleSkip(this.application.nextPn)
2732
+ tls.wipeSecret(TLS_SECRET_SERVER_APPLICATION)
2733
+ tls.wipeSecret(TLS_SECRET_CLIENT_APPLICATION)
2734
+ if (aead === QUIC_AEAD_AES_256_GCM) {
2735
+ this.application.writeKeys = this.keysFrom(writeSlot, aead, this.writeSecret384)
2736
+ this.application.readKeys = this.keysFrom(readSlot, aead, this.readSecret384)
2737
+ } else {
2738
+ this.application.writeKeys = this.keysFrom(writeSlot, aead, this.writeSecret256)
2739
+ this.application.readKeys = this.keysFrom(readSlot, aead, this.readSecret256)
2740
+ }
2472
2741
  this.prepareNextReadKeys()
2473
2742
  }
2474
2743
  }
@@ -2478,18 +2747,14 @@ export class QuicConnection {
2478
2747
  this.handshakeDonePending = true
2479
2748
  this.state = QUIC_STATE_CONNECTED
2480
2749
  this.discard(this.handshake)
2750
+ // The Handshake level's traffic secrets have nothing left to derive
2751
+ // once it is discarded (TLS wipes the handshake secret itself).
2752
+ tls.wipeSecret(TLS_SECRET_CLIENT_HANDSHAKE)
2753
+ tls.wipeSecret(TLS_SECRET_SERVER_HANDSHAKE)
2481
2754
  this.topUpConnectionIds()
2482
2755
  }
2483
2756
  }
2484
2757
 
2485
- /** `quicKeysInto` `slot` of a secret TLS has, or `null` when it has none yet. */
2486
- keysFor(slot: QuicKeysSlot, aead: i32, secret: u8[] | null): QuicKeys | null {
2487
- if (secret === null) {
2488
- return null
2489
- }
2490
- return quicKeysInto(this.kdf, slot, aead, secret)
2491
- }
2492
-
2493
2758
  /**
2494
2759
  * The client's transport parameters (RFC 9000 §7.3, §7.4, §18.2): they must
2495
2760
  * parse, and must name as `initial_source_connection_id` the SCID its first
@@ -2677,12 +2942,17 @@ export class QuicConnection {
2677
2942
  * When `handleTimer` is next due, in the caller's milliseconds: the idle
2678
2943
  * timeout, loss recovery's timer (a time-threshold loss or the probe
2679
2944
  * timeout), or the end of a key update's probe timeout, whichever comes
2680
- * first. -1 when nothing is timed: before the first packet, once the
2681
- * connection has closed, and with no idle timeout, nothing in flight and
2682
- * no update pending.
2945
+ * first; once the connection has closed, the end of its closing or
2946
+ * draining period, when its keys are wiped. -1 when nothing is timed:
2947
+ * before the first packet, while a closed connection's own close has not
2948
+ * gone out (`takeDatagram` sends it), once a closed connection's keys are
2949
+ * wiped, and with no idle timeout, nothing in flight and no update pending.
2683
2950
  */
2684
2951
  deadline(): i64 {
2685
- if (this.closed() || this.state === QUIC_STATE_WAIT_INITIAL) {
2952
+ if (this.closed()) {
2953
+ return this.closingUntil
2954
+ }
2955
+ if (this.state === QUIC_STATE_WAIT_INITIAL) {
2686
2956
  return -1
2687
2957
  }
2688
2958
  let due: i64 = this.idleDeadline()
@@ -2709,9 +2979,10 @@ export class QuicConnection {
2709
2979
  * Runs whatever is due by `now`, the caller's monotonic time in
2710
2980
  * milliseconds; call it at `deadline()`, or any time. Past the idle timeout
2711
2981
  * the connection closes silently (`QUIC_STATE_TIMED_OUT`, RFC 9000 §10.1):
2712
- * no CONNECTION_CLOSE, every key wiped. Past a key update's probe timeout
2713
- * the previous read keys are wiped and the next generation's derived (RFC
2714
- * 9001 §6.5). Past loss recovery's timer, packets lost by the time
2982
+ * no CONNECTION_CLOSE, every key wiped. Past the closing or draining
2983
+ * period of a connection that closed, every key is wiped (§10.2). Past a
2984
+ * key update's probe timeout the previous read keys are wiped and the next
2985
+ * generation's derived (RFC 9001 §6.5). Past loss recovery's timer, packets lost by the time
2715
2986
  * threshold have their frames queued again, or a probe timeout queues
2716
2987
  * every space's packets in flight and asks for a probe (RFC 9002 §6.1.2,
2717
2988
  * §6.2.4): `takeDatagram` then sends it. `receive` and `takeDatagram` run
@@ -2723,12 +2994,24 @@ export class QuicConnection {
2723
2994
  this.runRecoveryTimer()
2724
2995
  }
2725
2996
 
2726
- /** `handleTimer`'s idle timeout and key retention, which `receive` runs before it reads a datagram. */
2997
+ /**
2998
+ * `handleTimer`'s idle timeout, key retention and the end of the closing
2999
+ * or draining period, which `receive` runs before it reads a datagram.
3000
+ */
2727
3001
  runClocks(now: i64): void {
2728
3002
  if (now > this.now) {
2729
3003
  this.now = now
2730
3004
  }
2731
- if (this.closed() || this.state === QUIC_STATE_WAIT_INITIAL) {
3005
+ if (this.closed()) {
3006
+ // RFC 9000 §10.2: past the period, nothing more goes out, so the keys
3007
+ // kept for the CONNECTION_CLOSE are wiped (QUIC-2).
3008
+ if (this.closingUntil >= 0 && this.now >= this.closingUntil) {
3009
+ this.closeSent = true
3010
+ this.wipeAll()
3011
+ }
3012
+ return
3013
+ }
3014
+ if (this.state === QUIC_STATE_WAIT_INITIAL) {
2732
3015
  return
2733
3016
  }
2734
3017
  const idle: i64 = this.idleDeadline()
@@ -2783,6 +3066,24 @@ export class QuicConnection {
2783
3066
  return this.updateWriteKeys()
2784
3067
  }
2785
3068
 
3069
+ /**
3070
+ * Draws where the next Application Data packet number is skipped (RFC
3071
+ * 9000 §21.4): `QUIC_CONN_SKIP_MIN` plus up to `QUIC_CONN_SKIP_SPREAD - 1`
3072
+ * packets past `from`, the distance taken from the ID seed's HMAC under
3073
+ * `QUIC_CONN_SKIP_SEQUENCE`, so a client that saw where the last gaps fell
3074
+ * cannot tell where the next will.
3075
+ */
3076
+ scheduleSkip(from: i64): void {
3077
+ this.deriveConnectionId(QUIC_CONN_SKIP_SEQUENCE + this.skipsDrawn)
3078
+ this.skipsDrawn = this.skipsDrawn + 1
3079
+ let draw: i64 = 0
3080
+ for (let k: i32 = 0; k < 4 && k < toI32(this.cidScratch.length); k += 1) {
3081
+ draw = (draw << 8) | toI64(this.cidScratch[k])
3082
+ }
3083
+ secureZero(this.cidScratch)
3084
+ this.nextSkip = from + QUIC_CONN_SKIP_MIN + (draw % QUIC_CONN_SKIP_SPREAD)
3085
+ }
3086
+
2786
3087
  // ---- Streams ------------------------------------------------------------------
2787
3088
 
2788
3089
  /**
@@ -3028,6 +3329,7 @@ export class QuicConnection {
3028
3329
  return 0
3029
3330
  }
3030
3331
  let n: i32 = 0
3332
+ this.datagramEcn = 0
3031
3333
  if (this.state === QUIC_STATE_CLOSING) {
3032
3334
  using _scope = arena()
3033
3335
  n = this.takeCloseInto(out, at)
@@ -3041,6 +3343,8 @@ export class QuicConnection {
3041
3343
  if (this.amplificationBlocked()) {
3042
3344
  return 0
3043
3345
  }
3346
+ this.beforeDatagram()
3347
+ this.datagramEcn = this.ecnMarking ? this.recovery.ecnCodepoint() : 0
3044
3348
  {
3045
3349
  using _scope = arena()
3046
3350
  n = this.buildDatagram(out, at)
@@ -3048,6 +3352,32 @@ export class QuicConnection {
3048
3352
  return n
3049
3353
  }
3050
3354
 
3355
+ /**
3356
+ * What the next datagram's Application Data packet needs before it is
3357
+ * built, outside the arena block because a key update stores the keys it
3358
+ * derives. A datagram carries at most one such packet, so this runs once
3359
+ * a packet. From three quarters of the confidentiality limit, each tries a
3360
+ * key update, which goes when `updateKeys` allows (RFC 9001 §6.6). And
3361
+ * the packet number due to be skipped is (RFC 9000 §21.4): remembered,
3362
+ * never sealed nor recorded, and the next skip drawn.
3363
+ */
3364
+ beforeDatagram(): void {
3365
+ const limit: i64 = this.confidentialityLimit
3366
+ if (this.sealedUnderKey >= limit - limit / 4) {
3367
+ this.updateKeys()
3368
+ }
3369
+ const pn: i64 = this.application.nextPn
3370
+ if (pn !== this.nextSkip) {
3371
+ return
3372
+ }
3373
+ if (this.skipHead >= 0 && this.skipHead < toI32(this.skipped.length)) {
3374
+ this.skipped[this.skipHead] = pn
3375
+ }
3376
+ this.skipHead = (this.skipHead + 1) % QUIC_CONN_SKIP_RING
3377
+ this.application.nextPn = pn + 1
3378
+ this.scheduleSkip(pn + 1)
3379
+ }
3380
+
3051
3381
  /**
3052
3382
  * `takeDatagramInto`'s datagram, built and sealed into `out` at `at`: one
3053
3383
  * packet per space with something to carry, each written in place, the
@@ -3348,6 +3678,13 @@ export class QuicConnection {
3348
3678
  * `quicKeys` did not make cause.
3349
3679
  */
3350
3680
  sealAt(space: QuicConnSpace, out: u8[], start: i32, payloadEnd: i32): i32 {
3681
+ // RFC 9001 §6.6: at the confidentiality limit, with no key update made
3682
+ // (`beforeDatagram`), the connection closes, and no packet, its
3683
+ // CONNECTION_CLOSE included, is sealed under the key again.
3684
+ if (space.level === TLS_LEVEL_APPLICATION && this.sealedUnderKey >= this.confidentialityLimit) {
3685
+ this.fail(QUIC_ERROR_AEAD_LIMIT_REACHED, QUIC_CONN_NONE)
3686
+ return 0
3687
+ }
3351
3688
  const keys: QuicKeys | null = space.writeKeys
3352
3689
  const pn: i64 = space.nextPn
3353
3690
  const pnLength: i32 = quicPacketNumberLength(pn, space.largestAcked)
@@ -3387,7 +3724,13 @@ export class QuicConnection {
3387
3724
  return 0
3388
3725
  }
3389
3726
  space.nextPn = pn + 1
3390
- this.recordSent(space, pn, end - start)
3727
+ if (space.level === TLS_LEVEL_APPLICATION) {
3728
+ this.sealedUnderKey = this.sealedUnderKey + 1
3729
+ }
3730
+ const slot: i32 = this.recordSent(space, pn, end - start)
3731
+ if (this.datagramEcn !== 0) {
3732
+ this.recovery.onEctSent(space.level, slot)
3733
+ }
3391
3734
  return end - start
3392
3735
  }
3393
3736
 
@@ -3398,10 +3741,11 @@ export class QuicConnection {
3398
3741
  * recorded. `buildDatagram` builds nothing ack-eliciting into a space
3399
3742
  * whose record is full, so recovery does not refuse one; if it ever did,
3400
3743
  * what the packet carried is queued again at once, as though lost.
3744
+ * Answers the slot recovery recorded it in, or -1.
3401
3745
  */
3402
- recordSent(space: QuicConnSpace, pn: i64, size: i32): void {
3746
+ recordSent(space: QuicConnSpace, pn: i64, size: i32): i32 {
3403
3747
  if (!space.stagedEliciting) {
3404
- return
3748
+ return -1
3405
3749
  }
3406
3750
  const slot: i32 = this.recovery.onPacketSent(space.level, pn, size, this.now)
3407
3751
  if (slot >= 0) {
@@ -3419,6 +3763,7 @@ export class QuicConnection {
3419
3763
  this.requeue(space, space.staging)
3420
3764
  }
3421
3765
  space.clearStaged()
3766
+ return slot
3422
3767
  }
3423
3768
 
3424
3769
  /** An acknowledged packet in row `row`: each stream chunk and stream control frame it carried is settled. */
@@ -3464,6 +3809,7 @@ export class QuicConnection {
3464
3809
  return 0
3465
3810
  }
3466
3811
  this.closeSent = true
3812
+ this.startClosingPeriod()
3467
3813
  let position: i32 = at
3468
3814
  const limit: i32 = at + QUIC_CONN_DATAGRAM_SIZE
3469
3815
  for (let level: i32 = 0; level < 3; level += 1) {
@@ -3514,10 +3860,13 @@ export class QuicConnection {
3514
3860
  * secrets and expected client Finished, the ephemeral key, the
3515
3861
  * connection-ID seed and the stateless reset tokens (QUIC-2). Call it when
3516
3862
  * the connection is done with (or `reset` the slot, which does the same
3517
- * first); the connection is closed after it and sends nothing more.
3863
+ * first); the connection is closed after it and sends nothing more. Every
3864
+ * stream buffer it borrowed goes back to its pool, zeroed: nothing is sent
3865
+ * again from a closed connection.
3518
3866
  */
3519
3867
  release(): void {
3520
3868
  this.wipeAll()
3869
+ this.streams.giveBack()
3521
3870
  if (!this.closed()) {
3522
3871
  this.state = QUIC_STATE_CLOSING
3523
3872
  this.error = QUIC_ERROR_NO_ERROR
@@ -3527,9 +3876,10 @@ export class QuicConnection {
3527
3876
 
3528
3877
  /**
3529
3878
  * Wipes every key and secret the connection holds and discards every
3530
- * level: `release()`'s work, the idle timeout's and `reset`'s. That takes
3531
- * in both sides' stateless reset tokens and `TlsServer`'s expected client
3532
- * Finished.
3879
+ * level: `release()`'s work, the idle timeout's, the end of the closing or
3880
+ * draining period's and `reset`'s. That takes in both sides' stateless
3881
+ * reset tokens and `TlsServer`'s expected client Finished and resumption
3882
+ * secret.
3533
3883
  */
3534
3884
  wipeAll(): void {
3535
3885
  this.discard(this.initial)
@@ -3539,19 +3889,11 @@ export class QuicConnection {
3539
3889
  this.otherReadKeys = null
3540
3890
  this.otherIsNext = false
3541
3891
  this.keyRetainUntil = -1
3542
- secureZero(this.appReadSecret)
3543
- secureZero(this.appWriteSecret)
3544
- secureZero(this.nextReadSecret)
3545
- for (const secret of this.writeSecrets) {
3546
- secureZero(secret)
3547
- }
3548
- for (const secret of this.readSecrets) {
3549
- secureZero(secret)
3550
- }
3892
+ this.closingUntil = -1
3893
+ this.wipe()
3551
3894
  secureZero(this.initialClient)
3552
3895
  secureZero(this.initialServer)
3553
3896
  secureZero(this.cidSeed)
3554
- secureZero(this.ephemeralPrivate)
3555
3897
  // The stateless reset tokens of both sides' IDs: either one ends the
3556
3898
  // connection for whoever learns it (RFC 9000 §10.3).
3557
3899
  for (const entry of this.cids.local) {
@@ -3566,13 +3908,23 @@ export class QuicConnection {
3566
3908
  if (tls !== null) {
3567
3909
  // The server's encoded transport parameters carry its first ID's token.
3568
3910
  secureZero(tls.config.quicTransportParameters)
3569
- secureZero(tls.expectedClientFinished)
3570
- secureZero(tls.handshakeSecret)
3571
- secureZero(tls.clientHandshakeSecret)
3572
- secureZero(tls.serverHandshakeSecret)
3573
- secureZero(tls.clientApplicationSecret)
3574
- secureZero(tls.serverApplicationSecret)
3575
- secureZero(tls.exporterSecret)
3911
+ tls.wipe()
3576
3912
  }
3577
3913
  }
3914
+
3915
+ /**
3916
+ * Wipes the connection's `Secret` fields: the ephemeral key and both
3917
+ * widths' 1-RTT read, next read and write secrets. `wipeAll` calls it,
3918
+ * and so `release()`, `reset`, the idle timeout and the end of the
3919
+ * closing or draining period.
3920
+ */
3921
+ wipe(): void {
3922
+ wipe(this.ephemeral)
3923
+ wipe(this.readSecret256)
3924
+ wipe(this.nextReadSecret256)
3925
+ wipe(this.writeSecret256)
3926
+ wipe(this.readSecret384)
3927
+ wipe(this.nextReadSecret384)
3928
+ wipe(this.writeSecret384)
3929
+ }
3578
3930
  }