@amritk/nish-x86_64-linux 0.17.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +231 -20
- 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 +203 -0
- package/runtime/runtime-wasm.c +16 -3
- package/runtime/runtime.c +89 -23
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +225 -20
- package/std/README.md +86 -55
- 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 +1376 -42
- 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 +1412 -70
- 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 +231 -45
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +576 -219
- package/std/net/quic-packet.ts +443 -58
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +434 -28
- package/std/net/quic.ts +922 -206
- 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 +204 -57
- package/std/net/tls.ts +1132 -305
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/text.ts +77 -0
- package/std/threads.ts +438 -14
package/std/net/quic.ts
CHANGED
|
@@ -58,27 +58,46 @@
|
|
|
58
58
|
* its current value. When nothing is acknowledged for a probe timeout, with
|
|
59
59
|
* its backoff, everything in flight is queued again and a probe goes out in
|
|
60
60
|
* each space that has packets in flight, a PING if there is nothing to
|
|
61
|
-
* resend (§6.2.4)
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
61
|
+
* resend (§6.2.4) — two datagrams of them during the handshake, each with
|
|
62
|
+
* the oldest CRYPTO data not acknowledged. A client that shows it lacks the
|
|
63
|
+
* server's handshake data (an Initial repeating CRYPTO data, an
|
|
64
|
+
* ack-eliciting Handshake packet) gets it again at once, a datagram each
|
|
65
|
+
* time, up to `QUIC_CONN_EARLY_RESENDS` times (§6.2.3). NewReno's window
|
|
66
|
+
* holds back everything ack-eliciting but a probe; an ACK always goes.
|
|
67
|
+
* PATH_RESPONSE and DATAGRAM are never sent again (RFC 9000 §13.3, RFC 9221
|
|
68
|
+
* §5.2). Pacing, and GSO batching, are the carrier's:
|
|
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).
|
|
65
74
|
*
|
|
66
75
|
* **Nothing allocated per packet** (QUIC-3). A connection is a slot: every
|
|
67
|
-
* buffer it uses — the packet record, the
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
|
71
86
|
* and sealed (`takeDatagramInto`) inside a `using a = arena()` block, so the
|
|
72
87
|
* AEAD's and the header protection's temporaries go when the block ends, and
|
|
73
|
-
* the compiler refuses the block if anything in it could store one.
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* `
|
|
88
|
+
* the compiler refuses the block if anything in it could store one. The
|
|
89
|
+
* handshake keeps its state in the slot too: one `TlsServer`, made by the
|
|
90
|
+
* slot's first handshake and `restart`ed by each after it; every level's
|
|
91
|
+
* packet keys, and a key update's, derived in place into `QuicKeysSlot`s
|
|
92
|
+
* (`nish/net/quic-packet`) over the slot's HKDF scratch; the Initial
|
|
93
|
+
* secrets, the stateless reset tokens and the connection IDs computed in
|
|
94
|
+
* arena blocks; the transport parameters encoded and parsed into the
|
|
95
|
+
* slot's own objects. After the first connection in a slot, a connection
|
|
96
|
+
* keeps nothing at all (QUIC-3 in `docs/security/quic.md`).
|
|
78
97
|
*
|
|
79
98
|
* **Time.** The connection has no clock: `receive`, `takeDatagram` and
|
|
80
99
|
* `handleTimer` take the caller's monotonic time in milliseconds, and
|
|
81
|
-
* `deadline()` says when `handleTimer` is next due.
|
|
100
|
+
* `deadline()` says when `handleTimer` is next due. Four things run on it.
|
|
82
101
|
* The idle timeout (RFC 9000 §10.1) is the smaller of the two sides'
|
|
83
102
|
* `max_idle_timeout`, never under `QUIC_CONN_IDLE_FLOOR` or three probe
|
|
84
103
|
* timeouts; once it passes with nothing received, the connection closes
|
|
@@ -86,7 +105,13 @@
|
|
|
86
105
|
* timer declares packets lost by the time threshold or fires the probe
|
|
87
106
|
* timeout. And after a key update the previous read keys are kept for a
|
|
88
107
|
* probe timeout (`recovery.probeTimeout()`), for packets the network
|
|
89
|
-
* 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.
|
|
90
115
|
*
|
|
91
116
|
* **Key update** (RFC 9001 §6), both ways. A 1-RTT packet whose Key Phase
|
|
92
117
|
* bit differs from the current one is opened with the next generation's read
|
|
@@ -94,11 +119,17 @@
|
|
|
94
119
|
* derivation and shows no timing difference (§6.3); if it opens, the client
|
|
95
120
|
* has updated, and the server's write keys follow before anything is
|
|
96
121
|
* acknowledged (§6.2). `updateKeys()` starts an update from the server once
|
|
97
|
-
* the client has acknowledged a packet of the current phase (§6.1).
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* KEY_UPDATE_ERROR.
|
|
122
|
+
* the client has acknowledged a packet of the current phase (§6.1). The
|
|
123
|
+
* generations take turns in two key slots and two secrets a direction, so an
|
|
124
|
+
* update keeps nothing in the arena; each still costs two key derivations of
|
|
125
|
+
* work, so a connection takes at most `QUIC_CONN_MAX_KEY_UPDATES` updates the
|
|
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.
|
|
102
133
|
*
|
|
103
134
|
* **Sans-IO and deterministic.** No socket, no clock and no random device:
|
|
104
135
|
* every random choice (the TLS server random and ephemeral key, the server's
|
|
@@ -122,25 +153,31 @@
|
|
|
122
153
|
* `maxData`, the connection-ID table by the limit advertised, the received
|
|
123
154
|
* packet numbers by `QUIC_ACK_MAX_RANGES`, and datagrams by their ring.
|
|
124
155
|
*
|
|
125
|
-
* **Secrets.** The packet keys of each level live in this connection's
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
156
|
+
* **Secrets.** The packet keys of each level live in this connection's key
|
|
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.
|
|
137
172
|
*
|
|
138
173
|
* Written from RFC 9000, RFC 9001 and RFC 9221, in this module's own
|
|
139
174
|
* structure; nothing here is ported from another implementation. Private
|
|
140
175
|
* names carry the `quicConn` prefix (`docs/wp26-stdlib.md` §3e).
|
|
141
176
|
*/
|
|
142
177
|
import { timingSafeEqualAt } from "nish/crypto/ct"
|
|
143
|
-
import {
|
|
178
|
+
import { AesKey, aesKeyWipe } from "nish/crypto/aes"
|
|
179
|
+
import { HkdfScratch } from "nish/crypto/hkdf"
|
|
180
|
+
import { HmacSha256Scratch, hmacSha256 } from "nish/crypto/hmac"
|
|
144
181
|
import {
|
|
145
182
|
QUIC_AEAD_AES_128_GCM,
|
|
146
183
|
QUIC_AEAD_AES_256_GCM,
|
|
@@ -153,15 +190,16 @@ import {
|
|
|
153
190
|
QUIC_PACKET_INITIAL,
|
|
154
191
|
QUIC_PACKET_OK,
|
|
155
192
|
QUIC_PACKET_SHORT,
|
|
193
|
+
QUIC_PACKET_ZERO_RTT,
|
|
156
194
|
QuicHeader,
|
|
157
|
-
QuicInitialSecrets,
|
|
158
195
|
QuicKeys,
|
|
196
|
+
QuicKeysSlot,
|
|
159
197
|
QuicPacket,
|
|
160
198
|
quicDecryptPayload,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
199
|
+
quicInitialSecretsInto,
|
|
200
|
+
quicKeyUpdateSecretInto,
|
|
201
|
+
quicKeysInto,
|
|
202
|
+
quicKeysUpdateInto,
|
|
165
203
|
quicPacketNumberLength,
|
|
166
204
|
quicParseHeaderInto,
|
|
167
205
|
quicPutLongHeader,
|
|
@@ -170,6 +208,7 @@ import {
|
|
|
170
208
|
quicUnprotectHeader,
|
|
171
209
|
} from "nish/net/quic-packet"
|
|
172
210
|
import {
|
|
211
|
+
QUIC_ERROR_AEAD_LIMIT_REACHED,
|
|
173
212
|
QUIC_ERROR_APPLICATION,
|
|
174
213
|
QUIC_ERROR_CRYPTO,
|
|
175
214
|
QUIC_ERROR_CRYPTO_BUFFER_EXCEEDED,
|
|
@@ -218,8 +257,8 @@ import {
|
|
|
218
257
|
} from "nish/net/quic-frame"
|
|
219
258
|
import {
|
|
220
259
|
QuicTransportParameters,
|
|
221
|
-
|
|
222
|
-
|
|
260
|
+
quicEncodeTransportParametersInto,
|
|
261
|
+
quicParseTransportParametersInto,
|
|
223
262
|
} from "nish/net/quic-conn-params"
|
|
224
263
|
import { QuicAckRanges } from "nish/net/quic-conn-ack"
|
|
225
264
|
import {
|
|
@@ -249,13 +288,22 @@ import {
|
|
|
249
288
|
TLS_LEVEL_APPLICATION,
|
|
250
289
|
TLS_LEVEL_HANDSHAKE,
|
|
251
290
|
TLS_LEVEL_INITIAL,
|
|
291
|
+
TLS_SECRET_CLIENT_APPLICATION,
|
|
292
|
+
TLS_SECRET_CLIENT_HANDSHAKE,
|
|
293
|
+
TLS_SECRET_SERVER_APPLICATION,
|
|
294
|
+
TLS_SECRET_SERVER_HANDSHAKE,
|
|
252
295
|
TLS_STATE_CONNECTED,
|
|
253
296
|
TLS_STATE_WAIT_CLIENT_HELLO,
|
|
254
297
|
TLS_STATE_WAIT_SIGNATURE,
|
|
298
|
+
TlsSecretPool,
|
|
255
299
|
TlsServer,
|
|
256
300
|
TlsServerConfig,
|
|
301
|
+
tlsHeldClone,
|
|
302
|
+
tlsHeldCopyIn,
|
|
303
|
+
tlsHeldCopyOver,
|
|
257
304
|
} from "nish/net/tls"
|
|
258
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"
|
|
259
307
|
|
|
260
308
|
// ---- Sizes ----------------------------------------------------------------------
|
|
261
309
|
|
|
@@ -288,10 +336,11 @@ export const QUIC_CONN_MAX_STREAM_DATA: i64 = 1048576
|
|
|
288
336
|
/** The most streams of one kind a configuration may let the client open at once, or open itself. */
|
|
289
337
|
export const QUIC_CONN_MAX_STREAMS: i64 = 1024
|
|
290
338
|
/**
|
|
291
|
-
* The most bytes of stream buffer a configuration may
|
|
339
|
+
* The most bytes of stream buffer a configuration may let each connection
|
|
292
340
|
* hold: every stream slot (`maxStreamsBidi + maxStreamsUni + localStreams`)
|
|
293
|
-
*
|
|
294
|
-
* 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.
|
|
295
344
|
*/
|
|
296
345
|
export const QUIC_CONN_MAX_STREAM_BUFFERS: i64 = 67108864
|
|
297
346
|
/** The length of the configuration's static keys: the stateless reset key and the Retry token key. */
|
|
@@ -320,12 +369,62 @@ export const QUIC_CONN_PACKET_STREAMS: i32 = 4
|
|
|
320
369
|
export const QUIC_CONN_PACKET_CONTROL: i32 = 4
|
|
321
370
|
/**
|
|
322
371
|
* How many key updates a connection takes from its client (RFC 9001 §6).
|
|
323
|
-
* Each costs two key derivations
|
|
324
|
-
* cap bounds what a client can make the server derive (QUIC-4); the next one
|
|
372
|
+
* Each costs two key derivations of work, though none of their memory, so
|
|
373
|
+
* the cap bounds what a client can make the server derive (QUIC-4); the next one
|
|
325
374
|
* closes the connection with KEY_UPDATE_ERROR. A client also has to wait
|
|
326
375
|
* a probe timeout between two of them.
|
|
327
376
|
*/
|
|
328
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
|
|
409
|
+
/**
|
|
410
|
+
* How many ack-eliciting datagrams a probe timeout sends in the Initial and
|
|
411
|
+
* Handshake spaces (RFC 9002 §6.2.4 allows up to two). The server's first
|
|
412
|
+
* flight is usually one datagram, so with one probe a lossy path loses every
|
|
413
|
+
* copy of it about as often as it loses one datagram per probe timeout; two
|
|
414
|
+
* square that, and stay inside the amplification limit, which a one-datagram
|
|
415
|
+
* flight and two probes fill exactly. The Application Data space sends one.
|
|
416
|
+
*/
|
|
417
|
+
export const QUIC_CONN_HANDSHAKE_PROBES: i32 = 2
|
|
418
|
+
/**
|
|
419
|
+
* How many times a connection resends its handshake data before the probe
|
|
420
|
+
* timeout because the client showed it lacks it (RFC 9002 §6.2.3, "for a
|
|
421
|
+
* limited number of times per connection"): a client Initial carrying
|
|
422
|
+
* CRYPTO data the server already read, or an ack-eliciting Handshake packet,
|
|
423
|
+
* while the server's own CRYPTO data at that level is in flight. Each
|
|
424
|
+
* resend is one datagram, so a client cannot make the server answer more
|
|
425
|
+
* than this many packets that way, whatever it sends.
|
|
426
|
+
*/
|
|
427
|
+
export const QUIC_CONN_EARLY_RESENDS: i32 = 2
|
|
329
428
|
/**
|
|
330
429
|
* How many connection IDs a connection issues in all, its first included.
|
|
331
430
|
* The client retiring one asks for a replacement (§5.1.1, a SHOULD), and
|
|
@@ -381,6 +480,10 @@ export const QUIC_DATAGRAM_ERR_FULL: i32 = -5
|
|
|
381
480
|
|
|
382
481
|
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
383
482
|
const QUIC_CONN_FROM: i32 = 0
|
|
483
|
+
/** A space's key slots (`QuicConnSpace.keySlot`): the read keys', the write keys', and where Application Data's write keys start. */
|
|
484
|
+
const QUIC_CONN_READ_SLOT: i32 = 0
|
|
485
|
+
const QUIC_CONN_WRITE_SLOT: i32 = 1
|
|
486
|
+
const QUIC_CONN_APP_WRITE_SLOTS: i32 = 2
|
|
384
487
|
/** The same for the `i64` arguments of the methods below, where a literal is not given its parameter's type. */
|
|
385
488
|
const QUIC_CONN_NONE: i64 = 0
|
|
386
489
|
/** TLS's unexpected_message alert, which CRYPTO data at a level TLS is not reading is (RFC 8446 §6). */
|
|
@@ -577,6 +680,12 @@ class QuicConnSpace {
|
|
|
577
680
|
*/
|
|
578
681
|
cryptoOut: u8[]
|
|
579
682
|
cryptoOutLength: i32 = 0
|
|
683
|
+
/**
|
|
684
|
+
* How many ack-eliciting probe packets this space still owes, which the
|
|
685
|
+
* window does not hold back: what a probe timeout asked for (RFC 9002
|
|
686
|
+
* §6.2.4), or an early resend (§6.2.3).
|
|
687
|
+
*/
|
|
688
|
+
probes: i32 = 0
|
|
580
689
|
cryptoOutOffset: i64 = 0
|
|
581
690
|
/** How many CRYPTO bytes the client sent here that were handed to TLS, for a duplicate arriving after the next level started. */
|
|
582
691
|
cryptoInDelivered: i64 = 0
|
|
@@ -597,6 +706,12 @@ class QuicConnSpace {
|
|
|
597
706
|
sentControlCount: i32[]
|
|
598
707
|
sentControlKind: u8[]
|
|
599
708
|
sentControlValue: i64[]
|
|
709
|
+
/**
|
|
710
|
+
* Where this space's packet keys live, made once for the slot (QUIC-3):
|
|
711
|
+
* the read keys' and the write keys', and for the Application Data space a
|
|
712
|
+
* second of each, which a key update derives the next generation into.
|
|
713
|
+
*/
|
|
714
|
+
keySlots: QuicKeysSlot[]
|
|
600
715
|
/** `TLS_LEVEL_INITIAL`, `_HANDSHAKE` or `_APPLICATION`, which is also the space's index. */
|
|
601
716
|
level: i32 = 0
|
|
602
717
|
cryptoOutHead: i32 = 0
|
|
@@ -609,13 +724,16 @@ class QuicConnSpace {
|
|
|
609
724
|
discarded: boolean = false
|
|
610
725
|
/** Whether the packet being built elicits an acknowledgement, and so is recorded. */
|
|
611
726
|
stagedEliciting: boolean = false
|
|
612
|
-
/** Whether a probe timeout asked this space for an ack-eliciting packet, which the window does not hold back (RFC 9002 §6.2.4). */
|
|
613
|
-
probe: boolean = false
|
|
614
727
|
|
|
615
728
|
constructor(level: i32) {
|
|
616
729
|
this.level = level
|
|
617
730
|
this.received = new QuicAckRanges()
|
|
618
731
|
this.cryptoOut = []
|
|
732
|
+
this.keySlots = []
|
|
733
|
+
const slots: i32 = level === TLS_LEVEL_APPLICATION ? 4 : 2
|
|
734
|
+
for (let k: i32 = 0; k < slots; k += 1) {
|
|
735
|
+
this.keySlots.push(new QuicKeysSlot())
|
|
736
|
+
}
|
|
619
737
|
const capacity: i32 =
|
|
620
738
|
level === TLS_LEVEL_APPLICATION ? QUIC_RECOVERY_APPLICATION_CAPACITY : QUIC_RECOVERY_HANDSHAKE_CAPACITY
|
|
621
739
|
const rows: i32 = capacity + 1
|
|
@@ -649,23 +767,44 @@ class QuicConnSpace {
|
|
|
649
767
|
this.cryptoResendLow = -1
|
|
650
768
|
this.cryptoResendHigh = -1
|
|
651
769
|
this.discarded = false
|
|
652
|
-
this.
|
|
770
|
+
this.probes = 0
|
|
653
771
|
this.clearStaged()
|
|
654
772
|
}
|
|
655
773
|
|
|
656
|
-
/**
|
|
657
|
-
|
|
658
|
-
|
|
774
|
+
/**
|
|
775
|
+
* Appends `bytes[0 .. length)` to the CRYPTO stream kept, writing into the
|
|
776
|
+
* array's old room before growing it: TLS's output buffer is read in
|
|
777
|
+
* place, so its flight is not copied on the way.
|
|
778
|
+
*/
|
|
779
|
+
appendCrypto(bytes: u8[], length: i32): void {
|
|
780
|
+
for (let k: i32 = 0; k < length && k < toI32(bytes.length); k += 1) {
|
|
659
781
|
const at: i32 = this.cryptoOutLength
|
|
660
782
|
if (at >= 0 && at < toI32(this.cryptoOut.length)) {
|
|
661
|
-
this.cryptoOut[at] =
|
|
783
|
+
this.cryptoOut[at] = bytes[k]
|
|
662
784
|
} else {
|
|
663
|
-
this.cryptoOut.push(
|
|
785
|
+
this.cryptoOut.push(bytes[k])
|
|
664
786
|
}
|
|
665
787
|
this.cryptoOutLength = at + 1
|
|
666
788
|
}
|
|
667
789
|
}
|
|
668
790
|
|
|
791
|
+
/**
|
|
792
|
+
* Key slot `k` of this space: in the Initial and Handshake spaces 0 holds
|
|
793
|
+
* the read keys and 1 the write keys; in Application Data 0 and 1 hold
|
|
794
|
+
* the read keys' generations and 2 and 3 the write keys'.
|
|
795
|
+
*/
|
|
796
|
+
keySlot(k: i32): QuicKeysSlot {
|
|
797
|
+
const at: i32 = k >= 0 && k < toI32(this.keySlots.length) ? k : 0
|
|
798
|
+
return this.keySlots[at]
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** Zeroes every key this space's slots hold, the AES schedules included. */
|
|
802
|
+
wipeKeySlots(): void {
|
|
803
|
+
for (const slot of this.keySlots) {
|
|
804
|
+
slot.wipe()
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
669
808
|
/** The number of STREAM chunks row `row` holds, or 0. */
|
|
670
809
|
streamCount(row: i32): i32 {
|
|
671
810
|
return row >= 0 && row < toI32(this.sentStreamCount.length) ? this.sentStreamCount[row] : 0
|
|
@@ -853,7 +992,14 @@ const quicConnAead = (suite: i32): i32 => {
|
|
|
853
992
|
return QUIC_AEAD_AES_128_GCM
|
|
854
993
|
}
|
|
855
994
|
|
|
856
|
-
/**
|
|
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. */
|
|
857
1003
|
const quicConnWipeKeys = (keys: QuicKeys | null): void => {
|
|
858
1004
|
quicConnWipePacketKey(keys)
|
|
859
1005
|
if (keys !== null) {
|
|
@@ -862,14 +1008,19 @@ const quicConnWipeKeys = (keys: QuicKeys | null): void => {
|
|
|
862
1008
|
}
|
|
863
1009
|
|
|
864
1010
|
/**
|
|
865
|
-
* Wipes a key generation's packet key and
|
|
866
|
-
*
|
|
867
|
-
* §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.
|
|
868
1015
|
*/
|
|
869
1016
|
const quicConnWipePacketKey = (keys: QuicKeys | null): void => {
|
|
870
1017
|
if (keys !== null) {
|
|
871
1018
|
secureZero(keys.key)
|
|
872
1019
|
secureZero(keys.iv)
|
|
1020
|
+
const packetAes: AesKey | null = keys.packetAes
|
|
1021
|
+
if (packetAes !== null) {
|
|
1022
|
+
aesKeyWipe(packetAes)
|
|
1023
|
+
}
|
|
873
1024
|
}
|
|
874
1025
|
}
|
|
875
1026
|
|
|
@@ -888,6 +1039,59 @@ export const quicStatelessResetToken = (key: u8[], cid: u8[]): u8[] => {
|
|
|
888
1039
|
return token
|
|
889
1040
|
}
|
|
890
1041
|
|
|
1042
|
+
/**
|
|
1043
|
+
* `quicStatelessResetToken` of the ID `cid[at .. at + length)` into
|
|
1044
|
+
* `out[outAt ..]`, computed in a connection's own HMAC scratch inside an
|
|
1045
|
+
* arena block, so issuing an ID keeps nothing (QUIC-3). The scratch and the
|
|
1046
|
+
* full MAC are wiped before it returns.
|
|
1047
|
+
*/
|
|
1048
|
+
export const quicConnResetTokenInto = (
|
|
1049
|
+
mac: HmacSha256Scratch,
|
|
1050
|
+
key: u8[],
|
|
1051
|
+
cid: u8[],
|
|
1052
|
+
at: i32,
|
|
1053
|
+
length: i32,
|
|
1054
|
+
out: u8[],
|
|
1055
|
+
outAt: i32
|
|
1056
|
+
): void => {
|
|
1057
|
+
using _scope = arena()
|
|
1058
|
+
const tag: u8[] = new Array<u8>(32)
|
|
1059
|
+
mac.begin(key, QUIC_CONN_FROM, toI32(key.length))
|
|
1060
|
+
mac.update(cid, at, length)
|
|
1061
|
+
mac.finishInto(tag, QUIC_CONN_FROM)
|
|
1062
|
+
for (
|
|
1063
|
+
let k: i32 = 0;
|
|
1064
|
+
k < QUIC_RESET_TOKEN_SIZE && k < toI32(tag.length) && outAt + k < toI32(out.length);
|
|
1065
|
+
k += 1
|
|
1066
|
+
) {
|
|
1067
|
+
out[outAt + k] = tag[k]
|
|
1068
|
+
}
|
|
1069
|
+
secureZero(tag)
|
|
1070
|
+
mac.wipe()
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/** Puts `from[at .. at + length)` in `to`, emptied first, in the room `to` already has. */
|
|
1074
|
+
const quicConnRefill = (to: u8[], from: u8[], at: i32, length: i32): void => {
|
|
1075
|
+
while (to.length > 0) {
|
|
1076
|
+
to.pop()
|
|
1077
|
+
}
|
|
1078
|
+
for (let k: i32 = 0; k < length && at + k >= 0 && at + k < toI32(from.length); k += 1) {
|
|
1079
|
+
to.push(from[at + k])
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
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
|
|
1087
|
+
}
|
|
1088
|
+
|
|
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
|
+
}
|
|
1094
|
+
|
|
891
1095
|
/** Whether the configuration's limits are ones this module can honour. */
|
|
892
1096
|
const quicConnConfigFits = (config: QuicServerConfig): boolean =>
|
|
893
1097
|
config.maxStreamData >= 1 &&
|
|
@@ -931,7 +1135,8 @@ export class QuicConnection {
|
|
|
931
1135
|
/** For a transport error: the type of the frame that caused it, or 0. */
|
|
932
1136
|
errorFrameType: i64 = 0
|
|
933
1137
|
serverRandom: u8[]
|
|
934
|
-
|
|
1138
|
+
/** The x25519 private key `begin` takes from the entropy, until the handshake hands it to `TlsServer`. */
|
|
1139
|
+
readonly ephemeral: Secret<u8[]>
|
|
935
1140
|
cidSeed: u8[]
|
|
936
1141
|
/** The DCID of the client's first Initial, which the Initial keys come from: its first `originalDcidLength` bytes. */
|
|
937
1142
|
originalDcid: u8[]
|
|
@@ -940,6 +1145,37 @@ export class QuicConnection {
|
|
|
940
1145
|
/** The server's first connection ID, the SCID of every long-header packet it sends. */
|
|
941
1146
|
localScid: u8[]
|
|
942
1147
|
tls: TlsServer | null = null
|
|
1148
|
+
/** The slot's `TlsServer`, made by its first handshake and `restart`ed by every one after (QUIC-3). */
|
|
1149
|
+
tlsSlot: TlsServer | null = null
|
|
1150
|
+
/** Its configuration, made once: the parameters it carries are `localEncoded`, rewritten for each connection. */
|
|
1151
|
+
tlsConfig: TlsServerConfig
|
|
1152
|
+
/** The server's transport parameters, set for each connection, and their encoding. */
|
|
1153
|
+
localParameters: QuicTransportParameters
|
|
1154
|
+
localEncoded: u8[]
|
|
1155
|
+
/** The client's transport parameters, parsed into the slot's own object. */
|
|
1156
|
+
parsedParameters: QuicTransportParameters
|
|
1157
|
+
/** Where every key derivation and HMAC of the connection runs. */
|
|
1158
|
+
kdf: HkdfScratch
|
|
1159
|
+
/** The Initial secrets of RFC 9001 §5.2, the client's and the server's. */
|
|
1160
|
+
initialClient: u8[]
|
|
1161
|
+
initialServer: u8[]
|
|
1162
|
+
/**
|
|
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[]>
|
|
1176
|
+
/** A connection ID being issued and its stateless reset token, before the table copies them. */
|
|
1177
|
+
cidScratch: u8[]
|
|
1178
|
+
tokenScratch: u8[]
|
|
943
1179
|
initial: QuicConnSpace
|
|
944
1180
|
handshake: QuicConnSpace
|
|
945
1181
|
application: QuicConnSpace
|
|
@@ -980,14 +1216,10 @@ export class QuicConnection {
|
|
|
980
1216
|
/** When the idle timer last restarted (RFC 9000 §10.1), or -1 before the first packet. */
|
|
981
1217
|
idleSince: i64 = -1
|
|
982
1218
|
/**
|
|
983
|
-
* The
|
|
984
|
-
*
|
|
985
|
-
* the
|
|
986
|
-
* 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).
|
|
987
1222
|
*/
|
|
988
|
-
appReadSecret: u8[]
|
|
989
|
-
appWriteSecret: u8[]
|
|
990
|
-
nextReadSecret: u8[]
|
|
991
1223
|
otherReadKeys: QuicKeys | null = null
|
|
992
1224
|
/** The lowest and highest packet numbers opened with the current read keys, or -1. */
|
|
993
1225
|
readPhaseLowest: i64 = -1
|
|
@@ -996,10 +1228,49 @@ export class QuicConnection {
|
|
|
996
1228
|
writePhaseFirst: i64 = 0
|
|
997
1229
|
/** When the previous read keys go and the next are derived, or -1. */
|
|
998
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
|
|
999
1238
|
/** The packet number of a client key update read but not applied yet, or -1. */
|
|
1000
1239
|
phasePending: i64 = -1
|
|
1001
1240
|
/** The largest DATAGRAM frame the client takes (its `max_datagram_frame_size`), 0 for none. */
|
|
1002
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
|
|
1003
1274
|
originalDcidLength: i32 = 0
|
|
1004
1275
|
peerScidLength: i32 = 0
|
|
1005
1276
|
pathHead: i32 = 0
|
|
@@ -1009,6 +1280,15 @@ export class QuicConnection {
|
|
|
1009
1280
|
dropped: i32 = 0
|
|
1010
1281
|
/** Key updates the client started, against `QUIC_CONN_MAX_KEY_UPDATES`. */
|
|
1011
1282
|
keyUpdates: i32 = 0
|
|
1283
|
+
/** Where the next skipped packet number goes in `skipped`. */
|
|
1284
|
+
skipHead: i32 = 0
|
|
1285
|
+
/** Which of the Application Data space's two read slots, and two write slots, hold the current keys. */
|
|
1286
|
+
appReadSlot: i32 = 0
|
|
1287
|
+
appWriteSlot: i32 = 0
|
|
1288
|
+
/** Handshake data sent again early, against `QUIC_CONN_EARLY_RESENDS` (RFC 9002 §6.2.3). */
|
|
1289
|
+
earlyResends: i32 = 0
|
|
1290
|
+
/** The level at which the packet just read showed the client lacks the server's handshake data, or -1. */
|
|
1291
|
+
resendLevel: i32 = -1
|
|
1012
1292
|
/** Whether `error` is an application code (CONNECTION_CLOSE 0x1d) rather than a transport error. */
|
|
1013
1293
|
errorIsApplication: boolean = false
|
|
1014
1294
|
/** Whether the one CONNECTION_CLOSE this side owes has gone out. */
|
|
@@ -1018,6 +1298,12 @@ export class QuicConnection {
|
|
|
1018
1298
|
/** Whether the handshake is complete, which for a server is also confirmed (RFC 9001 §4.1.2). */
|
|
1019
1299
|
handshakeComplete: boolean = false
|
|
1020
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
|
|
1021
1307
|
/** Whether the connection was set up by `acceptRetry`. */
|
|
1022
1308
|
retried: boolean = false
|
|
1023
1309
|
/** Whether an ack-eliciting packet went out since the last packet was received. */
|
|
@@ -1042,7 +1328,7 @@ export class QuicConnection {
|
|
|
1042
1328
|
constructor(config: QuicServerConfig, entropy: u8[]) {
|
|
1043
1329
|
this.config = config
|
|
1044
1330
|
this.serverRandom = new Array<u8>(32)
|
|
1045
|
-
this.
|
|
1331
|
+
this.ephemeral = secret(new Array<u8>(32))
|
|
1046
1332
|
this.localScid = new Array<u8>(QUIC_CONN_CID_LENGTH)
|
|
1047
1333
|
this.cidSeed = new Array<u8>(32)
|
|
1048
1334
|
this.originalDcid = new Array<u8>(QUIC_MAX_CID_LENGTH)
|
|
@@ -1080,11 +1366,33 @@ export class QuicConnection {
|
|
|
1080
1366
|
this.header = new QuicHeader()
|
|
1081
1367
|
this.packet = new QuicPacket()
|
|
1082
1368
|
this.none = []
|
|
1083
|
-
this.retryOriginalDcid =
|
|
1084
|
-
this.retryScid =
|
|
1085
|
-
this.
|
|
1086
|
-
this.
|
|
1087
|
-
this.
|
|
1369
|
+
this.retryOriginalDcid = []
|
|
1370
|
+
this.retryScid = []
|
|
1371
|
+
this.localParameters = new QuicTransportParameters()
|
|
1372
|
+
this.localEncoded = []
|
|
1373
|
+
this.parsedParameters = new QuicTransportParameters()
|
|
1374
|
+
const extra: u8[] = []
|
|
1375
|
+
this.tlsConfig = {
|
|
1376
|
+
certificateChain: config.certificateChain,
|
|
1377
|
+
alpn: config.alpn,
|
|
1378
|
+
quicTransportParameters: this.localEncoded,
|
|
1379
|
+
extraExtensions: extra,
|
|
1380
|
+
signatureScheme: config.signatureScheme,
|
|
1381
|
+
quic: true,
|
|
1382
|
+
}
|
|
1383
|
+
this.kdf = new HkdfScratch()
|
|
1384
|
+
this.initialClient = new Array<u8>(32)
|
|
1385
|
+
this.initialServer = new Array<u8>(32)
|
|
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))
|
|
1392
|
+
this.cidScratch = new Array<u8>(QUIC_CONN_CID_LENGTH)
|
|
1393
|
+
this.tokenScratch = new Array<u8>(QUIC_RESET_TOKEN_SIZE)
|
|
1394
|
+
this.skipped = new Array<i64>(QUIC_CONN_SKIP_RING)
|
|
1395
|
+
quicConnForgetSkips(this.skipped)
|
|
1088
1396
|
this.begin(entropy)
|
|
1089
1397
|
}
|
|
1090
1398
|
|
|
@@ -1094,8 +1402,8 @@ export class QuicConnection {
|
|
|
1094
1402
|
* array): every secret of the last connection is wiped first, as
|
|
1095
1403
|
* `release()` does, and every buffer is kept and emptied. The limits are
|
|
1096
1404
|
* the constructor's configuration's. A slot reset this way allocates
|
|
1097
|
-
* nothing
|
|
1098
|
-
*
|
|
1405
|
+
* nothing, and neither does the next handshake (QUIC-3 in
|
|
1406
|
+
* `docs/security/quic.md`).
|
|
1099
1407
|
*/
|
|
1100
1408
|
reset(entropy: u8[]): void {
|
|
1101
1409
|
this.wipeAll()
|
|
@@ -1109,6 +1417,7 @@ export class QuicConnection {
|
|
|
1109
1417
|
this.datagramsIn.reset()
|
|
1110
1418
|
this.cryptoIn.reset(TLS_LEVEL_INITIAL)
|
|
1111
1419
|
this.peerParameters = this.defaultParameters
|
|
1420
|
+
// The slot keeps its `TlsServer`, wiped by `wipeAll`; the next handshake restarts it.
|
|
1112
1421
|
this.tls = null
|
|
1113
1422
|
this.error = 0
|
|
1114
1423
|
this.errorFrameType = 0
|
|
@@ -1116,18 +1425,20 @@ export class QuicConnection {
|
|
|
1116
1425
|
this.bytesSent = 0
|
|
1117
1426
|
this.alpn = ""
|
|
1118
1427
|
this.serverName = ""
|
|
1119
|
-
this.retryOriginalDcid
|
|
1120
|
-
this.retryScid
|
|
1121
|
-
this.appReadSecret = this.none
|
|
1122
|
-
this.appWriteSecret = this.none
|
|
1123
|
-
this.nextReadSecret = this.none
|
|
1428
|
+
quicConnRefill(this.retryOriginalDcid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
|
|
1429
|
+
quicConnRefill(this.retryScid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
|
|
1124
1430
|
this.otherReadKeys = null
|
|
1431
|
+
this.appReadSlot = 0
|
|
1432
|
+
this.appWriteSlot = 0
|
|
1433
|
+
this.earlyResends = 0
|
|
1434
|
+
this.resendLevel = -1
|
|
1125
1435
|
this.now = 0
|
|
1126
1436
|
this.idleSince = -1
|
|
1127
1437
|
this.readPhaseLowest = -1
|
|
1128
1438
|
this.readPhaseHighest = -1
|
|
1129
1439
|
this.writePhaseFirst = 0
|
|
1130
1440
|
this.keyRetainUntil = -1
|
|
1441
|
+
this.closingUntil = -1
|
|
1131
1442
|
this.phasePending = -1
|
|
1132
1443
|
this.peerMaxDatagramFrame = 0
|
|
1133
1444
|
this.originalDcidLength = 0
|
|
@@ -1137,10 +1448,20 @@ export class QuicConnection {
|
|
|
1137
1448
|
this.state = QUIC_STATE_WAIT_INITIAL
|
|
1138
1449
|
this.dropped = 0
|
|
1139
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
|
|
1140
1460
|
this.errorIsApplication = false
|
|
1141
1461
|
this.closeSent = false
|
|
1142
1462
|
this.addressValidated = false
|
|
1143
1463
|
this.handshakeComplete = false
|
|
1464
|
+
this.applicationTaken = false
|
|
1144
1465
|
this.handshakeDonePending = false
|
|
1145
1466
|
this.retried = false
|
|
1146
1467
|
this.elicitingSent = false
|
|
@@ -1155,7 +1476,7 @@ export class QuicConnection {
|
|
|
1155
1476
|
/** Takes `entropy` (and wipes it), or closes with INTERNAL_ERROR for entropy or a configuration that does not fit. */
|
|
1156
1477
|
begin(entropy: u8[]): void {
|
|
1157
1478
|
quicCidCopy(this.serverRandom, entropy, 0, 32)
|
|
1158
|
-
|
|
1479
|
+
exposeWith(this.ephemeral, entropy, quicConnEphemeralIn)
|
|
1159
1480
|
quicCidCopy(this.localScid, entropy, 64, QUIC_CONN_CID_LENGTH)
|
|
1160
1481
|
quicCidCopy(this.cidSeed, entropy, 72, 32)
|
|
1161
1482
|
const fits: boolean = toI32(entropy.length) === QUIC_CONN_ENTROPY_SIZE && quicConnConfigFits(this.config)
|
|
@@ -1164,6 +1485,7 @@ export class QuicConnection {
|
|
|
1164
1485
|
this.state = QUIC_STATE_CLOSING
|
|
1165
1486
|
this.error = QUIC_ERROR_INTERNAL
|
|
1166
1487
|
this.closeSent = true
|
|
1488
|
+
this.startClosingPeriod()
|
|
1167
1489
|
}
|
|
1168
1490
|
}
|
|
1169
1491
|
|
|
@@ -1201,8 +1523,8 @@ export class QuicConnection {
|
|
|
1201
1523
|
) {
|
|
1202
1524
|
return false
|
|
1203
1525
|
}
|
|
1204
|
-
this.retryOriginalDcid
|
|
1205
|
-
this.retryScid
|
|
1526
|
+
quicConnRefill(this.retryOriginalDcid, originalDcid, QUIC_CONN_FROM, toI32(originalDcid.length))
|
|
1527
|
+
quicConnRefill(this.retryScid, retryScid, QUIC_CONN_FROM, toI32(retryScid.length))
|
|
1206
1528
|
this.retried = true
|
|
1207
1529
|
return true
|
|
1208
1530
|
}
|
|
@@ -1234,6 +1556,19 @@ export class QuicConnection {
|
|
|
1234
1556
|
this.errorIsApplication = true
|
|
1235
1557
|
}
|
|
1236
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
|
+
|
|
1237
1572
|
/** The space of packets of `type`, or `null` for a 0-RTT or Retry packet. */
|
|
1238
1573
|
spaceOf(type: i32): QuicConnSpace | null {
|
|
1239
1574
|
if (type === QUIC_PACKET_INITIAL) {
|
|
@@ -1352,13 +1687,29 @@ export class QuicConnection {
|
|
|
1352
1687
|
) {
|
|
1353
1688
|
return false
|
|
1354
1689
|
}
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1690
|
+
const secrets: boolean = quicInitialSecretsInto(
|
|
1691
|
+
this.kdf,
|
|
1692
|
+
buf,
|
|
1693
|
+
header.dcidStart,
|
|
1694
|
+
header.dcidLength,
|
|
1695
|
+
this.initialClient,
|
|
1696
|
+
this.initialServer
|
|
1697
|
+
)
|
|
1698
|
+
if (!secrets) {
|
|
1358
1699
|
return false
|
|
1359
1700
|
}
|
|
1360
|
-
this.initial.readKeys =
|
|
1361
|
-
|
|
1701
|
+
this.initial.readKeys = quicKeysInto(
|
|
1702
|
+
this.kdf,
|
|
1703
|
+
this.initial.keySlot(QUIC_CONN_READ_SLOT),
|
|
1704
|
+
QUIC_AEAD_AES_128_GCM,
|
|
1705
|
+
this.initialClient
|
|
1706
|
+
)
|
|
1707
|
+
this.initial.writeKeys = quicKeysInto(
|
|
1708
|
+
this.kdf,
|
|
1709
|
+
this.initial.keySlot(QUIC_CONN_WRITE_SLOT),
|
|
1710
|
+
QUIC_AEAD_AES_128_GCM,
|
|
1711
|
+
this.initialServer
|
|
1712
|
+
)
|
|
1362
1713
|
quicCidCopy(this.originalDcid, buf, header.dcidStart, header.dcidLength)
|
|
1363
1714
|
this.originalDcidLength = header.dcidLength
|
|
1364
1715
|
quicCidCopy(this.peerScid, buf, header.scidStart, header.scidLength)
|
|
@@ -1369,14 +1720,24 @@ export class QuicConnection {
|
|
|
1369
1720
|
|
|
1370
1721
|
/**
|
|
1371
1722
|
* The rest of `start`, once the first Initial has opened: the first local
|
|
1372
|
-
* connection ID, and
|
|
1373
|
-
* IDs (RFC 9000 §7.3), the Retry's when
|
|
1374
|
-
* first ID's stateless reset token
|
|
1723
|
+
* connection ID, and the slot's `TlsServer` started with transport
|
|
1724
|
+
* parameters that name both IDs (RFC 9000 §7.3), the Retry's when
|
|
1725
|
+
* `acceptRetry` set one up, and the first ID's stateless reset token
|
|
1726
|
+
* (§18.2).
|
|
1375
1727
|
*/
|
|
1376
1728
|
establish(): void {
|
|
1377
1729
|
const none: u8[] = this.none
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1730
|
+
const token: u8[] = this.tokenScratch
|
|
1731
|
+
quicConnResetTokenInto(
|
|
1732
|
+
this.kdf.sha256,
|
|
1733
|
+
this.config.statelessResetKey,
|
|
1734
|
+
this.localScid,
|
|
1735
|
+
QUIC_CONN_FROM,
|
|
1736
|
+
QUIC_CONN_CID_LENGTH,
|
|
1737
|
+
token,
|
|
1738
|
+
QUIC_CONN_FROM
|
|
1739
|
+
)
|
|
1740
|
+
this.cids.addLocal(this.localScid, token)
|
|
1380
1741
|
this.cids.addPeerAt(
|
|
1381
1742
|
QUIC_CONN_NONE,
|
|
1382
1743
|
QUIC_CONN_NONE,
|
|
@@ -1390,17 +1751,27 @@ export class QuicConnection {
|
|
|
1390
1751
|
// §8.1.2: a Retry token the listener checked has validated the address.
|
|
1391
1752
|
this.addressValidated = this.retried
|
|
1392
1753
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1754
|
+
// The parameters, and their encoding, are rewritten in the slot's own
|
|
1755
|
+
// arrays, which grow only when a connection needs more than any before.
|
|
1756
|
+
const params: QuicTransportParameters = this.localParameters
|
|
1757
|
+
if (this.retried) {
|
|
1758
|
+
quicConnRefill(
|
|
1759
|
+
params.originalDcid,
|
|
1760
|
+
this.retryOriginalDcid,
|
|
1761
|
+
QUIC_CONN_FROM,
|
|
1762
|
+
toI32(this.retryOriginalDcid.length)
|
|
1763
|
+
)
|
|
1764
|
+
} else {
|
|
1765
|
+
quicConnRefill(params.originalDcid, this.originalDcid, QUIC_CONN_FROM, this.originalDcidLength)
|
|
1766
|
+
}
|
|
1397
1767
|
params.hasOriginalDcid = true
|
|
1398
1768
|
params.initialScid = this.localScid
|
|
1399
1769
|
params.hasInitialScid = true
|
|
1400
|
-
params.retryScid
|
|
1770
|
+
quicConnRefill(params.retryScid, this.retryScid, QUIC_CONN_FROM, toI32(this.retryScid.length))
|
|
1401
1771
|
params.hasRetryScid = this.retried
|
|
1402
|
-
params.statelessResetToken
|
|
1772
|
+
quicConnRefill(params.statelessResetToken, token, QUIC_CONN_FROM, QUIC_RESET_TOKEN_SIZE)
|
|
1403
1773
|
params.hasStatelessResetToken = true
|
|
1774
|
+
secureZero(token)
|
|
1404
1775
|
params.maxIdleTimeout = this.config.maxIdleTimeout
|
|
1405
1776
|
params.initialMaxData = this.config.maxData
|
|
1406
1777
|
params.initialMaxStreamDataBidiRemote = this.config.maxStreamData
|
|
@@ -1415,16 +1786,27 @@ export class QuicConnection {
|
|
|
1415
1786
|
// The server never migrates and asks the client not to (§9): path
|
|
1416
1787
|
// validation beyond answering PATH_CHALLENGE is not here.
|
|
1417
1788
|
params.disableActiveMigration = true
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1789
|
+
quicEncodeTransportParametersInto(params, this.localEncoded)
|
|
1790
|
+
// One `TlsServer` a slot, made by its first handshake (so its flight is
|
|
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.
|
|
1796
|
+
const tls: TlsServer | null = this.tlsSlot
|
|
1797
|
+
if (tls !== null) {
|
|
1798
|
+
{
|
|
1799
|
+
using _scope = arena()
|
|
1800
|
+
tls.restart(this.serverRandom, expose(this.ephemeral, tlsHeldClone))
|
|
1801
|
+
}
|
|
1802
|
+
this.tls = tls
|
|
1803
|
+
} else {
|
|
1804
|
+
const made: TlsServer = new TlsServer(this.tlsConfig, this.serverRandom, new Array<u8>(32))
|
|
1805
|
+
exposeWith(this.ephemeral, made.ephemeral, tlsHeldCopyOver)
|
|
1806
|
+
this.tlsSlot = made
|
|
1807
|
+
this.tls = made
|
|
1426
1808
|
}
|
|
1427
|
-
this.
|
|
1809
|
+
exposeWith(this.ephemeral, QUIC_CONN_FROM, quicConnZeroHeld)
|
|
1428
1810
|
}
|
|
1429
1811
|
|
|
1430
1812
|
/**
|
|
@@ -1460,10 +1842,10 @@ export class QuicConnection {
|
|
|
1460
1842
|
/**
|
|
1461
1843
|
* Opens one packet of a connection already started, and acts on its frames.
|
|
1462
1844
|
* The opening and the frames run inside an arena block (`openScoped`), so
|
|
1463
|
-
* their temporaries go when it ends; what
|
|
1464
|
-
* Initial's `TlsServer
|
|
1465
|
-
* bytes and what it answers — runs after it. Answers whether
|
|
1466
|
-
* was used.
|
|
1845
|
+
* their temporaries go when it ends; what may grow a buffer of the slot —
|
|
1846
|
+
* a first Initial's `TlsServer` and parameters, a key update, TLS reading
|
|
1847
|
+
* the CRYPTO bytes and what it answers — runs after it. Answers whether
|
|
1848
|
+
* the packet was used.
|
|
1467
1849
|
*/
|
|
1468
1850
|
openPacket(buf: u8[], header: QuicHeader, first: boolean): boolean {
|
|
1469
1851
|
const type: i32 = header.type
|
|
@@ -1481,10 +1863,22 @@ export class QuicConnection {
|
|
|
1481
1863
|
this.cryptoArrived = false
|
|
1482
1864
|
this.topUpOwed = false
|
|
1483
1865
|
this.phasePending = -1
|
|
1866
|
+
this.resendLevel = -1
|
|
1484
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
|
+
}
|
|
1485
1879
|
{
|
|
1486
1880
|
using _scope = arena()
|
|
1487
|
-
used = this.openScoped(buf, header, space, keys)
|
|
1881
|
+
used = this.openScoped(buf, header, space, keys, first)
|
|
1488
1882
|
}
|
|
1489
1883
|
if (!used) {
|
|
1490
1884
|
return false
|
|
@@ -1511,6 +1905,9 @@ export class QuicConnection {
|
|
|
1511
1905
|
if (this.topUpOwed) {
|
|
1512
1906
|
this.topUpConnectionIds()
|
|
1513
1907
|
}
|
|
1908
|
+
if (this.resendLevel >= 0 && !this.closed()) {
|
|
1909
|
+
this.resendEarly(this.resendLevel)
|
|
1910
|
+
}
|
|
1514
1911
|
return true
|
|
1515
1912
|
}
|
|
1516
1913
|
|
|
@@ -1521,7 +1918,7 @@ export class QuicConnection {
|
|
|
1521
1918
|
* an allocation, which is what lets the block release the packet's
|
|
1522
1919
|
* temporaries. Answers whether the packet was used rather than dropped.
|
|
1523
1920
|
*/
|
|
1524
|
-
openScoped(buf: u8[], header: QuicHeader, space: QuicConnSpace, keys: QuicKeys): boolean {
|
|
1921
|
+
openScoped(buf: u8[], header: QuicHeader, space: QuicConnSpace, keys: QuicKeys, first: boolean): boolean {
|
|
1525
1922
|
const packet: QuicPacket = this.packet
|
|
1526
1923
|
// Every key generation shares the header-protection key (RFC 9001 §6),
|
|
1527
1924
|
// so the current keys take it off whatever the Key Phase bit says.
|
|
@@ -1536,6 +1933,15 @@ export class QuicConnection {
|
|
|
1536
1933
|
const payload: u8[] | null = quicDecryptPayload(open, buf, header, clear, packet)
|
|
1537
1934
|
if (payload === null) {
|
|
1538
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
|
+
}
|
|
1539
1945
|
return false
|
|
1540
1946
|
}
|
|
1541
1947
|
// §17.2, §17.3.1: reserved bits set under a valid tag close the connection.
|
|
@@ -1555,6 +1961,11 @@ export class QuicConnection {
|
|
|
1555
1961
|
if (this.closed()) {
|
|
1556
1962
|
return true
|
|
1557
1963
|
}
|
|
1964
|
+
// RFC 9002 §6.2.3: a client that sends an ack-eliciting Handshake packet
|
|
1965
|
+
// while the server's Handshake data is in flight may not have it all.
|
|
1966
|
+
if (eliciting && type === QUIC_PACKET_HANDSHAKE) {
|
|
1967
|
+
this.resendLevel = TLS_LEVEL_HANDSHAKE
|
|
1968
|
+
}
|
|
1558
1969
|
space.received.record(pn, eliciting)
|
|
1559
1970
|
// §10.1: a packet received and processed restarts the idle timer.
|
|
1560
1971
|
this.idleSince = this.now
|
|
@@ -1619,9 +2030,14 @@ export class QuicConnection {
|
|
|
1619
2030
|
this.application.readKeys = this.otherReadKeys
|
|
1620
2031
|
this.otherReadKeys = previous
|
|
1621
2032
|
this.otherIsNext = false
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2033
|
+
this.appReadSlot = 1 - this.appReadSlot
|
|
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)
|
|
1625
2041
|
this.readPhase = !this.readPhase
|
|
1626
2042
|
this.readPhaseLowest = pn
|
|
1627
2043
|
this.readPhaseHighest = pn
|
|
@@ -1639,24 +2055,68 @@ export class QuicConnection {
|
|
|
1639
2055
|
if (keys === null) {
|
|
1640
2056
|
return false
|
|
1641
2057
|
}
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
2058
|
+
const slot: QuicKeysSlot = this.application.keySlot(QUIC_CONN_APP_WRITE_SLOTS + 1 - this.appWriteSlot)
|
|
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)
|
|
1647
2063
|
if (next === null) {
|
|
1648
|
-
secureZero(secret)
|
|
1649
2064
|
return false
|
|
1650
2065
|
}
|
|
1651
2066
|
quicConnWipePacketKey(keys)
|
|
1652
|
-
secureZero(this.appWriteSecret)
|
|
1653
2067
|
this.application.writeKeys = next
|
|
1654
|
-
this.
|
|
2068
|
+
this.appWriteSlot = 1 - this.appWriteSlot
|
|
1655
2069
|
this.writePhase = !this.writePhase
|
|
1656
2070
|
this.writePhaseFirst = this.application.nextPn
|
|
2071
|
+
this.sealedUnderKey = 0
|
|
1657
2072
|
return true
|
|
1658
2073
|
}
|
|
1659
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
|
+
|
|
1660
2120
|
/**
|
|
1661
2121
|
* Derives the next generation's read secret and keys from the current
|
|
1662
2122
|
* ones, into `otherReadKeys`, wiping the previous generation's key and IV
|
|
@@ -1673,12 +2133,11 @@ export class QuicConnection {
|
|
|
1673
2133
|
if (keys === null) {
|
|
1674
2134
|
return
|
|
1675
2135
|
}
|
|
1676
|
-
const
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
this.otherReadKeys = quicKeysUpdate(keys, secret)
|
|
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)
|
|
1682
2141
|
this.otherIsNext = this.otherReadKeys !== null
|
|
1683
2142
|
}
|
|
1684
2143
|
|
|
@@ -1873,6 +2332,7 @@ export class QuicConnection {
|
|
|
1873
2332
|
this.error = code
|
|
1874
2333
|
this.errorIsApplication = application
|
|
1875
2334
|
this.closeSent = true
|
|
2335
|
+
this.startClosingPeriod()
|
|
1876
2336
|
}
|
|
1877
2337
|
|
|
1878
2338
|
/**
|
|
@@ -1882,7 +2342,7 @@ export class QuicConnection {
|
|
|
1882
2342
|
* carried, and each it shows lost has its frames queued again.
|
|
1883
2343
|
*/
|
|
1884
2344
|
receiveAck(space: QuicConnSpace, frame: QuicFrame): void {
|
|
1885
|
-
if (frame.largest >= space.nextPn) {
|
|
2345
|
+
if (frame.largest >= space.nextPn || (space.level === TLS_LEVEL_APPLICATION && this.acksSkipped(frame))) {
|
|
1886
2346
|
this.fail(QUIC_ERROR_PROTOCOL_VIOLATION, toI64(frame.type))
|
|
1887
2347
|
return
|
|
1888
2348
|
}
|
|
@@ -1891,9 +2351,24 @@ export class QuicConnection {
|
|
|
1891
2351
|
}
|
|
1892
2352
|
const sent: QuicSentPackets | null = this.recovery.space(space.level)
|
|
1893
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
|
|
1894
2360
|
if (
|
|
1895
2361
|
sent === null ||
|
|
1896
|
-
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
|
|
1897
2372
|
) {
|
|
1898
2373
|
return
|
|
1899
2374
|
}
|
|
@@ -1903,6 +2378,26 @@ export class QuicConnection {
|
|
|
1903
2378
|
this.packetsLost(space, sent)
|
|
1904
2379
|
}
|
|
1905
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
|
+
|
|
1906
2401
|
/**
|
|
1907
2402
|
* An ACK frame's ACK Delay in milliseconds: the field scaled by the
|
|
1908
2403
|
* client's `ack_delay_exponent` (RFC 9000 §19.3, §18.2). A value too large
|
|
@@ -1984,10 +2479,12 @@ export class QuicConnection {
|
|
|
1984
2479
|
|
|
1985
2480
|
/**
|
|
1986
2481
|
* A probe timeout fired (RFC 9002 §6.2.4): in every space with packets in
|
|
1987
|
-
* flight, everything they carry is queued again and the next
|
|
1988
|
-
*
|
|
2482
|
+
* flight, everything they carry is queued again and the next packets are
|
|
2483
|
+
* probes, sent whatever the window says, with a PING if nothing is left to
|
|
1989
2484
|
* carry. During the handshake that resends the Initial and the Handshake
|
|
1990
|
-
* flight together, which is what a client that lost both needs
|
|
2485
|
+
* flight together, which is what a client that lost both needs, and does
|
|
2486
|
+
* it twice (`QUIC_CONN_HANDSHAKE_PROBES`), so that one more lost datagram
|
|
2487
|
+
* does not cost another, doubled, probe timeout.
|
|
1991
2488
|
*/
|
|
1992
2489
|
probe(): void {
|
|
1993
2490
|
for (let level: i32 = 0; level < 3; level += 1) {
|
|
@@ -2002,7 +2499,56 @@ export class QuicConnection {
|
|
|
2002
2499
|
this.requeue(space, slot)
|
|
2003
2500
|
}
|
|
2004
2501
|
}
|
|
2005
|
-
space.
|
|
2502
|
+
space.probes = QUIC_CONN_HANDSHAKE_PROBES
|
|
2503
|
+
if (level === TLS_LEVEL_APPLICATION) {
|
|
2504
|
+
space.probes = 1
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
/**
|
|
2510
|
+
* Speeds up the handshake (RFC 9002 §6.2.3): the client showed at `level`
|
|
2511
|
+
* that it lacks the server's handshake data there — an Initial with CRYPTO
|
|
2512
|
+
* data the server already read, or an ack-eliciting Handshake packet —
|
|
2513
|
+
* so, while the server's own packets at that level are in flight, the
|
|
2514
|
+
* oldest one's CRYPTO data at that level and at Handshake goes again now,
|
|
2515
|
+
* as one probe datagram, rather than at the probe timeout. It does so at
|
|
2516
|
+
* most `QUIC_CONN_EARLY_RESENDS` times a connection, and not while an
|
|
2517
|
+
* earlier probe is still owed, so each packet the client sends asks for
|
|
2518
|
+
* one datagram at most. The anti-amplification limit holds it like any
|
|
2519
|
+
* datagram (`takeDatagramInto`).
|
|
2520
|
+
*/
|
|
2521
|
+
resendEarly(level: i32): void {
|
|
2522
|
+
if (
|
|
2523
|
+
this.earlyResends >= QUIC_CONN_EARLY_RESENDS ||
|
|
2524
|
+
this.initial.probes > 0 ||
|
|
2525
|
+
this.handshake.probes > 0
|
|
2526
|
+
) {
|
|
2527
|
+
return
|
|
2528
|
+
}
|
|
2529
|
+
const asked: QuicSentPackets | null = this.recovery.space(level)
|
|
2530
|
+
if (asked === null || asked.inFlight === 0) {
|
|
2531
|
+
return
|
|
2532
|
+
}
|
|
2533
|
+
let queued: boolean = false
|
|
2534
|
+
for (let at: i32 = level; at <= TLS_LEVEL_HANDSHAKE; at += 1) {
|
|
2535
|
+
const space: QuicConnSpace = this.spaceAt(at)
|
|
2536
|
+
const sent: QuicSentPackets | null = this.recovery.space(at)
|
|
2537
|
+
if (sent === null || space.discarded || sent.inFlight === 0) {
|
|
2538
|
+
continue
|
|
2539
|
+
}
|
|
2540
|
+
// Compacted, the ring's oldest slot is the oldest packet in flight.
|
|
2541
|
+
sent.compact()
|
|
2542
|
+
const oldest: i32 = sent.slot(QUIC_CONN_FROM)
|
|
2543
|
+
const length: i32 = space.cryptoLength(oldest)
|
|
2544
|
+
if (length > 0) {
|
|
2545
|
+
space.resendCrypto(space.cryptoOffset(oldest), length)
|
|
2546
|
+
space.probes = 1
|
|
2547
|
+
queued = true
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
if (queued) {
|
|
2551
|
+
this.earlyResends += 1
|
|
2006
2552
|
}
|
|
2007
2553
|
}
|
|
2008
2554
|
|
|
@@ -2026,6 +2572,11 @@ export class QuicConnection {
|
|
|
2026
2572
|
this.spaceAt(reassembly.level).cryptoInDelivered = reassembly.delivered
|
|
2027
2573
|
reassembly.reset(space.level)
|
|
2028
2574
|
}
|
|
2575
|
+
// RFC 9002 §6.2.3: an Initial with CRYPTO data the server already read
|
|
2576
|
+
// says the client did not get the server's Initial flight.
|
|
2577
|
+
if (space.level === TLS_LEVEL_INITIAL && frame.offset < reassembly.delivered) {
|
|
2578
|
+
this.resendLevel = TLS_LEVEL_INITIAL
|
|
2579
|
+
}
|
|
2029
2580
|
if (!reassembly.insert(frame.offset, payload, frame.dataStart, frame.dataLength)) {
|
|
2030
2581
|
this.fail(QUIC_ERROR_CRYPTO_BUFFER_EXCEEDED, toI64(QUIC_FRAME_CRYPTO))
|
|
2031
2582
|
return
|
|
@@ -2116,10 +2667,12 @@ export class QuicConnection {
|
|
|
2116
2667
|
return
|
|
2117
2668
|
}
|
|
2118
2669
|
if (!this.initial.discarded) {
|
|
2119
|
-
this.initial.appendCrypto(tls.
|
|
2670
|
+
this.initial.appendCrypto(tls.outputInitial, tls.outputInitialLength)
|
|
2671
|
+
tls.clearOutput(TLS_LEVEL_INITIAL)
|
|
2120
2672
|
}
|
|
2121
2673
|
if (!this.handshake.discarded) {
|
|
2122
|
-
this.handshake.appendCrypto(tls.
|
|
2674
|
+
this.handshake.appendCrypto(tls.outputHandshake, tls.outputHandshakeLength)
|
|
2675
|
+
tls.clearOutput(TLS_LEVEL_HANDSHAKE)
|
|
2123
2676
|
}
|
|
2124
2677
|
const aead: i32 = quicConnAead(tls.suite)
|
|
2125
2678
|
if (
|
|
@@ -2132,23 +2685,59 @@ export class QuicConnection {
|
|
|
2132
2685
|
}
|
|
2133
2686
|
this.alpn = tls.alpn
|
|
2134
2687
|
this.serverName = tls.serverName
|
|
2135
|
-
|
|
2136
|
-
|
|
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
|
+
}
|
|
2137
2702
|
}
|
|
2138
2703
|
if (
|
|
2704
|
+
!this.applicationTaken &&
|
|
2139
2705
|
!this.application.discarded &&
|
|
2140
2706
|
this.application.writeKeys === null &&
|
|
2141
2707
|
tls.state !== TLS_STATE_WAIT_SIGNATURE
|
|
2142
2708
|
) {
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
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
|
|
2716
|
+
)
|
|
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
|
+
}
|
|
2152
2741
|
this.prepareNextReadKeys()
|
|
2153
2742
|
}
|
|
2154
2743
|
}
|
|
@@ -2158,18 +2747,14 @@ export class QuicConnection {
|
|
|
2158
2747
|
this.handshakeDonePending = true
|
|
2159
2748
|
this.state = QUIC_STATE_CONNECTED
|
|
2160
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)
|
|
2161
2754
|
this.topUpConnectionIds()
|
|
2162
2755
|
}
|
|
2163
2756
|
}
|
|
2164
2757
|
|
|
2165
|
-
/** `quicKeys` of a secret TLS has, or `null` when it has none yet. */
|
|
2166
|
-
keysFor(aead: i32, secret: u8[] | null): QuicKeys | null {
|
|
2167
|
-
if (secret === null) {
|
|
2168
|
-
return null
|
|
2169
|
-
}
|
|
2170
|
-
return quicKeys(aead, secret)
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
2758
|
/**
|
|
2174
2759
|
* The client's transport parameters (RFC 9000 §7.3, §7.4, §18.2): they must
|
|
2175
2760
|
* parse, and must name as `initial_source_connection_id` the SCID its first
|
|
@@ -2179,7 +2764,11 @@ export class QuicConnection {
|
|
|
2179
2764
|
* the datagrams its `max_datagram_frame_size`.
|
|
2180
2765
|
*/
|
|
2181
2766
|
checkPeerParameters(tls: TlsServer): boolean {
|
|
2182
|
-
const p: QuicTransportParameters =
|
|
2767
|
+
const p: QuicTransportParameters = quicParseTransportParametersInto(
|
|
2768
|
+
this.parsedParameters,
|
|
2769
|
+
tls.clientTransportParameters,
|
|
2770
|
+
false
|
|
2771
|
+
)
|
|
2183
2772
|
if (p.error !== QUIC_ERROR_NO_ERROR || !p.hasInitialScid) {
|
|
2184
2773
|
this.fail(QUIC_ERROR_TRANSPORT_PARAMETER, toI64(QUIC_FRAME_CRYPTO))
|
|
2185
2774
|
return false
|
|
@@ -2215,9 +2804,10 @@ export class QuicConnection {
|
|
|
2215
2804
|
this.recovery.discardSpace(space.level)
|
|
2216
2805
|
space.cryptoResendLow = -1
|
|
2217
2806
|
space.cryptoResendHigh = -1
|
|
2218
|
-
space.
|
|
2807
|
+
space.probes = 0
|
|
2219
2808
|
quicConnWipeKeys(space.readKeys)
|
|
2220
2809
|
quicConnWipeKeys(space.writeKeys)
|
|
2810
|
+
space.wipeKeySlots()
|
|
2221
2811
|
space.readKeys = null
|
|
2222
2812
|
space.writeKeys = null
|
|
2223
2813
|
space.cryptoOutLength = 0
|
|
@@ -2227,15 +2817,30 @@ export class QuicConnection {
|
|
|
2227
2817
|
/**
|
|
2228
2818
|
* The HMAC-SHA256 under the connection's seed of the sequence number
|
|
2229
2819
|
* `sequence`, whose first 8 bytes are the local connection ID of that
|
|
2230
|
-
* number
|
|
2231
|
-
* more entropy.
|
|
2820
|
+
* number, into `cidScratch`: so the IDs are unlinkable to anyone without
|
|
2821
|
+
* the seed and need no more entropy. It runs in the connection's HMAC
|
|
2822
|
+
* scratch inside an arena block, and wipes the MAC and the scratch.
|
|
2232
2823
|
*/
|
|
2233
|
-
deriveConnectionId(sequence: i64):
|
|
2824
|
+
deriveConnectionId(sequence: i64): void {
|
|
2825
|
+
const mac: HmacSha256Scratch = this.kdf.sha256
|
|
2826
|
+
using _scope = arena()
|
|
2234
2827
|
const counter: u8[] = new Array<u8>(8)
|
|
2235
2828
|
for (let k: i32 = 0; k < 8 && k < toI32(counter.length); k += 1) {
|
|
2236
2829
|
counter[k] = toU8(toI32((sequence >> (toI64(7 - k) * 8)) & 255))
|
|
2237
2830
|
}
|
|
2238
|
-
|
|
2831
|
+
const tag: u8[] = new Array<u8>(32)
|
|
2832
|
+
mac.begin(this.cidSeed, QUIC_CONN_FROM, toI32(this.cidSeed.length))
|
|
2833
|
+
mac.update(counter, QUIC_CONN_FROM, toI32(counter.length))
|
|
2834
|
+
mac.finishInto(tag, QUIC_CONN_FROM)
|
|
2835
|
+
for (
|
|
2836
|
+
let k: i32 = 0;
|
|
2837
|
+
k < QUIC_CONN_CID_LENGTH && k < toI32(tag.length) && k < toI32(this.cidScratch.length);
|
|
2838
|
+
k += 1
|
|
2839
|
+
) {
|
|
2840
|
+
this.cidScratch[k] = tag[k]
|
|
2841
|
+
}
|
|
2842
|
+
secureZero(tag)
|
|
2843
|
+
mac.wipe()
|
|
2239
2844
|
}
|
|
2240
2845
|
|
|
2241
2846
|
/**
|
|
@@ -2243,27 +2848,34 @@ export class QuicConnection {
|
|
|
2243
2848
|
* would take (its `active_connection_id_limit`), up to
|
|
2244
2849
|
* `QUIC_CONN_LOCAL_CIDS` at once and `QUIC_CONN_MAX_ISSUED_CIDS` in all;
|
|
2245
2850
|
* each goes out in a NEW_CONNECTION_ID frame with the stateless reset
|
|
2246
|
-
* token the configuration's static key gives it.
|
|
2247
|
-
*
|
|
2248
|
-
*
|
|
2249
|
-
* across its passes for no reason.
|
|
2851
|
+
* token the configuration's static key gives it. Each ID and token is made
|
|
2852
|
+
* in the slot's scratch and copied into the table, so issuing keeps
|
|
2853
|
+
* nothing (QUIC-3).
|
|
2250
2854
|
*/
|
|
2251
2855
|
topUpConnectionIds(): void {
|
|
2252
|
-
if (!this.handshakeComplete
|
|
2856
|
+
if (!this.handshakeComplete) {
|
|
2253
2857
|
return
|
|
2254
2858
|
}
|
|
2255
2859
|
let want: i64 = this.peerParameters.activeConnectionIdLimit
|
|
2256
2860
|
if (want > toI64(QUIC_CONN_LOCAL_CIDS)) {
|
|
2257
2861
|
want = toI64(QUIC_CONN_LOCAL_CIDS)
|
|
2258
2862
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2863
|
+
while (this.cids.nextLocal < QUIC_CONN_MAX_ISSUED_CIDS && toI64(this.cids.activeLocal()) < want) {
|
|
2864
|
+
this.deriveConnectionId(this.cids.nextLocal)
|
|
2865
|
+
quicConnResetTokenInto(
|
|
2866
|
+
this.kdf.sha256,
|
|
2867
|
+
this.config.statelessResetKey,
|
|
2868
|
+
this.cidScratch,
|
|
2869
|
+
QUIC_CONN_FROM,
|
|
2870
|
+
QUIC_CONN_CID_LENGTH,
|
|
2871
|
+
this.tokenScratch,
|
|
2872
|
+
QUIC_CONN_FROM
|
|
2873
|
+
)
|
|
2874
|
+
const added: i64 = this.cids.addLocal(this.cidScratch, this.tokenScratch)
|
|
2875
|
+
secureZero(this.tokenScratch)
|
|
2876
|
+
if (added < 0) {
|
|
2877
|
+
return
|
|
2878
|
+
}
|
|
2267
2879
|
}
|
|
2268
2880
|
}
|
|
2269
2881
|
|
|
@@ -2330,12 +2942,17 @@ export class QuicConnection {
|
|
|
2330
2942
|
* When `handleTimer` is next due, in the caller's milliseconds: the idle
|
|
2331
2943
|
* timeout, loss recovery's timer (a time-threshold loss or the probe
|
|
2332
2944
|
* timeout), or the end of a key update's probe timeout, whichever comes
|
|
2333
|
-
* first
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
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.
|
|
2336
2950
|
*/
|
|
2337
2951
|
deadline(): i64 {
|
|
2338
|
-
if (this.closed()
|
|
2952
|
+
if (this.closed()) {
|
|
2953
|
+
return this.closingUntil
|
|
2954
|
+
}
|
|
2955
|
+
if (this.state === QUIC_STATE_WAIT_INITIAL) {
|
|
2339
2956
|
return -1
|
|
2340
2957
|
}
|
|
2341
2958
|
let due: i64 = this.idleDeadline()
|
|
@@ -2362,9 +2979,10 @@ export class QuicConnection {
|
|
|
2362
2979
|
* Runs whatever is due by `now`, the caller's monotonic time in
|
|
2363
2980
|
* milliseconds; call it at `deadline()`, or any time. Past the idle timeout
|
|
2364
2981
|
* the connection closes silently (`QUIC_STATE_TIMED_OUT`, RFC 9000 §10.1):
|
|
2365
|
-
* no CONNECTION_CLOSE, every key wiped. Past
|
|
2366
|
-
*
|
|
2367
|
-
*
|
|
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
|
|
2368
2986
|
* threshold have their frames queued again, or a probe timeout queues
|
|
2369
2987
|
* every space's packets in flight and asks for a probe (RFC 9002 §6.1.2,
|
|
2370
2988
|
* §6.2.4): `takeDatagram` then sends it. `receive` and `takeDatagram` run
|
|
@@ -2376,12 +2994,24 @@ export class QuicConnection {
|
|
|
2376
2994
|
this.runRecoveryTimer()
|
|
2377
2995
|
}
|
|
2378
2996
|
|
|
2379
|
-
/**
|
|
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
|
+
*/
|
|
2380
3001
|
runClocks(now: i64): void {
|
|
2381
3002
|
if (now > this.now) {
|
|
2382
3003
|
this.now = now
|
|
2383
3004
|
}
|
|
2384
|
-
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) {
|
|
2385
3015
|
return
|
|
2386
3016
|
}
|
|
2387
3017
|
const idle: i64 = this.idleDeadline()
|
|
@@ -2436,6 +3066,24 @@ export class QuicConnection {
|
|
|
2436
3066
|
return this.updateWriteKeys()
|
|
2437
3067
|
}
|
|
2438
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
|
+
|
|
2439
3087
|
// ---- Streams ------------------------------------------------------------------
|
|
2440
3088
|
|
|
2441
3089
|
/**
|
|
@@ -2681,6 +3329,7 @@ export class QuicConnection {
|
|
|
2681
3329
|
return 0
|
|
2682
3330
|
}
|
|
2683
3331
|
let n: i32 = 0
|
|
3332
|
+
this.datagramEcn = 0
|
|
2684
3333
|
if (this.state === QUIC_STATE_CLOSING) {
|
|
2685
3334
|
using _scope = arena()
|
|
2686
3335
|
n = this.takeCloseInto(out, at)
|
|
@@ -2694,6 +3343,8 @@ export class QuicConnection {
|
|
|
2694
3343
|
if (this.amplificationBlocked()) {
|
|
2695
3344
|
return 0
|
|
2696
3345
|
}
|
|
3346
|
+
this.beforeDatagram()
|
|
3347
|
+
this.datagramEcn = this.ecnMarking ? this.recovery.ecnCodepoint() : 0
|
|
2697
3348
|
{
|
|
2698
3349
|
using _scope = arena()
|
|
2699
3350
|
n = this.buildDatagram(out, at)
|
|
@@ -2701,6 +3352,32 @@ export class QuicConnection {
|
|
|
2701
3352
|
return n
|
|
2702
3353
|
}
|
|
2703
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
|
+
|
|
2704
3381
|
/**
|
|
2705
3382
|
* `takeDatagramInto`'s datagram, built and sealed into `out` at `at`: one
|
|
2706
3383
|
* packet per space with something to carry, each written in place, the
|
|
@@ -2722,7 +3399,7 @@ export class QuicConnection {
|
|
|
2722
3399
|
if (sent === null || overhead === 0 || remaining <= overhead + 8) {
|
|
2723
3400
|
continue
|
|
2724
3401
|
}
|
|
2725
|
-
if (space.
|
|
3402
|
+
if (space.probes > 0 && sent.full()) {
|
|
2726
3403
|
// A probe has to be recorded: the oldest packet gives up its slot,
|
|
2727
3404
|
// and what it carried, already queued again by the probe, stays queued.
|
|
2728
3405
|
const slot: i32 = this.recovery.evictOldest(level)
|
|
@@ -2730,7 +3407,7 @@ export class QuicConnection {
|
|
|
2730
3407
|
this.requeue(space, slot)
|
|
2731
3408
|
}
|
|
2732
3409
|
}
|
|
2733
|
-
const elicit: boolean = space.
|
|
3410
|
+
const elicit: boolean = space.probes > 0 || (open && !sent.full())
|
|
2734
3411
|
const headerLength: i32 = overhead - QUIC_AEAD_TAG_SIZE
|
|
2735
3412
|
const payloadStart: i32 = position + headerLength
|
|
2736
3413
|
let payloadEnd: i32 = this.buildPayloadInto(
|
|
@@ -2842,7 +3519,7 @@ export class QuicConnection {
|
|
|
2842
3519
|
if (space.level === TLS_LEVEL_APPLICATION) {
|
|
2843
3520
|
at = this.buildApplication(space, out, at, end)
|
|
2844
3521
|
}
|
|
2845
|
-
if (space.
|
|
3522
|
+
if (space.probes > 0 && !space.stagedEliciting && at < end) {
|
|
2846
3523
|
at = quicPutTypeOnly(out, at, end, QUIC_FRAME_PING)
|
|
2847
3524
|
space.stagedEliciting = true
|
|
2848
3525
|
}
|
|
@@ -3001,6 +3678,13 @@ export class QuicConnection {
|
|
|
3001
3678
|
* `quicKeys` did not make cause.
|
|
3002
3679
|
*/
|
|
3003
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
|
+
}
|
|
3004
3688
|
const keys: QuicKeys | null = space.writeKeys
|
|
3005
3689
|
const pn: i64 = space.nextPn
|
|
3006
3690
|
const pnLength: i32 = quicPacketNumberLength(pn, space.largestAcked)
|
|
@@ -3040,7 +3724,13 @@ export class QuicConnection {
|
|
|
3040
3724
|
return 0
|
|
3041
3725
|
}
|
|
3042
3726
|
space.nextPn = pn + 1
|
|
3043
|
-
|
|
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
|
+
}
|
|
3044
3734
|
return end - start
|
|
3045
3735
|
}
|
|
3046
3736
|
|
|
@@ -3051,19 +3741,29 @@ export class QuicConnection {
|
|
|
3051
3741
|
* recorded. `buildDatagram` builds nothing ack-eliciting into a space
|
|
3052
3742
|
* whose record is full, so recovery does not refuse one; if it ever did,
|
|
3053
3743
|
* what the packet carried is queued again at once, as though lost.
|
|
3744
|
+
* Answers the slot recovery recorded it in, or -1.
|
|
3054
3745
|
*/
|
|
3055
|
-
recordSent(space: QuicConnSpace, pn: i64, size: i32):
|
|
3746
|
+
recordSent(space: QuicConnSpace, pn: i64, size: i32): i32 {
|
|
3056
3747
|
if (!space.stagedEliciting) {
|
|
3057
|
-
return
|
|
3748
|
+
return -1
|
|
3058
3749
|
}
|
|
3059
3750
|
const slot: i32 = this.recovery.onPacketSent(space.level, pn, size, this.now)
|
|
3060
3751
|
if (slot >= 0) {
|
|
3061
3752
|
space.commitStaged(slot)
|
|
3062
|
-
space.
|
|
3753
|
+
if (space.probes > 0) {
|
|
3754
|
+
space.probes -= 1
|
|
3755
|
+
// §6.2.4: a second probe carries unacknowledged data too. When the
|
|
3756
|
+
// first took everything queued, the oldest — what it just carried —
|
|
3757
|
+
// goes again.
|
|
3758
|
+
if (space.probes > 0 && space.cryptoResendLow < 0) {
|
|
3759
|
+
this.requeue(space, slot)
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3063
3762
|
} else {
|
|
3064
3763
|
this.requeue(space, space.staging)
|
|
3065
3764
|
}
|
|
3066
3765
|
space.clearStaged()
|
|
3766
|
+
return slot
|
|
3067
3767
|
}
|
|
3068
3768
|
|
|
3069
3769
|
/** An acknowledged packet in row `row`: each stream chunk and stream control frame it carried is settled. */
|
|
@@ -3109,6 +3809,7 @@ export class QuicConnection {
|
|
|
3109
3809
|
return 0
|
|
3110
3810
|
}
|
|
3111
3811
|
this.closeSent = true
|
|
3812
|
+
this.startClosingPeriod()
|
|
3112
3813
|
let position: i32 = at
|
|
3113
3814
|
const limit: i32 = at + QUIC_CONN_DATAGRAM_SIZE
|
|
3114
3815
|
for (let level: i32 = 0; level < 3; level += 1) {
|
|
@@ -3159,10 +3860,13 @@ export class QuicConnection {
|
|
|
3159
3860
|
* secrets and expected client Finished, the ephemeral key, the
|
|
3160
3861
|
* connection-ID seed and the stateless reset tokens (QUIC-2). Call it when
|
|
3161
3862
|
* the connection is done with (or `reset` the slot, which does the same
|
|
3162
|
-
* 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.
|
|
3163
3866
|
*/
|
|
3164
3867
|
release(): void {
|
|
3165
3868
|
this.wipeAll()
|
|
3869
|
+
this.streams.giveBack()
|
|
3166
3870
|
if (!this.closed()) {
|
|
3167
3871
|
this.state = QUIC_STATE_CLOSING
|
|
3168
3872
|
this.error = QUIC_ERROR_NO_ERROR
|
|
@@ -3172,9 +3876,10 @@ export class QuicConnection {
|
|
|
3172
3876
|
|
|
3173
3877
|
/**
|
|
3174
3878
|
* Wipes every key and secret the connection holds and discards every
|
|
3175
|
-
* level: `release()`'s work, the idle timeout's
|
|
3176
|
-
*
|
|
3177
|
-
* 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.
|
|
3178
3883
|
*/
|
|
3179
3884
|
wipeAll(): void {
|
|
3180
3885
|
this.discard(this.initial)
|
|
@@ -3184,11 +3889,11 @@ export class QuicConnection {
|
|
|
3184
3889
|
this.otherReadKeys = null
|
|
3185
3890
|
this.otherIsNext = false
|
|
3186
3891
|
this.keyRetainUntil = -1
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
secureZero(this.
|
|
3892
|
+
this.closingUntil = -1
|
|
3893
|
+
this.wipe()
|
|
3894
|
+
secureZero(this.initialClient)
|
|
3895
|
+
secureZero(this.initialServer)
|
|
3190
3896
|
secureZero(this.cidSeed)
|
|
3191
|
-
secureZero(this.ephemeralPrivate)
|
|
3192
3897
|
// The stateless reset tokens of both sides' IDs: either one ends the
|
|
3193
3898
|
// connection for whoever learns it (RFC 9000 §10.3).
|
|
3194
3899
|
for (const entry of this.cids.local) {
|
|
@@ -3198,17 +3903,28 @@ export class QuicConnection {
|
|
|
3198
3903
|
secureZero(entry.resetToken)
|
|
3199
3904
|
}
|
|
3200
3905
|
secureZero(this.peerParameters.statelessResetToken)
|
|
3906
|
+
secureZero(this.localParameters.statelessResetToken)
|
|
3201
3907
|
const tls: TlsServer | null = this.tls
|
|
3202
3908
|
if (tls !== null) {
|
|
3203
3909
|
// The server's encoded transport parameters carry its first ID's token.
|
|
3204
3910
|
secureZero(tls.config.quicTransportParameters)
|
|
3205
|
-
|
|
3206
|
-
secureZero(tls.handshakeSecret)
|
|
3207
|
-
secureZero(tls.clientHandshakeSecret)
|
|
3208
|
-
secureZero(tls.serverHandshakeSecret)
|
|
3209
|
-
secureZero(tls.clientApplicationSecret)
|
|
3210
|
-
secureZero(tls.serverApplicationSecret)
|
|
3211
|
-
secureZero(tls.exporterSecret)
|
|
3911
|
+
tls.wipe()
|
|
3212
3912
|
}
|
|
3213
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
|
+
}
|
|
3214
3930
|
}
|