@amritk/nish-x86_64-linux 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.h +20 -6
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +11 -0
- package/runtime/runtime.c +20 -10
- package/scripts/build.sh +13 -9
- package/std/README.md +9 -8
- package/std/crypto/p256.ts +473 -10
- package/std/json.ts +297 -41
- package/std/net/http3-server.ts +22 -16
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +489 -213
- package/std/net/quic-packet.ts +226 -2
- package/std/net/quic-stream.ts +10 -4
- package/std/net/quic.ts +484 -120
- package/std/net/tls-tcp.ts +8 -4
- package/std/net/tls.ts +114 -45
- package/std/text.ts +77 -0
package/std/net/quic.ts
CHANGED
|
@@ -58,10 +58,15 @@
|
|
|
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`.
|
|
65
70
|
*
|
|
66
71
|
* **Nothing allocated per packet** (QUIC-3). A connection is a slot: every
|
|
67
72
|
* buffer it uses — the packet record, the stream buffers, the CRYPTO
|
|
@@ -70,11 +75,15 @@
|
|
|
70
75
|
* the next peer. A packet is opened (`receiveWindow`) and a datagram built
|
|
71
76
|
* and sealed (`takeDatagramInto`) inside a `using a = arena()` block, so the
|
|
72
77
|
* 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
|
-
* `
|
|
78
|
+
* the compiler refuses the block if anything in it could store one. The
|
|
79
|
+
* handshake keeps its state in the slot too: one `TlsServer`, made by the
|
|
80
|
+
* slot's first handshake and `restart`ed by each after it; every level's
|
|
81
|
+
* packet keys, and a key update's, derived in place into `QuicKeysSlot`s
|
|
82
|
+
* (`nish/net/quic-packet`) over the slot's HKDF scratch; the Initial
|
|
83
|
+
* secrets, the stateless reset tokens and the connection IDs computed in
|
|
84
|
+
* arena blocks; the transport parameters encoded and parsed into the
|
|
85
|
+
* slot's own objects. After the first connection in a slot, a connection
|
|
86
|
+
* keeps nothing at all (QUIC-3 in `docs/security/quic.md`).
|
|
78
87
|
*
|
|
79
88
|
* **Time.** The connection has no clock: `receive`, `takeDatagram` and
|
|
80
89
|
* `handleTimer` take the caller's monotonic time in milliseconds, and
|
|
@@ -94,11 +103,11 @@
|
|
|
94
103
|
* derivation and shows no timing difference (§6.3); if it opens, the client
|
|
95
104
|
* has updated, and the server's write keys follow before anything is
|
|
96
105
|
* 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.
|
|
106
|
+
* the client has acknowledged a packet of the current phase (§6.1). The
|
|
107
|
+
* generations take turns in two key slots and two secrets a direction, so an
|
|
108
|
+
* update keeps nothing in the arena; each still costs two key derivations of
|
|
109
|
+
* work, so a connection takes at most `QUIC_CONN_MAX_KEY_UPDATES` updates the
|
|
110
|
+
* client starts and closes on the next with KEY_UPDATE_ERROR (QUIC-4).
|
|
102
111
|
*
|
|
103
112
|
* **Sans-IO and deterministic.** No socket, no clock and no random device:
|
|
104
113
|
* every random choice (the TLS server random and ephemeral key, the server's
|
|
@@ -122,25 +131,27 @@
|
|
|
122
131
|
* `maxData`, the connection-ID table by the limit advertised, the received
|
|
123
132
|
* packet numbers by `QUIC_ACK_MAX_RANGES`, and datagrams by their ring.
|
|
124
133
|
*
|
|
125
|
-
* **Secrets.** The packet keys of each level live in this connection's
|
|
126
|
-
*
|
|
134
|
+
* **Secrets.** The packet keys of each level live in this connection's key
|
|
135
|
+
* slots as long as the level does, and in `TlsServer`'s (TLS-1); so do the
|
|
127
136
|
* 1-RTT secrets the next key generation is derived from, and the next
|
|
128
137
|
* generation's read keys. A `Secret` may not be a field (NL2430), so they are
|
|
129
138
|
* plain bytes; `secureZero` wipes each level's key, IV and header-protection
|
|
130
|
-
* key
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* these structs
|
|
139
|
+
* key, and its AES schedules with ordinary stores, when the level is
|
|
140
|
+
* discarded, each generation's key, IV and secret when a key update
|
|
141
|
+
* replaces it, and `release()` (and `reset`) the rest — the 1-RTT keys, the
|
|
142
|
+
* key-update secrets, the traffic secrets `TlsServer` holds, its ephemeral
|
|
143
|
+
* key and the connection-ID seed. What no wipe reaches (`aesKeyInto`'s
|
|
144
|
+
* working copy of a schedule) is recorded as QUIC-2 in
|
|
145
|
+
* `docs/security/quic.md`, with #430, the follow-up that moves these structs
|
|
146
|
+
* onto `nish:secret`.
|
|
137
147
|
*
|
|
138
148
|
* Written from RFC 9000, RFC 9001 and RFC 9221, in this module's own
|
|
139
149
|
* structure; nothing here is ported from another implementation. Private
|
|
140
150
|
* names carry the `quicConn` prefix (`docs/wp26-stdlib.md` §3e).
|
|
141
151
|
*/
|
|
142
152
|
import { timingSafeEqualAt } from "nish/crypto/ct"
|
|
143
|
-
import {
|
|
153
|
+
import { HkdfScratch } from "nish/crypto/hkdf"
|
|
154
|
+
import { HmacSha256Scratch, hmacSha256 } from "nish/crypto/hmac"
|
|
144
155
|
import {
|
|
145
156
|
QUIC_AEAD_AES_128_GCM,
|
|
146
157
|
QUIC_AEAD_AES_256_GCM,
|
|
@@ -154,14 +165,14 @@ import {
|
|
|
154
165
|
QUIC_PACKET_OK,
|
|
155
166
|
QUIC_PACKET_SHORT,
|
|
156
167
|
QuicHeader,
|
|
157
|
-
QuicInitialSecrets,
|
|
158
168
|
QuicKeys,
|
|
169
|
+
QuicKeysSlot,
|
|
159
170
|
QuicPacket,
|
|
160
171
|
quicDecryptPayload,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
172
|
+
quicInitialSecretsInto,
|
|
173
|
+
quicKeyUpdateSecretInto,
|
|
174
|
+
quicKeysInto,
|
|
175
|
+
quicKeysUpdateInto,
|
|
165
176
|
quicPacketNumberLength,
|
|
166
177
|
quicParseHeaderInto,
|
|
167
178
|
quicPutLongHeader,
|
|
@@ -218,8 +229,8 @@ import {
|
|
|
218
229
|
} from "nish/net/quic-frame"
|
|
219
230
|
import {
|
|
220
231
|
QuicTransportParameters,
|
|
221
|
-
|
|
222
|
-
|
|
232
|
+
quicEncodeTransportParametersInto,
|
|
233
|
+
quicParseTransportParametersInto,
|
|
223
234
|
} from "nish/net/quic-conn-params"
|
|
224
235
|
import { QuicAckRanges } from "nish/net/quic-conn-ack"
|
|
225
236
|
import {
|
|
@@ -320,12 +331,31 @@ export const QUIC_CONN_PACKET_STREAMS: i32 = 4
|
|
|
320
331
|
export const QUIC_CONN_PACKET_CONTROL: i32 = 4
|
|
321
332
|
/**
|
|
322
333
|
* 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
|
|
334
|
+
* Each costs two key derivations of work, though none of their memory, so
|
|
335
|
+
* the cap bounds what a client can make the server derive (QUIC-4); the next one
|
|
325
336
|
* closes the connection with KEY_UPDATE_ERROR. A client also has to wait
|
|
326
337
|
* a probe timeout between two of them.
|
|
327
338
|
*/
|
|
328
339
|
export const QUIC_CONN_MAX_KEY_UPDATES: i32 = 64
|
|
340
|
+
/**
|
|
341
|
+
* How many ack-eliciting datagrams a probe timeout sends in the Initial and
|
|
342
|
+
* Handshake spaces (RFC 9002 §6.2.4 allows up to two). The server's first
|
|
343
|
+
* flight is usually one datagram, so with one probe a lossy path loses every
|
|
344
|
+
* copy of it about as often as it loses one datagram per probe timeout; two
|
|
345
|
+
* square that, and stay inside the amplification limit, which a one-datagram
|
|
346
|
+
* flight and two probes fill exactly. The Application Data space sends one.
|
|
347
|
+
*/
|
|
348
|
+
export const QUIC_CONN_HANDSHAKE_PROBES: i32 = 2
|
|
349
|
+
/**
|
|
350
|
+
* How many times a connection resends its handshake data before the probe
|
|
351
|
+
* timeout because the client showed it lacks it (RFC 9002 §6.2.3, "for a
|
|
352
|
+
* limited number of times per connection"): a client Initial carrying
|
|
353
|
+
* CRYPTO data the server already read, or an ack-eliciting Handshake packet,
|
|
354
|
+
* while the server's own CRYPTO data at that level is in flight. Each
|
|
355
|
+
* resend is one datagram, so a client cannot make the server answer more
|
|
356
|
+
* than this many packets that way, whatever it sends.
|
|
357
|
+
*/
|
|
358
|
+
export const QUIC_CONN_EARLY_RESENDS: i32 = 2
|
|
329
359
|
/**
|
|
330
360
|
* How many connection IDs a connection issues in all, its first included.
|
|
331
361
|
* The client retiring one asks for a replacement (§5.1.1, a SHOULD), and
|
|
@@ -381,6 +411,10 @@ export const QUIC_DATAGRAM_ERR_FULL: i32 = -5
|
|
|
381
411
|
|
|
382
412
|
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
383
413
|
const QUIC_CONN_FROM: i32 = 0
|
|
414
|
+
/** A space's key slots (`QuicConnSpace.keySlot`): the read keys', the write keys', and where Application Data's write keys start. */
|
|
415
|
+
const QUIC_CONN_READ_SLOT: i32 = 0
|
|
416
|
+
const QUIC_CONN_WRITE_SLOT: i32 = 1
|
|
417
|
+
const QUIC_CONN_APP_WRITE_SLOTS: i32 = 2
|
|
384
418
|
/** The same for the `i64` arguments of the methods below, where a literal is not given its parameter's type. */
|
|
385
419
|
const QUIC_CONN_NONE: i64 = 0
|
|
386
420
|
/** TLS's unexpected_message alert, which CRYPTO data at a level TLS is not reading is (RFC 8446 §6). */
|
|
@@ -577,6 +611,12 @@ class QuicConnSpace {
|
|
|
577
611
|
*/
|
|
578
612
|
cryptoOut: u8[]
|
|
579
613
|
cryptoOutLength: i32 = 0
|
|
614
|
+
/**
|
|
615
|
+
* How many ack-eliciting probe packets this space still owes, which the
|
|
616
|
+
* window does not hold back: what a probe timeout asked for (RFC 9002
|
|
617
|
+
* §6.2.4), or an early resend (§6.2.3).
|
|
618
|
+
*/
|
|
619
|
+
probes: i32 = 0
|
|
580
620
|
cryptoOutOffset: i64 = 0
|
|
581
621
|
/** How many CRYPTO bytes the client sent here that were handed to TLS, for a duplicate arriving after the next level started. */
|
|
582
622
|
cryptoInDelivered: i64 = 0
|
|
@@ -597,6 +637,12 @@ class QuicConnSpace {
|
|
|
597
637
|
sentControlCount: i32[]
|
|
598
638
|
sentControlKind: u8[]
|
|
599
639
|
sentControlValue: i64[]
|
|
640
|
+
/**
|
|
641
|
+
* Where this space's packet keys live, made once for the slot (QUIC-3):
|
|
642
|
+
* the read keys' and the write keys', and for the Application Data space a
|
|
643
|
+
* second of each, which a key update derives the next generation into.
|
|
644
|
+
*/
|
|
645
|
+
keySlots: QuicKeysSlot[]
|
|
600
646
|
/** `TLS_LEVEL_INITIAL`, `_HANDSHAKE` or `_APPLICATION`, which is also the space's index. */
|
|
601
647
|
level: i32 = 0
|
|
602
648
|
cryptoOutHead: i32 = 0
|
|
@@ -609,13 +655,16 @@ class QuicConnSpace {
|
|
|
609
655
|
discarded: boolean = false
|
|
610
656
|
/** Whether the packet being built elicits an acknowledgement, and so is recorded. */
|
|
611
657
|
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
658
|
|
|
615
659
|
constructor(level: i32) {
|
|
616
660
|
this.level = level
|
|
617
661
|
this.received = new QuicAckRanges()
|
|
618
662
|
this.cryptoOut = []
|
|
663
|
+
this.keySlots = []
|
|
664
|
+
const slots: i32 = level === TLS_LEVEL_APPLICATION ? 4 : 2
|
|
665
|
+
for (let k: i32 = 0; k < slots; k += 1) {
|
|
666
|
+
this.keySlots.push(new QuicKeysSlot())
|
|
667
|
+
}
|
|
619
668
|
const capacity: i32 =
|
|
620
669
|
level === TLS_LEVEL_APPLICATION ? QUIC_RECOVERY_APPLICATION_CAPACITY : QUIC_RECOVERY_HANDSHAKE_CAPACITY
|
|
621
670
|
const rows: i32 = capacity + 1
|
|
@@ -649,23 +698,44 @@ class QuicConnSpace {
|
|
|
649
698
|
this.cryptoResendLow = -1
|
|
650
699
|
this.cryptoResendHigh = -1
|
|
651
700
|
this.discarded = false
|
|
652
|
-
this.
|
|
701
|
+
this.probes = 0
|
|
653
702
|
this.clearStaged()
|
|
654
703
|
}
|
|
655
704
|
|
|
656
|
-
/**
|
|
657
|
-
|
|
658
|
-
|
|
705
|
+
/**
|
|
706
|
+
* Appends `bytes[0 .. length)` to the CRYPTO stream kept, writing into the
|
|
707
|
+
* array's old room before growing it: TLS's output buffer is read in
|
|
708
|
+
* place, so its flight is not copied on the way.
|
|
709
|
+
*/
|
|
710
|
+
appendCrypto(bytes: u8[], length: i32): void {
|
|
711
|
+
for (let k: i32 = 0; k < length && k < toI32(bytes.length); k += 1) {
|
|
659
712
|
const at: i32 = this.cryptoOutLength
|
|
660
713
|
if (at >= 0 && at < toI32(this.cryptoOut.length)) {
|
|
661
|
-
this.cryptoOut[at] =
|
|
714
|
+
this.cryptoOut[at] = bytes[k]
|
|
662
715
|
} else {
|
|
663
|
-
this.cryptoOut.push(
|
|
716
|
+
this.cryptoOut.push(bytes[k])
|
|
664
717
|
}
|
|
665
718
|
this.cryptoOutLength = at + 1
|
|
666
719
|
}
|
|
667
720
|
}
|
|
668
721
|
|
|
722
|
+
/**
|
|
723
|
+
* Key slot `k` of this space: in the Initial and Handshake spaces 0 holds
|
|
724
|
+
* the read keys and 1 the write keys; in Application Data 0 and 1 hold
|
|
725
|
+
* the read keys' generations and 2 and 3 the write keys'.
|
|
726
|
+
*/
|
|
727
|
+
keySlot(k: i32): QuicKeysSlot {
|
|
728
|
+
const at: i32 = k >= 0 && k < toI32(this.keySlots.length) ? k : 0
|
|
729
|
+
return this.keySlots[at]
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/** Zeroes every key this space's slots hold, the AES schedules included. */
|
|
733
|
+
wipeKeySlots(): void {
|
|
734
|
+
for (const slot of this.keySlots) {
|
|
735
|
+
slot.wipe()
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
669
739
|
/** The number of STREAM chunks row `row` holds, or 0. */
|
|
670
740
|
streamCount(row: i32): i32 {
|
|
671
741
|
return row >= 0 && row < toI32(this.sentStreamCount.length) ? this.sentStreamCount[row] : 0
|
|
@@ -888,6 +958,68 @@ export const quicStatelessResetToken = (key: u8[], cid: u8[]): u8[] => {
|
|
|
888
958
|
return token
|
|
889
959
|
}
|
|
890
960
|
|
|
961
|
+
/**
|
|
962
|
+
* `quicStatelessResetToken` of the ID `cid[at .. at + length)` into
|
|
963
|
+
* `out[outAt ..]`, computed in a connection's own HMAC scratch inside an
|
|
964
|
+
* arena block, so issuing an ID keeps nothing (QUIC-3). The scratch and the
|
|
965
|
+
* full MAC are wiped before it returns.
|
|
966
|
+
*/
|
|
967
|
+
export const quicConnResetTokenInto = (
|
|
968
|
+
mac: HmacSha256Scratch,
|
|
969
|
+
key: u8[],
|
|
970
|
+
cid: u8[],
|
|
971
|
+
at: i32,
|
|
972
|
+
length: i32,
|
|
973
|
+
out: u8[],
|
|
974
|
+
outAt: i32
|
|
975
|
+
): void => {
|
|
976
|
+
using _scope = arena()
|
|
977
|
+
const tag: u8[] = new Array<u8>(32)
|
|
978
|
+
mac.begin(key, QUIC_CONN_FROM, toI32(key.length))
|
|
979
|
+
mac.update(cid, at, length)
|
|
980
|
+
mac.finishInto(tag, QUIC_CONN_FROM)
|
|
981
|
+
for (
|
|
982
|
+
let k: i32 = 0;
|
|
983
|
+
k < QUIC_RESET_TOKEN_SIZE && k < toI32(tag.length) && outAt + k < toI32(out.length);
|
|
984
|
+
k += 1
|
|
985
|
+
) {
|
|
986
|
+
out[outAt + k] = tag[k]
|
|
987
|
+
}
|
|
988
|
+
secureZero(tag)
|
|
989
|
+
mac.wipe()
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/** Puts `from[at .. at + length)` in `to`, emptied first, in the room `to` already has. */
|
|
993
|
+
const quicConnRefill = (to: u8[], from: u8[], at: i32, length: i32): void => {
|
|
994
|
+
while (to.length > 0) {
|
|
995
|
+
to.pop()
|
|
996
|
+
}
|
|
997
|
+
for (let k: i32 = 0; k < length && at + k >= 0 && at + k < toI32(from.length); k += 1) {
|
|
998
|
+
to.push(from[at + k])
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Secret `index` (0 or 1) of a direction's pool, of the length `aead`'s hash
|
|
1004
|
+
* gives: what a key update derives the next traffic secret into.
|
|
1005
|
+
*/
|
|
1006
|
+
const quicConnSecretAt = (pool: u8[][], aead: i32, index: i32): u8[] => {
|
|
1007
|
+
let wanted: i32 = index === 1 ? 1 : 0
|
|
1008
|
+
if (aead === QUIC_AEAD_AES_256_GCM) {
|
|
1009
|
+
wanted = wanted + 2
|
|
1010
|
+
}
|
|
1011
|
+
const at: i32 = wanted < toI32(pool.length) ? wanted : 0
|
|
1012
|
+
return pool[at]
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/** Two secrets of SHA-256's length and two of SHA-384's, for one direction's key updates. */
|
|
1016
|
+
const quicConnSecretPool = (): u8[][] => [
|
|
1017
|
+
new Array<u8>(32),
|
|
1018
|
+
new Array<u8>(32),
|
|
1019
|
+
new Array<u8>(48),
|
|
1020
|
+
new Array<u8>(48),
|
|
1021
|
+
]
|
|
1022
|
+
|
|
891
1023
|
/** Whether the configuration's limits are ones this module can honour. */
|
|
892
1024
|
const quicConnConfigFits = (config: QuicServerConfig): boolean =>
|
|
893
1025
|
config.maxStreamData >= 1 &&
|
|
@@ -940,6 +1072,30 @@ export class QuicConnection {
|
|
|
940
1072
|
/** The server's first connection ID, the SCID of every long-header packet it sends. */
|
|
941
1073
|
localScid: u8[]
|
|
942
1074
|
tls: TlsServer | null = null
|
|
1075
|
+
/** The slot's `TlsServer`, made by its first handshake and `restart`ed by every one after (QUIC-3). */
|
|
1076
|
+
tlsSlot: TlsServer | null = null
|
|
1077
|
+
/** Its configuration, made once: the parameters it carries are `localEncoded`, rewritten for each connection. */
|
|
1078
|
+
tlsConfig: TlsServerConfig
|
|
1079
|
+
/** The server's transport parameters, set for each connection, and their encoding. */
|
|
1080
|
+
localParameters: QuicTransportParameters
|
|
1081
|
+
localEncoded: u8[]
|
|
1082
|
+
/** The client's transport parameters, parsed into the slot's own object. */
|
|
1083
|
+
parsedParameters: QuicTransportParameters
|
|
1084
|
+
/** Where every key derivation and HMAC of the connection runs. */
|
|
1085
|
+
kdf: HkdfScratch
|
|
1086
|
+
/** The Initial secrets of RFC 9001 §5.2, the client's and the server's. */
|
|
1087
|
+
initialClient: u8[]
|
|
1088
|
+
initialServer: u8[]
|
|
1089
|
+
/**
|
|
1090
|
+
* The 1-RTT secrets a key update derives, two of each hash's length a
|
|
1091
|
+
* direction (SHA-256's first, then SHA-384's), used in turn: the next
|
|
1092
|
+
* generation's goes in the one the current generation's is not in.
|
|
1093
|
+
*/
|
|
1094
|
+
writeSecrets: u8[][]
|
|
1095
|
+
readSecrets: u8[][]
|
|
1096
|
+
/** A connection ID being issued and its stateless reset token, before the table copies them. */
|
|
1097
|
+
cidScratch: u8[]
|
|
1098
|
+
tokenScratch: u8[]
|
|
943
1099
|
initial: QuicConnSpace
|
|
944
1100
|
handshake: QuicConnSpace
|
|
945
1101
|
application: QuicConnSpace
|
|
@@ -1009,6 +1165,16 @@ export class QuicConnection {
|
|
|
1009
1165
|
dropped: i32 = 0
|
|
1010
1166
|
/** Key updates the client started, against `QUIC_CONN_MAX_KEY_UPDATES`. */
|
|
1011
1167
|
keyUpdates: i32 = 0
|
|
1168
|
+
/** Which of the Application Data space's two read slots, and two write slots, hold the current keys. */
|
|
1169
|
+
appReadSlot: i32 = 0
|
|
1170
|
+
appWriteSlot: i32 = 0
|
|
1171
|
+
/** Which of `readSecrets` and `writeSecrets` (0 or 1) holds the current secret, or -1 for `TlsServer`'s own. */
|
|
1172
|
+
readSecretCurrent: i32 = -1
|
|
1173
|
+
writeSecretCurrent: i32 = -1
|
|
1174
|
+
/** Handshake data sent again early, against `QUIC_CONN_EARLY_RESENDS` (RFC 9002 §6.2.3). */
|
|
1175
|
+
earlyResends: i32 = 0
|
|
1176
|
+
/** The level at which the packet just read showed the client lacks the server's handshake data, or -1. */
|
|
1177
|
+
resendLevel: i32 = -1
|
|
1012
1178
|
/** Whether `error` is an application code (CONNECTION_CLOSE 0x1d) rather than a transport error. */
|
|
1013
1179
|
errorIsApplication: boolean = false
|
|
1014
1180
|
/** Whether the one CONNECTION_CLOSE this side owes has gone out. */
|
|
@@ -1080,8 +1246,27 @@ export class QuicConnection {
|
|
|
1080
1246
|
this.header = new QuicHeader()
|
|
1081
1247
|
this.packet = new QuicPacket()
|
|
1082
1248
|
this.none = []
|
|
1083
|
-
this.retryOriginalDcid =
|
|
1084
|
-
this.retryScid =
|
|
1249
|
+
this.retryOriginalDcid = []
|
|
1250
|
+
this.retryScid = []
|
|
1251
|
+
this.localParameters = new QuicTransportParameters()
|
|
1252
|
+
this.localEncoded = []
|
|
1253
|
+
this.parsedParameters = new QuicTransportParameters()
|
|
1254
|
+
const extra: u8[] = []
|
|
1255
|
+
this.tlsConfig = {
|
|
1256
|
+
certificateChain: config.certificateChain,
|
|
1257
|
+
alpn: config.alpn,
|
|
1258
|
+
quicTransportParameters: this.localEncoded,
|
|
1259
|
+
extraExtensions: extra,
|
|
1260
|
+
signatureScheme: config.signatureScheme,
|
|
1261
|
+
quic: true,
|
|
1262
|
+
}
|
|
1263
|
+
this.kdf = new HkdfScratch()
|
|
1264
|
+
this.initialClient = new Array<u8>(32)
|
|
1265
|
+
this.initialServer = new Array<u8>(32)
|
|
1266
|
+
this.writeSecrets = quicConnSecretPool()
|
|
1267
|
+
this.readSecrets = quicConnSecretPool()
|
|
1268
|
+
this.cidScratch = new Array<u8>(QUIC_CONN_CID_LENGTH)
|
|
1269
|
+
this.tokenScratch = new Array<u8>(QUIC_RESET_TOKEN_SIZE)
|
|
1085
1270
|
this.appReadSecret = this.none
|
|
1086
1271
|
this.appWriteSecret = this.none
|
|
1087
1272
|
this.nextReadSecret = this.none
|
|
@@ -1094,8 +1279,8 @@ export class QuicConnection {
|
|
|
1094
1279
|
* array): every secret of the last connection is wiped first, as
|
|
1095
1280
|
* `release()` does, and every buffer is kept and emptied. The limits are
|
|
1096
1281
|
* the constructor's configuration's. A slot reset this way allocates
|
|
1097
|
-
* nothing
|
|
1098
|
-
*
|
|
1282
|
+
* nothing, and neither does the next handshake (QUIC-3 in
|
|
1283
|
+
* `docs/security/quic.md`).
|
|
1099
1284
|
*/
|
|
1100
1285
|
reset(entropy: u8[]): void {
|
|
1101
1286
|
this.wipeAll()
|
|
@@ -1109,6 +1294,7 @@ export class QuicConnection {
|
|
|
1109
1294
|
this.datagramsIn.reset()
|
|
1110
1295
|
this.cryptoIn.reset(TLS_LEVEL_INITIAL)
|
|
1111
1296
|
this.peerParameters = this.defaultParameters
|
|
1297
|
+
// The slot keeps its `TlsServer`, wiped by `wipeAll`; the next handshake restarts it.
|
|
1112
1298
|
this.tls = null
|
|
1113
1299
|
this.error = 0
|
|
1114
1300
|
this.errorFrameType = 0
|
|
@@ -1116,12 +1302,18 @@ export class QuicConnection {
|
|
|
1116
1302
|
this.bytesSent = 0
|
|
1117
1303
|
this.alpn = ""
|
|
1118
1304
|
this.serverName = ""
|
|
1119
|
-
this.retryOriginalDcid
|
|
1120
|
-
this.retryScid
|
|
1305
|
+
quicConnRefill(this.retryOriginalDcid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
|
|
1306
|
+
quicConnRefill(this.retryScid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
|
|
1121
1307
|
this.appReadSecret = this.none
|
|
1122
1308
|
this.appWriteSecret = this.none
|
|
1123
1309
|
this.nextReadSecret = this.none
|
|
1124
1310
|
this.otherReadKeys = null
|
|
1311
|
+
this.appReadSlot = 0
|
|
1312
|
+
this.appWriteSlot = 0
|
|
1313
|
+
this.readSecretCurrent = -1
|
|
1314
|
+
this.writeSecretCurrent = -1
|
|
1315
|
+
this.earlyResends = 0
|
|
1316
|
+
this.resendLevel = -1
|
|
1125
1317
|
this.now = 0
|
|
1126
1318
|
this.idleSince = -1
|
|
1127
1319
|
this.readPhaseLowest = -1
|
|
@@ -1201,8 +1393,8 @@ export class QuicConnection {
|
|
|
1201
1393
|
) {
|
|
1202
1394
|
return false
|
|
1203
1395
|
}
|
|
1204
|
-
this.retryOriginalDcid
|
|
1205
|
-
this.retryScid
|
|
1396
|
+
quicConnRefill(this.retryOriginalDcid, originalDcid, QUIC_CONN_FROM, toI32(originalDcid.length))
|
|
1397
|
+
quicConnRefill(this.retryScid, retryScid, QUIC_CONN_FROM, toI32(retryScid.length))
|
|
1206
1398
|
this.retried = true
|
|
1207
1399
|
return true
|
|
1208
1400
|
}
|
|
@@ -1352,13 +1544,29 @@ export class QuicConnection {
|
|
|
1352
1544
|
) {
|
|
1353
1545
|
return false
|
|
1354
1546
|
}
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1547
|
+
const secrets: boolean = quicInitialSecretsInto(
|
|
1548
|
+
this.kdf,
|
|
1549
|
+
buf,
|
|
1550
|
+
header.dcidStart,
|
|
1551
|
+
header.dcidLength,
|
|
1552
|
+
this.initialClient,
|
|
1553
|
+
this.initialServer
|
|
1554
|
+
)
|
|
1555
|
+
if (!secrets) {
|
|
1358
1556
|
return false
|
|
1359
1557
|
}
|
|
1360
|
-
this.initial.readKeys =
|
|
1361
|
-
|
|
1558
|
+
this.initial.readKeys = quicKeysInto(
|
|
1559
|
+
this.kdf,
|
|
1560
|
+
this.initial.keySlot(QUIC_CONN_READ_SLOT),
|
|
1561
|
+
QUIC_AEAD_AES_128_GCM,
|
|
1562
|
+
this.initialClient
|
|
1563
|
+
)
|
|
1564
|
+
this.initial.writeKeys = quicKeysInto(
|
|
1565
|
+
this.kdf,
|
|
1566
|
+
this.initial.keySlot(QUIC_CONN_WRITE_SLOT),
|
|
1567
|
+
QUIC_AEAD_AES_128_GCM,
|
|
1568
|
+
this.initialServer
|
|
1569
|
+
)
|
|
1362
1570
|
quicCidCopy(this.originalDcid, buf, header.dcidStart, header.dcidLength)
|
|
1363
1571
|
this.originalDcidLength = header.dcidLength
|
|
1364
1572
|
quicCidCopy(this.peerScid, buf, header.scidStart, header.scidLength)
|
|
@@ -1369,14 +1577,24 @@ export class QuicConnection {
|
|
|
1369
1577
|
|
|
1370
1578
|
/**
|
|
1371
1579
|
* 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
|
|
1580
|
+
* connection ID, and the slot's `TlsServer` started with transport
|
|
1581
|
+
* parameters that name both IDs (RFC 9000 §7.3), the Retry's when
|
|
1582
|
+
* `acceptRetry` set one up, and the first ID's stateless reset token
|
|
1583
|
+
* (§18.2).
|
|
1375
1584
|
*/
|
|
1376
1585
|
establish(): void {
|
|
1377
1586
|
const none: u8[] = this.none
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1587
|
+
const token: u8[] = this.tokenScratch
|
|
1588
|
+
quicConnResetTokenInto(
|
|
1589
|
+
this.kdf.sha256,
|
|
1590
|
+
this.config.statelessResetKey,
|
|
1591
|
+
this.localScid,
|
|
1592
|
+
QUIC_CONN_FROM,
|
|
1593
|
+
QUIC_CONN_CID_LENGTH,
|
|
1594
|
+
token,
|
|
1595
|
+
QUIC_CONN_FROM
|
|
1596
|
+
)
|
|
1597
|
+
this.cids.addLocal(this.localScid, token)
|
|
1380
1598
|
this.cids.addPeerAt(
|
|
1381
1599
|
QUIC_CONN_NONE,
|
|
1382
1600
|
QUIC_CONN_NONE,
|
|
@@ -1390,17 +1608,27 @@ export class QuicConnection {
|
|
|
1390
1608
|
// §8.1.2: a Retry token the listener checked has validated the address.
|
|
1391
1609
|
this.addressValidated = this.retried
|
|
1392
1610
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1611
|
+
// The parameters, and their encoding, are rewritten in the slot's own
|
|
1612
|
+
// arrays, which grow only when a connection needs more than any before.
|
|
1613
|
+
const params: QuicTransportParameters = this.localParameters
|
|
1614
|
+
if (this.retried) {
|
|
1615
|
+
quicConnRefill(
|
|
1616
|
+
params.originalDcid,
|
|
1617
|
+
this.retryOriginalDcid,
|
|
1618
|
+
QUIC_CONN_FROM,
|
|
1619
|
+
toI32(this.retryOriginalDcid.length)
|
|
1620
|
+
)
|
|
1621
|
+
} else {
|
|
1622
|
+
quicConnRefill(params.originalDcid, this.originalDcid, QUIC_CONN_FROM, this.originalDcidLength)
|
|
1623
|
+
}
|
|
1397
1624
|
params.hasOriginalDcid = true
|
|
1398
1625
|
params.initialScid = this.localScid
|
|
1399
1626
|
params.hasInitialScid = true
|
|
1400
|
-
params.retryScid
|
|
1627
|
+
quicConnRefill(params.retryScid, this.retryScid, QUIC_CONN_FROM, toI32(this.retryScid.length))
|
|
1401
1628
|
params.hasRetryScid = this.retried
|
|
1402
|
-
params.statelessResetToken
|
|
1629
|
+
quicConnRefill(params.statelessResetToken, token, QUIC_CONN_FROM, QUIC_RESET_TOKEN_SIZE)
|
|
1403
1630
|
params.hasStatelessResetToken = true
|
|
1631
|
+
secureZero(token)
|
|
1404
1632
|
params.maxIdleTimeout = this.config.maxIdleTimeout
|
|
1405
1633
|
params.initialMaxData = this.config.maxData
|
|
1406
1634
|
params.initialMaxStreamDataBidiRemote = this.config.maxStreamData
|
|
@@ -1415,16 +1643,18 @@ export class QuicConnection {
|
|
|
1415
1643
|
// The server never migrates and asks the client not to (§9): path
|
|
1416
1644
|
// validation beyond answering PATH_CHALLENGE is not here.
|
|
1417
1645
|
params.disableActiveMigration = true
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1646
|
+
quicEncodeTransportParametersInto(params, this.localEncoded)
|
|
1647
|
+
// One `TlsServer` a slot, made by its first handshake (so its flight is
|
|
1648
|
+
// sized for these parameters) and restarted, wiped, by each after it.
|
|
1649
|
+
const tls: TlsServer | null = this.tlsSlot
|
|
1650
|
+
if (tls !== null) {
|
|
1651
|
+
tls.restart(this.serverRandom, this.ephemeralPrivate)
|
|
1652
|
+
this.tls = tls
|
|
1653
|
+
} else {
|
|
1654
|
+
const made: TlsServer = new TlsServer(this.tlsConfig, this.serverRandom, this.ephemeralPrivate)
|
|
1655
|
+
this.tlsSlot = made
|
|
1656
|
+
this.tls = made
|
|
1426
1657
|
}
|
|
1427
|
-
this.tls = new TlsServer(tlsConfig, this.serverRandom, this.ephemeralPrivate)
|
|
1428
1658
|
}
|
|
1429
1659
|
|
|
1430
1660
|
/**
|
|
@@ -1460,10 +1690,10 @@ export class QuicConnection {
|
|
|
1460
1690
|
/**
|
|
1461
1691
|
* Opens one packet of a connection already started, and acts on its frames.
|
|
1462
1692
|
* 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.
|
|
1693
|
+
* their temporaries go when it ends; what may grow a buffer of the slot —
|
|
1694
|
+
* a first Initial's `TlsServer` and parameters, a key update, TLS reading
|
|
1695
|
+
* the CRYPTO bytes and what it answers — runs after it. Answers whether
|
|
1696
|
+
* the packet was used.
|
|
1467
1697
|
*/
|
|
1468
1698
|
openPacket(buf: u8[], header: QuicHeader, first: boolean): boolean {
|
|
1469
1699
|
const type: i32 = header.type
|
|
@@ -1481,6 +1711,7 @@ export class QuicConnection {
|
|
|
1481
1711
|
this.cryptoArrived = false
|
|
1482
1712
|
this.topUpOwed = false
|
|
1483
1713
|
this.phasePending = -1
|
|
1714
|
+
this.resendLevel = -1
|
|
1484
1715
|
let used: boolean = false
|
|
1485
1716
|
{
|
|
1486
1717
|
using _scope = arena()
|
|
@@ -1511,6 +1742,9 @@ export class QuicConnection {
|
|
|
1511
1742
|
if (this.topUpOwed) {
|
|
1512
1743
|
this.topUpConnectionIds()
|
|
1513
1744
|
}
|
|
1745
|
+
if (this.resendLevel >= 0 && !this.closed()) {
|
|
1746
|
+
this.resendEarly(this.resendLevel)
|
|
1747
|
+
}
|
|
1514
1748
|
return true
|
|
1515
1749
|
}
|
|
1516
1750
|
|
|
@@ -1555,6 +1789,11 @@ export class QuicConnection {
|
|
|
1555
1789
|
if (this.closed()) {
|
|
1556
1790
|
return true
|
|
1557
1791
|
}
|
|
1792
|
+
// RFC 9002 §6.2.3: a client that sends an ack-eliciting Handshake packet
|
|
1793
|
+
// while the server's Handshake data is in flight may not have it all.
|
|
1794
|
+
if (eliciting && type === QUIC_PACKET_HANDSHAKE) {
|
|
1795
|
+
this.resendLevel = TLS_LEVEL_HANDSHAKE
|
|
1796
|
+
}
|
|
1558
1797
|
space.received.record(pn, eliciting)
|
|
1559
1798
|
// §10.1: a packet received and processed restarts the idle timer.
|
|
1560
1799
|
this.idleSince = this.now
|
|
@@ -1619,8 +1858,10 @@ export class QuicConnection {
|
|
|
1619
1858
|
this.application.readKeys = this.otherReadKeys
|
|
1620
1859
|
this.otherReadKeys = previous
|
|
1621
1860
|
this.otherIsNext = false
|
|
1861
|
+
this.appReadSlot = 1 - this.appReadSlot
|
|
1622
1862
|
secureZero(this.appReadSecret)
|
|
1623
1863
|
this.appReadSecret = this.nextReadSecret
|
|
1864
|
+
this.readSecretCurrent = this.readSecretCurrent === 0 ? 1 : 0
|
|
1624
1865
|
this.nextReadSecret = this.none
|
|
1625
1866
|
this.readPhase = !this.readPhase
|
|
1626
1867
|
this.readPhaseLowest = pn
|
|
@@ -1639,11 +1880,13 @@ export class QuicConnection {
|
|
|
1639
1880
|
if (keys === null) {
|
|
1640
1881
|
return false
|
|
1641
1882
|
}
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1883
|
+
const target: i32 = this.writeSecretCurrent === 0 ? 1 : 0
|
|
1884
|
+
const secret: u8[] = quicConnSecretAt(this.writeSecrets, keys.aead, target)
|
|
1885
|
+
if (!quicKeyUpdateSecretInto(this.kdf, keys.aead, this.appWriteSecret, secret)) {
|
|
1644
1886
|
return false
|
|
1645
1887
|
}
|
|
1646
|
-
const
|
|
1888
|
+
const slot: QuicKeysSlot = this.application.keySlot(QUIC_CONN_APP_WRITE_SLOTS + 1 - this.appWriteSlot)
|
|
1889
|
+
const next: QuicKeys | null = quicKeysUpdateInto(this.kdf, slot, keys, secret)
|
|
1647
1890
|
if (next === null) {
|
|
1648
1891
|
secureZero(secret)
|
|
1649
1892
|
return false
|
|
@@ -1652,6 +1895,8 @@ export class QuicConnection {
|
|
|
1652
1895
|
secureZero(this.appWriteSecret)
|
|
1653
1896
|
this.application.writeKeys = next
|
|
1654
1897
|
this.appWriteSecret = secret
|
|
1898
|
+
this.writeSecretCurrent = target
|
|
1899
|
+
this.appWriteSlot = 1 - this.appWriteSlot
|
|
1655
1900
|
this.writePhase = !this.writePhase
|
|
1656
1901
|
this.writePhaseFirst = this.application.nextPn
|
|
1657
1902
|
return true
|
|
@@ -1673,12 +1918,17 @@ export class QuicConnection {
|
|
|
1673
1918
|
if (keys === null) {
|
|
1674
1919
|
return
|
|
1675
1920
|
}
|
|
1676
|
-
const secret: u8[]
|
|
1677
|
-
if (
|
|
1921
|
+
const secret: u8[] = quicConnSecretAt(this.readSecrets, keys.aead, this.readSecretCurrent === 0 ? 1 : 0)
|
|
1922
|
+
if (!quicKeyUpdateSecretInto(this.kdf, keys.aead, this.appReadSecret, secret)) {
|
|
1678
1923
|
return
|
|
1679
1924
|
}
|
|
1680
1925
|
this.nextReadSecret = secret
|
|
1681
|
-
this.otherReadKeys =
|
|
1926
|
+
this.otherReadKeys = quicKeysUpdateInto(
|
|
1927
|
+
this.kdf,
|
|
1928
|
+
this.application.keySlot(1 - this.appReadSlot),
|
|
1929
|
+
keys,
|
|
1930
|
+
secret
|
|
1931
|
+
)
|
|
1682
1932
|
this.otherIsNext = this.otherReadKeys !== null
|
|
1683
1933
|
}
|
|
1684
1934
|
|
|
@@ -1984,10 +2234,12 @@ export class QuicConnection {
|
|
|
1984
2234
|
|
|
1985
2235
|
/**
|
|
1986
2236
|
* 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
|
-
*
|
|
2237
|
+
* flight, everything they carry is queued again and the next packets are
|
|
2238
|
+
* probes, sent whatever the window says, with a PING if nothing is left to
|
|
1989
2239
|
* carry. During the handshake that resends the Initial and the Handshake
|
|
1990
|
-
* flight together, which is what a client that lost both needs
|
|
2240
|
+
* flight together, which is what a client that lost both needs, and does
|
|
2241
|
+
* it twice (`QUIC_CONN_HANDSHAKE_PROBES`), so that one more lost datagram
|
|
2242
|
+
* does not cost another, doubled, probe timeout.
|
|
1991
2243
|
*/
|
|
1992
2244
|
probe(): void {
|
|
1993
2245
|
for (let level: i32 = 0; level < 3; level += 1) {
|
|
@@ -2002,7 +2254,56 @@ export class QuicConnection {
|
|
|
2002
2254
|
this.requeue(space, slot)
|
|
2003
2255
|
}
|
|
2004
2256
|
}
|
|
2005
|
-
space.
|
|
2257
|
+
space.probes = QUIC_CONN_HANDSHAKE_PROBES
|
|
2258
|
+
if (level === TLS_LEVEL_APPLICATION) {
|
|
2259
|
+
space.probes = 1
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
/**
|
|
2265
|
+
* Speeds up the handshake (RFC 9002 §6.2.3): the client showed at `level`
|
|
2266
|
+
* that it lacks the server's handshake data there — an Initial with CRYPTO
|
|
2267
|
+
* data the server already read, or an ack-eliciting Handshake packet —
|
|
2268
|
+
* so, while the server's own packets at that level are in flight, the
|
|
2269
|
+
* oldest one's CRYPTO data at that level and at Handshake goes again now,
|
|
2270
|
+
* as one probe datagram, rather than at the probe timeout. It does so at
|
|
2271
|
+
* most `QUIC_CONN_EARLY_RESENDS` times a connection, and not while an
|
|
2272
|
+
* earlier probe is still owed, so each packet the client sends asks for
|
|
2273
|
+
* one datagram at most. The anti-amplification limit holds it like any
|
|
2274
|
+
* datagram (`takeDatagramInto`).
|
|
2275
|
+
*/
|
|
2276
|
+
resendEarly(level: i32): void {
|
|
2277
|
+
if (
|
|
2278
|
+
this.earlyResends >= QUIC_CONN_EARLY_RESENDS ||
|
|
2279
|
+
this.initial.probes > 0 ||
|
|
2280
|
+
this.handshake.probes > 0
|
|
2281
|
+
) {
|
|
2282
|
+
return
|
|
2283
|
+
}
|
|
2284
|
+
const asked: QuicSentPackets | null = this.recovery.space(level)
|
|
2285
|
+
if (asked === null || asked.inFlight === 0) {
|
|
2286
|
+
return
|
|
2287
|
+
}
|
|
2288
|
+
let queued: boolean = false
|
|
2289
|
+
for (let at: i32 = level; at <= TLS_LEVEL_HANDSHAKE; at += 1) {
|
|
2290
|
+
const space: QuicConnSpace = this.spaceAt(at)
|
|
2291
|
+
const sent: QuicSentPackets | null = this.recovery.space(at)
|
|
2292
|
+
if (sent === null || space.discarded || sent.inFlight === 0) {
|
|
2293
|
+
continue
|
|
2294
|
+
}
|
|
2295
|
+
// Compacted, the ring's oldest slot is the oldest packet in flight.
|
|
2296
|
+
sent.compact()
|
|
2297
|
+
const oldest: i32 = sent.slot(QUIC_CONN_FROM)
|
|
2298
|
+
const length: i32 = space.cryptoLength(oldest)
|
|
2299
|
+
if (length > 0) {
|
|
2300
|
+
space.resendCrypto(space.cryptoOffset(oldest), length)
|
|
2301
|
+
space.probes = 1
|
|
2302
|
+
queued = true
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
if (queued) {
|
|
2306
|
+
this.earlyResends += 1
|
|
2006
2307
|
}
|
|
2007
2308
|
}
|
|
2008
2309
|
|
|
@@ -2026,6 +2327,11 @@ export class QuicConnection {
|
|
|
2026
2327
|
this.spaceAt(reassembly.level).cryptoInDelivered = reassembly.delivered
|
|
2027
2328
|
reassembly.reset(space.level)
|
|
2028
2329
|
}
|
|
2330
|
+
// RFC 9002 §6.2.3: an Initial with CRYPTO data the server already read
|
|
2331
|
+
// says the client did not get the server's Initial flight.
|
|
2332
|
+
if (space.level === TLS_LEVEL_INITIAL && frame.offset < reassembly.delivered) {
|
|
2333
|
+
this.resendLevel = TLS_LEVEL_INITIAL
|
|
2334
|
+
}
|
|
2029
2335
|
if (!reassembly.insert(frame.offset, payload, frame.dataStart, frame.dataLength)) {
|
|
2030
2336
|
this.fail(QUIC_ERROR_CRYPTO_BUFFER_EXCEEDED, toI64(QUIC_FRAME_CRYPTO))
|
|
2031
2337
|
return
|
|
@@ -2116,10 +2422,12 @@ export class QuicConnection {
|
|
|
2116
2422
|
return
|
|
2117
2423
|
}
|
|
2118
2424
|
if (!this.initial.discarded) {
|
|
2119
|
-
this.initial.appendCrypto(tls.
|
|
2425
|
+
this.initial.appendCrypto(tls.outputInitial, tls.outputInitialLength)
|
|
2426
|
+
tls.clearOutput(TLS_LEVEL_INITIAL)
|
|
2120
2427
|
}
|
|
2121
2428
|
if (!this.handshake.discarded) {
|
|
2122
|
-
this.handshake.appendCrypto(tls.
|
|
2429
|
+
this.handshake.appendCrypto(tls.outputHandshake, tls.outputHandshakeLength)
|
|
2430
|
+
tls.clearOutput(TLS_LEVEL_HANDSHAKE)
|
|
2123
2431
|
}
|
|
2124
2432
|
const aead: i32 = quicConnAead(tls.suite)
|
|
2125
2433
|
if (
|
|
@@ -2132,8 +2440,16 @@ export class QuicConnection {
|
|
|
2132
2440
|
}
|
|
2133
2441
|
this.alpn = tls.alpn
|
|
2134
2442
|
this.serverName = tls.serverName
|
|
2135
|
-
this.handshake.readKeys = this.keysFor(
|
|
2136
|
-
|
|
2443
|
+
this.handshake.readKeys = this.keysFor(
|
|
2444
|
+
this.handshake.keySlot(QUIC_CONN_READ_SLOT),
|
|
2445
|
+
aead,
|
|
2446
|
+
tls.readSecret(TLS_LEVEL_HANDSHAKE)
|
|
2447
|
+
)
|
|
2448
|
+
this.handshake.writeKeys = this.keysFor(
|
|
2449
|
+
this.handshake.keySlot(QUIC_CONN_WRITE_SLOT),
|
|
2450
|
+
aead,
|
|
2451
|
+
tls.writeSecret(TLS_LEVEL_HANDSHAKE)
|
|
2452
|
+
)
|
|
2137
2453
|
}
|
|
2138
2454
|
if (
|
|
2139
2455
|
!this.application.discarded &&
|
|
@@ -2143,8 +2459,12 @@ export class QuicConnection {
|
|
|
2143
2459
|
const write: u8[] | null = tls.writeSecret(TLS_LEVEL_APPLICATION)
|
|
2144
2460
|
const read: u8[] | null = tls.readSecret(TLS_LEVEL_APPLICATION)
|
|
2145
2461
|
if (write !== null && read !== null) {
|
|
2146
|
-
this.application.writeKeys = this.keysFor(
|
|
2147
|
-
|
|
2462
|
+
this.application.writeKeys = this.keysFor(
|
|
2463
|
+
this.application.keySlot(QUIC_CONN_APP_WRITE_SLOTS + this.appWriteSlot),
|
|
2464
|
+
aead,
|
|
2465
|
+
write
|
|
2466
|
+
)
|
|
2467
|
+
this.application.readKeys = this.keysFor(this.application.keySlot(this.appReadSlot), aead, read)
|
|
2148
2468
|
// The key update secrets start from `TlsServer`'s own arrays, so the
|
|
2149
2469
|
// first update wipes those too, rather than leaving a copy behind.
|
|
2150
2470
|
this.appWriteSecret = write
|
|
@@ -2162,12 +2482,12 @@ export class QuicConnection {
|
|
|
2162
2482
|
}
|
|
2163
2483
|
}
|
|
2164
2484
|
|
|
2165
|
-
/** `
|
|
2166
|
-
keysFor(aead: i32, secret: u8[] | null): QuicKeys | null {
|
|
2485
|
+
/** `quicKeysInto` `slot` of a secret TLS has, or `null` when it has none yet. */
|
|
2486
|
+
keysFor(slot: QuicKeysSlot, aead: i32, secret: u8[] | null): QuicKeys | null {
|
|
2167
2487
|
if (secret === null) {
|
|
2168
2488
|
return null
|
|
2169
2489
|
}
|
|
2170
|
-
return
|
|
2490
|
+
return quicKeysInto(this.kdf, slot, aead, secret)
|
|
2171
2491
|
}
|
|
2172
2492
|
|
|
2173
2493
|
/**
|
|
@@ -2179,7 +2499,11 @@ export class QuicConnection {
|
|
|
2179
2499
|
* the datagrams its `max_datagram_frame_size`.
|
|
2180
2500
|
*/
|
|
2181
2501
|
checkPeerParameters(tls: TlsServer): boolean {
|
|
2182
|
-
const p: QuicTransportParameters =
|
|
2502
|
+
const p: QuicTransportParameters = quicParseTransportParametersInto(
|
|
2503
|
+
this.parsedParameters,
|
|
2504
|
+
tls.clientTransportParameters,
|
|
2505
|
+
false
|
|
2506
|
+
)
|
|
2183
2507
|
if (p.error !== QUIC_ERROR_NO_ERROR || !p.hasInitialScid) {
|
|
2184
2508
|
this.fail(QUIC_ERROR_TRANSPORT_PARAMETER, toI64(QUIC_FRAME_CRYPTO))
|
|
2185
2509
|
return false
|
|
@@ -2215,9 +2539,10 @@ export class QuicConnection {
|
|
|
2215
2539
|
this.recovery.discardSpace(space.level)
|
|
2216
2540
|
space.cryptoResendLow = -1
|
|
2217
2541
|
space.cryptoResendHigh = -1
|
|
2218
|
-
space.
|
|
2542
|
+
space.probes = 0
|
|
2219
2543
|
quicConnWipeKeys(space.readKeys)
|
|
2220
2544
|
quicConnWipeKeys(space.writeKeys)
|
|
2545
|
+
space.wipeKeySlots()
|
|
2221
2546
|
space.readKeys = null
|
|
2222
2547
|
space.writeKeys = null
|
|
2223
2548
|
space.cryptoOutLength = 0
|
|
@@ -2227,15 +2552,30 @@ export class QuicConnection {
|
|
|
2227
2552
|
/**
|
|
2228
2553
|
* The HMAC-SHA256 under the connection's seed of the sequence number
|
|
2229
2554
|
* `sequence`, whose first 8 bytes are the local connection ID of that
|
|
2230
|
-
* number
|
|
2231
|
-
* more entropy.
|
|
2555
|
+
* number, into `cidScratch`: so the IDs are unlinkable to anyone without
|
|
2556
|
+
* the seed and need no more entropy. It runs in the connection's HMAC
|
|
2557
|
+
* scratch inside an arena block, and wipes the MAC and the scratch.
|
|
2232
2558
|
*/
|
|
2233
|
-
deriveConnectionId(sequence: i64):
|
|
2559
|
+
deriveConnectionId(sequence: i64): void {
|
|
2560
|
+
const mac: HmacSha256Scratch = this.kdf.sha256
|
|
2561
|
+
using _scope = arena()
|
|
2234
2562
|
const counter: u8[] = new Array<u8>(8)
|
|
2235
2563
|
for (let k: i32 = 0; k < 8 && k < toI32(counter.length); k += 1) {
|
|
2236
2564
|
counter[k] = toU8(toI32((sequence >> (toI64(7 - k) * 8)) & 255))
|
|
2237
2565
|
}
|
|
2238
|
-
|
|
2566
|
+
const tag: u8[] = new Array<u8>(32)
|
|
2567
|
+
mac.begin(this.cidSeed, QUIC_CONN_FROM, toI32(this.cidSeed.length))
|
|
2568
|
+
mac.update(counter, QUIC_CONN_FROM, toI32(counter.length))
|
|
2569
|
+
mac.finishInto(tag, QUIC_CONN_FROM)
|
|
2570
|
+
for (
|
|
2571
|
+
let k: i32 = 0;
|
|
2572
|
+
k < QUIC_CONN_CID_LENGTH && k < toI32(tag.length) && k < toI32(this.cidScratch.length);
|
|
2573
|
+
k += 1
|
|
2574
|
+
) {
|
|
2575
|
+
this.cidScratch[k] = tag[k]
|
|
2576
|
+
}
|
|
2577
|
+
secureZero(tag)
|
|
2578
|
+
mac.wipe()
|
|
2239
2579
|
}
|
|
2240
2580
|
|
|
2241
2581
|
/**
|
|
@@ -2243,27 +2583,34 @@ export class QuicConnection {
|
|
|
2243
2583
|
* would take (its `active_connection_id_limit`), up to
|
|
2244
2584
|
* `QUIC_CONN_LOCAL_CIDS` at once and `QUIC_CONN_MAX_ISSUED_CIDS` in all;
|
|
2245
2585
|
* 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.
|
|
2586
|
+
* token the configuration's static key gives it. Each ID and token is made
|
|
2587
|
+
* in the slot's scratch and copied into the table, so issuing keeps
|
|
2588
|
+
* nothing (QUIC-3).
|
|
2250
2589
|
*/
|
|
2251
2590
|
topUpConnectionIds(): void {
|
|
2252
|
-
if (!this.handshakeComplete
|
|
2591
|
+
if (!this.handshakeComplete) {
|
|
2253
2592
|
return
|
|
2254
2593
|
}
|
|
2255
2594
|
let want: i64 = this.peerParameters.activeConnectionIdLimit
|
|
2256
2595
|
if (want > toI64(QUIC_CONN_LOCAL_CIDS)) {
|
|
2257
2596
|
want = toI64(QUIC_CONN_LOCAL_CIDS)
|
|
2258
2597
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2598
|
+
while (this.cids.nextLocal < QUIC_CONN_MAX_ISSUED_CIDS && toI64(this.cids.activeLocal()) < want) {
|
|
2599
|
+
this.deriveConnectionId(this.cids.nextLocal)
|
|
2600
|
+
quicConnResetTokenInto(
|
|
2601
|
+
this.kdf.sha256,
|
|
2602
|
+
this.config.statelessResetKey,
|
|
2603
|
+
this.cidScratch,
|
|
2604
|
+
QUIC_CONN_FROM,
|
|
2605
|
+
QUIC_CONN_CID_LENGTH,
|
|
2606
|
+
this.tokenScratch,
|
|
2607
|
+
QUIC_CONN_FROM
|
|
2608
|
+
)
|
|
2609
|
+
const added: i64 = this.cids.addLocal(this.cidScratch, this.tokenScratch)
|
|
2610
|
+
secureZero(this.tokenScratch)
|
|
2611
|
+
if (added < 0) {
|
|
2612
|
+
return
|
|
2613
|
+
}
|
|
2267
2614
|
}
|
|
2268
2615
|
}
|
|
2269
2616
|
|
|
@@ -2722,7 +3069,7 @@ export class QuicConnection {
|
|
|
2722
3069
|
if (sent === null || overhead === 0 || remaining <= overhead + 8) {
|
|
2723
3070
|
continue
|
|
2724
3071
|
}
|
|
2725
|
-
if (space.
|
|
3072
|
+
if (space.probes > 0 && sent.full()) {
|
|
2726
3073
|
// A probe has to be recorded: the oldest packet gives up its slot,
|
|
2727
3074
|
// and what it carried, already queued again by the probe, stays queued.
|
|
2728
3075
|
const slot: i32 = this.recovery.evictOldest(level)
|
|
@@ -2730,7 +3077,7 @@ export class QuicConnection {
|
|
|
2730
3077
|
this.requeue(space, slot)
|
|
2731
3078
|
}
|
|
2732
3079
|
}
|
|
2733
|
-
const elicit: boolean = space.
|
|
3080
|
+
const elicit: boolean = space.probes > 0 || (open && !sent.full())
|
|
2734
3081
|
const headerLength: i32 = overhead - QUIC_AEAD_TAG_SIZE
|
|
2735
3082
|
const payloadStart: i32 = position + headerLength
|
|
2736
3083
|
let payloadEnd: i32 = this.buildPayloadInto(
|
|
@@ -2842,7 +3189,7 @@ export class QuicConnection {
|
|
|
2842
3189
|
if (space.level === TLS_LEVEL_APPLICATION) {
|
|
2843
3190
|
at = this.buildApplication(space, out, at, end)
|
|
2844
3191
|
}
|
|
2845
|
-
if (space.
|
|
3192
|
+
if (space.probes > 0 && !space.stagedEliciting && at < end) {
|
|
2846
3193
|
at = quicPutTypeOnly(out, at, end, QUIC_FRAME_PING)
|
|
2847
3194
|
space.stagedEliciting = true
|
|
2848
3195
|
}
|
|
@@ -3059,7 +3406,15 @@ export class QuicConnection {
|
|
|
3059
3406
|
const slot: i32 = this.recovery.onPacketSent(space.level, pn, size, this.now)
|
|
3060
3407
|
if (slot >= 0) {
|
|
3061
3408
|
space.commitStaged(slot)
|
|
3062
|
-
space.
|
|
3409
|
+
if (space.probes > 0) {
|
|
3410
|
+
space.probes -= 1
|
|
3411
|
+
// §6.2.4: a second probe carries unacknowledged data too. When the
|
|
3412
|
+
// first took everything queued, the oldest — what it just carried —
|
|
3413
|
+
// goes again.
|
|
3414
|
+
if (space.probes > 0 && space.cryptoResendLow < 0) {
|
|
3415
|
+
this.requeue(space, slot)
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3063
3418
|
} else {
|
|
3064
3419
|
this.requeue(space, space.staging)
|
|
3065
3420
|
}
|
|
@@ -3187,6 +3542,14 @@ export class QuicConnection {
|
|
|
3187
3542
|
secureZero(this.appReadSecret)
|
|
3188
3543
|
secureZero(this.appWriteSecret)
|
|
3189
3544
|
secureZero(this.nextReadSecret)
|
|
3545
|
+
for (const secret of this.writeSecrets) {
|
|
3546
|
+
secureZero(secret)
|
|
3547
|
+
}
|
|
3548
|
+
for (const secret of this.readSecrets) {
|
|
3549
|
+
secureZero(secret)
|
|
3550
|
+
}
|
|
3551
|
+
secureZero(this.initialClient)
|
|
3552
|
+
secureZero(this.initialServer)
|
|
3190
3553
|
secureZero(this.cidSeed)
|
|
3191
3554
|
secureZero(this.ephemeralPrivate)
|
|
3192
3555
|
// The stateless reset tokens of both sides' IDs: either one ends the
|
|
@@ -3198,6 +3561,7 @@ export class QuicConnection {
|
|
|
3198
3561
|
secureZero(entry.resetToken)
|
|
3199
3562
|
}
|
|
3200
3563
|
secureZero(this.peerParameters.statelessResetToken)
|
|
3564
|
+
secureZero(this.localParameters.statelessResetToken)
|
|
3201
3565
|
const tls: TlsServer | null = this.tls
|
|
3202
3566
|
if (tls !== null) {
|
|
3203
3567
|
// The server's encoded transport parameters carry its first ID's token.
|