@amritk/nish-x86_64-linux 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +30 -7
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-wasm.c +1 -0
- package/runtime/runtime.c +46 -8
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +166 -35
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +148 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +905 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +392 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +653 -0
- package/std/net/quic-packet.ts +1474 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1603 -0
- package/std/net/quic.ts +3214 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +410 -0
- package/std/net/tls.ts +1368 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
|
@@ -0,0 +1,1474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/quic-packet` — QUIC version 1's packets, as RFC 9000 §16-§17
|
|
3
|
+
* frames them and RFC 9001 §5 protects them: the variable-length integer,
|
|
4
|
+
* the long and short headers, packet-number encoding and recovery, the
|
|
5
|
+
* Initial secrets, AEAD packet protection, header protection and the Retry
|
|
6
|
+
* integrity tag.
|
|
7
|
+
*
|
|
8
|
+
* import { quicInitialSecrets, quicKeys, quicParseHeader, quicOpenPacket,
|
|
9
|
+
* QUIC_AEAD_AES_128_GCM, QUIC_PACKET_OK } from "nish/net/quic-packet";
|
|
10
|
+
*
|
|
11
|
+
* const secrets = quicInitialSecrets(clientDcid); // RFC 9001 §5.2
|
|
12
|
+
* const keys = quicKeys(QUIC_AEAD_AES_128_GCM, secrets.client);
|
|
13
|
+
* const header = quicParseHeader(datagram, 0, 8); // 8: our short-header DCID length
|
|
14
|
+
* const packet = quicOpenPacket(keys, datagram, header, largestReceived);
|
|
15
|
+
* if (packet.error === QUIC_PACKET_OK) { … packet.payload holds the frames … }
|
|
16
|
+
*
|
|
17
|
+
* The module is sans-IO: bytes in, bytes out, no socket and no clock. It
|
|
18
|
+
* frames and protects packets and stops there, so it knows nothing of frames,
|
|
19
|
+
* of a connection's state or of which keys a level is on; that is
|
|
20
|
+
* `nish/net/quic` (WP34 Q2). Only version 1 (`0x00000001`) is spoken. A long
|
|
21
|
+
* header of another version is parsed as far as RFC 8999's invariants go,
|
|
22
|
+
* which is what a server needs to send Version Negotiation, and reported as
|
|
23
|
+
* `QUIC_ERR_VERSION`.
|
|
24
|
+
*
|
|
25
|
+
* **What a parse refuses.** Every read is bounds-checked against the
|
|
26
|
+
* datagram, so nothing a peer sends can make this module panic. A header
|
|
27
|
+
* that runs past the datagram is `QUIC_ERR_TRUNCATED`; a clear fixed bit
|
|
28
|
+
* `QUIC_ERR_FIXED_BIT`; a connection ID over 20 bytes in a version 1 header
|
|
29
|
+
* `QUIC_ERR_CID_LENGTH`; a Length field reaching past the datagram
|
|
30
|
+
* `QUIC_ERR_LENGTH`; a packet too short to sample for header protection
|
|
31
|
+
* `QUIC_ERR_SAMPLE`; a payload that does not authenticate `QUIC_ERR_DECRYPT`;
|
|
32
|
+
* and nonzero reserved bits once both protections are off
|
|
33
|
+
* `QUIC_ERR_RESERVED_BITS`, which RFC 9000 §17.2 makes a connection error
|
|
34
|
+
* rather than a drop, so the payload is still handed back with it. The
|
|
35
|
+
* builders, whose inputs come from this side rather than from a peer, answer
|
|
36
|
+
* `null` for an argument out of range.
|
|
37
|
+
*
|
|
38
|
+
* **In place.** For a connection that allocates nothing per packet,
|
|
39
|
+
* `quicParseHeaderInto` reads a header as windows into the datagram, the
|
|
40
|
+
* `quicPut*` builders write a header into a caller's buffer, `quicSealInPlace`
|
|
41
|
+
* seals a packet where it lies, and `quicUnprotectHeader` and
|
|
42
|
+
* `quicDecryptPayload` open one storing only numbers into the `QuicPacket`
|
|
43
|
+
* they are given. The AEADs allocate; `quicSealInPlace` releases what they
|
|
44
|
+
* allocated before it answers, and the opening halves answer arrays the
|
|
45
|
+
* caller releases, inside its own arena block.
|
|
46
|
+
*
|
|
47
|
+
* **Coalesced packets** (RFC 9000 §12.2) are split by length: a long header
|
|
48
|
+
* carries its own Length, and `QuicHeader.end` is where the next packet in the
|
|
49
|
+
* datagram starts. A short header has none, so it runs to the datagram's end.
|
|
50
|
+
*
|
|
51
|
+
* **Secrets are not wiped.** `QuicKeys` holds a packet key, an IV and a
|
|
52
|
+
* header-protection key, and `quicKeys`, `quicKeyUpdateSecret` and the seal
|
|
53
|
+
* and open functions hold traffic secrets and keys in arena memory until that
|
|
54
|
+
* memory is reused. `nish/crypto`'s signers and X25519 now hold their keys as
|
|
55
|
+
* a `Secret` from `nish:secret` and wipe them (ECC-2, X509-7); this module
|
|
56
|
+
* does not yet, and `docs/security/quic.md` records that as QUIC-1. The
|
|
57
|
+
* Initial keys are not secret, because anyone who reads the client's first
|
|
58
|
+
* DCID can derive them (RFC 9001 §5.2); the Handshake and 1-RTT keys are.
|
|
59
|
+
*
|
|
60
|
+
* **Constant time.** The AEADs and the header-protection masks are
|
|
61
|
+
* `nish/crypto/aes` and `nish/crypto/chacha20poly1305`, with their guarantees.
|
|
62
|
+
* What this module branches on is public: lengths, the packet type, the
|
|
63
|
+
* packet number once header protection is off (it is in the clear to anyone
|
|
64
|
+
* who holds the header-protection key, and RFC 9001 §5.4.1 protects it against
|
|
65
|
+
* observers, not timing), and whether a payload authenticated.
|
|
66
|
+
*
|
|
67
|
+
* Written from RFC 9000 and RFC 9001, in this module's own structure: nothing
|
|
68
|
+
* here is ported from another implementation. The packet-number decoding
|
|
69
|
+
* follows the arithmetic of RFC 9000 §A.3 and the length choice the rule of
|
|
70
|
+
* §A.2, neither line by line.
|
|
71
|
+
*
|
|
72
|
+
* Private names carry the `quicPacket` prefix because a `std/` module's
|
|
73
|
+
* private functions share the importing program's flat symbol namespace
|
|
74
|
+
* (`docs/wp26-stdlib.md` §3e).
|
|
75
|
+
*/
|
|
76
|
+
import { aesGcmOpen, aesGcmSeal, aesHeaderMask, aesKey, AesKey } from "nish/crypto/aes"
|
|
77
|
+
import { chacha20HeaderMask, chacha20Poly1305Open, chacha20Poly1305Seal } from "nish/crypto/chacha20poly1305"
|
|
78
|
+
import { timingSafeEqual } from "nish/crypto/ct"
|
|
79
|
+
import { hkdfExpandLabelSha256, hkdfExpandLabelSha384, hkdfExtractSha256 } from "nish/crypto/hkdf"
|
|
80
|
+
|
|
81
|
+
/** QUIC version 1 (RFC 9000 §15), the only version this module protects. */
|
|
82
|
+
export const QUIC_VERSION_1: i64 = 1
|
|
83
|
+
/** The longest connection ID version 1 allows (RFC 9000 §17.2). */
|
|
84
|
+
export const QUIC_MAX_CID_LENGTH: i32 = 20
|
|
85
|
+
/**
|
|
86
|
+
* The largest value a variable-length integer holds, 2^62 - 1 (RFC 9000 §16),
|
|
87
|
+
* and so the largest packet number. Spelled as 2^30 × 2^32 - 1 because an
|
|
88
|
+
* `i64` literal past 2^53 is refused (`docs/LANGUAGE.md`, numeric literals).
|
|
89
|
+
*/
|
|
90
|
+
export const QUIC_MAX_VARINT: i64 = 1073741824 * 4294967296 - 1
|
|
91
|
+
/** Every version 1 AEAD's tag, and so what each protected payload adds, in bytes (RFC 9001 §5.3). */
|
|
92
|
+
export const QUIC_AEAD_TAG_SIZE: i32 = 16
|
|
93
|
+
/** The bytes of ciphertext header protection samples (RFC 9001 §5.4.2). */
|
|
94
|
+
export const QUIC_SAMPLE_SIZE: i32 = 16
|
|
95
|
+
/** The Retry integrity tag, in bytes (RFC 9001 §5.8). */
|
|
96
|
+
export const QUIC_RETRY_TAG_SIZE: i32 = 16
|
|
97
|
+
|
|
98
|
+
/** A long-header Initial packet (RFC 9000 §17.2.2). */
|
|
99
|
+
export const QUIC_PACKET_INITIAL: i32 = 0
|
|
100
|
+
/** A long-header 0-RTT packet (RFC 9000 §17.2.3): parsed, since a server may be sent one; `quicLongHeader` builds one for a client. */
|
|
101
|
+
export const QUIC_PACKET_ZERO_RTT: i32 = 1
|
|
102
|
+
/** A long-header Handshake packet (RFC 9000 §17.2.4). */
|
|
103
|
+
export const QUIC_PACKET_HANDSHAKE: i32 = 2
|
|
104
|
+
/** A long-header Retry packet (RFC 9000 §17.2.5). */
|
|
105
|
+
export const QUIC_PACKET_RETRY: i32 = 3
|
|
106
|
+
/** A short-header, 1-RTT packet (RFC 9000 §17.3.1). */
|
|
107
|
+
export const QUIC_PACKET_SHORT: i32 = 4
|
|
108
|
+
|
|
109
|
+
/** AEAD_AES_128_GCM with SHA-256, the Initial packets' AEAD (TLS_AES_128_GCM_SHA256). */
|
|
110
|
+
export const QUIC_AEAD_AES_128_GCM: i32 = 1
|
|
111
|
+
/** AEAD_AES_256_GCM with SHA-384 (TLS_AES_256_GCM_SHA384). */
|
|
112
|
+
export const QUIC_AEAD_AES_256_GCM: i32 = 2
|
|
113
|
+
/** AEAD_CHACHA20_POLY1305 with SHA-256 (TLS_CHACHA20_POLY1305_SHA256). */
|
|
114
|
+
export const QUIC_AEAD_CHACHA20_POLY1305: i32 = 3
|
|
115
|
+
|
|
116
|
+
/** A packet parsed, or opened, with nothing wrong. */
|
|
117
|
+
export const QUIC_PACKET_OK: i32 = 0
|
|
118
|
+
/** The header runs past the end of the datagram. */
|
|
119
|
+
export const QUIC_ERR_TRUNCATED: i32 = 1
|
|
120
|
+
/** The fixed bit, 0x40 of the first byte, is clear (RFC 9000 §17.2, §17.3.1). */
|
|
121
|
+
export const QUIC_ERR_FIXED_BIT: i32 = 2
|
|
122
|
+
/** A long header of a version other than 1; `version`, `dcid` and `scid` are still filled in (RFC 8999 §5.1). */
|
|
123
|
+
export const QUIC_ERR_VERSION: i32 = 3
|
|
124
|
+
/** A version 1 connection ID longer than 20 bytes (RFC 9000 §17.2). */
|
|
125
|
+
export const QUIC_ERR_CID_LENGTH: i32 = 4
|
|
126
|
+
/** A long header whose Length field reaches past the end of the datagram (RFC 9000 §17.2). */
|
|
127
|
+
export const QUIC_ERR_LENGTH: i32 = 5
|
|
128
|
+
/** Too few bytes after the packet number's offset to take the 16-byte sample (RFC 9001 §5.4.2). */
|
|
129
|
+
export const QUIC_ERR_SAMPLE: i32 = 6
|
|
130
|
+
/** The payload does not authenticate under the keys given (RFC 9001 §5.3). */
|
|
131
|
+
export const QUIC_ERR_DECRYPT: i32 = 7
|
|
132
|
+
/** The payload authenticated but a reserved bit is set, a PROTOCOL_VIOLATION (RFC 9000 §17.2, §17.3.1). */
|
|
133
|
+
export const QUIC_ERR_RESERVED_BITS: i32 = 8
|
|
134
|
+
/** The packet carries no protected payload: a Retry (RFC 9000 §17.2.5). */
|
|
135
|
+
export const QUIC_ERR_NOT_PROTECTED: i32 = 9
|
|
136
|
+
/** Keys `quicKeys` or `quicKeysUpdate` did not make: a caller's own mistake, never a peer's. */
|
|
137
|
+
export const QUIC_ERR_KEYS: i32 = 10
|
|
138
|
+
|
|
139
|
+
/** RFC 9001 §5.2's `initial_salt` for version 1. A function, because a module constant cannot be an array. */
|
|
140
|
+
const quicPacketInitialSalt = (): u8[] => [
|
|
141
|
+
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb,
|
|
142
|
+
0x7f, 0x0a,
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
/** RFC 9001 §5.8's fixed AES-128-GCM key for version 1's Retry integrity tag. */
|
|
146
|
+
const quicPacketRetryKey = (): u8[] => [
|
|
147
|
+
0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e,
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
/** RFC 9001 §5.8's fixed nonce for version 1's Retry integrity tag. */
|
|
151
|
+
const quicPacketRetryNonce = (): u8[] => [
|
|
152
|
+
0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
/** The largest Length a long header carries, the most a two-byte varint holds. */
|
|
156
|
+
const QUIC_MAX_LENGTH: i32 = 16383
|
|
157
|
+
|
|
158
|
+
/** The AEAD nonce, and so every packet IV, in bytes (RFC 9001 §5.3). */
|
|
159
|
+
const QUIC_IV_SIZE: i32 = 12
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* One packet's header as `quicParseHeader` read it from a datagram. The
|
|
163
|
+
* fields after `error` are meaningful as far as the parse got: all of them
|
|
164
|
+
* when `error` is `QUIC_PACKET_OK`, and `version`, `dcid` and `scid` for
|
|
165
|
+
* `QUIC_ERR_VERSION`, which is what Version Negotiation needs.
|
|
166
|
+
*
|
|
167
|
+
* The packet number and the low bits of the first byte are still under
|
|
168
|
+
* header protection here, so the header carries where they are rather than
|
|
169
|
+
* what they hold; `quicOpenPacket` reads them.
|
|
170
|
+
*/
|
|
171
|
+
export class QuicHeader {
|
|
172
|
+
/** `QUIC_PACKET_OK`, or the `QUIC_ERR_*` that stopped the parse. */
|
|
173
|
+
error: i32 = 0
|
|
174
|
+
/** `QUIC_PACKET_INITIAL`, `_ZERO_RTT`, `_HANDSHAKE`, `_RETRY` or `_SHORT`. */
|
|
175
|
+
type: i32 = 0
|
|
176
|
+
/** The long header's 32-bit version, held in an `i64`; 0 for a short header, which has none. */
|
|
177
|
+
version: i64 = 0
|
|
178
|
+
/** The Destination Connection ID. */
|
|
179
|
+
dcid: u8[]
|
|
180
|
+
/** The Source Connection ID; empty for a short header. */
|
|
181
|
+
scid: u8[]
|
|
182
|
+
/** An Initial's token, or a Retry's; empty for every other type. */
|
|
183
|
+
token: u8[]
|
|
184
|
+
/** A Retry's integrity tag, the datagram's last 16 bytes; empty for every other type. */
|
|
185
|
+
retryTag: u8[]
|
|
186
|
+
/** Where in the datagram the packet starts. */
|
|
187
|
+
start: i32 = 0
|
|
188
|
+
/**
|
|
189
|
+
* Where the protected packet number starts: the header-protection sample
|
|
190
|
+
* starts 4 bytes after it. For a Retry, which has none, where its tag starts.
|
|
191
|
+
*/
|
|
192
|
+
pnOffset: i32 = 0
|
|
193
|
+
/** One past the packet's last byte, which is where the next coalesced packet starts. */
|
|
194
|
+
end: i32 = 0
|
|
195
|
+
/**
|
|
196
|
+
* Where `dcid`, `scid` and `token` sit in the datagram, as far as the
|
|
197
|
+
* parse got. `quicParseHeaderInto` fills these and nothing else, so a
|
|
198
|
+
* connection reads a header with no copy; `quicParseHeader` copies the
|
|
199
|
+
* windows out into the arrays above.
|
|
200
|
+
*/
|
|
201
|
+
dcidStart: i32 = 0
|
|
202
|
+
dcidLength: i32 = 0
|
|
203
|
+
scidStart: i32 = 0
|
|
204
|
+
scidLength: i32 = 0
|
|
205
|
+
tokenStart: i32 = 0
|
|
206
|
+
tokenLength: i32 = 0
|
|
207
|
+
|
|
208
|
+
constructor() {
|
|
209
|
+
this.dcid = []
|
|
210
|
+
this.scid = []
|
|
211
|
+
this.token = []
|
|
212
|
+
this.retryTag = []
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* One packet's protection keys (RFC 9001 §5.1): the AEAD's key and IV, and
|
|
218
|
+
* the header-protection key, all derived from one traffic secret by
|
|
219
|
+
* `quicKeys`. For the AES suites the two keys are also kept expanded, so a
|
|
220
|
+
* connection expands them once rather than once a packet.
|
|
221
|
+
*/
|
|
222
|
+
export class QuicKeys {
|
|
223
|
+
/** `QUIC_AEAD_AES_128_GCM`, `_AES_256_GCM` or `_CHACHA20_POLY1305`. */
|
|
224
|
+
aead: i32 = 0
|
|
225
|
+
/** The packet-protection key: 16 bytes for AES-128-GCM, 32 for the others. */
|
|
226
|
+
key: u8[]
|
|
227
|
+
/** The 12-byte IV the packet number is XORed into. */
|
|
228
|
+
iv: u8[]
|
|
229
|
+
/** The header-protection key, the same length as `key`. */
|
|
230
|
+
hp: u8[]
|
|
231
|
+
/** `key` expanded, for the AES suites; `null` for ChaCha20-Poly1305. */
|
|
232
|
+
packetAes: AesKey | null = null
|
|
233
|
+
/** `hp` expanded, for the AES suites; `null` for ChaCha20-Poly1305. */
|
|
234
|
+
hpAes: AesKey | null = null
|
|
235
|
+
|
|
236
|
+
constructor(aead: i32, key: u8[], iv: u8[], hp: u8[]) {
|
|
237
|
+
this.aead = aead
|
|
238
|
+
this.key = key
|
|
239
|
+
this.iv = iv
|
|
240
|
+
this.hp = hp
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** The two Initial secrets RFC 9001 §5.2 derives from the client's first Destination Connection ID. */
|
|
245
|
+
export class QuicInitialSecrets {
|
|
246
|
+
/** `client_initial_secret`: what protects the client's Initial packets. */
|
|
247
|
+
client: u8[]
|
|
248
|
+
/** `server_initial_secret`: what protects the server's. */
|
|
249
|
+
server: u8[]
|
|
250
|
+
|
|
251
|
+
constructor(client: u8[], server: u8[]) {
|
|
252
|
+
this.client = client
|
|
253
|
+
this.server = server
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* One packet with its protection removed by `quicOpenPacket`, or as far as
|
|
259
|
+
* that got. With `error` at `QUIC_PACKET_OK` or `QUIC_ERR_RESERVED_BITS`
|
|
260
|
+
* every field is filled in; with `QUIC_ERR_DECRYPT` all but `payload`.
|
|
261
|
+
*/
|
|
262
|
+
export class QuicPacket {
|
|
263
|
+
/** The full packet number, recovered from the truncated one (RFC 9000 §17.1). */
|
|
264
|
+
packetNumber: i64 = 0
|
|
265
|
+
/** The header in the clear, packet number included: the AEAD's associated data. */
|
|
266
|
+
header: u8[]
|
|
267
|
+
/** The decrypted frames. */
|
|
268
|
+
payload: u8[]
|
|
269
|
+
/** `QUIC_PACKET_OK`, or the `QUIC_ERR_*` that stopped the open. */
|
|
270
|
+
error: i32 = 0
|
|
271
|
+
/** The first byte with header protection removed. */
|
|
272
|
+
firstByte: i32 = 0
|
|
273
|
+
/** The packet number's length on the wire, 1 to 4 bytes. */
|
|
274
|
+
pnLength: i32 = 0
|
|
275
|
+
/** A short header's Key Phase bit (RFC 9001 §6); `false` for a long header. */
|
|
276
|
+
keyPhase: boolean = false
|
|
277
|
+
|
|
278
|
+
constructor() {
|
|
279
|
+
this.header = []
|
|
280
|
+
this.payload = []
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* HKDF-Expand-Label (RFC 8446 §7.1, RFC 9001 §5.1) with an empty context,
|
|
286
|
+
* under the hash `aead` pairs with: SHA-384 for AES-256-GCM, SHA-256 for the
|
|
287
|
+
* others. Every label and length here is a fixed one the expand accepts.
|
|
288
|
+
*/
|
|
289
|
+
const quicPacketExpandLabel = (aead: i32, secret: u8[], label: string, length: i32): u8[] =>
|
|
290
|
+
aead === QUIC_AEAD_AES_256_GCM
|
|
291
|
+
? hkdfExpandLabelSha384(secret, label, [], length)
|
|
292
|
+
: hkdfExpandLabelSha256(secret, label, [], length)
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The bytes `bytes[from .. to)` as a fresh array of exactly that size, so a
|
|
296
|
+
* packet-sized copy is one allocation rather than a push-grown one; the
|
|
297
|
+
* window is clamped to `bytes`, though every caller has checked it.
|
|
298
|
+
*/
|
|
299
|
+
const quicPacketSlice = (bytes: u8[], from: i32, to: i32): u8[] => {
|
|
300
|
+
const length: i32 = toI32(bytes.length)
|
|
301
|
+
const start: i32 = from < 0 ? 0 : from
|
|
302
|
+
const stop: i32 = to > length ? length : to
|
|
303
|
+
const size: i32 = stop > start ? stop - start : 0
|
|
304
|
+
const out: u8[] = new Array<u8>(size)
|
|
305
|
+
const outLength: i32 = toI32(out.length)
|
|
306
|
+
for (let k: i32 = 0; k < outLength; k += 1) {
|
|
307
|
+
if (start + k < toI32(bytes.length)) {
|
|
308
|
+
out[k] = bytes[start + k]
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return out
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Appends every byte of `bytes` to `out`. */
|
|
315
|
+
const quicPacketAppend = (out: u8[], bytes: u8[]): void => {
|
|
316
|
+
for (const b of bytes) {
|
|
317
|
+
out.push(b)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The bytes a variable-length integer needs (RFC 9000 §16): 1, 2, 4 or 8, or
|
|
323
|
+
* 0 for a value that has no encoding, below zero or past 2^62 - 1.
|
|
324
|
+
*/
|
|
325
|
+
export const quicVarintSize = (value: i64): i32 => {
|
|
326
|
+
if (value < 0 || value > QUIC_MAX_VARINT) {
|
|
327
|
+
return 0
|
|
328
|
+
}
|
|
329
|
+
if (value <= 63) {
|
|
330
|
+
return 1
|
|
331
|
+
}
|
|
332
|
+
if (value <= 16383) {
|
|
333
|
+
return 2
|
|
334
|
+
}
|
|
335
|
+
if (value <= 1073741823) {
|
|
336
|
+
return 4
|
|
337
|
+
}
|
|
338
|
+
return 8
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Writes `value` into `buf[at .. at + size)` as a variable-length integer of
|
|
343
|
+
* exactly `size` bytes, which may be longer than it needs (RFC 9000 §16
|
|
344
|
+
* allows that, and a Length field written before its packet is complete uses
|
|
345
|
+
* it). Answers `at + size`, or -1, writing nothing, when `size` is not 1, 2,
|
|
346
|
+
* 4 or 8, is too short for `value`, or does not fit in `buf` from `at`.
|
|
347
|
+
*/
|
|
348
|
+
export const quicVarintPut = (buf: u8[], at: i32, value: i64, size: i32): i32 => {
|
|
349
|
+
const needed: i32 = quicVarintSize(value)
|
|
350
|
+
if (needed === 0 || size < needed || at < 0 || at > toI32(buf.length) - size) {
|
|
351
|
+
return -1
|
|
352
|
+
}
|
|
353
|
+
// The two-bit prefix is log2 of the size: 0, 1, 2 or 3 for 1, 2, 4 or 8 bytes.
|
|
354
|
+
let prefix: i32 = 0
|
|
355
|
+
if (size === 2) {
|
|
356
|
+
prefix = 1
|
|
357
|
+
} else if (size === 4) {
|
|
358
|
+
prefix = 2
|
|
359
|
+
} else if (size === 8) {
|
|
360
|
+
prefix = 3
|
|
361
|
+
} else if (size !== 1) {
|
|
362
|
+
return -1
|
|
363
|
+
}
|
|
364
|
+
for (let k: i32 = 0; k < size; k += 1) {
|
|
365
|
+
const shift: i64 = toI64(size - 1 - k) * 8
|
|
366
|
+
let b: i32 = toI32((value >> shift) & 255)
|
|
367
|
+
if (k === 0) {
|
|
368
|
+
b = b | (prefix << 6)
|
|
369
|
+
}
|
|
370
|
+
if (at + k < toI32(buf.length)) {
|
|
371
|
+
buf[at + k] = toU8(b)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return at + size
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Appends `count` zero bytes to `out`, which a writer then fills in place
|
|
379
|
+
* with the `put` form of what it appends: one encoder serves both.
|
|
380
|
+
*/
|
|
381
|
+
export const quicPacketGrow = (out: u8[], count: i32): void => {
|
|
382
|
+
for (let k: i32 = 0; k < count; k += 1) {
|
|
383
|
+
out.push(toU8(0))
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Appends `value` to `out` as a variable-length integer of exactly `size`
|
|
389
|
+
* bytes, as `quicVarintPut` writes it. Answers `false`, appending nothing,
|
|
390
|
+
* when `size` is not 1, 2, 4 or 8 or is too short for `value`.
|
|
391
|
+
*/
|
|
392
|
+
export const quicVarintPushSized = (out: u8[], value: i64, size: i32): boolean => {
|
|
393
|
+
const needed: i32 = quicVarintSize(value)
|
|
394
|
+
if (needed === 0 || size < needed || (size !== 1 && size !== 2 && size !== 4 && size !== 8)) {
|
|
395
|
+
return false
|
|
396
|
+
}
|
|
397
|
+
const at: i32 = toI32(out.length)
|
|
398
|
+
quicPacketGrow(out, size)
|
|
399
|
+
return quicVarintPut(out, at, value, size) >= 0
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Appends `value` to `out` as a variable-length integer in the fewest bytes
|
|
404
|
+
* (RFC 9000 §16). Answers `false`, appending nothing, for a value below zero
|
|
405
|
+
* or past 2^62 - 1.
|
|
406
|
+
*/
|
|
407
|
+
export const quicVarintPush = (out: u8[], value: i64): boolean =>
|
|
408
|
+
quicVarintPushSized(out, value, quicVarintSize(value))
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* The length of the variable-length integer whose first byte is at
|
|
412
|
+
* `bytes[at]`, read from its two-bit prefix: 1, 2, 4 or 8. Answers 0 when
|
|
413
|
+
* `at` is outside `bytes`.
|
|
414
|
+
*/
|
|
415
|
+
export const quicVarintLength = (bytes: u8[], at: i32): i32 => {
|
|
416
|
+
if (at < 0 || at >= toI32(bytes.length)) {
|
|
417
|
+
return 0
|
|
418
|
+
}
|
|
419
|
+
return 1 << toI32(bytes[at] >> 6)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The variable-length integer at `bytes[at]` (RFC 9000 §16, §A.1), read only
|
|
424
|
+
* from `bytes[at .. end)`. Answers -1, which no varint holds, when it does not
|
|
425
|
+
* fit in that window or the window is not inside `bytes`. Advance past it by
|
|
426
|
+
* `quicVarintLength(bytes, at)`.
|
|
427
|
+
*/
|
|
428
|
+
export const quicVarintRead = (bytes: u8[], at: i32, end: i32): i64 => {
|
|
429
|
+
const size: i32 = quicVarintLength(bytes, at)
|
|
430
|
+
if (size === 0 || end > toI32(bytes.length) || at + size > end) {
|
|
431
|
+
return -1
|
|
432
|
+
}
|
|
433
|
+
let value: i64 = toI64(bytes[at] & 0x3f)
|
|
434
|
+
for (let k: i32 = 1; k < size; k += 1) {
|
|
435
|
+
value = (value << 8) | toI64(bytes[at + k])
|
|
436
|
+
}
|
|
437
|
+
return value
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* How many bytes to send packet number `fullPn` in (RFC 9000 §17.1, §A.2),
|
|
442
|
+
* when `largestAcked` is the largest the peer has acknowledged in its space,
|
|
443
|
+
* or -1 if none: enough that twice the unacknowledged range fits, 1 to 4.
|
|
444
|
+
* Answers 0 when no length will do — `fullPn` not above `largestAcked`, not a
|
|
445
|
+
* packet number, or more than 2^31 packets unacknowledged.
|
|
446
|
+
*/
|
|
447
|
+
export const quicPacketNumberLength = (fullPn: i64, largestAcked: i64): i32 => {
|
|
448
|
+
if (fullPn < 0 || fullPn > QUIC_MAX_VARINT || largestAcked < -1 || fullPn <= largestAcked) {
|
|
449
|
+
return 0
|
|
450
|
+
}
|
|
451
|
+
// `fullPn - largestAcked` packets are in flight with the new one, and the
|
|
452
|
+
// receiver's window is centred on what it expects, so the encoding has to
|
|
453
|
+
// span twice that: `unacked <= 2^(8n - 1)` for n bytes.
|
|
454
|
+
const unacked: i64 = fullPn - largestAcked
|
|
455
|
+
for (let n: i32 = 1; n <= 4; n += 1) {
|
|
456
|
+
const half: i64 = toI64(1) << (toI64(n) * 8 - 1)
|
|
457
|
+
if (unacked <= half) {
|
|
458
|
+
return n
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return 0
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* The full packet number from the `pnLength`-byte `truncatedPn` on the wire,
|
|
466
|
+
* given `largestPn`, the largest packet number this endpoint has processed in
|
|
467
|
+
* that space, or -1 for none (RFC 9000 §17.1, §A.3): the one closest to
|
|
468
|
+
* `largestPn + 1` whose low bytes are `truncatedPn`. Answers -1 when
|
|
469
|
+
* `pnLength` is not 1 to 4 or `truncatedPn` does not fit in it.
|
|
470
|
+
*/
|
|
471
|
+
export const quicPacketNumberDecode = (largestPn: i64, truncatedPn: i64, pnLength: i32): i64 => {
|
|
472
|
+
if (pnLength < 1 || pnLength > 4 || largestPn < -1 || largestPn > QUIC_MAX_VARINT) {
|
|
473
|
+
return -1
|
|
474
|
+
}
|
|
475
|
+
const window: i64 = toI64(1) << (toI64(pnLength) * 8)
|
|
476
|
+
if (truncatedPn < 0 || truncatedPn >= window) {
|
|
477
|
+
return -1
|
|
478
|
+
}
|
|
479
|
+
const expected: i64 = largestPn + 1
|
|
480
|
+
const halfWindow: i64 = window >> 1
|
|
481
|
+
const candidate: i64 = (expected & ~(window - 1)) | truncatedPn
|
|
482
|
+
// The candidate shares `expected`'s high bits; one window up or down is
|
|
483
|
+
// nearer when it falls outside (expected - half, expected + half]. The
|
|
484
|
+
// bound checks keep the answer inside [0, 2^62). One case is left that
|
|
485
|
+
// RFC 9000 §A.3's pseudocode does not catch: with `largestPn` at 2^62 - 1,
|
|
486
|
+
// `expected` is 2^62 itself, and a candidate equal to it falls through
|
|
487
|
+
// both tests. Every packet number below 2^62 with these low bytes is a
|
|
488
|
+
// window down, and the nearest of them is the answer.
|
|
489
|
+
if (candidate <= expected - halfWindow && candidate < QUIC_MAX_VARINT + 1 - window) {
|
|
490
|
+
return candidate + window
|
|
491
|
+
}
|
|
492
|
+
if ((candidate > expected + halfWindow || candidate > QUIC_MAX_VARINT) && candidate >= window) {
|
|
493
|
+
return candidate - window
|
|
494
|
+
}
|
|
495
|
+
return candidate
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** Whether `cid` is a connection ID version 1 allows: 0 to 20 bytes. */
|
|
499
|
+
const quicPacketCidFits = (cid: u8[]): boolean => toI32(cid.length) <= QUIC_MAX_CID_LENGTH
|
|
500
|
+
|
|
501
|
+
/** Whether `length` bytes from the start of `cid` are a connection ID version 1 allows: 0 to 20 bytes, inside `cid`. */
|
|
502
|
+
const quicPacketCidWindowFits = (cid: u8[], length: i32): boolean =>
|
|
503
|
+
length >= 0 && length <= QUIC_MAX_CID_LENGTH && length <= toI32(cid.length)
|
|
504
|
+
|
|
505
|
+
/** Writes `bytes[from .. from + length)` into `buf` at `at`; the caller has checked both windows. */
|
|
506
|
+
export const quicPacketCopy = (buf: u8[], at: i32, bytes: u8[], from: i32, length: i32): void => {
|
|
507
|
+
for (let k: i32 = 0; k < length; k += 1) {
|
|
508
|
+
if (at + k >= 0 && at + k < toI32(buf.length) && from + k >= 0 && from + k < toI32(bytes.length)) {
|
|
509
|
+
buf[at + k] = bytes[from + k]
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Writes one byte at `at` when it is inside `buf`. */
|
|
515
|
+
const quicPacketPutByte = (buf: u8[], at: i32, value: i32): void => {
|
|
516
|
+
if (at >= 0 && at < toI32(buf.length)) {
|
|
517
|
+
buf[at] = toU8(value)
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Writes what every version 1 long header starts with (RFC 9000 §17.2) into
|
|
523
|
+
* `buf` at `at`: the first byte, the version, and each connection ID (the
|
|
524
|
+
* first `dcidLength` and `scidLength` bytes of `dcid` and `scid`) after its
|
|
525
|
+
* length. Answers the offset past it; the caller has checked that it fits.
|
|
526
|
+
*/
|
|
527
|
+
const quicPacketPutLongPrefix = (
|
|
528
|
+
buf: u8[],
|
|
529
|
+
at: i32,
|
|
530
|
+
first: i32,
|
|
531
|
+
dcid: u8[],
|
|
532
|
+
dcidLength: i32,
|
|
533
|
+
scid: u8[],
|
|
534
|
+
scidLength: i32
|
|
535
|
+
): i32 => {
|
|
536
|
+
const version: i32 = toI32(QUIC_VERSION_1)
|
|
537
|
+
quicPacketPutByte(buf, at, first)
|
|
538
|
+
quicPacketPutByte(buf, at + 1, (version >> 24) & 255)
|
|
539
|
+
quicPacketPutByte(buf, at + 2, (version >> 16) & 255)
|
|
540
|
+
quicPacketPutByte(buf, at + 3, (version >> 8) & 255)
|
|
541
|
+
quicPacketPutByte(buf, at + 4, version & 255)
|
|
542
|
+
quicPacketPutByte(buf, at + 5, dcidLength)
|
|
543
|
+
quicPacketCopy(buf, at + 6, dcid, 0, dcidLength)
|
|
544
|
+
const scidAt: i32 = at + 6 + dcidLength
|
|
545
|
+
quicPacketPutByte(buf, scidAt, scidLength)
|
|
546
|
+
quicPacketCopy(buf, scidAt + 1, scid, 0, scidLength)
|
|
547
|
+
return scidAt + 1 + scidLength
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Writes the low `pnLength` bytes of `packetNumber` into `buf` at `at`, big-endian, and answers the offset past them. */
|
|
551
|
+
const quicPacketPutNumber = (buf: u8[], at: i32, packetNumber: i64, pnLength: i32): i32 => {
|
|
552
|
+
for (let k: i32 = 0; k < pnLength; k += 1) {
|
|
553
|
+
if (at + k >= 0 && at + k < toI32(buf.length)) {
|
|
554
|
+
buf[at + k] = toU8(toI32((packetNumber >> (toI64(pnLength - 1 - k) * 8)) & 255))
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return at + pnLength
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* The size of the long header `quicPutLongHeader` writes for these
|
|
562
|
+
* arguments, or 0 when it would refuse them (see `quicLongHeader`).
|
|
563
|
+
*/
|
|
564
|
+
export const quicLongHeaderSize = (
|
|
565
|
+
type: i32,
|
|
566
|
+
dcidLength: i32,
|
|
567
|
+
scidLength: i32,
|
|
568
|
+
tokenLength: i32,
|
|
569
|
+
packetNumber: i64,
|
|
570
|
+
pnLength: i32,
|
|
571
|
+
payloadLength: i32
|
|
572
|
+
): i32 => {
|
|
573
|
+
if (
|
|
574
|
+
(type !== QUIC_PACKET_INITIAL && type !== QUIC_PACKET_ZERO_RTT && type !== QUIC_PACKET_HANDSHAKE) ||
|
|
575
|
+
(type !== QUIC_PACKET_INITIAL && tokenLength !== 0) ||
|
|
576
|
+
dcidLength < 0 ||
|
|
577
|
+
dcidLength > QUIC_MAX_CID_LENGTH ||
|
|
578
|
+
scidLength < 0 ||
|
|
579
|
+
scidLength > QUIC_MAX_CID_LENGTH ||
|
|
580
|
+
tokenLength < 0 ||
|
|
581
|
+
pnLength < 1 ||
|
|
582
|
+
pnLength > 4 ||
|
|
583
|
+
packetNumber < 0 ||
|
|
584
|
+
packetNumber > QUIC_MAX_VARINT ||
|
|
585
|
+
payloadLength < 0 ||
|
|
586
|
+
payloadLength > QUIC_MAX_LENGTH - pnLength - QUIC_AEAD_TAG_SIZE
|
|
587
|
+
) {
|
|
588
|
+
return 0
|
|
589
|
+
}
|
|
590
|
+
const token: i32 = type === QUIC_PACKET_INITIAL ? quicVarintSize(toI64(tokenLength)) + tokenLength : 0
|
|
591
|
+
return 7 + dcidLength + scidLength + token + 2 + pnLength
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Writes into `buf` at `at` the unprotected long header `quicLongHeader`
|
|
596
|
+
* answers, with the connection IDs the first `dcidLength` and `scidLength`
|
|
597
|
+
* bytes of `dcid` and `scid`, and answers the offset past it: where the
|
|
598
|
+
* payload goes. Answers -1, writing nothing, where `quicLongHeader` answers
|
|
599
|
+
* `null`, and for a header that does not fit in `buf` from `at`.
|
|
600
|
+
*/
|
|
601
|
+
export const quicPutLongHeader = (
|
|
602
|
+
buf: u8[],
|
|
603
|
+
at: i32,
|
|
604
|
+
type: i32,
|
|
605
|
+
dcid: u8[],
|
|
606
|
+
dcidLength: i32,
|
|
607
|
+
scid: u8[],
|
|
608
|
+
scidLength: i32,
|
|
609
|
+
token: u8[],
|
|
610
|
+
packetNumber: i64,
|
|
611
|
+
pnLength: i32,
|
|
612
|
+
payloadLength: i32
|
|
613
|
+
): i32 => {
|
|
614
|
+
const tokenLength: i32 = toI32(token.length)
|
|
615
|
+
const size: i32 = quicLongHeaderSize(
|
|
616
|
+
type,
|
|
617
|
+
dcidLength,
|
|
618
|
+
scidLength,
|
|
619
|
+
tokenLength,
|
|
620
|
+
packetNumber,
|
|
621
|
+
pnLength,
|
|
622
|
+
payloadLength
|
|
623
|
+
)
|
|
624
|
+
if (
|
|
625
|
+
size === 0 ||
|
|
626
|
+
!quicPacketCidWindowFits(dcid, dcidLength) ||
|
|
627
|
+
!quicPacketCidWindowFits(scid, scidLength) ||
|
|
628
|
+
at < 0 ||
|
|
629
|
+
at > toI32(buf.length) - size
|
|
630
|
+
) {
|
|
631
|
+
return -1
|
|
632
|
+
}
|
|
633
|
+
let cursor: i32 = quicPacketPutLongPrefix(
|
|
634
|
+
buf,
|
|
635
|
+
at,
|
|
636
|
+
(type << 4) | (pnLength - 1) | 0xc0,
|
|
637
|
+
dcid,
|
|
638
|
+
dcidLength,
|
|
639
|
+
scid,
|
|
640
|
+
scidLength
|
|
641
|
+
)
|
|
642
|
+
if (type === QUIC_PACKET_INITIAL) {
|
|
643
|
+
cursor = quicVarintPut(buf, cursor, toI64(tokenLength), quicVarintSize(toI64(tokenLength)))
|
|
644
|
+
quicPacketCopy(buf, cursor, token, 0, tokenLength)
|
|
645
|
+
cursor = cursor + tokenLength
|
|
646
|
+
}
|
|
647
|
+
cursor = quicVarintPut(buf, cursor, toI64(pnLength + payloadLength + QUIC_AEAD_TAG_SIZE), 2)
|
|
648
|
+
return quicPacketPutNumber(buf, cursor, packetNumber, pnLength)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* The unprotected long header of an Initial, 0-RTT or Handshake packet
|
|
653
|
+
* (RFC 9000 §17.2), ending with the packet number in `pnLength` bytes, for a
|
|
654
|
+
* payload of `payloadLength` bytes that `quicSealPacket` will protect. The
|
|
655
|
+
* Length field counts the packet number, the payload and the AEAD tag, and is
|
|
656
|
+
* always written in two bytes (RFC 9000 §16 allows the longer form), so the
|
|
657
|
+
* header's size does not move with the payload's and a caller padding a
|
|
658
|
+
* packet to a size knows the header first. `token` goes in an Initial and
|
|
659
|
+
* must be empty otherwise.
|
|
660
|
+
*
|
|
661
|
+
* Answers `null` for another `type`, a connection ID over 20 bytes, a token
|
|
662
|
+
* on a non-Initial packet, a `pnLength` outside 1 to 4, a packet number that
|
|
663
|
+
* is not one, a negative `payloadLength`, or a Length past 16383, the most two
|
|
664
|
+
* bytes hold and far more than a datagram carries.
|
|
665
|
+
*/
|
|
666
|
+
export const quicLongHeader = (
|
|
667
|
+
type: i32,
|
|
668
|
+
dcid: u8[],
|
|
669
|
+
scid: u8[],
|
|
670
|
+
token: u8[],
|
|
671
|
+
packetNumber: i64,
|
|
672
|
+
pnLength: i32,
|
|
673
|
+
payloadLength: i32
|
|
674
|
+
): u8[] | null => {
|
|
675
|
+
const dcidLength: i32 = toI32(dcid.length)
|
|
676
|
+
const scidLength: i32 = toI32(scid.length)
|
|
677
|
+
const size: i32 = quicLongHeaderSize(
|
|
678
|
+
type,
|
|
679
|
+
dcidLength,
|
|
680
|
+
scidLength,
|
|
681
|
+
toI32(token.length),
|
|
682
|
+
packetNumber,
|
|
683
|
+
pnLength,
|
|
684
|
+
payloadLength
|
|
685
|
+
)
|
|
686
|
+
if (size === 0) {
|
|
687
|
+
return null
|
|
688
|
+
}
|
|
689
|
+
const out: u8[] = new Array<u8>(size)
|
|
690
|
+
if (
|
|
691
|
+
quicPutLongHeader(
|
|
692
|
+
out,
|
|
693
|
+
0,
|
|
694
|
+
type,
|
|
695
|
+
dcid,
|
|
696
|
+
dcidLength,
|
|
697
|
+
scid,
|
|
698
|
+
scidLength,
|
|
699
|
+
token,
|
|
700
|
+
packetNumber,
|
|
701
|
+
pnLength,
|
|
702
|
+
payloadLength
|
|
703
|
+
) < 0
|
|
704
|
+
) {
|
|
705
|
+
return null
|
|
706
|
+
}
|
|
707
|
+
return out
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Writes into `buf` at `at` the unprotected short header `quicShortHeader`
|
|
712
|
+
* answers, with the Destination Connection ID the first `dcidLength` bytes of
|
|
713
|
+
* `dcid`, and answers the offset past it. Answers -1, writing nothing, where
|
|
714
|
+
* `quicShortHeader` answers `null`, and for a header that does not fit.
|
|
715
|
+
*/
|
|
716
|
+
export const quicPutShortHeader = (
|
|
717
|
+
buf: u8[],
|
|
718
|
+
at: i32,
|
|
719
|
+
dcid: u8[],
|
|
720
|
+
dcidLength: i32,
|
|
721
|
+
spin: boolean,
|
|
722
|
+
keyPhase: boolean,
|
|
723
|
+
packetNumber: i64,
|
|
724
|
+
pnLength: i32
|
|
725
|
+
): i32 => {
|
|
726
|
+
const size: i32 = 1 + dcidLength + pnLength
|
|
727
|
+
if (
|
|
728
|
+
!quicPacketCidWindowFits(dcid, dcidLength) ||
|
|
729
|
+
pnLength < 1 ||
|
|
730
|
+
pnLength > 4 ||
|
|
731
|
+
packetNumber < 0 ||
|
|
732
|
+
packetNumber > QUIC_MAX_VARINT ||
|
|
733
|
+
at < 0 ||
|
|
734
|
+
at > toI32(buf.length) - size
|
|
735
|
+
) {
|
|
736
|
+
return -1
|
|
737
|
+
}
|
|
738
|
+
const spinBit: i32 = spin ? 0x20 : 0
|
|
739
|
+
const keyPhaseBit: i32 = keyPhase ? 0x04 : 0
|
|
740
|
+
buf[at] = toU8((pnLength - 1) | spinBit | keyPhaseBit | 0x40)
|
|
741
|
+
quicPacketCopy(buf, at + 1, dcid, 0, dcidLength)
|
|
742
|
+
return quicPacketPutNumber(buf, at + 1 + dcidLength, packetNumber, pnLength)
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* The unprotected short header of a 1-RTT packet (RFC 9000 §17.3.1): the
|
|
747
|
+
* first byte with the spin and key-phase bits, the Destination Connection ID
|
|
748
|
+
* and the packet number in `pnLength` bytes. Answers `null` for a connection
|
|
749
|
+
* ID over 20 bytes, a `pnLength` outside 1 to 4 or a packet number that is
|
|
750
|
+
* not one.
|
|
751
|
+
*/
|
|
752
|
+
export const quicShortHeader = (
|
|
753
|
+
dcid: u8[],
|
|
754
|
+
spin: boolean,
|
|
755
|
+
keyPhase: boolean,
|
|
756
|
+
packetNumber: i64,
|
|
757
|
+
pnLength: i32
|
|
758
|
+
): u8[] | null => {
|
|
759
|
+
const dcidLength: i32 = toI32(dcid.length)
|
|
760
|
+
if (!quicPacketCidFits(dcid) || pnLength < 1 || pnLength > 4) {
|
|
761
|
+
return null
|
|
762
|
+
}
|
|
763
|
+
const out: u8[] = new Array<u8>(1 + dcidLength + pnLength)
|
|
764
|
+
if (quicPutShortHeader(out, 0, dcid, dcidLength, spin, keyPhase, packetNumber, pnLength) < 0) {
|
|
765
|
+
return null
|
|
766
|
+
}
|
|
767
|
+
return out
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/** A header that stopped at `error`, with what it had read so far; answers the error. */
|
|
771
|
+
const quicPacketRefuse = (header: QuicHeader, error: i32): i32 => {
|
|
772
|
+
header.error = error
|
|
773
|
+
return error
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Reads a one-byte connection-ID length at `at` inside `[at, end)`. Answers
|
|
778
|
+
* the length, or -1 when the length byte or the ID after it does not fit.
|
|
779
|
+
*/
|
|
780
|
+
const quicPacketCidLength = (bytes: u8[], at: i32, end: i32): i32 => {
|
|
781
|
+
if (at < 0 || at >= end || at >= toI32(bytes.length)) {
|
|
782
|
+
return -1
|
|
783
|
+
}
|
|
784
|
+
const length: i32 = toI32(bytes[at])
|
|
785
|
+
return at + 1 + length > end ? -1 : length
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Parses the header of the packet starting at `datagram[at]` into `header`,
|
|
790
|
+
* filling only its numbers and the windows of its connection IDs and token
|
|
791
|
+
* (`dcidStart`, `dcidLength` and the rest), so nothing is copied or
|
|
792
|
+
* allocated: the form a connection reads every packet with. Answers
|
|
793
|
+
* `header.error`. Every field is reset first, so one `QuicHeader` serves
|
|
794
|
+
* every packet; its `dcid`, `scid`, `token` and `retryTag` arrays are left
|
|
795
|
+
* as they were. The datagram is `datagram[at .. limit)`, so one buffer can
|
|
796
|
+
* hold several (a GRO receive); a `limit` past the array reads as its length.
|
|
797
|
+
* See `quicParseHeader` for what is read and refused.
|
|
798
|
+
*/
|
|
799
|
+
export const quicParseHeaderInto = (
|
|
800
|
+
header: QuicHeader,
|
|
801
|
+
datagram: u8[],
|
|
802
|
+
at: i32,
|
|
803
|
+
limit: i32,
|
|
804
|
+
shortDcidLength: i32
|
|
805
|
+
): i32 => {
|
|
806
|
+
const end: i32 = limit > toI32(datagram.length) ? toI32(datagram.length) : limit
|
|
807
|
+
header.error = QUIC_PACKET_OK
|
|
808
|
+
header.type = 0
|
|
809
|
+
header.version = 0
|
|
810
|
+
header.start = at
|
|
811
|
+
header.end = end
|
|
812
|
+
header.pnOffset = 0
|
|
813
|
+
header.dcidStart = 0
|
|
814
|
+
header.dcidLength = 0
|
|
815
|
+
header.scidStart = 0
|
|
816
|
+
header.scidLength = 0
|
|
817
|
+
header.tokenStart = 0
|
|
818
|
+
header.tokenLength = 0
|
|
819
|
+
if (at < 0 || at >= end) {
|
|
820
|
+
return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
|
|
821
|
+
}
|
|
822
|
+
const first: i32 = toI32(datagram[at])
|
|
823
|
+
if ((first & 0x80) === 0) {
|
|
824
|
+
header.type = QUIC_PACKET_SHORT
|
|
825
|
+
if ((first & 0x40) === 0) {
|
|
826
|
+
return quicPacketRefuse(header, QUIC_ERR_FIXED_BIT)
|
|
827
|
+
}
|
|
828
|
+
if (shortDcidLength < 0 || shortDcidLength > QUIC_MAX_CID_LENGTH || at + 1 + shortDcidLength > end) {
|
|
829
|
+
return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
|
|
830
|
+
}
|
|
831
|
+
header.dcidStart = at + 1
|
|
832
|
+
header.dcidLength = shortDcidLength
|
|
833
|
+
header.pnOffset = at + 1 + shortDcidLength
|
|
834
|
+
return QUIC_PACKET_OK
|
|
835
|
+
}
|
|
836
|
+
if (at + 5 > end) {
|
|
837
|
+
return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
|
|
838
|
+
}
|
|
839
|
+
let version: i64 = 0
|
|
840
|
+
for (let k: i32 = at + 1; k < at + 5; k += 1) {
|
|
841
|
+
if (k < toI32(datagram.length)) {
|
|
842
|
+
version = (version << 8) | toI64(datagram[k])
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
header.version = version
|
|
846
|
+
const dcidLength: i32 = quicPacketCidLength(datagram, at + 5, end)
|
|
847
|
+
if (dcidLength < 0) {
|
|
848
|
+
return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
|
|
849
|
+
}
|
|
850
|
+
header.dcidStart = at + 6
|
|
851
|
+
header.dcidLength = dcidLength
|
|
852
|
+
const afterDcid: i32 = at + 6 + dcidLength
|
|
853
|
+
const scidLength: i32 = quicPacketCidLength(datagram, afterDcid, end)
|
|
854
|
+
if (scidLength < 0) {
|
|
855
|
+
return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
|
|
856
|
+
}
|
|
857
|
+
header.scidStart = afterDcid + 1
|
|
858
|
+
header.scidLength = scidLength
|
|
859
|
+
let cursor: i32 = afterDcid + 1 + scidLength
|
|
860
|
+
if (version !== QUIC_VERSION_1) {
|
|
861
|
+
return quicPacketRefuse(header, QUIC_ERR_VERSION)
|
|
862
|
+
}
|
|
863
|
+
if ((first & 0x40) === 0) {
|
|
864
|
+
return quicPacketRefuse(header, QUIC_ERR_FIXED_BIT)
|
|
865
|
+
}
|
|
866
|
+
if (dcidLength > QUIC_MAX_CID_LENGTH || scidLength > QUIC_MAX_CID_LENGTH) {
|
|
867
|
+
return quicPacketRefuse(header, QUIC_ERR_CID_LENGTH)
|
|
868
|
+
}
|
|
869
|
+
const type: i32 = (first >> 4) & 3
|
|
870
|
+
header.type = type
|
|
871
|
+
if (type === QUIC_PACKET_RETRY) {
|
|
872
|
+
// A Retry has no Length and no packet number: the token runs to the
|
|
873
|
+
// integrity tag, and the tag to the end of the datagram (§17.2.5).
|
|
874
|
+
if (cursor + QUIC_RETRY_TAG_SIZE > end) {
|
|
875
|
+
return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
|
|
876
|
+
}
|
|
877
|
+
header.tokenStart = cursor
|
|
878
|
+
header.tokenLength = end - QUIC_RETRY_TAG_SIZE - cursor
|
|
879
|
+
header.pnOffset = end - QUIC_RETRY_TAG_SIZE
|
|
880
|
+
return QUIC_PACKET_OK
|
|
881
|
+
}
|
|
882
|
+
if (type === QUIC_PACKET_INITIAL) {
|
|
883
|
+
const tokenLength: i64 = quicVarintRead(datagram, cursor, end)
|
|
884
|
+
if (tokenLength < 0) {
|
|
885
|
+
return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
|
|
886
|
+
}
|
|
887
|
+
cursor += quicVarintLength(datagram, cursor)
|
|
888
|
+
if (tokenLength > toI64(end - cursor)) {
|
|
889
|
+
return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
|
|
890
|
+
}
|
|
891
|
+
header.tokenStart = cursor
|
|
892
|
+
header.tokenLength = toI32(tokenLength)
|
|
893
|
+
cursor = cursor + toI32(tokenLength)
|
|
894
|
+
}
|
|
895
|
+
const length: i64 = quicVarintRead(datagram, cursor, end)
|
|
896
|
+
if (length < 0) {
|
|
897
|
+
return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
|
|
898
|
+
}
|
|
899
|
+
cursor += quicVarintLength(datagram, cursor)
|
|
900
|
+
if (length > toI64(end - cursor)) {
|
|
901
|
+
return quicPacketRefuse(header, QUIC_ERR_LENGTH)
|
|
902
|
+
}
|
|
903
|
+
header.pnOffset = cursor
|
|
904
|
+
header.end = cursor + toI32(length)
|
|
905
|
+
return QUIC_PACKET_OK
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Parses the header of the packet starting at `datagram[at]`, which is how a
|
|
910
|
+
* datagram of coalesced packets is walked: parse at 0, then at the answer's
|
|
911
|
+
* `end`, until `end` is the datagram's length (RFC 9000 §12.2).
|
|
912
|
+
* `shortDcidLength` is the length of the connection IDs this endpoint hands
|
|
913
|
+
* out, which is the only way to know where a short header's DCID ends
|
|
914
|
+
* (RFC 9000 §17.3.1); one outside 0 to 20 reads as `QUIC_ERR_TRUNCATED`.
|
|
915
|
+
* The connection IDs, the token and a Retry's tag are copied out into the
|
|
916
|
+
* answer's arrays; `quicParseHeaderInto` reads the same without a copy.
|
|
917
|
+
*
|
|
918
|
+
* The answer's `error` says what stopped it; nothing a peer sends panics.
|
|
919
|
+
*/
|
|
920
|
+
export const quicParseHeader = (datagram: u8[], at: i32, shortDcidLength: i32): QuicHeader => {
|
|
921
|
+
const header: QuicHeader = new QuicHeader()
|
|
922
|
+
quicParseHeaderInto(header, datagram, at, toI32(datagram.length), shortDcidLength)
|
|
923
|
+
header.dcid = quicPacketSlice(datagram, header.dcidStart, header.dcidStart + header.dcidLength)
|
|
924
|
+
header.scid = quicPacketSlice(datagram, header.scidStart, header.scidStart + header.scidLength)
|
|
925
|
+
header.token = quicPacketSlice(datagram, header.tokenStart, header.tokenStart + header.tokenLength)
|
|
926
|
+
if (header.type === QUIC_PACKET_RETRY && header.error === QUIC_PACKET_OK) {
|
|
927
|
+
header.retryTag = quicPacketSlice(datagram, header.end - QUIC_RETRY_TAG_SIZE, header.end)
|
|
928
|
+
}
|
|
929
|
+
return header
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* The secrets of RFC 9001 §5.2 for the client's first Destination Connection
|
|
934
|
+
* ID, `dcid`: `initial_secret` is HKDF-Extract under version 1's salt, and the
|
|
935
|
+
* client's and the server's are expanded from it with the labels `client in`
|
|
936
|
+
* and `server in`. Answers `null` for a `dcid` over 20 bytes.
|
|
937
|
+
*/
|
|
938
|
+
export const quicInitialSecrets = (dcid: u8[]): QuicInitialSecrets | null => {
|
|
939
|
+
if (!quicPacketCidFits(dcid)) {
|
|
940
|
+
return null
|
|
941
|
+
}
|
|
942
|
+
const initial: u8[] = hkdfExtractSha256(quicPacketInitialSalt(), dcid)
|
|
943
|
+
return new QuicInitialSecrets(
|
|
944
|
+
quicPacketExpandLabel(QUIC_AEAD_AES_128_GCM, initial, "client in", 32),
|
|
945
|
+
quicPacketExpandLabel(QUIC_AEAD_AES_128_GCM, initial, "server in", 32)
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/** The key length of `aead` in bytes, or 0 for no AEAD this module knows. */
|
|
950
|
+
const quicPacketKeyLength = (aead: i32): i32 => {
|
|
951
|
+
if (aead === QUIC_AEAD_AES_128_GCM) {
|
|
952
|
+
return 16
|
|
953
|
+
}
|
|
954
|
+
if (aead === QUIC_AEAD_AES_256_GCM || aead === QUIC_AEAD_CHACHA20_POLY1305) {
|
|
955
|
+
return 32
|
|
956
|
+
}
|
|
957
|
+
return 0
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/** The length of `aead`'s hash, which is its traffic secret's length: 48 for SHA-384, else 32. */
|
|
961
|
+
const quicPacketSecretLength = (aead: i32): i32 => (aead === QUIC_AEAD_AES_256_GCM ? 48 : 32)
|
|
962
|
+
|
|
963
|
+
/** Whether `secret` is a traffic secret for `aead`: an AEAD this module knows, and its hash's length. */
|
|
964
|
+
const quicPacketSecretFits = (aead: i32, secret: u8[]): boolean =>
|
|
965
|
+
quicPacketKeyLength(aead) !== 0 && toI32(secret.length) === quicPacketSecretLength(aead)
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Whether `keys` has the shape `quicKeys` gives: a known AEAD, key and
|
|
969
|
+
* header-protection key of its length, a 12-byte IV, and for the AES suites
|
|
970
|
+
* both keys expanded. The constructor is public only because the language
|
|
971
|
+
* has no private one, so keys made by hand are checked here, once, rather
|
|
972
|
+
* than failing deep inside an AEAD as though a peer had forged the packet.
|
|
973
|
+
*/
|
|
974
|
+
const quicPacketKeysUsable = (keys: QuicKeys): boolean => {
|
|
975
|
+
const keyLength: i32 = quicPacketKeyLength(keys.aead)
|
|
976
|
+
if (
|
|
977
|
+
keyLength === 0 ||
|
|
978
|
+
toI32(keys.key.length) !== keyLength ||
|
|
979
|
+
toI32(keys.hp.length) !== keyLength ||
|
|
980
|
+
toI32(keys.iv.length) !== QUIC_IV_SIZE
|
|
981
|
+
) {
|
|
982
|
+
return false
|
|
983
|
+
}
|
|
984
|
+
return keys.aead === QUIC_AEAD_CHACHA20_POLY1305 || (keys.packetAes !== null && keys.hpAes !== null)
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Keys for `aead` with the packet key and IV derived from `secret` (RFC 9001
|
|
989
|
+
* §5.1) and the header-protection key given, which a key update keeps
|
|
990
|
+
* (§6). The caller has checked `secret` with `quicPacketSecretFits`.
|
|
991
|
+
*/
|
|
992
|
+
const quicPacketDerive = (aead: i32, secret: u8[], hp: u8[], hpAes: AesKey | null): QuicKeys => {
|
|
993
|
+
const keys: QuicKeys = new QuicKeys(
|
|
994
|
+
aead,
|
|
995
|
+
quicPacketExpandLabel(aead, secret, "quic key", quicPacketKeyLength(aead)),
|
|
996
|
+
quicPacketExpandLabel(aead, secret, "quic iv", QUIC_IV_SIZE),
|
|
997
|
+
hp
|
|
998
|
+
)
|
|
999
|
+
keys.hpAes = hpAes
|
|
1000
|
+
if (aead !== QUIC_AEAD_CHACHA20_POLY1305) {
|
|
1001
|
+
keys.packetAes = aesKey(keys.key)
|
|
1002
|
+
}
|
|
1003
|
+
return keys
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* The packet-protection keys of RFC 9001 §5.1 from a traffic `secret`: the
|
|
1008
|
+
* AEAD key (`quic key`), the IV (`quic iv`) and the header-protection key
|
|
1009
|
+
* (`quic hp`), each HKDF-Expand-Label with an empty context under the hash
|
|
1010
|
+
* `aead` pairs with. Answers `null` for an `aead` this module does not know,
|
|
1011
|
+
* or a secret that is not that hash's length.
|
|
1012
|
+
*/
|
|
1013
|
+
export const quicKeys = (aead: i32, secret: u8[]): QuicKeys | null => {
|
|
1014
|
+
if (!quicPacketSecretFits(aead, secret)) {
|
|
1015
|
+
return null
|
|
1016
|
+
}
|
|
1017
|
+
const hp: u8[] = quicPacketExpandLabel(aead, secret, "quic hp", quicPacketKeyLength(aead))
|
|
1018
|
+
return quicPacketDerive(aead, secret, hp, aead === QUIC_AEAD_CHACHA20_POLY1305 ? null : aesKey(hp))
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* The next generation's traffic secret for a key update (RFC 9001 §6.1):
|
|
1023
|
+
* HKDF-Expand-Label(`secret`, `quic ku`, "", Hash.length). The
|
|
1024
|
+
* header-protection key does not change with it (§6), so the new keys are
|
|
1025
|
+
* `quicKeysUpdate` of the old keys and this. Answers `null` where `quicKeys`
|
|
1026
|
+
* would.
|
|
1027
|
+
*/
|
|
1028
|
+
export const quicKeyUpdateSecret = (aead: i32, secret: u8[]): u8[] | null => {
|
|
1029
|
+
if (!quicPacketSecretFits(aead, secret)) {
|
|
1030
|
+
return null
|
|
1031
|
+
}
|
|
1032
|
+
return quicPacketExpandLabel(aead, secret, "quic ku", quicPacketSecretLength(aead))
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* The next generation's keys in a key update (RFC 9001 §6): the packet key
|
|
1037
|
+
* and IV derived from `nextSecret`, which `quicKeyUpdateSecret` made from the
|
|
1038
|
+
* current secret, and `keys`' header-protection key kept, expanded as it was,
|
|
1039
|
+
* since header protection never changes with the phase. Answers `null` for
|
|
1040
|
+
* keys `quicKeys` did not make or a secret of the wrong length.
|
|
1041
|
+
*/
|
|
1042
|
+
export const quicKeysUpdate = (keys: QuicKeys, nextSecret: u8[]): QuicKeys | null => {
|
|
1043
|
+
if (!quicPacketKeysUsable(keys) || !quicPacketSecretFits(keys.aead, nextSecret)) {
|
|
1044
|
+
return null
|
|
1045
|
+
}
|
|
1046
|
+
return quicPacketDerive(keys.aead, nextSecret, keys.hp, keys.hpAes)
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/** The bits of the first byte header protection covers: four in a long header, five in a short one (RFC 9001 §5.4.1). */
|
|
1050
|
+
const quicPacketProtectedBits = (first: i32): i32 => ((first & 0x80) !== 0 ? 0x0f : 0x1f)
|
|
1051
|
+
|
|
1052
|
+
/** The first byte's reserved bits: 0x0c in a long header, 0x18 in a short one (RFC 9000 §17.2, §17.3.1). */
|
|
1053
|
+
const quicPacketReservedBits = (first: i32): i32 => ((first & 0x80) !== 0 ? 0x0c : 0x18)
|
|
1054
|
+
|
|
1055
|
+
/** The AEAD nonce for `packetNumber` (RFC 9001 §5.3): the IV with the number XORed into its low 8 bytes, big-endian. */
|
|
1056
|
+
const quicPacketNonce = (keys: QuicKeys, packetNumber: i64): u8[] => {
|
|
1057
|
+
const nonce: u8[] = new Array<u8>(QUIC_IV_SIZE)
|
|
1058
|
+
const nonceLength: i32 = toI32(nonce.length)
|
|
1059
|
+
for (let k: i32 = 0; k < nonceLength; k += 1) {
|
|
1060
|
+
// Byte `k` of the nonce takes byte `k - 4` of the number's eight.
|
|
1061
|
+
const pn: i64 = k < 4 ? 0 : (packetNumber >> (toI64(11 - k) * 8)) & 255
|
|
1062
|
+
const iv: i32 = k < toI32(keys.iv.length) ? toI32(keys.iv[k]) : 0
|
|
1063
|
+
nonce[k] = toU8(iv ^ toI32(pn))
|
|
1064
|
+
}
|
|
1065
|
+
return nonce
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/** The five-byte header-protection mask for `sample` under `keys` (RFC 9001 §5.4.3, §5.4.4), or `null`. */
|
|
1069
|
+
const quicPacketMask = (keys: QuicKeys, sample: u8[]): u8[] | null => {
|
|
1070
|
+
if (keys.aead === QUIC_AEAD_CHACHA20_POLY1305) {
|
|
1071
|
+
return chacha20HeaderMask(keys.hp, sample)
|
|
1072
|
+
}
|
|
1073
|
+
const hpAes: AesKey | null = keys.hpAes
|
|
1074
|
+
if (hpAes !== null) {
|
|
1075
|
+
return aesHeaderMask(hpAes, sample)
|
|
1076
|
+
}
|
|
1077
|
+
return null
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/** `keys`' AEAD over `plaintext` with `aad`: the ciphertext and its tag, or `null`. */
|
|
1081
|
+
const quicPacketSeal = (keys: QuicKeys, nonce: u8[], aad: u8[], plaintext: u8[]): u8[] | null => {
|
|
1082
|
+
if (keys.aead === QUIC_AEAD_CHACHA20_POLY1305) {
|
|
1083
|
+
return chacha20Poly1305Seal(keys.key, nonce, aad, plaintext)
|
|
1084
|
+
}
|
|
1085
|
+
const packetAes: AesKey | null = keys.packetAes
|
|
1086
|
+
if (packetAes !== null) {
|
|
1087
|
+
return aesGcmSeal(packetAes, nonce, aad, plaintext)
|
|
1088
|
+
}
|
|
1089
|
+
return null
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/** `keys`' AEAD opening `sealed` with `aad`: the plaintext, or `null` when it does not authenticate. */
|
|
1093
|
+
const quicPacketOpen = (keys: QuicKeys, nonce: u8[], aad: u8[], sealed: u8[]): u8[] | null => {
|
|
1094
|
+
if (keys.aead === QUIC_AEAD_CHACHA20_POLY1305) {
|
|
1095
|
+
return chacha20Poly1305Open(keys.key, nonce, aad, sealed)
|
|
1096
|
+
}
|
|
1097
|
+
const packetAes: AesKey | null = keys.packetAes
|
|
1098
|
+
if (packetAes !== null) {
|
|
1099
|
+
return aesGcmOpen(packetAes, nonce, aad, sealed)
|
|
1100
|
+
}
|
|
1101
|
+
return null
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Protects one packet in place (RFC 9001 §5.3, §5.4.1). `buf[start ..
|
|
1106
|
+
* start + headerLength)` is a header `quicPutLongHeader` or
|
|
1107
|
+
* `quicPutShortHeader` wrote, ending with the packet number, and the
|
|
1108
|
+
* `payloadLength` bytes after it are the frames; `packetNumber` is the full
|
|
1109
|
+
* number the header's last bytes truncate. The payload is sealed where it
|
|
1110
|
+
* lies, its tag written after it, and header protection put on: the answer
|
|
1111
|
+
* is the offset past the tag, which is where the packet ends, or -1, with
|
|
1112
|
+
* `buf` not to be sent, for anything `quicSealPacket` refuses and for a tag
|
|
1113
|
+
* that does not fit in `buf`. The AEAD's temporaries are released before it
|
|
1114
|
+
* answers, so sealing allocates nothing that outlives the call.
|
|
1115
|
+
*/
|
|
1116
|
+
export const quicSealInPlace = (
|
|
1117
|
+
keys: QuicKeys,
|
|
1118
|
+
buf: u8[],
|
|
1119
|
+
start: i32,
|
|
1120
|
+
headerLength: i32,
|
|
1121
|
+
payloadLength: i32,
|
|
1122
|
+
packetNumber: i64
|
|
1123
|
+
): i32 => {
|
|
1124
|
+
const end: i32 = start + headerLength + payloadLength + QUIC_AEAD_TAG_SIZE
|
|
1125
|
+
if (start < 0 || headerLength < 2 || payloadLength < 0 || end > toI32(buf.length) || end < start) {
|
|
1126
|
+
return -1
|
|
1127
|
+
}
|
|
1128
|
+
const first: i32 = toI32(buf[start])
|
|
1129
|
+
const pnLength: i32 = (first & 3) + 1
|
|
1130
|
+
const pnOffset: i32 = start + headerLength - pnLength
|
|
1131
|
+
if (
|
|
1132
|
+
!quicPacketKeysUsable(keys) ||
|
|
1133
|
+
pnOffset < start + 1 ||
|
|
1134
|
+
pnLength + payloadLength < 4 ||
|
|
1135
|
+
packetNumber < 0 ||
|
|
1136
|
+
packetNumber > QUIC_MAX_VARINT
|
|
1137
|
+
) {
|
|
1138
|
+
return -1
|
|
1139
|
+
}
|
|
1140
|
+
for (let k: i32 = 0; k < pnLength; k += 1) {
|
|
1141
|
+
const want: i32 = toI32((packetNumber >> (toI64(pnLength - 1 - k) * 8)) & 255)
|
|
1142
|
+
if (pnOffset + k < toI32(buf.length) && toI32(buf[pnOffset + k]) !== want) {
|
|
1143
|
+
return -1
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (
|
|
1147
|
+
(first & 0x80) !== 0 &&
|
|
1148
|
+
quicVarintRead(buf, pnOffset - 2, pnOffset) !== toI64(pnLength + payloadLength + QUIC_AEAD_TAG_SIZE)
|
|
1149
|
+
) {
|
|
1150
|
+
return -1
|
|
1151
|
+
}
|
|
1152
|
+
const payloadAt: i32 = start + headerLength
|
|
1153
|
+
{
|
|
1154
|
+
using _scope = arena()
|
|
1155
|
+
const sealed: u8[] | null = quicPacketSeal(
|
|
1156
|
+
keys,
|
|
1157
|
+
quicPacketNonce(keys, packetNumber),
|
|
1158
|
+
quicPacketSlice(buf, start, payloadAt),
|
|
1159
|
+
quicPacketSlice(buf, payloadAt, payloadAt + payloadLength)
|
|
1160
|
+
)
|
|
1161
|
+
if (sealed === null || payloadAt + toI32(sealed.length) > toI32(buf.length)) {
|
|
1162
|
+
return -1
|
|
1163
|
+
}
|
|
1164
|
+
buf.set(sealed, payloadAt)
|
|
1165
|
+
const sampleAt: i32 = pnOffset + 4
|
|
1166
|
+
const mask: u8[] | null = quicPacketMask(
|
|
1167
|
+
keys,
|
|
1168
|
+
quicPacketSlice(buf, sampleAt, sampleAt + QUIC_SAMPLE_SIZE)
|
|
1169
|
+
)
|
|
1170
|
+
if (mask === null || toI32(mask.length) < 5) {
|
|
1171
|
+
return -1
|
|
1172
|
+
}
|
|
1173
|
+
buf[start] = toU8(first ^ (toI32(mask[0]) & quicPacketProtectedBits(first)))
|
|
1174
|
+
for (let k: i32 = 0; k < pnLength && k + 1 < toI32(mask.length); k += 1) {
|
|
1175
|
+
if (pnOffset + k >= 0 && pnOffset + k < toI32(buf.length)) {
|
|
1176
|
+
buf[pnOffset + k] = buf[pnOffset + k] ^ mask[k + 1]
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return end
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Protects one packet (RFC 9001 §5.3, §5.4.1): `header` is what
|
|
1185
|
+
* `quicLongHeader` or `quicShortHeader` built, ending with the packet number,
|
|
1186
|
+
* and `packetNumber` is the full number its last bytes truncate. The answer
|
|
1187
|
+
* is the header under header protection followed by the AEAD-sealed payload
|
|
1188
|
+
* and its tag, ready to send or to coalesce; `quicSealInPlace` does the same
|
|
1189
|
+
* in a caller's buffer.
|
|
1190
|
+
*
|
|
1191
|
+
* The header and the arguments are held to each other, because a packet that
|
|
1192
|
+
* disagrees with its own header is one the peer silently drops: the header's
|
|
1193
|
+
* packet-number bytes must be `packetNumber`'s low bytes, and a long header's
|
|
1194
|
+
* two-byte Length must count this `payload`. The sample is the 16 bytes from
|
|
1195
|
+
* 4 past the packet number's start, so the packet number and payload together
|
|
1196
|
+
* must be at least 4 bytes; a shorter payload has to be padded first (RFC 9001
|
|
1197
|
+
* §5.4.2). Answers `null` for any of those, for a header too short for its
|
|
1198
|
+
* packet-number length, and for keys `quicKeys` did not make.
|
|
1199
|
+
*/
|
|
1200
|
+
export const quicSealPacket = (
|
|
1201
|
+
keys: QuicKeys,
|
|
1202
|
+
header: u8[],
|
|
1203
|
+
packetNumber: i64,
|
|
1204
|
+
payload: u8[]
|
|
1205
|
+
): u8[] | null => {
|
|
1206
|
+
const headerLength: i32 = toI32(header.length)
|
|
1207
|
+
const payloadLength: i32 = toI32(payload.length)
|
|
1208
|
+
const packet: u8[] = new Array<u8>(headerLength + payloadLength + QUIC_AEAD_TAG_SIZE)
|
|
1209
|
+
packet.set(header)
|
|
1210
|
+
packet.set(payload, headerLength)
|
|
1211
|
+
if (quicSealInPlace(keys, packet, 0, headerLength, payloadLength, packetNumber) < 0) {
|
|
1212
|
+
return null
|
|
1213
|
+
}
|
|
1214
|
+
return packet
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Removes header protection from the packet `header` describes (RFC 9001
|
|
1219
|
+
* §5.4.1) without changing `datagram`: `packet`'s `error`, `firstByte`,
|
|
1220
|
+
* `pnLength`, `packetNumber` and `keyPhase` are set, and the answer is the
|
|
1221
|
+
* header in the clear, packet number included, which is the AEAD's
|
|
1222
|
+
* associated data: empty when `error` is not `QUIC_PACKET_OK`. Only numbers
|
|
1223
|
+
* are stored into `packet`, so a connection reuses one for every packet and
|
|
1224
|
+
* the answer is its own to let go. See `quicRemoveHeaderProtection`.
|
|
1225
|
+
*/
|
|
1226
|
+
export const quicUnprotectHeader = (
|
|
1227
|
+
keys: QuicKeys,
|
|
1228
|
+
datagram: u8[],
|
|
1229
|
+
header: QuicHeader,
|
|
1230
|
+
largestPn: i64,
|
|
1231
|
+
packet: QuicPacket
|
|
1232
|
+
): u8[] => {
|
|
1233
|
+
packet.error = QUIC_PACKET_OK
|
|
1234
|
+
packet.firstByte = 0
|
|
1235
|
+
packet.pnLength = 0
|
|
1236
|
+
packet.packetNumber = 0
|
|
1237
|
+
packet.keyPhase = false
|
|
1238
|
+
const datagramLength: i32 = toI32(datagram.length)
|
|
1239
|
+
if (header.error !== QUIC_PACKET_OK) {
|
|
1240
|
+
packet.error = header.error
|
|
1241
|
+
return []
|
|
1242
|
+
}
|
|
1243
|
+
if (header.type === QUIC_PACKET_RETRY) {
|
|
1244
|
+
packet.error = QUIC_ERR_NOT_PROTECTED
|
|
1245
|
+
return []
|
|
1246
|
+
}
|
|
1247
|
+
if (!quicPacketKeysUsable(keys)) {
|
|
1248
|
+
packet.error = QUIC_ERR_KEYS
|
|
1249
|
+
return []
|
|
1250
|
+
}
|
|
1251
|
+
const pnOffset: i32 = header.pnOffset
|
|
1252
|
+
const sampleAt: i32 = pnOffset + 4
|
|
1253
|
+
if (
|
|
1254
|
+
header.start < 0 ||
|
|
1255
|
+
header.start >= pnOffset ||
|
|
1256
|
+
header.end > datagramLength ||
|
|
1257
|
+
sampleAt + QUIC_SAMPLE_SIZE > header.end
|
|
1258
|
+
) {
|
|
1259
|
+
packet.error = QUIC_ERR_SAMPLE
|
|
1260
|
+
return []
|
|
1261
|
+
}
|
|
1262
|
+
const mask: u8[] | null = quicPacketMask(
|
|
1263
|
+
keys,
|
|
1264
|
+
quicPacketSlice(datagram, sampleAt, sampleAt + QUIC_SAMPLE_SIZE)
|
|
1265
|
+
)
|
|
1266
|
+
if (mask === null || toI32(mask.length) < 5) {
|
|
1267
|
+
packet.error = QUIC_ERR_KEYS
|
|
1268
|
+
return []
|
|
1269
|
+
}
|
|
1270
|
+
const protectedFirst: i32 = toI32(datagram[header.start])
|
|
1271
|
+
const long: boolean = (protectedFirst & 0x80) !== 0
|
|
1272
|
+
const first: i32 = protectedFirst ^ (toI32(mask[0]) & quicPacketProtectedBits(protectedFirst))
|
|
1273
|
+
const pnLength: i32 = (first & 3) + 1
|
|
1274
|
+
// The sample check above put 20 bytes after `pnOffset` inside the packet,
|
|
1275
|
+
// so the packet number's at most 4 are there too.
|
|
1276
|
+
const clear: u8[] = quicPacketSlice(datagram, header.start, pnOffset + pnLength)
|
|
1277
|
+
clear[0] = toU8(first)
|
|
1278
|
+
let truncated: i64 = 0
|
|
1279
|
+
for (let k: i32 = 0; k < pnLength; k += 1) {
|
|
1280
|
+
const at: i32 = pnOffset - header.start + k
|
|
1281
|
+
if (at >= 0 && at < toI32(clear.length) && k + 1 < toI32(mask.length)) {
|
|
1282
|
+
const b: u8 = clear[at] ^ mask[k + 1]
|
|
1283
|
+
clear[at] = b
|
|
1284
|
+
truncated = (truncated << 8) | toI64(b)
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
packet.firstByte = first
|
|
1288
|
+
packet.pnLength = pnLength
|
|
1289
|
+
packet.packetNumber = quicPacketNumberDecode(largestPn, truncated, pnLength)
|
|
1290
|
+
packet.keyPhase = !long && (first & 0x04) !== 0
|
|
1291
|
+
return clear
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Removes header protection from the packet `header` describes (RFC 9001
|
|
1296
|
+
* §5.4.1): the answer's `firstByte`, `pnLength`, `packetNumber`, `keyPhase`
|
|
1297
|
+
* and `header` are filled in, the packet number recovered against
|
|
1298
|
+
* `largestPn`, the largest this endpoint has processed in the packet's space
|
|
1299
|
+
* (or -1). The header-protection key never changes with a key update, so a
|
|
1300
|
+
* caller holding two generations of keys calls this, reads `keyPhase`, and
|
|
1301
|
+
* hands the answer to `quicDecryptPacket` with the generation it names
|
|
1302
|
+
* (RFC 9001 §6.3).
|
|
1303
|
+
*
|
|
1304
|
+
* `error` is the header's own for a header that did not parse,
|
|
1305
|
+
* `QUIC_ERR_NOT_PROTECTED` for a Retry, `QUIC_ERR_KEYS` for keys `quicKeys`
|
|
1306
|
+
* did not make, and `QUIC_ERR_SAMPLE` for a packet too short to sample.
|
|
1307
|
+
*/
|
|
1308
|
+
export const quicRemoveHeaderProtection = (
|
|
1309
|
+
keys: QuicKeys,
|
|
1310
|
+
datagram: u8[],
|
|
1311
|
+
header: QuicHeader,
|
|
1312
|
+
largestPn: i64
|
|
1313
|
+
): QuicPacket => {
|
|
1314
|
+
const packet: QuicPacket = new QuicPacket()
|
|
1315
|
+
packet.header = quicUnprotectHeader(keys, datagram, header, largestPn, packet)
|
|
1316
|
+
return packet
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/**
|
|
1320
|
+
* Decrypts the payload of the packet `header` describes, whose header in the
|
|
1321
|
+
* clear `quicUnprotectHeader` answered as `clear` (RFC 9001 §5.3), without
|
|
1322
|
+
* changing `datagram`. Sets `packet.error` as `quicDecryptPacket` does and
|
|
1323
|
+
* answers the plaintext frames, or `null` when they did not authenticate
|
|
1324
|
+
* (or `packet` was already in error). Only numbers are stored into `packet`.
|
|
1325
|
+
*/
|
|
1326
|
+
export const quicDecryptPayload = (
|
|
1327
|
+
keys: QuicKeys,
|
|
1328
|
+
datagram: u8[],
|
|
1329
|
+
header: QuicHeader,
|
|
1330
|
+
clear: u8[],
|
|
1331
|
+
packet: QuicPacket
|
|
1332
|
+
): u8[] | null => {
|
|
1333
|
+
if (packet.error !== QUIC_PACKET_OK) {
|
|
1334
|
+
return null
|
|
1335
|
+
}
|
|
1336
|
+
if (!quicPacketKeysUsable(keys)) {
|
|
1337
|
+
packet.error = QUIC_ERR_KEYS
|
|
1338
|
+
return null
|
|
1339
|
+
}
|
|
1340
|
+
const payloadStart: i32 = header.start + toI32(clear.length)
|
|
1341
|
+
const plain: u8[] | null = quicPacketOpen(
|
|
1342
|
+
keys,
|
|
1343
|
+
quicPacketNonce(keys, packet.packetNumber),
|
|
1344
|
+
clear,
|
|
1345
|
+
quicPacketSlice(datagram, payloadStart, header.end)
|
|
1346
|
+
)
|
|
1347
|
+
if (plain === null) {
|
|
1348
|
+
packet.error = QUIC_ERR_DECRYPT
|
|
1349
|
+
return null
|
|
1350
|
+
}
|
|
1351
|
+
if ((packet.firstByte & quicPacketReservedBits(packet.firstByte)) !== 0) {
|
|
1352
|
+
packet.error = QUIC_ERR_RESERVED_BITS
|
|
1353
|
+
}
|
|
1354
|
+
return plain
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Decrypts the payload of a packet whose header protection
|
|
1359
|
+
* `quicRemoveHeaderProtection` took off (RFC 9001 §5.3), filling in
|
|
1360
|
+
* `packet.payload` and setting `packet.error`: `QUIC_ERR_KEYS` for keys
|
|
1361
|
+
* `quicKeys` did not make, `QUIC_ERR_DECRYPT` when it does not authenticate
|
|
1362
|
+
* under `keys`, and `QUIC_ERR_RESERVED_BITS` when it
|
|
1363
|
+
* does but the header's reserved bits are not zero (RFC 9000 §17.2, §17.3.1),
|
|
1364
|
+
* which the caller is to close the connection over. Answers whether the
|
|
1365
|
+
* payload authenticated. A packet already in error is left as it is.
|
|
1366
|
+
*/
|
|
1367
|
+
export const quicDecryptPacket = (
|
|
1368
|
+
keys: QuicKeys,
|
|
1369
|
+
datagram: u8[],
|
|
1370
|
+
header: QuicHeader,
|
|
1371
|
+
packet: QuicPacket
|
|
1372
|
+
): boolean => {
|
|
1373
|
+
const plain: u8[] | null = quicDecryptPayload(keys, datagram, header, packet.header, packet)
|
|
1374
|
+
if (plain === null) {
|
|
1375
|
+
return false
|
|
1376
|
+
}
|
|
1377
|
+
packet.payload = plain
|
|
1378
|
+
return true
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Opens one packet of a datagram (RFC 9001 §5.3, §5.4): header protection
|
|
1383
|
+
* off, the packet number recovered against `largestPn` (or -1), and the
|
|
1384
|
+
* payload decrypted. The answer's `error` is `QUIC_PACKET_OK` with `payload`
|
|
1385
|
+
* holding the frames, or the `QUIC_ERR_*` of the step that failed — see
|
|
1386
|
+
* `quicRemoveHeaderProtection` and `quicDecryptPacket`.
|
|
1387
|
+
*/
|
|
1388
|
+
export const quicOpenPacket = (
|
|
1389
|
+
keys: QuicKeys,
|
|
1390
|
+
datagram: u8[],
|
|
1391
|
+
header: QuicHeader,
|
|
1392
|
+
largestPn: i64
|
|
1393
|
+
): QuicPacket => {
|
|
1394
|
+
const packet: QuicPacket = quicRemoveHeaderProtection(keys, datagram, header, largestPn)
|
|
1395
|
+
quicDecryptPacket(keys, datagram, header, packet)
|
|
1396
|
+
return packet
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* The Retry integrity tag of RFC 9001 §5.8: AES-128-GCM under version 1's
|
|
1401
|
+
* fixed key and nonce over an empty plaintext, with the Retry pseudo-packet —
|
|
1402
|
+
* `odcid`'s length and bytes, then the Retry packet up to its tag — as the
|
|
1403
|
+
* associated data. `odcid` is the Destination Connection ID of the client's
|
|
1404
|
+
* Initial the Retry answers. Answers `null` for an `odcid` over 20 bytes.
|
|
1405
|
+
*/
|
|
1406
|
+
export const quicRetryIntegrityTag = (odcid: u8[], retryWithoutTag: u8[]): u8[] | null => {
|
|
1407
|
+
const key: AesKey | null = aesKey(quicPacketRetryKey())
|
|
1408
|
+
if (key === null || !quicPacketCidFits(odcid)) {
|
|
1409
|
+
return null
|
|
1410
|
+
}
|
|
1411
|
+
const pseudo: u8[] = []
|
|
1412
|
+
pseudo.push(toU8(toI32(odcid.length)))
|
|
1413
|
+
quicPacketAppend(pseudo, odcid)
|
|
1414
|
+
quicPacketAppend(pseudo, retryWithoutTag)
|
|
1415
|
+
return aesGcmSeal(key, quicPacketRetryNonce(), pseudo, [])
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* A whole Retry packet (RFC 9000 §17.2.5, RFC 9001 §5.8): the long header with
|
|
1420
|
+
* `unused` in the first byte's low four bits (the server picks them; mask
|
|
1421
|
+
* them with randomness), the client's SCID as `dcid`, the server's new SCID,
|
|
1422
|
+
* the address-validation `token`, and the integrity tag over `odcid`, the
|
|
1423
|
+
* DCID the client's Initial was sent to. Answers `null` for a connection ID
|
|
1424
|
+
* over 20 bytes, an empty token (a client discards that Retry, §17.2.5.2) or
|
|
1425
|
+
* `unused` outside 0 to 15.
|
|
1426
|
+
*/
|
|
1427
|
+
export const quicRetryPacket = (
|
|
1428
|
+
dcid: u8[],
|
|
1429
|
+
scid: u8[],
|
|
1430
|
+
token: u8[],
|
|
1431
|
+
odcid: u8[],
|
|
1432
|
+
unused: i32
|
|
1433
|
+
): u8[] | null => {
|
|
1434
|
+
if (
|
|
1435
|
+
!quicPacketCidFits(dcid) ||
|
|
1436
|
+
!quicPacketCidFits(scid) ||
|
|
1437
|
+
toI32(token.length) === 0 ||
|
|
1438
|
+
unused < 0 ||
|
|
1439
|
+
unused > 15
|
|
1440
|
+
) {
|
|
1441
|
+
return null
|
|
1442
|
+
}
|
|
1443
|
+
const dcidLength: i32 = toI32(dcid.length)
|
|
1444
|
+
const scidLength: i32 = toI32(scid.length)
|
|
1445
|
+
const out: u8[] = new Array<u8>(7 + dcidLength + scidLength)
|
|
1446
|
+
quicPacketPutLongPrefix(out, 0, unused | 0xf0, dcid, dcidLength, scid, scidLength)
|
|
1447
|
+
quicPacketAppend(out, token)
|
|
1448
|
+
const tag: u8[] | null = quicRetryIntegrityTag(odcid, out)
|
|
1449
|
+
if (tag === null) {
|
|
1450
|
+
return null
|
|
1451
|
+
}
|
|
1452
|
+
quicPacketAppend(out, tag)
|
|
1453
|
+
return out
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* Whether the Retry `header` describes carries a valid integrity tag for
|
|
1458
|
+
* `odcid`, the DCID of the Initial this client sent (RFC 9001 §5.8). The tag
|
|
1459
|
+
* is recomputed over the datagram's bytes and compared in constant time.
|
|
1460
|
+
* Answers `false` for a header that is not a Retry that parsed.
|
|
1461
|
+
*/
|
|
1462
|
+
export const quicRetryVerify = (odcid: u8[], datagram: u8[], header: QuicHeader): boolean => {
|
|
1463
|
+
if (header.error !== QUIC_PACKET_OK || header.type !== QUIC_PACKET_RETRY) {
|
|
1464
|
+
return false
|
|
1465
|
+
}
|
|
1466
|
+
const tag: u8[] | null = quicRetryIntegrityTag(
|
|
1467
|
+
odcid,
|
|
1468
|
+
quicPacketSlice(datagram, header.start, header.pnOffset)
|
|
1469
|
+
)
|
|
1470
|
+
if (tag === null) {
|
|
1471
|
+
return false
|
|
1472
|
+
}
|
|
1473
|
+
return timingSafeEqual(tag, header.retryTag)
|
|
1474
|
+
}
|