@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.
- package/INSTALL.md +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +215 -18
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1120 -34
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- 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
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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.
|
|
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
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* key
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* `
|
|
146
|
-
*
|
|
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
|
|
339
|
+
* The most bytes of stream buffer a configuration may let each connection
|
|
303
340
|
* hold: every stream slot (`maxStreamsBidi + maxStreamsUni + localStreams`)
|
|
304
|
-
*
|
|
305
|
-
* configuration past 64 MiB of them is refused
|
|
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
|
-
/**
|
|
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
|
|
936
|
-
*
|
|
937
|
-
* §6), so
|
|
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
|
-
|
|
1004
|
-
|
|
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
|
-
/**
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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
|
-
|
|
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
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
|
1140
|
-
*
|
|
1141
|
-
* the
|
|
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.
|
|
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.
|
|
1267
|
-
this.
|
|
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.
|
|
1271
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
this.
|
|
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 =
|
|
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
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
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.
|
|
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
|
-
|
|
2444
|
-
this.handshake.
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
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
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
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
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
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
|
|
2681
|
-
*
|
|
2682
|
-
*
|
|
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()
|
|
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
|
|
2713
|
-
*
|
|
2714
|
-
*
|
|
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
|
-
/**
|
|
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()
|
|
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
|
-
|
|
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):
|
|
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
|
|
3531
|
-
*
|
|
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
|
-
|
|
3543
|
-
|
|
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
|
-
|
|
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
|
}
|