@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
package/std/net/tls.ts
ADDED
|
@@ -0,0 +1,1368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/tls` — the server side of a TLS 1.3 handshake (RFC 8446), from
|
|
3
|
+
* the client's first ClientHello to its Finished, carrier-agnostic and
|
|
4
|
+
* sans-IO.
|
|
5
|
+
*
|
|
6
|
+
* import { TlsServer, TLS_LEVEL_INITIAL } from "nish/net/tls";
|
|
7
|
+
*
|
|
8
|
+
* const server = new TlsServer(config, serverRandom, x25519Private);
|
|
9
|
+
* const alert: i32 = server.receive(TLS_LEVEL_INITIAL, bytes, 0, n);
|
|
10
|
+
* // …and for the slot's next connection:
|
|
11
|
+
* server.restart(nextRandom, nextPrivate);
|
|
12
|
+
* // send server.takeOutput(TLS_LEVEL_INITIAL), install the handshake
|
|
13
|
+
* // secrets, sign server.signatureInput(), call server.sign(signature), …
|
|
14
|
+
*
|
|
15
|
+
* **Carrier-agnostic.** The server reads and writes handshake *messages*, not
|
|
16
|
+
* records or packets. Bytes go in and come out tagged with the level they
|
|
17
|
+
* belong to — Initial (cleartext: ClientHello, ServerHello), Handshake
|
|
18
|
+
* (EncryptedExtensions through both Finished messages) or Application — and
|
|
19
|
+
* the secrets for each level are readable as soon as they are known. TLS over
|
|
20
|
+
* TCP (WP34 T2) wraps a level's bytes in records protected under that level's
|
|
21
|
+
* secret; QUIC (Q2) puts them in CRYPTO frames of the matching packet number
|
|
22
|
+
* space (RFC 9001 §4.1.3). Neither needs anything else from here.
|
|
23
|
+
*
|
|
24
|
+
* **Sans-IO.** Nothing here reads a socket, a clock or the random device. The
|
|
25
|
+
* server random and the ephemeral x25519 private key are constructor
|
|
26
|
+
* arguments, so a test injects RFC 8448's and a server draws them with
|
|
27
|
+
* `crypto.getRandomValues`. Signing is handed off the same way: once the
|
|
28
|
+
* Certificate is written the server stops at `TLS_STATE_WAIT_SIGNATURE`, the
|
|
29
|
+
* caller signs `signatureInput()` with its own key (`tlsSignEcdsaP256` for a
|
|
30
|
+
* P-256 key) and hands the signature to `sign`. That keeps the private key out
|
|
31
|
+
* of this module, and it lets RFC 8448's trace, which signs with RSA-PSS, be
|
|
32
|
+
* replayed with its own signature.
|
|
33
|
+
*
|
|
34
|
+
* **What is negotiated.** TLS 1.3 only. The suites `TLS_AES_128_GCM_SHA256`,
|
|
35
|
+
* `TLS_CHACHA20_POLY1305_SHA256` and `TLS_AES_256_GCM_SHA384`, the first of
|
|
36
|
+
* them the client lists; the group x25519; the configuration's one signature
|
|
37
|
+
* scheme. A client that offers x25519 in `supported_groups` without a share
|
|
38
|
+
* gets one HelloRetryRequest (§4.1.4), and a second ClientHello still without
|
|
39
|
+
* the share is refused. `server_name` is read and exposed as `serverName`.
|
|
40
|
+
* ALPN picks the first of the configuration's protocols the client offers.
|
|
41
|
+
* `quic_transport_parameters` is read and written only when the configuration
|
|
42
|
+
* says the carrier is QUIC. PSKs, 0-RTT, NewSessionTicket and client
|
|
43
|
+
* authentication are not here: a PSK or early-data offer is ignored, so the
|
|
44
|
+
* client falls back to a full handshake.
|
|
45
|
+
*
|
|
46
|
+
* **Refusals are alerts.** `receive` and `sign` answer 0 or a TLS alert
|
|
47
|
+
* description (`TLS_ALERT_*` in `nish/net/tls/codec`); after one the server
|
|
48
|
+
* is in `TLS_STATE_FAILED` and answers the same alert to every call. Nothing
|
|
49
|
+
* the peer sends can make it panic.
|
|
50
|
+
*
|
|
51
|
+
* **Secrets.** The ECDHE secret, and the copy of the ephemeral key it is
|
|
52
|
+
* computed with, are `Secret`s (`nish:secret`) wiped on every path. What the
|
|
53
|
+
* server keeps in its fields — the caller's ephemeral key bytes, since a
|
|
54
|
+
* `Secret` may not be a field, and the handshake, traffic and exporter secrets
|
|
55
|
+
* its carrier reads — is wiped by its carrier, and by `restart` before the
|
|
56
|
+
* next connection uses the arrays (CLAUDE.md §Security; TLS-1 in
|
|
57
|
+
* `docs/security/tls.md`).
|
|
58
|
+
*
|
|
59
|
+
* **Memory.** A `TlsServer` keeps its state in buffers it allocates once —
|
|
60
|
+
* the messages in and out, the transcript, the secrets, the HKDF scratch —
|
|
61
|
+
* and `restart` hands them to the next connection, so a carrier that keeps
|
|
62
|
+
* one per slot runs every handshake after the first without moving the arena
|
|
63
|
+
* (TLS-3; `tests/link/net_tls_memory`). What a message allocates on the way
|
|
64
|
+
* dies inside a `using a = arena()` block or an arena scope of its own.
|
|
65
|
+
*
|
|
66
|
+
* The state is a class and a `switch`, with no closures. Written from RFC
|
|
67
|
+
* 8446, RFC 7301, RFC 6066 and RFC 9001, not ported from another
|
|
68
|
+
* implementation.
|
|
69
|
+
*/
|
|
70
|
+
import { Secret, expose, secret, wipe } from "nish:secret"
|
|
71
|
+
import { HkdfScratch, hkdfExtractSha256, hkdfExtractSha384 } from "nish/crypto/hkdf"
|
|
72
|
+
import { SHA256_SIZE } from "nish/crypto/sha256"
|
|
73
|
+
import { SHA384_SIZE } from "nish/crypto/sha512"
|
|
74
|
+
import { p256SignSha256 } from "nish/crypto/p256"
|
|
75
|
+
import { timingSafeEqual, timingSafeEqualAt } from "nish/crypto/ct"
|
|
76
|
+
import { X25519_SIZE, x25519, x25519Base } from "nish/crypto/x25519"
|
|
77
|
+
import {
|
|
78
|
+
TLS_ALERT_DECODE_ERROR,
|
|
79
|
+
TLS_ALERT_DECRYPT_ERROR,
|
|
80
|
+
TLS_ALERT_HANDSHAKE_FAILURE,
|
|
81
|
+
TLS_ALERT_ILLEGAL_PARAMETER,
|
|
82
|
+
TLS_ALERT_INTERNAL_ERROR,
|
|
83
|
+
TLS_ALERT_MISSING_EXTENSION,
|
|
84
|
+
TLS_ALERT_NO_APPLICATION_PROTOCOL,
|
|
85
|
+
TLS_ALERT_PROTOCOL_VERSION,
|
|
86
|
+
TLS_ALERT_UNEXPECTED_MESSAGE,
|
|
87
|
+
TLS_GROUP_X25519,
|
|
88
|
+
TLS_HANDSHAKE_CLIENT_HELLO,
|
|
89
|
+
TLS_HANDSHAKE_FINISHED,
|
|
90
|
+
TLS_RANDOM_SIZE,
|
|
91
|
+
TLS_VERSION_13,
|
|
92
|
+
TLS_X25519_SHARE_SIZE,
|
|
93
|
+
TlsClientHelloView,
|
|
94
|
+
tlsHexInto,
|
|
95
|
+
tlsReadClientHello,
|
|
96
|
+
tlsWindowHasU16,
|
|
97
|
+
tlsWindowString,
|
|
98
|
+
tlsWriteCertificate,
|
|
99
|
+
tlsWriteCertificateVerify,
|
|
100
|
+
tlsWriteCertificateVerifyContent,
|
|
101
|
+
tlsWriteEncryptedExtensions,
|
|
102
|
+
tlsWriteFinished,
|
|
103
|
+
tlsWriteHelloRetryRequest,
|
|
104
|
+
tlsWriteServerHello,
|
|
105
|
+
} from "nish/net/tls/codec"
|
|
106
|
+
import {
|
|
107
|
+
TlsTranscript,
|
|
108
|
+
tlsDeriveSecretInto,
|
|
109
|
+
tlsFinishedVerifyDataInto,
|
|
110
|
+
tlsMasterSecretInto,
|
|
111
|
+
tlsSuiteHashLength,
|
|
112
|
+
} from "nish/net/tls/schedule"
|
|
113
|
+
|
|
114
|
+
// ---- Levels ------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
/** Cleartext: ClientHello, HelloRetryRequest and ServerHello (QUIC's Initial packets). */
|
|
117
|
+
export const TLS_LEVEL_INITIAL: i32 = 0
|
|
118
|
+
/** Under the handshake traffic secrets: EncryptedExtensions through both Finished messages. */
|
|
119
|
+
export const TLS_LEVEL_HANDSHAKE: i32 = 1
|
|
120
|
+
/** Under the application traffic secrets. T1 writes nothing here; the level exists for its secrets. */
|
|
121
|
+
export const TLS_LEVEL_APPLICATION: i32 = 2
|
|
122
|
+
|
|
123
|
+
// ---- States --------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
/** Waiting for a ClientHello at the Initial level, the first or the one after a HelloRetryRequest. */
|
|
126
|
+
export const TLS_STATE_WAIT_CLIENT_HELLO: i32 = 0
|
|
127
|
+
/** The Certificate is written; `signatureInput()` is ready and `sign` is due. */
|
|
128
|
+
export const TLS_STATE_WAIT_SIGNATURE: i32 = 1
|
|
129
|
+
/** The server's flight is written; waiting for the client's Finished at the Handshake level. */
|
|
130
|
+
export const TLS_STATE_WAIT_FINISHED: i32 = 2
|
|
131
|
+
/** The client's Finished verified. Application data may flow both ways. */
|
|
132
|
+
export const TLS_STATE_CONNECTED: i32 = 3
|
|
133
|
+
/** An alert was answered; `alert` holds it, and every later call answers it again. */
|
|
134
|
+
export const TLS_STATE_FAILED: i32 = 4
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The longest handshake message accepted, in bytes. A ClientHello carrying a
|
|
138
|
+
* post-quantum share is a few kilobytes; nothing a client sends a TLS 1.3
|
|
139
|
+
* server comes near this, and the cap is what bounds the buffer a peer can
|
|
140
|
+
* make the server hold.
|
|
141
|
+
*/
|
|
142
|
+
export const TLS_MAX_HANDSHAKE_MESSAGE: i32 = 65536
|
|
143
|
+
|
|
144
|
+
/** SSL 3.0's version number; a ClientHello whose `legacy_version` is this or below is refused (RFC 8446 §D.5). */
|
|
145
|
+
const TLS_SSL3_VERSION: i32 = 0x0300
|
|
146
|
+
|
|
147
|
+
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
148
|
+
const TLS_FROM: i32 = 0
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* What a server is configured with, the same for every connection. An
|
|
152
|
+
* interface, so a caller writes it as an object literal.
|
|
153
|
+
*/
|
|
154
|
+
export interface TlsServerConfig {
|
|
155
|
+
/** DER certificates, leaf first. At least one. */
|
|
156
|
+
certificateChain: u8[][]
|
|
157
|
+
/**
|
|
158
|
+
* The ALPN protocols the server speaks, most preferred first. Empty means
|
|
159
|
+
* the server does not negotiate ALPN over TCP, and refuses every client
|
|
160
|
+
* over QUIC, where ALPN is mandatory (RFC 9001 §8.1).
|
|
161
|
+
*/
|
|
162
|
+
alpn: string[]
|
|
163
|
+
/** The server's transport parameters, opaque, sent in EncryptedExtensions only when `quic`. */
|
|
164
|
+
quicTransportParameters: u8[]
|
|
165
|
+
/**
|
|
166
|
+
* Extensions this module does not interpret, already encoded (each a type,
|
|
167
|
+
* a length and a body), written first in EncryptedExtensions:
|
|
168
|
+
* `record_size_limit` is the record layer's, for one. Empty for none.
|
|
169
|
+
*/
|
|
170
|
+
extraExtensions: u8[]
|
|
171
|
+
/**
|
|
172
|
+
* The signature scheme the caller's key signs with, sent in
|
|
173
|
+
* CertificateVerify: `TLS_SIGNATURE_ECDSA_SECP256R1_SHA256` for a P-256
|
|
174
|
+
* key. A client that does not list it is refused with `handshake_failure`.
|
|
175
|
+
*/
|
|
176
|
+
signatureScheme: i32
|
|
177
|
+
/** Whether the carrier is QUIC, which turns `quic_transport_parameters` on both ways. */
|
|
178
|
+
quic: boolean
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The most a 24-bit length can say: the bound on a handshake message's body (RFC 8446 §4). */
|
|
182
|
+
const TLS_MAX_U24: i32 = 16777215
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Whether `config` can be written at all: at least one certificate, every
|
|
186
|
+
* certificate and the whole Certificate message within their 24-bit lengths,
|
|
187
|
+
* every ALPN protocol 1 to 255 bytes, and EncryptedExtensions' extensions
|
|
188
|
+
* within their 16-bit length. A configuration that breaks one would make the
|
|
189
|
+
* server write a message whose length field is wrong, so the constructor
|
|
190
|
+
* refuses it instead.
|
|
191
|
+
*/
|
|
192
|
+
const tlsConfigFits = (config: TlsServerConfig): boolean => {
|
|
193
|
+
if (toI32(config.certificateChain.length) === 0) {
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
// The request context (1) and the list's length (3), then per certificate
|
|
197
|
+
// its length (3), its bytes and its empty extensions (2).
|
|
198
|
+
let certificate: i32 = 4
|
|
199
|
+
for (const der of config.certificateChain) {
|
|
200
|
+
const n: i32 = toI32(der.length)
|
|
201
|
+
if (n < 1 || n > TLS_MAX_U24 - certificate - 5) {
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
certificate = certificate + 5 + n
|
|
205
|
+
}
|
|
206
|
+
// ALPN's type, lengths and the one name (at most 4 + 2 + 1 + 255), the
|
|
207
|
+
// server_name acknowledgement (4), and the transport parameters' type and
|
|
208
|
+
// length (4), beside what the caller passes through.
|
|
209
|
+
const extensions: i32 =
|
|
210
|
+
266 + toI32(config.extraExtensions.length) + toI32(config.quicTransportParameters.length)
|
|
211
|
+
for (const protocol of config.alpn) {
|
|
212
|
+
const n: i32 = toI32(protocol.length)
|
|
213
|
+
if (n < 1 || n > 255) {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return extensions <= 65535
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Whether the bytes `data[at .. at + length)` spell `text`, one character a
|
|
222
|
+
* byte: ALPN's byte-wise comparison (RFC 7301 §3.2), and the one a host name
|
|
223
|
+
* is held to against the last.
|
|
224
|
+
*/
|
|
225
|
+
const tlsBytesSpell = (data: u8[], at: i32, length: i32, text: string): boolean => {
|
|
226
|
+
if (toI32(text.length) !== length) {
|
|
227
|
+
return false
|
|
228
|
+
}
|
|
229
|
+
for (
|
|
230
|
+
let k: i32 = 0;
|
|
231
|
+
k < length && k < toI32(text.length) && at + k >= 0 && at + k < toI32(data.length);
|
|
232
|
+
k++
|
|
233
|
+
) {
|
|
234
|
+
if (toI32(data[at + k]) !== toI32(text.charCodeAt(k))) {
|
|
235
|
+
return false
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return true
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** The minimal DER INTEGER for an unsigned big-endian magnitude: leading zeros dropped, one added when the top bit is set. */
|
|
242
|
+
const tlsDerInteger = (magnitude: u8[]): u8[] => {
|
|
243
|
+
const body: u8[] = []
|
|
244
|
+
let leading: boolean = true
|
|
245
|
+
for (const b of magnitude) {
|
|
246
|
+
if (leading && toI32(b) === 0) {
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
if (leading && toI32(b) >= 0x80) {
|
|
250
|
+
body.push(toU8(0))
|
|
251
|
+
}
|
|
252
|
+
leading = false
|
|
253
|
+
body.push(b)
|
|
254
|
+
}
|
|
255
|
+
// Zero is one zero octet, not an empty integer (X.690 §8.3.1).
|
|
256
|
+
if (toI32(body.length) === 0) {
|
|
257
|
+
body.push(toU8(0))
|
|
258
|
+
}
|
|
259
|
+
const out: u8[] = [toU8(0x02), toU8(toI32(body.length))]
|
|
260
|
+
for (const b of body) {
|
|
261
|
+
out.push(b)
|
|
262
|
+
}
|
|
263
|
+
return out
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* An ECDSA signature `r || s` (32 bytes each, as `nish/crypto/p256` answers
|
|
268
|
+
* it) as the DER `ECDSA-Sig-Value` a CertificateVerify carries (RFC 8446
|
|
269
|
+
* §4.2.3), or `null` when `rs` is not 64 bytes.
|
|
270
|
+
*/
|
|
271
|
+
export const tlsEcdsaDerSignature = (rs: u8[]): u8[] | null => {
|
|
272
|
+
if (toI32(rs.length) !== 64) {
|
|
273
|
+
return null
|
|
274
|
+
}
|
|
275
|
+
const r: u8[] = []
|
|
276
|
+
const s: u8[] = []
|
|
277
|
+
for (let k: i32 = 0; k < toI32(rs.length); k++) {
|
|
278
|
+
if (k < 32) {
|
|
279
|
+
r.push(rs[k])
|
|
280
|
+
} else {
|
|
281
|
+
s.push(rs[k])
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const body: u8[] = tlsDerInteger(r)
|
|
285
|
+
for (const b of tlsDerInteger(s)) {
|
|
286
|
+
body.push(b)
|
|
287
|
+
}
|
|
288
|
+
// At most 2 * (2 + 33) = 70 bytes, so the length is one short-form byte.
|
|
289
|
+
const out: u8[] = [toU8(0x30), toU8(toI32(body.length))]
|
|
290
|
+
for (const b of body) {
|
|
291
|
+
out.push(b)
|
|
292
|
+
}
|
|
293
|
+
return out
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Signs a CertificateVerify input for `ecdsa_secp256r1_sha256` with the P-256
|
|
298
|
+
* private key `priv`: `p256SignSha256` (RFC 6979's deterministic nonce), DER
|
|
299
|
+
* encoded. `null` for a malformed key. The answer is what `TlsServer.sign`
|
|
300
|
+
* takes. The key is borrowed, as every `Secret` parameter is: the caller made
|
|
301
|
+
* it, and wipes it.
|
|
302
|
+
*/
|
|
303
|
+
export const tlsSignEcdsaP256 = (priv: Secret<u8[]>, content: u8[]): u8[] | null => {
|
|
304
|
+
const rs: u8[] | null = p256SignSha256(priv, content)
|
|
305
|
+
if (rs === null) {
|
|
306
|
+
return null
|
|
307
|
+
}
|
|
308
|
+
return tlsEcdsaDerSignature(rs)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Whether an x25519 shared secret is all zeros, reading every byte (run inside `expose`). */
|
|
312
|
+
const tlsIsZeroSecret = (shared: u8[]): boolean => timingSafeEqual(shared, new Array<u8>(X25519_SIZE))
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* `Derive-Secret(early, "derived", "")` with no PSK, the salt the handshake
|
|
316
|
+
* secret is extracted under: a constant of the hash alone (RFC 8448 §3 prints
|
|
317
|
+
* the SHA-256 one; the SHA-384 one was computed with Python's hmac and
|
|
318
|
+
* hashlib). It is written out rather than derived because the functions below
|
|
319
|
+
* run on the ECDHE secret inside `expose`, and Expand-Label may panic with a
|
|
320
|
+
* computed message, which such a function may not reach.
|
|
321
|
+
*/
|
|
322
|
+
const TLS_DERIVED_SALT_SHA256: string = "6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba"
|
|
323
|
+
const TLS_DERIVED_SALT_SHA384: string =
|
|
324
|
+
"1591dac5cbbf0330a4a84de9c753330e92d01f0a88214b4464972fd668049e93e52f2b16fad922fdc0584478428f282b"
|
|
325
|
+
|
|
326
|
+
/** The bytes of a lowercase hex constant of this module, in a fresh array. */
|
|
327
|
+
const tlsHexBytes = (text: string): u8[] => {
|
|
328
|
+
const out: u8[] = new Array<u8>(toI32(text.length) >> 1)
|
|
329
|
+
tlsHexInto(text, out, TLS_FROM)
|
|
330
|
+
return out
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** The SHA-256 handshake secret: HKDF-Extract of the ECDHE secret under the derived salt (run inside `expose`). */
|
|
334
|
+
const tlsHandshakeFromEcdhe256 = (ecdhe: u8[]): u8[] =>
|
|
335
|
+
hkdfExtractSha256(tlsHexBytes(TLS_DERIVED_SALT_SHA256), ecdhe)
|
|
336
|
+
|
|
337
|
+
/** The SHA-384 handshake secret: HKDF-Extract of the ECDHE secret under the derived salt (run inside `expose`). */
|
|
338
|
+
const tlsHandshakeFromEcdhe384 = (ecdhe: u8[]): u8[] =>
|
|
339
|
+
hkdfExtractSha384(tlsHexBytes(TLS_DERIVED_SALT_SHA384), ecdhe)
|
|
340
|
+
|
|
341
|
+
/** The secret of `level` from one side's pair, or `null` for any other level or one not derived yet (still empty). */
|
|
342
|
+
const tlsSecretAt = (level: i32, handshake: u8[], application: u8[]): u8[] | null => {
|
|
343
|
+
const chosen: u8[] = level === TLS_LEVEL_APPLICATION ? application : handshake
|
|
344
|
+
if ((level !== TLS_LEVEL_HANDSHAKE && level !== TLS_LEVEL_APPLICATION) || toI32(chosen.length) === 0) {
|
|
345
|
+
return null
|
|
346
|
+
}
|
|
347
|
+
return chosen
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ---- The exchange, in a scope of its own --------------------------------------
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* The x25519 exchange's inputs and answers as big-endian 64-bit words: the
|
|
354
|
+
* ephemeral private key and the client's share in, the server's public key
|
|
355
|
+
* and the handshake secret out, and the alert. It is a class of numbers and
|
|
356
|
+
* nothing else on purpose. `x25519` answers its shared secret as a `Secret`,
|
|
357
|
+
* which is an allocation it stores, so neither an automatic arena scope nor a
|
|
358
|
+
* `using a = arena()` block may be put around a call to it (NL2424). A
|
|
359
|
+
* function whose every parameter is an object of numbers can be handed no
|
|
360
|
+
* memory a pointer fits in, so everything it and its callees allocate is
|
|
361
|
+
* garbage when it returns, and the compiler gives it a scope of its own
|
|
362
|
+
* (docs/LANGUAGE.md, "Memory model", item 2): `tlsExchange` takes only this.
|
|
363
|
+
*
|
|
364
|
+
* Its fields must stay numbers: one that could hold a pointer would cost
|
|
365
|
+
* `tlsExchange` that scope without a word from the compiler, and only
|
|
366
|
+
* `tests/link/net_tls_memory` would notice.
|
|
367
|
+
*
|
|
368
|
+
* The words hold secrets — the private key and the handshake secret — in
|
|
369
|
+
* fields, where `secureZero` cannot reach, so each is zeroed by an ordinary
|
|
370
|
+
* store as soon as it has been read, which stands because the words stay
|
|
371
|
+
* reachable from their `TlsServer` (TLS-1 in `docs/security/tls.md`).
|
|
372
|
+
*/
|
|
373
|
+
class TlsExchangeWords {
|
|
374
|
+
key0: u64 = 0
|
|
375
|
+
key1: u64 = 0
|
|
376
|
+
key2: u64 = 0
|
|
377
|
+
key3: u64 = 0
|
|
378
|
+
share0: u64 = 0
|
|
379
|
+
share1: u64 = 0
|
|
380
|
+
share2: u64 = 0
|
|
381
|
+
share3: u64 = 0
|
|
382
|
+
public0: u64 = 0
|
|
383
|
+
public1: u64 = 0
|
|
384
|
+
public2: u64 = 0
|
|
385
|
+
public3: u64 = 0
|
|
386
|
+
secret0: u64 = 0
|
|
387
|
+
secret1: u64 = 0
|
|
388
|
+
secret2: u64 = 0
|
|
389
|
+
secret3: u64 = 0
|
|
390
|
+
secret4: u64 = 0
|
|
391
|
+
secret5: u64 = 0
|
|
392
|
+
/** 32 or 48: which handshake secret to extract. */
|
|
393
|
+
hashLength: i32 = 0
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** The eight bytes `b[at .. at + 8)` as a big-endian word; a byte outside `b` reads as zero. */
|
|
397
|
+
const tlsWordAt = (b: u8[], at: i32): u64 => {
|
|
398
|
+
let v: u64 = 0
|
|
399
|
+
for (let k: i32 = 0; k < 8; k++) {
|
|
400
|
+
const byte: u64 = at + k >= 0 && at + k < toI32(b.length) ? toU64(b[at + k]) : toU64(0)
|
|
401
|
+
v = (v << toU64(8)) | byte
|
|
402
|
+
}
|
|
403
|
+
return v
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** `v` big-endian into `b[at .. at + 8)`; a byte outside `b` is dropped. */
|
|
407
|
+
const tlsWordInto = (b: u8[], at: i32, v: u64): void => {
|
|
408
|
+
for (let k: i32 = 0; k < 8; k++) {
|
|
409
|
+
const shift: i32 = (7 - k) << 3
|
|
410
|
+
if (at + k >= 0 && at + k < toI32(b.length)) {
|
|
411
|
+
b[at + k] = toU8(v >> toU64(shift))
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* The exchange of RFC 8446 §7.4.2 on `w`: the server's public key and the
|
|
418
|
+
* handshake secret from the ephemeral key and the client's share, both
|
|
419
|
+
* written back as words, the key's words zeroed once read. Answers 0,
|
|
420
|
+
* `illegal_parameter` for a share whose shared secret is all zeros (§7.4.2,
|
|
421
|
+
* RFC 7748 §6.1), or `internal_error` when the exchange answers nothing. The
|
|
422
|
+
* copy of the key and the ECDHE secret are `Secret`s wiped on every path, and
|
|
423
|
+
* the handshake secret's bytes with `secureZero` once they are words. The
|
|
424
|
+
* compiler brackets this function with an arena scope of its own (see
|
|
425
|
+
* `TlsExchangeWords`), so what x25519 and HKDF allocate is gone when it
|
|
426
|
+
* returns.
|
|
427
|
+
*/
|
|
428
|
+
const tlsExchange = (w: TlsExchangeWords): i32 => {
|
|
429
|
+
const raw: u8[] = new Array<u8>(X25519_SIZE)
|
|
430
|
+
tlsWordInto(raw, 0, w.key0)
|
|
431
|
+
tlsWordInto(raw, 8, w.key1)
|
|
432
|
+
tlsWordInto(raw, 16, w.key2)
|
|
433
|
+
tlsWordInto(raw, 24, w.key3)
|
|
434
|
+
w.key0 = toU64(0)
|
|
435
|
+
w.key1 = toU64(0)
|
|
436
|
+
w.key2 = toU64(0)
|
|
437
|
+
w.key3 = toU64(0)
|
|
438
|
+
const share: u8[] = new Array<u8>(X25519_SIZE)
|
|
439
|
+
tlsWordInto(share, 0, w.share0)
|
|
440
|
+
tlsWordInto(share, 8, w.share1)
|
|
441
|
+
tlsWordInto(share, 16, w.share2)
|
|
442
|
+
tlsWordInto(share, 24, w.share3)
|
|
443
|
+
const key: Secret<u8[]> = secret(raw)
|
|
444
|
+
const serverPublic: u8[] | null = x25519Base(key)
|
|
445
|
+
const shared: Secret<u8[]> | null = x25519(key, share)
|
|
446
|
+
wipe(key)
|
|
447
|
+
if (shared === null) {
|
|
448
|
+
return TLS_ALERT_INTERNAL_ERROR
|
|
449
|
+
}
|
|
450
|
+
// RFC 7748 §6.1 leaves the all-zero check to the protocol, and RFC 8446
|
|
451
|
+
// §7.4.2 makes it: a low-order client share gives the zero secret, which
|
|
452
|
+
// anybody can compute. The secret is secret, so the compare reads it all.
|
|
453
|
+
if (serverPublic === null || expose(shared, tlsIsZeroSecret)) {
|
|
454
|
+
wipe(shared)
|
|
455
|
+
return serverPublic === null ? TLS_ALERT_INTERNAL_ERROR : TLS_ALERT_ILLEGAL_PARAMETER
|
|
456
|
+
}
|
|
457
|
+
const handshake: u8[] =
|
|
458
|
+
w.hashLength === SHA384_SIZE
|
|
459
|
+
? expose(shared, tlsHandshakeFromEcdhe384)
|
|
460
|
+
: expose(shared, tlsHandshakeFromEcdhe256)
|
|
461
|
+
wipe(shared)
|
|
462
|
+
w.public0 = tlsWordAt(serverPublic, 0)
|
|
463
|
+
w.public1 = tlsWordAt(serverPublic, 8)
|
|
464
|
+
w.public2 = tlsWordAt(serverPublic, 16)
|
|
465
|
+
w.public3 = tlsWordAt(serverPublic, 24)
|
|
466
|
+
w.secret0 = tlsWordAt(handshake, 0)
|
|
467
|
+
w.secret1 = tlsWordAt(handshake, 8)
|
|
468
|
+
w.secret2 = tlsWordAt(handshake, 16)
|
|
469
|
+
w.secret3 = tlsWordAt(handshake, 24)
|
|
470
|
+
w.secret4 = tlsWordAt(handshake, 32)
|
|
471
|
+
w.secret5 = tlsWordAt(handshake, 40)
|
|
472
|
+
secureZero(handshake)
|
|
473
|
+
return 0
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ---- Buffers -------------------------------------------------------------------
|
|
477
|
+
|
|
478
|
+
/** The first input buffer: room for the ClientHello of every common client. It grows only for a longer one. */
|
|
479
|
+
const TLS_INPUT_START: i32 = 2048
|
|
480
|
+
/** The Initial output: a HelloRetryRequest and a ServerHello, each with a 32-byte session id, fit with room. */
|
|
481
|
+
const TLS_INITIAL_OUTPUT_SIZE: i32 = 256
|
|
482
|
+
/** The longest DER ECDSA P-256 signature, what the flight is first sized for: two 33-byte integers and their headers. */
|
|
483
|
+
const TLS_ECDSA_SIGNATURE_MAX: i32 = 72
|
|
484
|
+
/** Which secret each of a server's secret arrays holds, by index into `secrets256` and `secrets384`. */
|
|
485
|
+
const TLS_SECRET_HANDSHAKE: i32 = 0
|
|
486
|
+
const TLS_SECRET_CLIENT_HANDSHAKE: i32 = 1
|
|
487
|
+
const TLS_SECRET_SERVER_HANDSHAKE: i32 = 2
|
|
488
|
+
const TLS_SECRET_CLIENT_APPLICATION: i32 = 3
|
|
489
|
+
const TLS_SECRET_SERVER_APPLICATION: i32 = 4
|
|
490
|
+
const TLS_SECRET_EXPORTER: i32 = 5
|
|
491
|
+
const TLS_SECRET_CLIENT_FINISHED: i32 = 6
|
|
492
|
+
/** The CertificateVerify input sits beside them: 98 bytes and the transcript hash. */
|
|
493
|
+
const TLS_SECRET_TO_BE_SIGNED: i32 = 7
|
|
494
|
+
const TLS_SECRET_COUNT: i32 = 8
|
|
495
|
+
/** What CertificateVerify signs besides the hash (RFC 8446 §4.4.3): 64 spaces, the 33-byte context and a zero. */
|
|
496
|
+
const TLS_CERTIFICATE_VERIFY_PREFIX: i32 = 98
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* The most the server's Handshake-level flight under `config` takes with an
|
|
500
|
+
* ECDSA P-256 signature: EncryptedExtensions with every extension it may
|
|
501
|
+
* carry and the longest configured ALPN protocol, the Certificate, a
|
|
502
|
+
* CertificateVerify and a SHA-384 Finished.
|
|
503
|
+
*/
|
|
504
|
+
const tlsFlightCapacity = (config: TlsServerConfig): i32 => {
|
|
505
|
+
const none: u8[] = []
|
|
506
|
+
let longest: string = ""
|
|
507
|
+
for (const protocol of config.alpn) {
|
|
508
|
+
if (toI32(protocol.length) > toI32(longest.length)) {
|
|
509
|
+
longest = protocol
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const extensions: i32 = tlsWriteEncryptedExtensions(
|
|
513
|
+
none,
|
|
514
|
+
TLS_FROM,
|
|
515
|
+
config.extraExtensions,
|
|
516
|
+
longest,
|
|
517
|
+
true,
|
|
518
|
+
config.quic ? config.quicTransportParameters : null
|
|
519
|
+
)
|
|
520
|
+
const certificate: i32 = tlsWriteCertificate(none, TLS_FROM, config.certificateChain)
|
|
521
|
+
return extensions + certificate + 8 + TLS_ECDSA_SIGNATURE_MAX + 4 + SHA384_SIZE
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* One TLS 1.3 server handshake. Make one per connection, or one per slot and
|
|
526
|
+
* `restart` it for each connection the slot takes; feed it what the client
|
|
527
|
+
* sends with `receive`, send what `takeOutput` answers (or what the output
|
|
528
|
+
* buffers hold, for a carrier that reads them in place), and sign when the
|
|
529
|
+
* state says so.
|
|
530
|
+
*
|
|
531
|
+
* The fields are readable: `state` and `alert`, the negotiated `suite`,
|
|
532
|
+
* `serverName`, `alpn` and the client's `clientTransportParameters`, and the
|
|
533
|
+
* traffic secrets. `readSecret` and `writeSecret` answer the last by level.
|
|
534
|
+
*
|
|
535
|
+
* **What it allocates.** Everything it keeps is allocated by the constructor
|
|
536
|
+
* and reused by `restart`: the input and output buffers, the transcript's
|
|
537
|
+
* hashers, the HKDF scratch, and an array of exactly HashLen bytes for every
|
|
538
|
+
* secret under each hash, which the secret fields point at as each is
|
|
539
|
+
* derived and away from again when the next connection starts. `receive` and
|
|
540
|
+
* `sign` do their work inside `using a = arena()` blocks, so the compiler has
|
|
541
|
+
* proved that what they allocate on the way dies before they return. Four
|
|
542
|
+
* things grow or are made outside those blocks: the input buffer, doubled up
|
|
543
|
+
* to the largest message when a ClientHello does not fit (once per slot);
|
|
544
|
+
* the Handshake-level output, when a signature is longer than an ECDSA one;
|
|
545
|
+
* the `serverName` string, made when the client's host name differs from the
|
|
546
|
+
* last one this server held; and, over QUIC only, a copy of the client's
|
|
547
|
+
* transport parameters (TLS-3).
|
|
548
|
+
*/
|
|
549
|
+
export class TlsServer {
|
|
550
|
+
config: TlsServerConfig
|
|
551
|
+
serverRandom: u8[]
|
|
552
|
+
ephemeralPrivate: u8[]
|
|
553
|
+
/** The client's `host_name`, or empty. */
|
|
554
|
+
serverName: string = ""
|
|
555
|
+
/** The ALPN protocol chosen, one of the configuration's, or empty. */
|
|
556
|
+
alpn: string = ""
|
|
557
|
+
/** The last host name a client of this server sent, kept across `restart` so that the next one naming it reuses the string. */
|
|
558
|
+
lastServerName: string = ""
|
|
559
|
+
/** The client's `quic_transport_parameters`, opaque; empty unless the carrier is QUIC. */
|
|
560
|
+
clientTransportParameters: u8[]
|
|
561
|
+
transcript: TlsTranscript
|
|
562
|
+
/** The bytes received at the current level and not yet a whole message: `input[0 .. inputLength)`. */
|
|
563
|
+
input: u8[]
|
|
564
|
+
/** What the server has written at the Initial level and not handed on: `outputInitial[0 .. outputInitialLength)`. */
|
|
565
|
+
outputInitial: u8[]
|
|
566
|
+
/** What the server has written at the Handshake level and not handed on: `outputHandshake[0 .. outputHandshakeLength)`. */
|
|
567
|
+
outputHandshake: u8[]
|
|
568
|
+
handshakeSecret: u8[]
|
|
569
|
+
clientHandshakeSecret: u8[]
|
|
570
|
+
serverHandshakeSecret: u8[]
|
|
571
|
+
clientApplicationSecret: u8[]
|
|
572
|
+
serverApplicationSecret: u8[]
|
|
573
|
+
/** `exporter_master_secret` (RFC 8446 §7.5), for a caller's own exporters. */
|
|
574
|
+
exporterSecret: u8[]
|
|
575
|
+
/** The CertificateVerify input while `state` is `TLS_STATE_WAIT_SIGNATURE`. */
|
|
576
|
+
toBeSigned: u8[]
|
|
577
|
+
/** The client Finished's `verify_data`, known once the server's Finished is written. */
|
|
578
|
+
expectedClientFinished: u8[]
|
|
579
|
+
/** The empty array every secret field points at until its secret is derived. */
|
|
580
|
+
none: u8[]
|
|
581
|
+
/** The arrays the secret fields point into: `TLS_SECRET_COUNT` of each hash's lengths. */
|
|
582
|
+
secrets256: u8[][]
|
|
583
|
+
secrets384: u8[][]
|
|
584
|
+
/** The ClientHello last read, as windows into `input`. */
|
|
585
|
+
hello: TlsClientHelloView
|
|
586
|
+
/** Where every HKDF and HMAC of the schedule runs. */
|
|
587
|
+
kdf: HkdfScratch
|
|
588
|
+
/** The exchange's words (`tlsExchange`). */
|
|
589
|
+
exchange: TlsExchangeWords
|
|
590
|
+
/** The server's x25519 public key, unpacked from the exchange's words for the ServerHello. */
|
|
591
|
+
serverPublic: u8[]
|
|
592
|
+
/** A transcript hash on its way into the schedule: HashLen bytes of it. */
|
|
593
|
+
hashes: u8[]
|
|
594
|
+
state: i32 = 0
|
|
595
|
+
/** The alert the server failed with, or 0. */
|
|
596
|
+
alert: i32 = 0
|
|
597
|
+
/** The negotiated cipher suite, or 0 before the first ClientHello is read. */
|
|
598
|
+
suite: i32 = 0
|
|
599
|
+
hashLength: i32 = 0
|
|
600
|
+
inputLength: i32 = 0
|
|
601
|
+
outputInitialLength: i32 = 0
|
|
602
|
+
outputHandshakeLength: i32 = 0
|
|
603
|
+
/** Whether a HelloRetryRequest was sent, so a second miss is fatal. */
|
|
604
|
+
retried: boolean = false
|
|
605
|
+
/** Whether `config` passed `tlsConfigFits`, decided once by the constructor. */
|
|
606
|
+
configFits: boolean = false
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* A server for one connection under `config`, with the 32-byte server
|
|
610
|
+
* random and the 32-byte x25519 private key drawn for it. A random or key of
|
|
611
|
+
* another length, or a configuration `tlsConfigFits` refuses, leaves the
|
|
612
|
+
* server failed with `internal_error` before it reads anything.
|
|
613
|
+
*/
|
|
614
|
+
constructor(config: TlsServerConfig, serverRandom: u8[], ephemeralPrivate: u8[]) {
|
|
615
|
+
this.config = config
|
|
616
|
+
this.serverRandom = serverRandom
|
|
617
|
+
this.ephemeralPrivate = ephemeralPrivate
|
|
618
|
+
this.none = []
|
|
619
|
+
this.clientTransportParameters = this.none
|
|
620
|
+
this.transcript = new TlsTranscript(0)
|
|
621
|
+
this.configFits = tlsConfigFits(config)
|
|
622
|
+
this.input = new Array<u8>(TLS_INPUT_START)
|
|
623
|
+
this.outputInitial = new Array<u8>(TLS_INITIAL_OUTPUT_SIZE)
|
|
624
|
+
this.outputHandshake = new Array<u8>(this.configFits ? tlsFlightCapacity(config) : TLS_FROM)
|
|
625
|
+
this.secrets256 = []
|
|
626
|
+
this.secrets384 = []
|
|
627
|
+
for (let k: i32 = 0; k < TLS_SECRET_COUNT; k++) {
|
|
628
|
+
const extra: i32 = k === TLS_SECRET_TO_BE_SIGNED ? TLS_CERTIFICATE_VERIFY_PREFIX : 0
|
|
629
|
+
this.secrets256.push(new Array<u8>(SHA256_SIZE + extra))
|
|
630
|
+
this.secrets384.push(new Array<u8>(SHA384_SIZE + extra))
|
|
631
|
+
}
|
|
632
|
+
this.hello = new TlsClientHelloView()
|
|
633
|
+
this.kdf = new HkdfScratch()
|
|
634
|
+
this.exchange = new TlsExchangeWords()
|
|
635
|
+
this.serverPublic = new Array<u8>(X25519_SIZE)
|
|
636
|
+
this.hashes = new Array<u8>(SHA384_SIZE)
|
|
637
|
+
this.handshakeSecret = this.none
|
|
638
|
+
this.clientHandshakeSecret = this.none
|
|
639
|
+
this.serverHandshakeSecret = this.none
|
|
640
|
+
this.clientApplicationSecret = this.none
|
|
641
|
+
this.serverApplicationSecret = this.none
|
|
642
|
+
this.exporterSecret = this.none
|
|
643
|
+
this.toBeSigned = this.none
|
|
644
|
+
this.expectedClientFinished = this.none
|
|
645
|
+
this.restart(serverRandom, ephemeralPrivate)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Starts the next connection in this server's buffers, with its own
|
|
650
|
+
* 32-byte random and x25519 private key, as a fresh `TlsServer` would:
|
|
651
|
+
* every secret the last connection left in them is wiped with `secureZero`
|
|
652
|
+
* and every secret field points at nothing again. The two arrays are kept,
|
|
653
|
+
* not copied, as the constructor keeps them. A random or key of another
|
|
654
|
+
* length, or a configuration `tlsConfigFits` refused, leaves the server
|
|
655
|
+
* failed with `internal_error`.
|
|
656
|
+
*/
|
|
657
|
+
restart(serverRandom: u8[], ephemeralPrivate: u8[]): void {
|
|
658
|
+
this.serverRandom = serverRandom
|
|
659
|
+
this.ephemeralPrivate = ephemeralPrivate
|
|
660
|
+
this.state = TLS_STATE_WAIT_CLIENT_HELLO
|
|
661
|
+
this.alert = 0
|
|
662
|
+
this.suite = 0
|
|
663
|
+
this.hashLength = 0
|
|
664
|
+
this.retried = false
|
|
665
|
+
this.serverName = ""
|
|
666
|
+
this.alpn = ""
|
|
667
|
+
this.clientTransportParameters = this.none
|
|
668
|
+
this.inputLength = 0
|
|
669
|
+
this.outputInitialLength = 0
|
|
670
|
+
this.outputHandshakeLength = 0
|
|
671
|
+
for (let k: i32 = 0; k < toI32(this.secrets256.length) && k < toI32(this.secrets384.length); k++) {
|
|
672
|
+
secureZero(this.secrets256[k])
|
|
673
|
+
secureZero(this.secrets384[k])
|
|
674
|
+
}
|
|
675
|
+
this.handshakeSecret = this.none
|
|
676
|
+
this.clientHandshakeSecret = this.none
|
|
677
|
+
this.serverHandshakeSecret = this.none
|
|
678
|
+
this.clientApplicationSecret = this.none
|
|
679
|
+
this.serverApplicationSecret = this.none
|
|
680
|
+
this.exporterSecret = this.none
|
|
681
|
+
this.toBeSigned = this.none
|
|
682
|
+
this.expectedClientFinished = this.none
|
|
683
|
+
if (
|
|
684
|
+
toI32(serverRandom.length) !== TLS_RANDOM_SIZE ||
|
|
685
|
+
toI32(ephemeralPrivate.length) !== X25519_SIZE ||
|
|
686
|
+
!this.configFits
|
|
687
|
+
) {
|
|
688
|
+
this.state = TLS_STATE_FAILED
|
|
689
|
+
this.alert = TLS_ALERT_INTERNAL_ERROR
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/** Fails the handshake with `alert` and answers it. */
|
|
694
|
+
fail(alert: i32): i32 {
|
|
695
|
+
this.state = TLS_STATE_FAILED
|
|
696
|
+
this.alert = alert
|
|
697
|
+
return alert
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** The array secret `index` lives in under the negotiated hash: exactly HashLen bytes (the CertificateVerify input, 98 more). */
|
|
701
|
+
secretArray(index: i32): u8[] {
|
|
702
|
+
const pool: u8[][] = this.hashLength === SHA384_SIZE ? this.secrets384 : this.secrets256
|
|
703
|
+
return index >= 0 && index < toI32(pool.length) ? pool[index] : this.none
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/** The level the client's next bytes must arrive at, or -1 when the state expects none. */
|
|
707
|
+
expectedLevel(): i32 {
|
|
708
|
+
switch (this.state) {
|
|
709
|
+
case TLS_STATE_WAIT_CLIENT_HELLO:
|
|
710
|
+
return TLS_LEVEL_INITIAL
|
|
711
|
+
case TLS_STATE_WAIT_FINISHED:
|
|
712
|
+
return TLS_LEVEL_HANDSHAKE
|
|
713
|
+
default:
|
|
714
|
+
return -1
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Makes `input` hold at least `need` bytes, keeping the ones it holds. It
|
|
720
|
+
* doubles, so a slot grows it at most a handful of times however its
|
|
721
|
+
* clients cut their messages, and never past the largest message `receive`
|
|
722
|
+
* takes.
|
|
723
|
+
*/
|
|
724
|
+
reserveInput(need: i32): void {
|
|
725
|
+
const capacity: i32 = toI32(this.input.length)
|
|
726
|
+
if (need <= capacity) {
|
|
727
|
+
return
|
|
728
|
+
}
|
|
729
|
+
let size: i32 = capacity
|
|
730
|
+
while (size < need) {
|
|
731
|
+
size = size * 2
|
|
732
|
+
}
|
|
733
|
+
const most: i32 = TLS_MAX_HANDSHAKE_MESSAGE + 4
|
|
734
|
+
const grown: u8[] = new Array<u8>(size > most ? most : size)
|
|
735
|
+
for (let k: i32 = 0; k < this.inputLength && k < toI32(grown.length) && k < capacity; k++) {
|
|
736
|
+
grown[k] = this.input[k]
|
|
737
|
+
}
|
|
738
|
+
this.input = grown
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Makes the output of `level` hold at least `need` bytes, keeping the ones
|
|
743
|
+
* it holds. Both are sized by the constructor for what this configuration
|
|
744
|
+
* writes with an ECDSA signature, so only a longer signature grows the
|
|
745
|
+
* Handshake level's, once per slot.
|
|
746
|
+
*/
|
|
747
|
+
reserveOutput(level: i32, need: i32): void {
|
|
748
|
+
const initial: boolean = level === TLS_LEVEL_INITIAL
|
|
749
|
+
const buffer: u8[] = initial ? this.outputInitial : this.outputHandshake
|
|
750
|
+
const held: i32 = initial ? this.outputInitialLength : this.outputHandshakeLength
|
|
751
|
+
const capacity: i32 = toI32(buffer.length)
|
|
752
|
+
if (need <= capacity) {
|
|
753
|
+
return
|
|
754
|
+
}
|
|
755
|
+
const grown: u8[] = new Array<u8>(need)
|
|
756
|
+
for (let k: i32 = 0; k < held && k < toI32(buffer.length) && k < toI32(grown.length); k++) {
|
|
757
|
+
grown[k] = buffer[k]
|
|
758
|
+
}
|
|
759
|
+
if (initial) {
|
|
760
|
+
this.outputInitial = grown
|
|
761
|
+
} else {
|
|
762
|
+
this.outputHandshake = grown
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Takes the handshake bytes `data[off .. off + len)` the client sent at
|
|
768
|
+
* `level`, a whole message, part of one or several, and handles every
|
|
769
|
+
* message they complete. Answers 0, or the alert the server fails with.
|
|
770
|
+
*
|
|
771
|
+
* A level other than the one the state expects is `unexpected_message`, as
|
|
772
|
+
* is a byte left over once a message ends the client's flight: each of the
|
|
773
|
+
* three messages a client sends here (two ClientHellos and a Finished) is
|
|
774
|
+
* the last of its flight, and RFC 8446 §5.1 forbids a message to straddle a
|
|
775
|
+
* key change. A window outside `data` is `internal_error`, the caller's
|
|
776
|
+
* fault and not the peer's. Once the handshake is done, any further bytes
|
|
777
|
+
* are `unexpected_message` too: the post-handshake messages a client may
|
|
778
|
+
* send (KeyUpdate) belong to the record layer, which handles them itself.
|
|
779
|
+
*/
|
|
780
|
+
receive(level: i32, data: u8[], off: i32, len: i32): i32 {
|
|
781
|
+
if (this.state === TLS_STATE_FAILED) {
|
|
782
|
+
return this.alert
|
|
783
|
+
}
|
|
784
|
+
if (off < 0 || len < 0 || off > toI32(data.length) - len) {
|
|
785
|
+
return this.fail(TLS_ALERT_INTERNAL_ERROR)
|
|
786
|
+
}
|
|
787
|
+
if (len === 0) {
|
|
788
|
+
return 0
|
|
789
|
+
}
|
|
790
|
+
if (level !== this.expectedLevel()) {
|
|
791
|
+
return this.fail(TLS_ALERT_UNEXPECTED_MESSAGE)
|
|
792
|
+
}
|
|
793
|
+
// The buffer only ever holds one message, so bytes that would take it
|
|
794
|
+
// past the largest one allowed are refused before they are copied.
|
|
795
|
+
if (len > TLS_MAX_HANDSHAKE_MESSAGE + 4 - this.inputLength) {
|
|
796
|
+
return this.fail(TLS_ALERT_DECODE_ERROR)
|
|
797
|
+
}
|
|
798
|
+
this.reserveInput(this.inputLength + len)
|
|
799
|
+
for (
|
|
800
|
+
let k: i32 = 0;
|
|
801
|
+
k < len && off + k < toI32(data.length) && this.inputLength < toI32(this.input.length);
|
|
802
|
+
k++
|
|
803
|
+
) {
|
|
804
|
+
this.input[this.inputLength] = data[off + k]
|
|
805
|
+
this.inputLength = this.inputLength + 1
|
|
806
|
+
}
|
|
807
|
+
// Every message a client sends here ends its flight, so the buffer holds
|
|
808
|
+
// at most one: a byte past its end is refused below.
|
|
809
|
+
if (this.inputLength < 4) {
|
|
810
|
+
return 0
|
|
811
|
+
}
|
|
812
|
+
const length: i32 = (toI32(this.input[1]) << 16) | (toI32(this.input[2]) << 8) | toI32(this.input[3])
|
|
813
|
+
if (length > TLS_MAX_HANDSHAKE_MESSAGE) {
|
|
814
|
+
return this.fail(TLS_ALERT_DECODE_ERROR)
|
|
815
|
+
}
|
|
816
|
+
const whole: i32 = 4 + length
|
|
817
|
+
if (this.inputLength < whole) {
|
|
818
|
+
return 0
|
|
819
|
+
}
|
|
820
|
+
if (this.inputLength > whole) {
|
|
821
|
+
return this.fail(TLS_ALERT_UNEXPECTED_MESSAGE)
|
|
822
|
+
}
|
|
823
|
+
// The message stays where it is while it is handled; the buffer is
|
|
824
|
+
// empty again for whatever comes next.
|
|
825
|
+
this.inputLength = 0
|
|
826
|
+
const alert: i32 = this.handleMessage(length)
|
|
827
|
+
if (alert !== 0) {
|
|
828
|
+
return this.fail(alert)
|
|
829
|
+
}
|
|
830
|
+
return 0
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Dispatches one whole message, `input[0 .. 4 + length)`. `receive`
|
|
835
|
+
* admits bytes only in the two states that expect them, so the state is
|
|
836
|
+
* one of those two here.
|
|
837
|
+
*/
|
|
838
|
+
handleMessage(length: i32): i32 {
|
|
839
|
+
const type: i32 = toI32(this.input[0])
|
|
840
|
+
if (this.state === TLS_STATE_WAIT_CLIENT_HELLO) {
|
|
841
|
+
return type === TLS_HANDSHAKE_CLIENT_HELLO
|
|
842
|
+
? this.handleClientHello(length)
|
|
843
|
+
: TLS_ALERT_UNEXPECTED_MESSAGE
|
|
844
|
+
}
|
|
845
|
+
return type === TLS_HANDSHAKE_FINISHED ? this.handleFinished(length) : TLS_ALERT_UNEXPECTED_MESSAGE
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* The first suite the client lists that this module negotiates, or 0
|
|
850
|
+
* (§4.1.1: the server picks; following the client's order is the choice
|
|
851
|
+
* RFC 8448's server makes).
|
|
852
|
+
*/
|
|
853
|
+
chooseSuite(hello: TlsClientHelloView): i32 {
|
|
854
|
+
const data: u8[] = hello.data
|
|
855
|
+
const end: i32 = hello.suitesAt + hello.suitesLength
|
|
856
|
+
for (
|
|
857
|
+
let k: i32 = hello.suitesAt;
|
|
858
|
+
k + 1 < end && k >= 0 && k < toI32(data.length) && k + 1 < toI32(data.length);
|
|
859
|
+
k += 2
|
|
860
|
+
) {
|
|
861
|
+
const suite: i32 = (toI32(data[k]) << 8) | toI32(data[k + 1])
|
|
862
|
+
if (tlsSuiteHashLength(suite) > 0) {
|
|
863
|
+
return suite
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return 0
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* ALPN (RFC 7301 §3.2): the first configured protocol the client offers.
|
|
871
|
+
* Answers 0 with `alpn` set, or empty when nothing is negotiated, or
|
|
872
|
+
* `no_application_protocol`.
|
|
873
|
+
*/
|
|
874
|
+
chooseAlpn(hello: TlsClientHelloView): i32 {
|
|
875
|
+
this.alpn = ""
|
|
876
|
+
if (hello.hasAlpn && toI32(this.config.alpn.length) > 0) {
|
|
877
|
+
const data: u8[] = hello.data
|
|
878
|
+
const end: i32 = hello.alpnAt + hello.alpnLength
|
|
879
|
+
for (const protocol of this.config.alpn) {
|
|
880
|
+
let at: i32 = hello.alpnAt
|
|
881
|
+
while (at < end && at >= 0 && at < toI32(data.length)) {
|
|
882
|
+
const n: i32 = toI32(data[at])
|
|
883
|
+
if (tlsBytesSpell(data, at + 1, n, protocol)) {
|
|
884
|
+
this.alpn = protocol
|
|
885
|
+
return 0
|
|
886
|
+
}
|
|
887
|
+
at = at + 1 + n
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return TLS_ALERT_NO_APPLICATION_PROTOCOL
|
|
891
|
+
}
|
|
892
|
+
return this.config.quic ? TLS_ALERT_NO_APPLICATION_PROTOCOL : 0
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* `serverName` for the host name the hello carries: kept as it is when it
|
|
897
|
+
* already spells those bytes, so a slot whose clients name one host makes
|
|
898
|
+
* the string once, and made again only when the name changes.
|
|
899
|
+
*/
|
|
900
|
+
takeServerName(hello: TlsClientHelloView): void {
|
|
901
|
+
if (!hello.hasServerName) {
|
|
902
|
+
this.serverName = ""
|
|
903
|
+
return
|
|
904
|
+
}
|
|
905
|
+
if (!tlsBytesSpell(hello.data, hello.serverNameAt, hello.serverNameLength, this.lastServerName)) {
|
|
906
|
+
this.lastServerName = tlsWindowString(hello.data, hello.serverNameAt, hello.serverNameLength)
|
|
907
|
+
}
|
|
908
|
+
this.serverName = this.lastServerName
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* A ClientHello, the first or the second: refuse it, answer it with a
|
|
913
|
+
* HelloRetryRequest, or answer it with ServerHello, EncryptedExtensions and
|
|
914
|
+
* Certificate and wait for the signature. The checks run in the order a
|
|
915
|
+
* reader of RFC 8446 would make them: the structure, then the version, then
|
|
916
|
+
* the suite, the extensions §9.2 requires, the group, the signature scheme,
|
|
917
|
+
* the share itself, and the application protocol.
|
|
918
|
+
*/
|
|
919
|
+
handleClientHello(length: i32): i32 {
|
|
920
|
+
const hello: TlsClientHelloView = this.hello
|
|
921
|
+
const data: u8[] = this.input
|
|
922
|
+
tlsReadClientHello(hello, data, 4, length)
|
|
923
|
+
if (hello.alert !== 0) {
|
|
924
|
+
return hello.alert
|
|
925
|
+
}
|
|
926
|
+
// §4.2.1: the version is negotiated by `supported_versions` alone, and
|
|
927
|
+
// `legacy_version` is not consulted for it. The one exception is SSL 3.0
|
|
928
|
+
// or below, which §D.5 lets a server refuse outright. Without
|
|
929
|
+
// `supported_versions` the client offers TLS 1.2 or earlier, which this
|
|
930
|
+
// server does not speak.
|
|
931
|
+
if (hello.legacyVersion <= TLS_SSL3_VERSION) {
|
|
932
|
+
return TLS_ALERT_PROTOCOL_VERSION
|
|
933
|
+
}
|
|
934
|
+
if (
|
|
935
|
+
!hello.hasSupportedVersions ||
|
|
936
|
+
!tlsWindowHasU16(data, hello.versionsAt, hello.versionsLength, TLS_VERSION_13)
|
|
937
|
+
) {
|
|
938
|
+
return TLS_ALERT_PROTOCOL_VERSION
|
|
939
|
+
}
|
|
940
|
+
if (!hello.nullCompressionOnly) {
|
|
941
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
942
|
+
}
|
|
943
|
+
const suite: i32 = this.chooseSuite(hello)
|
|
944
|
+
if (suite === 0) {
|
|
945
|
+
return TLS_ALERT_HANDSHAKE_FAILURE
|
|
946
|
+
}
|
|
947
|
+
// §4.1.4: the second ClientHello must allow the suite the
|
|
948
|
+
// HelloRetryRequest chose, since the transcript is already under its hash.
|
|
949
|
+
if (this.retried && suite !== this.suite) {
|
|
950
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
951
|
+
}
|
|
952
|
+
// §9.2: a certificate-authenticated handshake needs all three, and
|
|
953
|
+
// `supported_groups` and `key_share` come as a pair.
|
|
954
|
+
if (!hello.hasSignatureAlgorithms || !hello.hasSupportedGroups || !hello.hasKeyShare) {
|
|
955
|
+
return TLS_ALERT_MISSING_EXTENSION
|
|
956
|
+
}
|
|
957
|
+
if (this.config.quic && !hello.hasQuicTransportParameters) {
|
|
958
|
+
return TLS_ALERT_MISSING_EXTENSION
|
|
959
|
+
}
|
|
960
|
+
if (!tlsWindowHasU16(data, hello.groupsAt, hello.groupsLength, TLS_GROUP_X25519)) {
|
|
961
|
+
return TLS_ALERT_HANDSHAKE_FAILURE
|
|
962
|
+
}
|
|
963
|
+
if (!tlsWindowHasU16(data, hello.schemesAt, hello.schemesLength, this.config.signatureScheme)) {
|
|
964
|
+
return TLS_ALERT_HANDSHAKE_FAILURE
|
|
965
|
+
}
|
|
966
|
+
if (!this.retried) {
|
|
967
|
+
this.suite = suite
|
|
968
|
+
this.hashLength = tlsSuiteHashLength(suite)
|
|
969
|
+
this.transcript.reset(this.hashLength)
|
|
970
|
+
}
|
|
971
|
+
this.transcript.update(data, TLS_FROM, 4 + length)
|
|
972
|
+
if (!hello.hasX25519Share) {
|
|
973
|
+
// §4.1.4: one HelloRetryRequest; a second ClientHello without the share
|
|
974
|
+
// the first one asked for is `illegal_parameter`.
|
|
975
|
+
if (this.retried) {
|
|
976
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
977
|
+
}
|
|
978
|
+
this.retried = true
|
|
979
|
+
const none: u8[] = []
|
|
980
|
+
this.reserveOutput(
|
|
981
|
+
TLS_LEVEL_INITIAL,
|
|
982
|
+
tlsWriteHelloRetryRequest(
|
|
983
|
+
none,
|
|
984
|
+
this.outputInitialLength,
|
|
985
|
+
data,
|
|
986
|
+
hello.sessionIdAt,
|
|
987
|
+
hello.sessionIdLength,
|
|
988
|
+
suite,
|
|
989
|
+
TLS_GROUP_X25519
|
|
990
|
+
)
|
|
991
|
+
)
|
|
992
|
+
using _scope = arena()
|
|
993
|
+
this.transcript.restartWithMessageHash()
|
|
994
|
+
const at: i32 = this.outputInitialLength
|
|
995
|
+
const end: i32 = tlsWriteHelloRetryRequest(
|
|
996
|
+
this.outputInitial,
|
|
997
|
+
at,
|
|
998
|
+
data,
|
|
999
|
+
hello.sessionIdAt,
|
|
1000
|
+
hello.sessionIdLength,
|
|
1001
|
+
this.suite,
|
|
1002
|
+
TLS_GROUP_X25519
|
|
1003
|
+
)
|
|
1004
|
+
this.sent(TLS_LEVEL_INITIAL, at, end)
|
|
1005
|
+
return 0
|
|
1006
|
+
}
|
|
1007
|
+
if (hello.x25519ShareLength !== TLS_X25519_SHARE_SIZE) {
|
|
1008
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
1009
|
+
}
|
|
1010
|
+
const alpnAlert: i32 = this.chooseAlpn(hello)
|
|
1011
|
+
if (alpnAlert !== 0) {
|
|
1012
|
+
return alpnAlert
|
|
1013
|
+
}
|
|
1014
|
+
this.takeServerName(hello)
|
|
1015
|
+
if (this.config.quic) {
|
|
1016
|
+
const parameters: u8[] = new Array<u8>(hello.quicLength)
|
|
1017
|
+
for (
|
|
1018
|
+
let k: i32 = 0;
|
|
1019
|
+
k < toI32(parameters.length) && hello.quicAt + k >= 0 && hello.quicAt + k < toI32(data.length);
|
|
1020
|
+
k++
|
|
1021
|
+
) {
|
|
1022
|
+
parameters[k] = data[hello.quicAt + k]
|
|
1023
|
+
}
|
|
1024
|
+
this.clientTransportParameters = parameters
|
|
1025
|
+
}
|
|
1026
|
+
const exchangeAlert: i32 = this.exchangeKeys()
|
|
1027
|
+
if (exchangeAlert !== 0) {
|
|
1028
|
+
return exchangeAlert
|
|
1029
|
+
}
|
|
1030
|
+
this.writeServerFlight()
|
|
1031
|
+
return 0
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Runs the x25519 exchange on the caller's ephemeral key and the client's
|
|
1036
|
+
* share (`tlsExchange`), outside any arena block since its scope is its
|
|
1037
|
+
* own, and unpacks the server's public key and the handshake secret from
|
|
1038
|
+
* the words, zeroing them. The ephemeral key is held as the caller's plain
|
|
1039
|
+
* bytes, because a `Secret` may not live in a field (NL2430); the record
|
|
1040
|
+
* layer wipes them once the ServerHello is written (TLS-1).
|
|
1041
|
+
*/
|
|
1042
|
+
exchangeKeys(): i32 {
|
|
1043
|
+
const w: TlsExchangeWords = this.exchange
|
|
1044
|
+
const key: u8[] = this.ephemeralPrivate
|
|
1045
|
+
const share: u8[] = this.input
|
|
1046
|
+
const at: i32 = this.hello.x25519ShareAt
|
|
1047
|
+
w.key0 = tlsWordAt(key, 0)
|
|
1048
|
+
w.key1 = tlsWordAt(key, 8)
|
|
1049
|
+
w.key2 = tlsWordAt(key, 16)
|
|
1050
|
+
w.key3 = tlsWordAt(key, 24)
|
|
1051
|
+
w.share0 = tlsWordAt(share, at)
|
|
1052
|
+
w.share1 = tlsWordAt(share, at + 8)
|
|
1053
|
+
w.share2 = tlsWordAt(share, at + 16)
|
|
1054
|
+
w.share3 = tlsWordAt(share, at + 24)
|
|
1055
|
+
w.hashLength = this.hashLength
|
|
1056
|
+
const alert: i32 = tlsExchange(w)
|
|
1057
|
+
if (alert !== 0) {
|
|
1058
|
+
return alert
|
|
1059
|
+
}
|
|
1060
|
+
const pub: u8[] = this.serverPublic
|
|
1061
|
+
tlsWordInto(pub, 0, w.public0)
|
|
1062
|
+
tlsWordInto(pub, 8, w.public1)
|
|
1063
|
+
tlsWordInto(pub, 16, w.public2)
|
|
1064
|
+
tlsWordInto(pub, 24, w.public3)
|
|
1065
|
+
this.handshakeSecret = this.secretArray(TLS_SECRET_HANDSHAKE)
|
|
1066
|
+
const handshake: u8[] = this.handshakeSecret
|
|
1067
|
+
tlsWordInto(handshake, 0, w.secret0)
|
|
1068
|
+
tlsWordInto(handshake, 8, w.secret1)
|
|
1069
|
+
tlsWordInto(handshake, 16, w.secret2)
|
|
1070
|
+
tlsWordInto(handshake, 24, w.secret3)
|
|
1071
|
+
tlsWordInto(handshake, 32, w.secret4)
|
|
1072
|
+
tlsWordInto(handshake, 40, w.secret5)
|
|
1073
|
+
w.secret0 = toU64(0)
|
|
1074
|
+
w.secret1 = toU64(0)
|
|
1075
|
+
w.secret2 = toU64(0)
|
|
1076
|
+
w.secret3 = toU64(0)
|
|
1077
|
+
w.secret4 = toU64(0)
|
|
1078
|
+
w.secret5 = toU64(0)
|
|
1079
|
+
return 0
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* ServerHello at the Initial level, the handshake traffic secrets, and
|
|
1084
|
+
* EncryptedExtensions and Certificate at the Handshake level, then the
|
|
1085
|
+
* CertificateVerify input: all in place, inside one arena block.
|
|
1086
|
+
*/
|
|
1087
|
+
writeServerFlight(): void {
|
|
1088
|
+
const hello: TlsClientHelloView = this.hello
|
|
1089
|
+
const h: i32 = this.hashLength
|
|
1090
|
+
// Room for the messages first, outside the block, measured by writing
|
|
1091
|
+
// them over nothing; the constructor sized both buffers for these, so
|
|
1092
|
+
// neither grows.
|
|
1093
|
+
const none: u8[] = []
|
|
1094
|
+
this.reserveOutput(
|
|
1095
|
+
TLS_LEVEL_INITIAL,
|
|
1096
|
+
tlsWriteServerHello(
|
|
1097
|
+
none,
|
|
1098
|
+
this.outputInitialLength,
|
|
1099
|
+
this.serverRandom,
|
|
1100
|
+
hello.data,
|
|
1101
|
+
hello.sessionIdAt,
|
|
1102
|
+
hello.sessionIdLength,
|
|
1103
|
+
this.suite,
|
|
1104
|
+
this.serverPublic
|
|
1105
|
+
)
|
|
1106
|
+
)
|
|
1107
|
+
const extensionsEnd: i32 = tlsWriteEncryptedExtensions(
|
|
1108
|
+
none,
|
|
1109
|
+
this.outputHandshakeLength,
|
|
1110
|
+
this.config.extraExtensions,
|
|
1111
|
+
this.alpn,
|
|
1112
|
+
hello.hasServerName,
|
|
1113
|
+
this.config.quic ? this.config.quicTransportParameters : null
|
|
1114
|
+
)
|
|
1115
|
+
this.reserveOutput(
|
|
1116
|
+
TLS_LEVEL_HANDSHAKE,
|
|
1117
|
+
tlsWriteCertificate(none, extensionsEnd, this.config.certificateChain)
|
|
1118
|
+
)
|
|
1119
|
+
// The secrets' arrays are the slot's, chosen before the block opens.
|
|
1120
|
+
this.clientHandshakeSecret = this.secretArray(TLS_SECRET_CLIENT_HANDSHAKE)
|
|
1121
|
+
this.serverHandshakeSecret = this.secretArray(TLS_SECRET_SERVER_HANDSHAKE)
|
|
1122
|
+
this.toBeSigned = this.secretArray(TLS_SECRET_TO_BE_SIGNED)
|
|
1123
|
+
using _scope = arena()
|
|
1124
|
+
const helloAt: i32 = this.outputInitialLength
|
|
1125
|
+
const helloEnd: i32 = tlsWriteServerHello(
|
|
1126
|
+
this.outputInitial,
|
|
1127
|
+
helloAt,
|
|
1128
|
+
this.serverRandom,
|
|
1129
|
+
hello.data,
|
|
1130
|
+
hello.sessionIdAt,
|
|
1131
|
+
hello.sessionIdLength,
|
|
1132
|
+
this.suite,
|
|
1133
|
+
this.serverPublic
|
|
1134
|
+
)
|
|
1135
|
+
this.sent(TLS_LEVEL_INITIAL, helloAt, helloEnd)
|
|
1136
|
+
|
|
1137
|
+
this.transcript.hashInto(this.hashes, TLS_FROM)
|
|
1138
|
+
tlsDeriveSecretInto(
|
|
1139
|
+
this.kdf,
|
|
1140
|
+
h,
|
|
1141
|
+
this.handshakeSecret,
|
|
1142
|
+
"c hs traffic",
|
|
1143
|
+
this.hashes,
|
|
1144
|
+
TLS_FROM,
|
|
1145
|
+
this.clientHandshakeSecret
|
|
1146
|
+
)
|
|
1147
|
+
tlsDeriveSecretInto(
|
|
1148
|
+
this.kdf,
|
|
1149
|
+
h,
|
|
1150
|
+
this.handshakeSecret,
|
|
1151
|
+
"s hs traffic",
|
|
1152
|
+
this.hashes,
|
|
1153
|
+
TLS_FROM,
|
|
1154
|
+
this.serverHandshakeSecret
|
|
1155
|
+
)
|
|
1156
|
+
|
|
1157
|
+
const extensionsAt: i32 = this.outputHandshakeLength
|
|
1158
|
+
const written: i32 = tlsWriteEncryptedExtensions(
|
|
1159
|
+
this.outputHandshake,
|
|
1160
|
+
extensionsAt,
|
|
1161
|
+
this.config.extraExtensions,
|
|
1162
|
+
this.alpn,
|
|
1163
|
+
hello.hasServerName,
|
|
1164
|
+
this.config.quic ? this.config.quicTransportParameters : null
|
|
1165
|
+
)
|
|
1166
|
+
this.sent(TLS_LEVEL_HANDSHAKE, extensionsAt, written)
|
|
1167
|
+
const certificateEnd: i32 = tlsWriteCertificate(
|
|
1168
|
+
this.outputHandshake,
|
|
1169
|
+
written,
|
|
1170
|
+
this.config.certificateChain
|
|
1171
|
+
)
|
|
1172
|
+
this.sent(TLS_LEVEL_HANDSHAKE, written, certificateEnd)
|
|
1173
|
+
|
|
1174
|
+
this.transcript.hashInto(this.hashes, TLS_FROM)
|
|
1175
|
+
tlsWriteCertificateVerifyContent(this.toBeSigned, TLS_FROM, this.hashes, TLS_FROM, h)
|
|
1176
|
+
this.state = TLS_STATE_WAIT_SIGNATURE
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* What CertificateVerify signs (RFC 8446 §4.4.3), while the state is
|
|
1181
|
+
* `TLS_STATE_WAIT_SIGNATURE`; `null` at any other time.
|
|
1182
|
+
*/
|
|
1183
|
+
signatureInput(): u8[] | null {
|
|
1184
|
+
if (this.state !== TLS_STATE_WAIT_SIGNATURE) {
|
|
1185
|
+
return null
|
|
1186
|
+
}
|
|
1187
|
+
return this.toBeSigned
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Takes the signature over `signatureInput()`, as it goes on the wire (DER
|
|
1192
|
+
* for ECDSA), and writes CertificateVerify and the server's Finished, after
|
|
1193
|
+
* which the application secrets are known. Answers 0, or `internal_error`
|
|
1194
|
+
* when called in another state or with an empty or over-long signature.
|
|
1195
|
+
* The signature is not checked here: a wrong one is the caller's bug, and
|
|
1196
|
+
* the client's verification is what catches it.
|
|
1197
|
+
*/
|
|
1198
|
+
sign(signature: u8[]): i32 {
|
|
1199
|
+
if (this.state === TLS_STATE_FAILED) {
|
|
1200
|
+
return this.alert
|
|
1201
|
+
}
|
|
1202
|
+
const signatureLength: i32 = toI32(signature.length)
|
|
1203
|
+
if (this.state !== TLS_STATE_WAIT_SIGNATURE || signatureLength === 0 || signatureLength > 65535) {
|
|
1204
|
+
return this.fail(TLS_ALERT_INTERNAL_ERROR)
|
|
1205
|
+
}
|
|
1206
|
+
const h: i32 = this.hashLength
|
|
1207
|
+
this.reserveOutput(TLS_LEVEL_HANDSHAKE, this.outputHandshakeLength + 8 + signatureLength + 4 + h)
|
|
1208
|
+
// The secrets' arrays are the slot's, chosen before the block opens.
|
|
1209
|
+
this.clientApplicationSecret = this.secretArray(TLS_SECRET_CLIENT_APPLICATION)
|
|
1210
|
+
this.serverApplicationSecret = this.secretArray(TLS_SECRET_SERVER_APPLICATION)
|
|
1211
|
+
this.exporterSecret = this.secretArray(TLS_SECRET_EXPORTER)
|
|
1212
|
+
this.expectedClientFinished = this.secretArray(TLS_SECRET_CLIENT_FINISHED)
|
|
1213
|
+
using _scope = arena()
|
|
1214
|
+
const verifyAt: i32 = this.outputHandshakeLength
|
|
1215
|
+
const verifyEnd: i32 = tlsWriteCertificateVerify(
|
|
1216
|
+
this.outputHandshake,
|
|
1217
|
+
verifyAt,
|
|
1218
|
+
this.config.signatureScheme,
|
|
1219
|
+
signature
|
|
1220
|
+
)
|
|
1221
|
+
this.sent(TLS_LEVEL_HANDSHAKE, verifyAt, verifyEnd)
|
|
1222
|
+
this.transcript.hashInto(this.hashes, TLS_FROM)
|
|
1223
|
+
// The Finished's verify_data goes straight into its place in the flight.
|
|
1224
|
+
tlsFinishedVerifyDataInto(
|
|
1225
|
+
this.kdf,
|
|
1226
|
+
h,
|
|
1227
|
+
this.serverHandshakeSecret,
|
|
1228
|
+
this.hashes,
|
|
1229
|
+
TLS_FROM,
|
|
1230
|
+
this.outputHandshake,
|
|
1231
|
+
verifyEnd + 4
|
|
1232
|
+
)
|
|
1233
|
+
const finishedEnd: i32 = tlsWriteFinished(
|
|
1234
|
+
this.outputHandshake,
|
|
1235
|
+
verifyEnd,
|
|
1236
|
+
this.outputHandshake,
|
|
1237
|
+
verifyEnd + 4,
|
|
1238
|
+
h
|
|
1239
|
+
)
|
|
1240
|
+
this.sent(TLS_LEVEL_HANDSHAKE, verifyEnd, finishedEnd)
|
|
1241
|
+
|
|
1242
|
+
this.transcript.hashInto(this.hashes, TLS_FROM)
|
|
1243
|
+
const master: u8[] = new Array<u8>(h)
|
|
1244
|
+
tlsMasterSecretInto(this.kdf, h, this.handshakeSecret, master)
|
|
1245
|
+
tlsDeriveSecretInto(
|
|
1246
|
+
this.kdf,
|
|
1247
|
+
h,
|
|
1248
|
+
master,
|
|
1249
|
+
"c ap traffic",
|
|
1250
|
+
this.hashes,
|
|
1251
|
+
TLS_FROM,
|
|
1252
|
+
this.clientApplicationSecret
|
|
1253
|
+
)
|
|
1254
|
+
tlsDeriveSecretInto(
|
|
1255
|
+
this.kdf,
|
|
1256
|
+
h,
|
|
1257
|
+
master,
|
|
1258
|
+
"s ap traffic",
|
|
1259
|
+
this.hashes,
|
|
1260
|
+
TLS_FROM,
|
|
1261
|
+
this.serverApplicationSecret
|
|
1262
|
+
)
|
|
1263
|
+
tlsDeriveSecretInto(this.kdf, h, master, "exp master", this.hashes, TLS_FROM, this.exporterSecret)
|
|
1264
|
+
tlsFinishedVerifyDataInto(
|
|
1265
|
+
this.kdf,
|
|
1266
|
+
h,
|
|
1267
|
+
this.clientHandshakeSecret,
|
|
1268
|
+
this.hashes,
|
|
1269
|
+
TLS_FROM,
|
|
1270
|
+
this.expectedClientFinished,
|
|
1271
|
+
TLS_FROM
|
|
1272
|
+
)
|
|
1273
|
+
secureZero(master)
|
|
1274
|
+
this.toBeSigned = this.none
|
|
1275
|
+
this.state = TLS_STATE_WAIT_FINISHED
|
|
1276
|
+
return 0
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* The client's Finished (§4.4.4), `input[0 .. 4 + length)`: its
|
|
1281
|
+
* `verify_data` is HashLen bytes and must equal the HMAC the server
|
|
1282
|
+
* computed, compared in constant time. A wrong length is `decode_error`, a
|
|
1283
|
+
* wrong value `decrypt_error`.
|
|
1284
|
+
*/
|
|
1285
|
+
handleFinished(length: i32): i32 {
|
|
1286
|
+
if (length !== this.hashLength) {
|
|
1287
|
+
return TLS_ALERT_DECODE_ERROR
|
|
1288
|
+
}
|
|
1289
|
+
if (!timingSafeEqualAt(this.input, 4, this.expectedClientFinished, TLS_FROM, this.hashLength)) {
|
|
1290
|
+
return TLS_ALERT_DECRYPT_ERROR
|
|
1291
|
+
}
|
|
1292
|
+
this.transcript.update(this.input, TLS_FROM, 4 + length)
|
|
1293
|
+
// The handshake secret has done its work: wiped, and no longer held.
|
|
1294
|
+
secureZero(this.handshakeSecret)
|
|
1295
|
+
this.handshakeSecret = this.none
|
|
1296
|
+
this.state = TLS_STATE_CONNECTED
|
|
1297
|
+
return 0
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* A message the server wrote at `level`, `output[at .. end)` of that
|
|
1302
|
+
* level's buffer, goes into the transcript and stays on the output.
|
|
1303
|
+
*/
|
|
1304
|
+
sent(level: i32, at: i32, end: i32): void {
|
|
1305
|
+
if (level === TLS_LEVEL_INITIAL) {
|
|
1306
|
+
this.transcript.update(this.outputInitial, at, end - at)
|
|
1307
|
+
this.outputInitialLength = end
|
|
1308
|
+
} else {
|
|
1309
|
+
this.transcript.update(this.outputHandshake, at, end - at)
|
|
1310
|
+
this.outputHandshakeLength = end
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* The handshake bytes written at `level` since the last call, in a fresh
|
|
1316
|
+
* array, and forgets them. Bytes are only ever written at a level whose
|
|
1317
|
+
* secrets are already known, so a carrier that installs keys as they appear
|
|
1318
|
+
* can always protect what this answers. A carrier that reads the output
|
|
1319
|
+
* buffers in place calls `clearOutput` instead, and allocates nothing.
|
|
1320
|
+
*/
|
|
1321
|
+
takeOutput(level: i32): u8[] {
|
|
1322
|
+
const none: u8[] = []
|
|
1323
|
+
switch (level) {
|
|
1324
|
+
case TLS_LEVEL_INITIAL: {
|
|
1325
|
+
const out: u8[] = new Array<u8>(this.outputInitialLength)
|
|
1326
|
+
for (let k: i32 = 0; k < toI32(out.length) && k < toI32(this.outputInitial.length); k++) {
|
|
1327
|
+
out[k] = this.outputInitial[k]
|
|
1328
|
+
}
|
|
1329
|
+
this.outputInitialLength = 0
|
|
1330
|
+
return out
|
|
1331
|
+
}
|
|
1332
|
+
case TLS_LEVEL_HANDSHAKE: {
|
|
1333
|
+
const out: u8[] = new Array<u8>(this.outputHandshakeLength)
|
|
1334
|
+
for (let k: i32 = 0; k < toI32(out.length) && k < toI32(this.outputHandshake.length); k++) {
|
|
1335
|
+
out[k] = this.outputHandshake[k]
|
|
1336
|
+
}
|
|
1337
|
+
this.outputHandshakeLength = 0
|
|
1338
|
+
return out
|
|
1339
|
+
}
|
|
1340
|
+
default:
|
|
1341
|
+
// Nothing is written at the Application level before NewSessionTicket, which is out of scope.
|
|
1342
|
+
return none
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/** Forgets the handshake bytes written at `level`, which a carrier has read out of the buffer itself. */
|
|
1347
|
+
clearOutput(level: i32): void {
|
|
1348
|
+
if (level === TLS_LEVEL_INITIAL) {
|
|
1349
|
+
this.outputInitialLength = 0
|
|
1350
|
+
} else if (level === TLS_LEVEL_HANDSHAKE) {
|
|
1351
|
+
this.outputHandshakeLength = 0
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* The secret the server *reads* `level` under — the client's traffic secret
|
|
1357
|
+
* — or `null` while it is not known yet. The Initial level has none: it is
|
|
1358
|
+
* cleartext in TLS, and QUIC derives its own from the connection id.
|
|
1359
|
+
*/
|
|
1360
|
+
readSecret(level: i32): u8[] | null {
|
|
1361
|
+
return tlsSecretAt(level, this.clientHandshakeSecret, this.clientApplicationSecret)
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/** The secret the server *writes* `level` under — its own traffic secret — or `null` while it is not known yet. */
|
|
1365
|
+
writeSecret(level: i32): u8[] | null {
|
|
1366
|
+
return tlsSecretAt(level, this.serverHandshakeSecret, this.serverApplicationSecret)
|
|
1367
|
+
}
|
|
1368
|
+
}
|