@amritk/nish-x86_64-linux 0.23.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nish +0 -0
- package/docs/AI.md +50 -23
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/arrays.d.ts +0 -2
- package/runtime/core/arrays.mjs +0 -2
- package/runtime/core/host.d.ts +8 -0
- package/runtime/core/host.mjs +50 -2
- package/runtime/core/numbers.mjs +2 -3
- package/runtime/nish.d.ts +100 -4
- package/runtime/nish.h +34 -0
- package/runtime/nish.mjs +23 -5
- package/runtime/node.mjs +57 -6
- package/runtime/runtime-net.c +45 -4
- package/runtime/runtime-os.c +115 -12
- package/runtime/runtime-term.c +2 -2
- package/runtime/runtime.c +1 -1
- package/scripts/build.sh +13 -3
- package/std/README.md +44 -7
- package/std/cli-args.ts +1216 -0
- package/std/cli-flags.ts +585 -0
- package/std/cli.ts +308 -19
- package/std/compress/checksum.ts +290 -0
- package/std/compress/deflate.ts +2790 -0
- package/std/compress/gzip.ts +761 -0
- package/std/compress/zlib.ts +168 -0
- package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
- package/std/crypto/p256.ts +63 -1
- package/std/crypto/p384.ts +6044 -0
- package/std/crypto/pki-roots.ts +322 -0
- package/std/crypto/pki.ts +1207 -0
- package/std/crypto/rsa.ts +663 -0
- package/std/crypto/sha256.ts +2 -3
- package/std/crypto/x25519.ts +3 -4
- package/std/crypto/x509.ts +1054 -58
- package/std/net/dns-config.ts +734 -0
- package/std/net/dns-message.ts +517 -0
- package/std/net/dns-resolver.ts +1015 -0
- package/std/net/dns.ts +478 -0
- package/std/net/http-compress.ts +393 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +101 -1
- package/std/net/http1.ts +9 -9
- package/std/net/tls/client.ts +1094 -0
- package/std/net/tls/codec.ts +585 -3
- package/std/net/tls/record-client.ts +1192 -0
- package/std/net/tls/verify.ts +221 -0
- package/std/net/tls-tcp.ts +642 -11
- package/std/net/tls.ts +2 -2
- package/std/net/url.ts +559 -4
- package/std/yaml-write.ts +415 -0
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/tls/client` — the client side of a TLS 1.3 handshake (RFC 8446),
|
|
3
|
+
* from its ClientHello to its Finished, carrier-agnostic and sans-IO: the
|
|
4
|
+
* mirror of `nish/net/tls`'s `TlsServer`.
|
|
5
|
+
*
|
|
6
|
+
* import { TlsClient, TlsClientConfig } from "nish/net/tls/client";
|
|
7
|
+
*
|
|
8
|
+
* const client = new TlsClient(new TlsClientConfig("example.com", ["h2"]), random, x25519Key, p256Key);
|
|
9
|
+
* client.start();
|
|
10
|
+
* // send client.takeOutput(TLS_LEVEL_INITIAL); hand the server's bytes to
|
|
11
|
+
* // client.receive(level, bytes, 0, n); install secrets as they are known;
|
|
12
|
+
* // at TLS_CLIENT_STATE_WAIT_AUTH check peerCertificates() and the
|
|
13
|
+
* // signature, then client.authenticate(ok) (or client.reject(alert)).
|
|
14
|
+
*
|
|
15
|
+
* It lives in a module of its own, beside `nish/net/tls` and not re-exported
|
|
16
|
+
* by it, since Nish has no re-exports; the levels and `TLS_SECRET_*` indices
|
|
17
|
+
* it shares with the server are `nish/net/tls`'s.
|
|
18
|
+
*
|
|
19
|
+
* **Carrier-agnostic.** As the server does, the client reads and writes
|
|
20
|
+
* handshake *messages* tagged with their level — the ClientHello and the
|
|
21
|
+
* ServerHello at Initial, the rest of the server's flight and the client's
|
|
22
|
+
* Finished at Handshake — and its secrets are readable as they become known.
|
|
23
|
+
* `nish/net/tls/record-client` puts it on a byte stream.
|
|
24
|
+
*
|
|
25
|
+
* **Sans-IO.** The client random and both private keys, x25519's and P-256's,
|
|
26
|
+
* are constructor arguments. Nothing here reads a socket, a clock or the
|
|
27
|
+
* random device.
|
|
28
|
+
*
|
|
29
|
+
* **Authentication is the caller's.** Once the server's CertificateVerify is
|
|
30
|
+
* read, the client stops at `TLS_CLIENT_STATE_WAIT_AUTH` without looking at
|
|
31
|
+
* the chain or the signature: `peerCertificates`, `verifyScheme`,
|
|
32
|
+
* `verifyContent` and `verifySignature` hand them out, and only
|
|
33
|
+
* `authenticate(true)` goes on to the server's Finished and writes the
|
|
34
|
+
* client's. A caller that drives this class directly must make that call
|
|
35
|
+
* itself, after checking the chain against its trust anchors and the
|
|
36
|
+
* signature over `verifyContent()` under the leaf's key: nothing here does
|
|
37
|
+
* either, and nothing proves the peer is who `serverName` says until then
|
|
38
|
+
* (`docs/security/tls.md`, "The client").
|
|
39
|
+
*
|
|
40
|
+
* **What is offered.** TLS 1.3 only, with a 32-byte `legacy_session_id` for
|
|
41
|
+
* middlebox compatibility (§D.4). The suites, groups and first key share are
|
|
42
|
+
* the configuration's: by default the three suites this tree negotiates and
|
|
43
|
+
* the group x25519; `secp256r1` may be configured too, for the first share or
|
|
44
|
+
* for a HelloRetryRequest to pick. `signature_algorithms` lists ECDSA
|
|
45
|
+
* P-256 and P-384 and RSA-PSS; `server_name` is sent unless the name is an
|
|
46
|
+
* IP literal (RFC 6066 §3); ALPN is the configuration's. One
|
|
47
|
+
* HelloRetryRequest is answered (§4.1.4); resumption, 0-RTT and client
|
|
48
|
+
* certificates are not here, and a NewSessionTicket is read and dropped.
|
|
49
|
+
*
|
|
50
|
+
* **Refusals are alerts.** `receive`, `authenticate` and `reject` answer 0 or
|
|
51
|
+
* a TLS alert description (`TLS_ALERT_*` in `nish/net/tls/codec`); after one
|
|
52
|
+
* the client is in `TLS_CLIENT_STATE_FAILED`, every secret it held is wiped,
|
|
53
|
+
* and it answers the same alert to every call. Nothing the peer sends can
|
|
54
|
+
* make it panic.
|
|
55
|
+
*
|
|
56
|
+
* **Secrets.** Both private keys are copied into `Secret` fields and the
|
|
57
|
+
* caller's arrays zeroed; the one the server did not pick is wiped when it
|
|
58
|
+
* picks, and both once the exchange is done. The ECDHE secret is a `Secret`
|
|
59
|
+
* wiped on every path; the handshake, traffic and exporter secrets are the
|
|
60
|
+
* fields of two `TlsSecretPool`s. `fail` and `wipe` zero them all.
|
|
61
|
+
*
|
|
62
|
+
* The state is a class and a `switch`, with no closures. Written from RFC
|
|
63
|
+
* 8446, RFC 7301 and RFC 6066, not ported from another implementation.
|
|
64
|
+
*/
|
|
65
|
+
import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
|
|
66
|
+
import { SHA256_SIZE } from "nish/crypto/sha256"
|
|
67
|
+
import { SHA384_SIZE } from "nish/crypto/sha512"
|
|
68
|
+
import { timingSafeEqualAt } from "nish/crypto/ct"
|
|
69
|
+
import { X25519_SIZE, x25519, x25519Base } from "nish/crypto/x25519"
|
|
70
|
+
import { P256_SCALAR_SIZE, p256Ecdh, p256PublicKey } from "nish/crypto/p256"
|
|
71
|
+
import {
|
|
72
|
+
TLS_ALERT_BAD_CERTIFICATE,
|
|
73
|
+
TLS_ALERT_DECODE_ERROR,
|
|
74
|
+
TLS_ALERT_DECRYPT_ERROR,
|
|
75
|
+
TLS_ALERT_ILLEGAL_PARAMETER,
|
|
76
|
+
TLS_ALERT_INTERNAL_ERROR,
|
|
77
|
+
TLS_ALERT_MISSING_EXTENSION,
|
|
78
|
+
TLS_ALERT_PROTOCOL_VERSION,
|
|
79
|
+
TLS_ALERT_UNEXPECTED_MESSAGE,
|
|
80
|
+
TLS_ALERT_UNSUPPORTED_EXTENSION,
|
|
81
|
+
TLS_GROUP_SECP256R1,
|
|
82
|
+
TLS_GROUP_X25519,
|
|
83
|
+
TLS_HANDSHAKE_CERTIFICATE,
|
|
84
|
+
TLS_HANDSHAKE_CERTIFICATE_VERIFY,
|
|
85
|
+
TLS_HANDSHAKE_ENCRYPTED_EXTENSIONS,
|
|
86
|
+
TLS_HANDSHAKE_FINISHED,
|
|
87
|
+
TLS_HANDSHAKE_NEW_SESSION_TICKET,
|
|
88
|
+
TLS_HANDSHAKE_SERVER_HELLO,
|
|
89
|
+
TLS_LEGACY_VERSION,
|
|
90
|
+
TLS_P256_SHARE_SIZE,
|
|
91
|
+
TLS_RANDOM_SIZE,
|
|
92
|
+
TLS_VERSION_13,
|
|
93
|
+
TLS_X25519_SHARE_SIZE,
|
|
94
|
+
TlsServerFlightView,
|
|
95
|
+
tlsCertificateVerifyContent,
|
|
96
|
+
tlsClientOffersScheme,
|
|
97
|
+
tlsCopyCertificates,
|
|
98
|
+
tlsReadCertificate,
|
|
99
|
+
tlsReadCertificateVerify,
|
|
100
|
+
tlsReadEncryptedExtensions,
|
|
101
|
+
tlsReadNewSessionTicket,
|
|
102
|
+
tlsReadServerHello,
|
|
103
|
+
tlsServerHelloDowngrade,
|
|
104
|
+
tlsCopyWindow,
|
|
105
|
+
tlsWindowString,
|
|
106
|
+
tlsWriteClientHello,
|
|
107
|
+
tlsWriteFinished,
|
|
108
|
+
} from "nish/net/tls/codec"
|
|
109
|
+
import {
|
|
110
|
+
TLS_AES_128_GCM_SHA256,
|
|
111
|
+
TLS_AES_256_GCM_SHA384,
|
|
112
|
+
TLS_CHACHA20_POLY1305_SHA256,
|
|
113
|
+
TlsTranscript,
|
|
114
|
+
tlsDeriveSecretHeld,
|
|
115
|
+
tlsEarlySecret,
|
|
116
|
+
tlsFinishedVerifyDataHeld,
|
|
117
|
+
tlsHandshakeSecretHeld,
|
|
118
|
+
tlsMasterSecretHeld,
|
|
119
|
+
tlsSuiteHashLength,
|
|
120
|
+
} from "nish/net/tls/schedule"
|
|
121
|
+
import {
|
|
122
|
+
TLS_LEVEL_APPLICATION,
|
|
123
|
+
TLS_LEVEL_HANDSHAKE,
|
|
124
|
+
TLS_LEVEL_INITIAL,
|
|
125
|
+
TLS_MAX_HANDSHAKE_MESSAGE,
|
|
126
|
+
TLS_SECRET_CLIENT_APPLICATION,
|
|
127
|
+
TLS_SECRET_CLIENT_HANDSHAKE,
|
|
128
|
+
TLS_SECRET_EXPORTER,
|
|
129
|
+
TLS_SECRET_HANDSHAKE,
|
|
130
|
+
TLS_SECRET_SERVER_APPLICATION,
|
|
131
|
+
TLS_SECRET_SERVER_HANDSHAKE,
|
|
132
|
+
TlsSecretPool,
|
|
133
|
+
tlsHeldCopyIn,
|
|
134
|
+
tlsIsZeroSecret,
|
|
135
|
+
tlsZeroHeld,
|
|
136
|
+
} from "nish/net/tls"
|
|
137
|
+
|
|
138
|
+
/** Built; `start` has not written the ClientHello yet. */
|
|
139
|
+
export const TLS_CLIENT_STATE_START: i32 = 0
|
|
140
|
+
/** The ClientHello is out; a ServerHello or HelloRetryRequest comes next, at the Initial level. */
|
|
141
|
+
export const TLS_CLIENT_STATE_WAIT_SERVER_HELLO: i32 = 1
|
|
142
|
+
/** The handshake secrets are known; EncryptedExtensions comes next, at the Handshake level. */
|
|
143
|
+
export const TLS_CLIENT_STATE_WAIT_ENCRYPTED_EXTENSIONS: i32 = 2
|
|
144
|
+
export const TLS_CLIENT_STATE_WAIT_CERTIFICATE: i32 = 3
|
|
145
|
+
export const TLS_CLIENT_STATE_WAIT_CERTIFICATE_VERIFY: i32 = 4
|
|
146
|
+
/**
|
|
147
|
+
* The server's chain and signature are read and wait for the caller's
|
|
148
|
+
* verdict: `authenticate` or `reject`. Bytes that arrive meanwhile are held,
|
|
149
|
+
* not read.
|
|
150
|
+
*/
|
|
151
|
+
export const TLS_CLIENT_STATE_WAIT_AUTH: i32 = 5
|
|
152
|
+
/** The caller accepted the server; its Finished comes next. */
|
|
153
|
+
export const TLS_CLIENT_STATE_WAIT_FINISHED: i32 = 6
|
|
154
|
+
/** Both Finished are done and the application secrets known. */
|
|
155
|
+
export const TLS_CLIENT_STATE_CONNECTED: i32 = 7
|
|
156
|
+
export const TLS_CLIENT_STATE_FAILED: i32 = 8
|
|
157
|
+
|
|
158
|
+
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
159
|
+
const TLS_CLIENT_FROM: i32 = 0
|
|
160
|
+
|
|
161
|
+
/** The legacy_session_id the client sends (§4.1.2, §D.4): 32 bytes, so that middleboxes see a resumption. */
|
|
162
|
+
const TLS_CLIENT_SESSION_ID_SIZE: i32 = 32
|
|
163
|
+
|
|
164
|
+
/** The longest `server_name` a ClientHello carries: a DNS name's 253 characters, under the extension's own bound. */
|
|
165
|
+
const TLS_CLIENT_SERVER_NAME_MAX: i32 = 253
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* What a client is configured with, the same for every connection. The
|
|
169
|
+
* constructor fills in the defaults; a caller may narrow `suites` or `groups`
|
|
170
|
+
* or change `shareGroup` before handing it to a `TlsClient`, which checks it.
|
|
171
|
+
*/
|
|
172
|
+
export class TlsClientConfig {
|
|
173
|
+
/** The host the client means to reach: sent as `server_name` unless it is an IP literal, and the name the caller authenticates. */
|
|
174
|
+
serverName: string
|
|
175
|
+
/** The ALPN protocols offered, most preferred first; empty sends no ALPN. Each is 1 to 255 bytes. */
|
|
176
|
+
alpn: string[]
|
|
177
|
+
/** The cipher suites offered, most preferred first: a non-empty subset of the three this tree negotiates. */
|
|
178
|
+
suites: i32[]
|
|
179
|
+
/** The groups offered in `supported_groups`: a non-empty subset of x25519 and secp256r1. */
|
|
180
|
+
groups: i32[]
|
|
181
|
+
/** The group of the one key share the first ClientHello carries; one of `groups`. */
|
|
182
|
+
shareGroup: i32
|
|
183
|
+
|
|
184
|
+
constructor(serverName: string, alpn: string[]) {
|
|
185
|
+
this.serverName = serverName
|
|
186
|
+
this.alpn = alpn
|
|
187
|
+
this.suites = [TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256]
|
|
188
|
+
this.groups = [TLS_GROUP_X25519]
|
|
189
|
+
this.shareGroup = TLS_GROUP_X25519
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Whether `suite` is one this tree negotiates. */
|
|
194
|
+
const tlsClientKnowsSuite = (suite: i32): boolean => tlsSuiteHashLength(suite) !== 0
|
|
195
|
+
|
|
196
|
+
/** Whether `group` is one the client can send a share for. */
|
|
197
|
+
const tlsClientKnowsGroup = (group: i32): boolean =>
|
|
198
|
+
group === TLS_GROUP_X25519 || group === TLS_GROUP_SECP256R1
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Whether `name` is an IP literal, which RFC 6066 §3 keeps out of
|
|
202
|
+
* `server_name`: dotted decimal (digits and dots only), or anything with a
|
|
203
|
+
* colon, which no DNS name has.
|
|
204
|
+
*/
|
|
205
|
+
export const tlsClientIsIpLiteral = (name: string): boolean => {
|
|
206
|
+
let digits: boolean = true
|
|
207
|
+
for (let k: i32 = 0; k < toI32(name.length); k++) {
|
|
208
|
+
const c: i32 = toI32(name.charCodeAt(k))
|
|
209
|
+
if (c === 58) {
|
|
210
|
+
return true
|
|
211
|
+
}
|
|
212
|
+
if (c !== 46 && (c < 48 || c > 57)) {
|
|
213
|
+
digits = false
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return digits && toI32(name.length) > 0
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The ClientHello's bytes before its extensions, less the session id and
|
|
221
|
+
* suites: the message header (4), version (2), random (32), the session id's
|
|
222
|
+
* length (1), the suites' length (2), compression (2) and the extensions'
|
|
223
|
+
* own length (2).
|
|
224
|
+
*/
|
|
225
|
+
const TLS_CLIENT_HELLO_FIXED: i32 = 45
|
|
226
|
+
|
|
227
|
+
/** Whether every character of `text` is ASCII, so it goes on the wire a byte each as it is. */
|
|
228
|
+
const tlsClientIsAscii = (text: string): boolean => {
|
|
229
|
+
for (let k: i32 = 0; k < toI32(text.length); k++) {
|
|
230
|
+
if (toI32(text.charCodeAt(k)) > 127) {
|
|
231
|
+
return false
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return true
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Whether `config` can be offered: suites and groups non-empty and known, the
|
|
239
|
+
* share's group among the groups, every ALPN protocol 1 to 255 bytes, and a
|
|
240
|
+
* server name of at most 253 bytes.
|
|
241
|
+
*/
|
|
242
|
+
const tlsClientConfigFits = (config: TlsClientConfig): boolean => {
|
|
243
|
+
if (toI32(config.suites.length) === 0 || toI32(config.groups.length) === 0) {
|
|
244
|
+
return false
|
|
245
|
+
}
|
|
246
|
+
for (const suite of config.suites) {
|
|
247
|
+
if (!tlsClientKnowsSuite(suite)) {
|
|
248
|
+
return false
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
for (const group of config.groups) {
|
|
252
|
+
if (!tlsClientKnowsGroup(group)) {
|
|
253
|
+
return false
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (config.groups.indexOf(config.shareGroup) < 0) {
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
for (const protocol of config.alpn) {
|
|
260
|
+
if (toI32(protocol.length) < 1 || toI32(protocol.length) > 255 || !tlsClientIsAscii(protocol)) {
|
|
261
|
+
return false
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return toI32(config.serverName.length) <= TLS_CLIENT_SERVER_NAME_MAX && tlsClientIsAscii(config.serverName)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export class TlsClient {
|
|
268
|
+
config: TlsClientConfig
|
|
269
|
+
clientRandom: u8[]
|
|
270
|
+
/** The x25519 private key, copied in by the constructor; wiped once the server picks a group, unless it picked x25519, then once the exchange is done. */
|
|
271
|
+
readonly x25519Key: Secret<u8[]>
|
|
272
|
+
/** The P-256 private key, held the same way. */
|
|
273
|
+
readonly p256Key: Secret<u8[]>
|
|
274
|
+
/** The legacy_session_id sent, which the ServerHello must echo. */
|
|
275
|
+
sessionId: u8[]
|
|
276
|
+
transcript: TlsTranscript
|
|
277
|
+
/**
|
|
278
|
+
* The first ClientHello, kept plain until the ServerHello names the hash:
|
|
279
|
+
* the transcript feeds one hash only, so it starts from this copy.
|
|
280
|
+
*/
|
|
281
|
+
firstHello: u8[]
|
|
282
|
+
/** The bytes received at the current level and not yet read: `input[0 .. inputLength)`, whole messages and the start of the next. */
|
|
283
|
+
input: u8[]
|
|
284
|
+
/** What the client has written at the Initial level and not handed on. */
|
|
285
|
+
outputInitial: u8[]
|
|
286
|
+
/** What the client has written at the Handshake level and not handed on: its Finished. */
|
|
287
|
+
outputHandshake: u8[]
|
|
288
|
+
secrets256: TlsSecretPool
|
|
289
|
+
secrets384: TlsSecretPool
|
|
290
|
+
/** The server message last read, as windows into `input`. */
|
|
291
|
+
view: TlsServerFlightView
|
|
292
|
+
/** The HelloRetryRequest's cookie, echoed in the second ClientHello; empty if it sent none. */
|
|
293
|
+
cookie: u8[]
|
|
294
|
+
/** The server's DER certificates, leaf first, once its Certificate is read. */
|
|
295
|
+
certificates: u8[][]
|
|
296
|
+
/** What the server's CertificateVerify signs (§4.4.3), once its Certificate is read. */
|
|
297
|
+
content: u8[]
|
|
298
|
+
/** The CertificateVerify signature, as it came on the wire. */
|
|
299
|
+
signature: u8[]
|
|
300
|
+
/** A transcript hash on its way into the schedule: HashLen bytes of it. */
|
|
301
|
+
hashes: u8[]
|
|
302
|
+
/** The ALPN protocol the server chose, one of the configuration's, or empty. */
|
|
303
|
+
alpnChosen: string = ""
|
|
304
|
+
/** Which of the negotiated pool's secrets are known: bit `1 << TLS_SECRET_*` for each, as `TlsServer.held`. */
|
|
305
|
+
held: i32 = 0
|
|
306
|
+
state: i32 = 0
|
|
307
|
+
/** The alert the client failed with, or 0. */
|
|
308
|
+
alert: i32 = 0
|
|
309
|
+
/** The negotiated cipher suite, or 0 before the ServerHello (or HelloRetryRequest) is read. */
|
|
310
|
+
suite: i32 = 0
|
|
311
|
+
hashLength: i32 = 0
|
|
312
|
+
/** The group of the key share the last ClientHello carried. */
|
|
313
|
+
shareGroup: i32 = 0
|
|
314
|
+
/** The CertificateVerify scheme. */
|
|
315
|
+
scheme: i32 = 0
|
|
316
|
+
inputLength: i32 = 0
|
|
317
|
+
outputInitialLength: i32 = 0
|
|
318
|
+
outputHandshakeLength: i32 = 0
|
|
319
|
+
/** Whether a HelloRetryRequest was read, so a second is fatal. */
|
|
320
|
+
retried: boolean = false
|
|
321
|
+
/** Whether `server_name` was sent, so that only then may the server acknowledge it. */
|
|
322
|
+
sentServerName: boolean = false
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* A client for one connection under `config`, with the 32-byte client
|
|
326
|
+
* random and the 32-byte x25519 and P-256 private keys drawn for it; both
|
|
327
|
+
* keys are copied in and the caller's arrays zeroed. A random or key of
|
|
328
|
+
* another length, or a configuration it cannot offer, leaves the client
|
|
329
|
+
* failed with `internal_error` before it writes anything.
|
|
330
|
+
*/
|
|
331
|
+
constructor(config: TlsClientConfig, clientRandom: u8[], x25519Private: u8[], p256Private: u8[]) {
|
|
332
|
+
const x25519Length: i32 = toI32(x25519Private.length)
|
|
333
|
+
const p256Length: i32 = toI32(p256Private.length)
|
|
334
|
+
this.config = config
|
|
335
|
+
this.clientRandom =
|
|
336
|
+
toI32(clientRandom.length) === TLS_RANDOM_SIZE
|
|
337
|
+
? tlsCopyWindow(clientRandom, TLS_CLIENT_FROM, TLS_RANDOM_SIZE)
|
|
338
|
+
: []
|
|
339
|
+
this.x25519Key = secret(new Array<u8>(X25519_SIZE))
|
|
340
|
+
this.p256Key = secret(new Array<u8>(P256_SCALAR_SIZE))
|
|
341
|
+
exposeWith(this.x25519Key, x25519Private, tlsHeldCopyIn)
|
|
342
|
+
exposeWith(this.p256Key, p256Private, tlsHeldCopyIn)
|
|
343
|
+
secureZero(x25519Private)
|
|
344
|
+
secureZero(p256Private)
|
|
345
|
+
// Public, and derived from the public random, so it gives nothing away;
|
|
346
|
+
// its only job is to make the hello look like a resumption (§D.4).
|
|
347
|
+
this.sessionId = tlsCopyWindow(this.clientRandom, TLS_CLIENT_FROM, TLS_CLIENT_SESSION_ID_SIZE)
|
|
348
|
+
this.transcript = new TlsTranscript(0)
|
|
349
|
+
this.firstHello = []
|
|
350
|
+
this.input = new Array<u8>(1024)
|
|
351
|
+
this.outputInitial = []
|
|
352
|
+
this.outputHandshake = new Array<u8>(4 + SHA384_SIZE)
|
|
353
|
+
this.secrets256 = new TlsSecretPool(SHA256_SIZE)
|
|
354
|
+
this.secrets384 = new TlsSecretPool(SHA384_SIZE)
|
|
355
|
+
this.view = new TlsServerFlightView()
|
|
356
|
+
this.cookie = []
|
|
357
|
+
this.certificates = []
|
|
358
|
+
this.content = []
|
|
359
|
+
this.signature = []
|
|
360
|
+
this.hashes = new Array<u8>(SHA384_SIZE)
|
|
361
|
+
this.shareGroup = config.shareGroup
|
|
362
|
+
if (
|
|
363
|
+
toI32(this.clientRandom.length) !== TLS_RANDOM_SIZE ||
|
|
364
|
+
x25519Length !== X25519_SIZE ||
|
|
365
|
+
p256Length !== P256_SCALAR_SIZE ||
|
|
366
|
+
!tlsClientConfigFits(config)
|
|
367
|
+
) {
|
|
368
|
+
this.fail(TLS_ALERT_INTERNAL_ERROR)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Writes the first ClientHello at the Initial level. Answers 0, or the alert the client failed with; a second call is `internal_error`. */
|
|
373
|
+
start(): i32 {
|
|
374
|
+
if (this.state === TLS_CLIENT_STATE_FAILED) {
|
|
375
|
+
return this.alert
|
|
376
|
+
}
|
|
377
|
+
if (this.state !== TLS_CLIENT_STATE_START) {
|
|
378
|
+
return this.fail(TLS_ALERT_INTERNAL_ERROR)
|
|
379
|
+
}
|
|
380
|
+
const at: i32 = this.outputInitialLength
|
|
381
|
+
const end: i32 = this.writeHello()
|
|
382
|
+
if (end < 0) {
|
|
383
|
+
return this.fail(TLS_ALERT_INTERNAL_ERROR)
|
|
384
|
+
}
|
|
385
|
+
this.firstHello = tlsCopyWindow(this.outputInitial, at, end - at)
|
|
386
|
+
this.state = TLS_CLIENT_STATE_WAIT_SERVER_HELLO
|
|
387
|
+
return 0
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Writes a ClientHello with a share of `shareGroup` and the cookie, if any,
|
|
392
|
+
* after what the Initial output holds, and answers where it ends; -1 when
|
|
393
|
+
* the key for the share gives no public key, or when the extensions would
|
|
394
|
+
* pass the 65,535 bytes their length can say (a HelloRetryRequest's cookie
|
|
395
|
+
* near that size).
|
|
396
|
+
*/
|
|
397
|
+
writeHello(): i32 {
|
|
398
|
+
const share: u8[] | null =
|
|
399
|
+
this.shareGroup === TLS_GROUP_X25519 ? x25519Base(this.x25519Key) : p256PublicKey(this.p256Key)
|
|
400
|
+
if (share === null) {
|
|
401
|
+
return -1
|
|
402
|
+
}
|
|
403
|
+
const serverName: string = tlsClientIsIpLiteral(this.config.serverName) ? "" : this.config.serverName
|
|
404
|
+
this.sentServerName = toI32(serverName.length) > 0
|
|
405
|
+
const none: u8[] = []
|
|
406
|
+
const at: i32 = this.outputInitialLength
|
|
407
|
+
const length: i32 = tlsWriteClientHello(
|
|
408
|
+
none,
|
|
409
|
+
TLS_CLIENT_FROM,
|
|
410
|
+
this.clientRandom,
|
|
411
|
+
this.sessionId,
|
|
412
|
+
this.config.suites,
|
|
413
|
+
this.config.groups,
|
|
414
|
+
this.shareGroup,
|
|
415
|
+
share,
|
|
416
|
+
serverName,
|
|
417
|
+
this.config.alpn,
|
|
418
|
+
this.cookie
|
|
419
|
+
)
|
|
420
|
+
const before: i32 =
|
|
421
|
+
TLS_CLIENT_HELLO_FIXED + toI32(this.sessionId.length) + 2 * toI32(this.config.suites.length)
|
|
422
|
+
if (length - before > 65535) {
|
|
423
|
+
return -1
|
|
424
|
+
}
|
|
425
|
+
this.reserveOutput(at + length)
|
|
426
|
+
const end: i32 = tlsWriteClientHello(
|
|
427
|
+
this.outputInitial,
|
|
428
|
+
at,
|
|
429
|
+
this.clientRandom,
|
|
430
|
+
this.sessionId,
|
|
431
|
+
this.config.suites,
|
|
432
|
+
this.config.groups,
|
|
433
|
+
this.shareGroup,
|
|
434
|
+
share,
|
|
435
|
+
serverName,
|
|
436
|
+
this.config.alpn,
|
|
437
|
+
this.cookie
|
|
438
|
+
)
|
|
439
|
+
this.sent(TLS_LEVEL_INITIAL, at, end)
|
|
440
|
+
return end
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Fails the handshake with `alert` and answers it, wiping every secret the client holds. */
|
|
444
|
+
fail(alert: i32): i32 {
|
|
445
|
+
this.wipe()
|
|
446
|
+
this.state = TLS_CLIENT_STATE_FAILED
|
|
447
|
+
this.alert = alert
|
|
448
|
+
return alert
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** The pool of the negotiated hash: SHA-384's for a SHA-384 suite, SHA-256's otherwise. */
|
|
452
|
+
secrets(): TlsSecretPool {
|
|
453
|
+
return this.hashLength === SHA384_SIZE ? this.secrets384 : this.secrets256
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** Whether the handshake has derived secret `index` (`TLS_SECRET_*`). */
|
|
457
|
+
known(index: i32): boolean {
|
|
458
|
+
return index >= 0 && index < 31 && (this.held & (1 << index)) !== 0
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Whether the secret the client *reads* `level` under — the server's, `serverHandshake` or `serverApplication` — is known yet. */
|
|
462
|
+
readKnown(level: i32): boolean {
|
|
463
|
+
if (level === TLS_LEVEL_HANDSHAKE) {
|
|
464
|
+
return this.known(TLS_SECRET_SERVER_HANDSHAKE)
|
|
465
|
+
}
|
|
466
|
+
return level === TLS_LEVEL_APPLICATION && this.known(TLS_SECRET_SERVER_APPLICATION)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Whether the secret the client *writes* `level` under — `clientHandshake` or `clientApplication` — is known yet. */
|
|
470
|
+
writeKnown(level: i32): boolean {
|
|
471
|
+
if (level === TLS_LEVEL_HANDSHAKE) {
|
|
472
|
+
return this.known(TLS_SECRET_CLIENT_HANDSHAKE)
|
|
473
|
+
}
|
|
474
|
+
return level === TLS_LEVEL_APPLICATION && this.known(TLS_SECRET_CLIENT_APPLICATION)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Zeroes secret `index` of `secrets()` once a carrier has taken what it needs from it; it stays known. */
|
|
478
|
+
wipeSecret(index: i32): void {
|
|
479
|
+
this.secrets().zero(index)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Wipes every secret the client holds, both private keys and both pools,
|
|
484
|
+
* and fails it: a wiped client has nothing left to go on with. `fail`
|
|
485
|
+
* calls it, and the carriers when a connection ends; wiped without a
|
|
486
|
+
* failure, it answers `internal_error` from then on.
|
|
487
|
+
*/
|
|
488
|
+
wipe(): void {
|
|
489
|
+
wipe(this.x25519Key)
|
|
490
|
+
wipe(this.p256Key)
|
|
491
|
+
this.secrets256.wipe()
|
|
492
|
+
this.secrets384.wipe()
|
|
493
|
+
this.state = TLS_CLIENT_STATE_FAILED
|
|
494
|
+
if (this.alert === 0) {
|
|
495
|
+
this.alert = TLS_ALERT_INTERNAL_ERROR
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** The level the server's next bytes must arrive at, or -1 when the state expects none. */
|
|
500
|
+
expectedLevel(): i32 {
|
|
501
|
+
switch (this.state) {
|
|
502
|
+
case TLS_CLIENT_STATE_WAIT_SERVER_HELLO:
|
|
503
|
+
return TLS_LEVEL_INITIAL
|
|
504
|
+
case TLS_CLIENT_STATE_WAIT_ENCRYPTED_EXTENSIONS:
|
|
505
|
+
case TLS_CLIENT_STATE_WAIT_CERTIFICATE:
|
|
506
|
+
case TLS_CLIENT_STATE_WAIT_CERTIFICATE_VERIFY:
|
|
507
|
+
case TLS_CLIENT_STATE_WAIT_AUTH:
|
|
508
|
+
case TLS_CLIENT_STATE_WAIT_FINISHED:
|
|
509
|
+
return TLS_LEVEL_HANDSHAKE
|
|
510
|
+
case TLS_CLIENT_STATE_CONNECTED:
|
|
511
|
+
return TLS_LEVEL_APPLICATION
|
|
512
|
+
default:
|
|
513
|
+
return -1
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Makes `input` hold at least `need` bytes, keeping the ones it holds; it doubles, and never grows past one largest message. */
|
|
518
|
+
reserveInput(need: i32): void {
|
|
519
|
+
const capacity: i32 = toI32(this.input.length)
|
|
520
|
+
if (need <= capacity) {
|
|
521
|
+
return
|
|
522
|
+
}
|
|
523
|
+
let size: i32 = capacity
|
|
524
|
+
while (size < need) {
|
|
525
|
+
size = size * 2
|
|
526
|
+
}
|
|
527
|
+
const most: i32 = TLS_MAX_HANDSHAKE_MESSAGE + 4
|
|
528
|
+
const grown: u8[] = new Array<u8>(size > most ? most : size)
|
|
529
|
+
for (let k: i32 = 0; k < this.inputLength && k < toI32(grown.length) && k < capacity; k++) {
|
|
530
|
+
grown[k] = this.input[k]
|
|
531
|
+
}
|
|
532
|
+
this.input = grown
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** Makes the Initial output hold at least `need` bytes, keeping the ones it holds. */
|
|
536
|
+
reserveOutput(need: i32): void {
|
|
537
|
+
const capacity: i32 = toI32(this.outputInitial.length)
|
|
538
|
+
if (need <= capacity) {
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
const grown: u8[] = new Array<u8>(need)
|
|
542
|
+
for (let k: i32 = 0; k < toI32(grown.length) && k < this.outputInitialLength && k < capacity; k++) {
|
|
543
|
+
grown[k] = this.outputInitial[k]
|
|
544
|
+
}
|
|
545
|
+
this.outputInitial = grown
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Takes the handshake bytes `data[off .. off + len)` the server sent at
|
|
550
|
+
* `level`, a whole message, part of one or several, and reads every
|
|
551
|
+
* message they complete. Answers 0, or the alert the client fails with.
|
|
552
|
+
*
|
|
553
|
+
* A level other than the one the state expects is `unexpected_message`, as
|
|
554
|
+
* is a byte left over once a message ends the server's flight at its level
|
|
555
|
+
* (a ServerHello, a HelloRetryRequest or a Finished): RFC 8446 §5.1 forbids
|
|
556
|
+
* a message to straddle a key change. A window outside `data` is
|
|
557
|
+
* `internal_error`. At `TLS_CLIENT_STATE_WAIT_AUTH` the bytes are held
|
|
558
|
+
* until `authenticate`. Once connected, the Application level takes
|
|
559
|
+
* NewSessionTickets, which are checked and dropped; anything else there is
|
|
560
|
+
* `unexpected_message`, KeyUpdate being the record layer's.
|
|
561
|
+
*/
|
|
562
|
+
receive(level: i32, data: u8[], off: i32, len: i32): i32 {
|
|
563
|
+
if (this.state === TLS_CLIENT_STATE_FAILED) {
|
|
564
|
+
return this.alert
|
|
565
|
+
}
|
|
566
|
+
if (off < 0 || len < 0 || off > toI32(data.length) - len) {
|
|
567
|
+
return this.fail(TLS_ALERT_INTERNAL_ERROR)
|
|
568
|
+
}
|
|
569
|
+
if (len === 0) {
|
|
570
|
+
return 0
|
|
571
|
+
}
|
|
572
|
+
if (level !== this.expectedLevel()) {
|
|
573
|
+
return this.fail(TLS_ALERT_UNEXPECTED_MESSAGE)
|
|
574
|
+
}
|
|
575
|
+
const most: i32 = TLS_MAX_HANDSHAKE_MESSAGE + 4
|
|
576
|
+
let taken: i32 = 0
|
|
577
|
+
while (taken < len) {
|
|
578
|
+
// A level's bytes may hold several messages, so they are taken a
|
|
579
|
+
// largest message at a time, each read before the next is copied in.
|
|
580
|
+
if (level !== this.expectedLevel()) {
|
|
581
|
+
return this.fail(TLS_ALERT_UNEXPECTED_MESSAGE)
|
|
582
|
+
}
|
|
583
|
+
const room: i32 = most - this.inputLength
|
|
584
|
+
if (room <= 0) {
|
|
585
|
+
return this.fail(TLS_ALERT_DECODE_ERROR)
|
|
586
|
+
}
|
|
587
|
+
const take: i32 = len - taken < room ? len - taken : room
|
|
588
|
+
this.reserveInput(this.inputLength + take)
|
|
589
|
+
for (
|
|
590
|
+
let k: i32 = 0;
|
|
591
|
+
k < take && off + taken + k < toI32(data.length) && this.inputLength < toI32(this.input.length);
|
|
592
|
+
k++
|
|
593
|
+
) {
|
|
594
|
+
this.input[this.inputLength] = data[off + taken + k]
|
|
595
|
+
this.inputLength = this.inputLength + 1
|
|
596
|
+
}
|
|
597
|
+
taken = taken + take
|
|
598
|
+
const alert: i32 = this.drain()
|
|
599
|
+
if (alert !== 0) {
|
|
600
|
+
return this.fail(alert)
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return 0
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Reads every whole message at the front of `input`, dropping each once
|
|
608
|
+
* read, until a partial one is left or the state waits on the caller.
|
|
609
|
+
* Answers 0 or the alert.
|
|
610
|
+
*/
|
|
611
|
+
drain(): i32 {
|
|
612
|
+
while (this.state !== TLS_CLIENT_STATE_WAIT_AUTH && this.inputLength >= 4) {
|
|
613
|
+
const length: i32 = (toI32(this.input[1]) << 16) | (toI32(this.input[2]) << 8) | toI32(this.input[3])
|
|
614
|
+
if (length > TLS_MAX_HANDSHAKE_MESSAGE) {
|
|
615
|
+
return TLS_ALERT_DECODE_ERROR
|
|
616
|
+
}
|
|
617
|
+
const whole: i32 = 4 + length
|
|
618
|
+
if (this.inputLength < whole) {
|
|
619
|
+
return 0
|
|
620
|
+
}
|
|
621
|
+
const type: i32 = toI32(this.input[0])
|
|
622
|
+
const alert: i32 = this.handleMessage(type, length)
|
|
623
|
+
if (alert !== 0) {
|
|
624
|
+
return alert
|
|
625
|
+
}
|
|
626
|
+
this.consume(whole)
|
|
627
|
+
const endsFlight: boolean = type === TLS_HANDSHAKE_SERVER_HELLO || type === TLS_HANDSHAKE_FINISHED
|
|
628
|
+
if (endsFlight && this.inputLength > 0) {
|
|
629
|
+
return TLS_ALERT_UNEXPECTED_MESSAGE
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return 0
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Drops the first `count` bytes of `input`, moving the rest to its front. */
|
|
636
|
+
consume(count: i32): void {
|
|
637
|
+
const rest: i32 = this.inputLength - count
|
|
638
|
+
for (let k: i32 = 0; k < rest && count + k < toI32(this.input.length); k++) {
|
|
639
|
+
this.input[k] = this.input[count + k]
|
|
640
|
+
}
|
|
641
|
+
this.inputLength = rest
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** Dispatches one whole message, `input[0 .. 4 + length)`, of type `type`, by state. */
|
|
645
|
+
handleMessage(type: i32, length: i32): i32 {
|
|
646
|
+
switch (this.state) {
|
|
647
|
+
case TLS_CLIENT_STATE_WAIT_SERVER_HELLO:
|
|
648
|
+
return type === TLS_HANDSHAKE_SERVER_HELLO
|
|
649
|
+
? this.handleServerHello(length)
|
|
650
|
+
: TLS_ALERT_UNEXPECTED_MESSAGE
|
|
651
|
+
case TLS_CLIENT_STATE_WAIT_ENCRYPTED_EXTENSIONS:
|
|
652
|
+
return type === TLS_HANDSHAKE_ENCRYPTED_EXTENSIONS
|
|
653
|
+
? this.handleEncryptedExtensions(length)
|
|
654
|
+
: TLS_ALERT_UNEXPECTED_MESSAGE
|
|
655
|
+
case TLS_CLIENT_STATE_WAIT_CERTIFICATE:
|
|
656
|
+
return type === TLS_HANDSHAKE_CERTIFICATE
|
|
657
|
+
? this.handleCertificate(length)
|
|
658
|
+
: TLS_ALERT_UNEXPECTED_MESSAGE
|
|
659
|
+
case TLS_CLIENT_STATE_WAIT_CERTIFICATE_VERIFY:
|
|
660
|
+
return type === TLS_HANDSHAKE_CERTIFICATE_VERIFY
|
|
661
|
+
? this.handleCertificateVerify(length)
|
|
662
|
+
: TLS_ALERT_UNEXPECTED_MESSAGE
|
|
663
|
+
case TLS_CLIENT_STATE_WAIT_FINISHED:
|
|
664
|
+
return type === TLS_HANDSHAKE_FINISHED ? this.handleFinished(length) : TLS_ALERT_UNEXPECTED_MESSAGE
|
|
665
|
+
case TLS_CLIENT_STATE_CONNECTED:
|
|
666
|
+
return type === TLS_HANDSHAKE_NEW_SESSION_TICKET
|
|
667
|
+
? this.handleTicket(length)
|
|
668
|
+
: TLS_ALERT_UNEXPECTED_MESSAGE
|
|
669
|
+
default:
|
|
670
|
+
return TLS_ALERT_UNEXPECTED_MESSAGE
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* The checks a ServerHello and a HelloRetryRequest share, in the order
|
|
676
|
+
* that names a TLS 1.2 server for its version: the structure, then
|
|
677
|
+
* `supported_versions` and the downgrade sentinel (§4.1.3), then the
|
|
678
|
+
* extensions, the echoed session id, null compression and an offered
|
|
679
|
+
* suite. Answers 0 or the alert.
|
|
680
|
+
*/
|
|
681
|
+
checkHello(view: TlsServerFlightView): i32 {
|
|
682
|
+
if (view.alert !== 0) {
|
|
683
|
+
return view.alert
|
|
684
|
+
}
|
|
685
|
+
if (!view.hasSupportedVersions) {
|
|
686
|
+
return !view.isRetry && tlsServerHelloDowngrade(view)
|
|
687
|
+
? TLS_ALERT_ILLEGAL_PARAMETER
|
|
688
|
+
: TLS_ALERT_PROTOCOL_VERSION
|
|
689
|
+
}
|
|
690
|
+
if (view.selectedVersion !== TLS_VERSION_13 || view.legacyVersion !== TLS_LEGACY_VERSION) {
|
|
691
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
692
|
+
}
|
|
693
|
+
if (view.extensionAlert !== 0) {
|
|
694
|
+
return view.extensionAlert
|
|
695
|
+
}
|
|
696
|
+
if (
|
|
697
|
+
view.sessionIdLength !== TLS_CLIENT_SESSION_ID_SIZE ||
|
|
698
|
+
!timingSafeEqualAt(
|
|
699
|
+
view.data,
|
|
700
|
+
view.sessionIdAt,
|
|
701
|
+
this.sessionId,
|
|
702
|
+
TLS_CLIENT_FROM,
|
|
703
|
+
TLS_CLIENT_SESSION_ID_SIZE
|
|
704
|
+
) ||
|
|
705
|
+
view.compression !== 0
|
|
706
|
+
) {
|
|
707
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
708
|
+
}
|
|
709
|
+
if (this.config.suites.indexOf(view.suite) < 0) {
|
|
710
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
711
|
+
}
|
|
712
|
+
// §4.1.4: the ServerHello after a HelloRetryRequest keeps its suite.
|
|
713
|
+
if (this.retried && view.suite !== this.suite) {
|
|
714
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
715
|
+
}
|
|
716
|
+
return 0
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** A ServerHello or HelloRetryRequest, `input[0 .. 4 + length)`. */
|
|
720
|
+
handleServerHello(length: i32): i32 {
|
|
721
|
+
const view: TlsServerFlightView = this.view
|
|
722
|
+
tlsReadServerHello(view, this.input, 4, length)
|
|
723
|
+
if (view.isRetry && this.retried) {
|
|
724
|
+
// §4.1.4: a second HelloRetryRequest.
|
|
725
|
+
return TLS_ALERT_UNEXPECTED_MESSAGE
|
|
726
|
+
}
|
|
727
|
+
const checked: i32 = this.checkHello(view)
|
|
728
|
+
if (checked !== 0) {
|
|
729
|
+
return checked
|
|
730
|
+
}
|
|
731
|
+
if (view.isRetry) {
|
|
732
|
+
return this.handleRetry(length)
|
|
733
|
+
}
|
|
734
|
+
if (!view.hasKeyShare) {
|
|
735
|
+
return TLS_ALERT_MISSING_EXTENSION
|
|
736
|
+
}
|
|
737
|
+
if (view.shareGroup !== this.shareGroup) {
|
|
738
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
739
|
+
}
|
|
740
|
+
const shareSize: i32 = this.shareGroup === TLS_GROUP_X25519 ? TLS_X25519_SHARE_SIZE : TLS_P256_SHARE_SIZE
|
|
741
|
+
if (view.shareLength !== shareSize) {
|
|
742
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
743
|
+
}
|
|
744
|
+
if (!this.retried) {
|
|
745
|
+
this.startTranscript(view.suite)
|
|
746
|
+
}
|
|
747
|
+
this.transcript.update(this.input, TLS_CLIENT_FROM, 4 + length)
|
|
748
|
+
const alert: i32 = this.exchangeKeys()
|
|
749
|
+
if (alert !== 0) {
|
|
750
|
+
return alert
|
|
751
|
+
}
|
|
752
|
+
const pool: TlsSecretPool = this.secrets()
|
|
753
|
+
const h: i32 = this.hashLength
|
|
754
|
+
this.transcript.hashInto(this.hashes, TLS_CLIENT_FROM)
|
|
755
|
+
tlsDeriveSecretHeld(h, pool.handshake, "c hs traffic", this.hashes, TLS_CLIENT_FROM, pool.clientHandshake)
|
|
756
|
+
tlsDeriveSecretHeld(h, pool.handshake, "s hs traffic", this.hashes, TLS_CLIENT_FROM, pool.serverHandshake)
|
|
757
|
+
this.held =
|
|
758
|
+
this.held |
|
|
759
|
+
(1 << TLS_SECRET_HANDSHAKE) |
|
|
760
|
+
(1 << TLS_SECRET_CLIENT_HANDSHAKE) |
|
|
761
|
+
(1 << TLS_SECRET_SERVER_HANDSHAKE)
|
|
762
|
+
this.state = TLS_CLIENT_STATE_WAIT_ENCRYPTED_EXTENSIONS
|
|
763
|
+
return 0
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/** Starts the transcript over `suite`'s hash, from the first ClientHello. */
|
|
767
|
+
startTranscript(suite: i32): void {
|
|
768
|
+
this.suite = suite
|
|
769
|
+
this.hashLength = tlsSuiteHashLength(suite)
|
|
770
|
+
this.transcript.reset(this.hashLength)
|
|
771
|
+
this.transcript.update(this.firstHello, TLS_CLIENT_FROM, toI32(this.firstHello.length))
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* A HelloRetryRequest (§4.1.4), checked by `checkHello` already: it must
|
|
776
|
+
* name a group the client offered and did not send a share for, or carry
|
|
777
|
+
* a cookie, and changes the hello. The transcript restarts from
|
|
778
|
+
* `message_hash` (§4.4.1), and the second ClientHello carries a share of
|
|
779
|
+
* the new group and echoes the cookie. The key of the group not picked is
|
|
780
|
+
* wiped now.
|
|
781
|
+
*/
|
|
782
|
+
handleRetry(length: i32): i32 {
|
|
783
|
+
const view: TlsServerFlightView = this.view
|
|
784
|
+
if (!view.hasKeyShare && !view.hasCookie) {
|
|
785
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
786
|
+
}
|
|
787
|
+
if (
|
|
788
|
+
view.hasKeyShare &&
|
|
789
|
+
(this.config.groups.indexOf(view.shareGroup) < 0 || view.shareGroup === this.shareGroup)
|
|
790
|
+
) {
|
|
791
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
792
|
+
}
|
|
793
|
+
this.retried = true
|
|
794
|
+
this.startTranscript(view.suite)
|
|
795
|
+
this.transcript.restartWithMessageHash()
|
|
796
|
+
this.transcript.update(this.input, TLS_CLIENT_FROM, 4 + length)
|
|
797
|
+
if (view.hasCookie) {
|
|
798
|
+
this.cookie = tlsCopyWindow(view.data, view.cookieAt, view.cookieLength)
|
|
799
|
+
}
|
|
800
|
+
if (view.hasKeyShare) {
|
|
801
|
+
this.shareGroup = view.shareGroup
|
|
802
|
+
}
|
|
803
|
+
this.wipeUnpicked()
|
|
804
|
+
// The first hello is not needed again; the second goes out after it.
|
|
805
|
+
this.firstHello = []
|
|
806
|
+
if (this.writeHello() < 0) {
|
|
807
|
+
return TLS_ALERT_INTERNAL_ERROR
|
|
808
|
+
}
|
|
809
|
+
return 0
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/** Zeroes both private keys, once the exchange is done or cannot be. */
|
|
813
|
+
wipeKeys(): void {
|
|
814
|
+
exposeWith(this.x25519Key, TLS_CLIENT_FROM, tlsZeroHeld)
|
|
815
|
+
exposeWith(this.p256Key, TLS_CLIENT_FROM, tlsZeroHeld)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/** Zeroes the private key of the group the client's share is not of. */
|
|
819
|
+
wipeUnpicked(): void {
|
|
820
|
+
if (this.shareGroup === TLS_GROUP_X25519) {
|
|
821
|
+
exposeWith(this.p256Key, TLS_CLIENT_FROM, tlsZeroHeld)
|
|
822
|
+
} else {
|
|
823
|
+
exposeWith(this.x25519Key, TLS_CLIENT_FROM, tlsZeroHeld)
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* The ECDHE exchange with the server's share, `input[view.shareAt ..)`, and
|
|
829
|
+
* the handshake secret from it. Both private keys are wiped on every path,
|
|
830
|
+
* and the shared and early secrets before it returns. An all-zero x25519
|
|
831
|
+
* secret is `illegal_parameter` (RFC 7748 §6.1, RFC 8446 §7.4.2), and so is
|
|
832
|
+
* a secp256r1 share that is not an uncompressed point on the curve
|
|
833
|
+
* (§4.2.8.2); its shared secret is the product's x, which may be zero.
|
|
834
|
+
*/
|
|
835
|
+
exchangeKeys(): i32 {
|
|
836
|
+
const view: TlsServerFlightView = this.view
|
|
837
|
+
const peer: u8[] = tlsCopyWindow(view.data, view.shareAt, view.shareLength)
|
|
838
|
+
const isX25519: boolean = this.shareGroup === TLS_GROUP_X25519
|
|
839
|
+
const shared: Secret<u8[]> | null = isX25519 ? x25519(this.x25519Key, peer) : p256Ecdh(this.p256Key, peer)
|
|
840
|
+
this.wipeKeys()
|
|
841
|
+
if (shared === null) {
|
|
842
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
843
|
+
}
|
|
844
|
+
if (isX25519 && expose(shared, tlsIsZeroSecret)) {
|
|
845
|
+
wipe(shared)
|
|
846
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
847
|
+
}
|
|
848
|
+
const h: i32 = this.hashLength
|
|
849
|
+
const early: Secret<u8[]> = tlsEarlySecret(h)
|
|
850
|
+
tlsHandshakeSecretHeld(h, early, shared, this.secrets().handshake)
|
|
851
|
+
wipe(early)
|
|
852
|
+
wipe(shared)
|
|
853
|
+
return 0
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/** EncryptedExtensions (§4.3.1): ALPN, if any, must be one the client offered, and `server_name` acknowledged only if sent. */
|
|
857
|
+
handleEncryptedExtensions(length: i32): i32 {
|
|
858
|
+
const view: TlsServerFlightView = this.view
|
|
859
|
+
tlsReadEncryptedExtensions(view, this.input, 4, length)
|
|
860
|
+
if (view.alert !== 0) {
|
|
861
|
+
return view.alert
|
|
862
|
+
}
|
|
863
|
+
if (view.extensionAlert !== 0) {
|
|
864
|
+
return view.extensionAlert
|
|
865
|
+
}
|
|
866
|
+
if (view.serverNameAcknowledged && !this.sentServerName) {
|
|
867
|
+
return TLS_ALERT_UNSUPPORTED_EXTENSION
|
|
868
|
+
}
|
|
869
|
+
if (view.hasAlpn) {
|
|
870
|
+
const chosen: string = tlsWindowString(view.data, view.alpnAt, view.alpnLength)
|
|
871
|
+
if (toI32(this.config.alpn.length) === 0) {
|
|
872
|
+
// An extension the client never sent (§4.2).
|
|
873
|
+
return TLS_ALERT_UNSUPPORTED_EXTENSION
|
|
874
|
+
}
|
|
875
|
+
if (this.config.alpn.indexOf(chosen) < 0) {
|
|
876
|
+
// RFC 7301 §3.2 leaves the alert open; a protocol never offered is
|
|
877
|
+
// a value the client cannot accept.
|
|
878
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
879
|
+
}
|
|
880
|
+
this.alpnChosen = chosen
|
|
881
|
+
}
|
|
882
|
+
this.transcript.update(this.input, TLS_CLIENT_FROM, 4 + length)
|
|
883
|
+
this.state = TLS_CLIENT_STATE_WAIT_CERTIFICATE
|
|
884
|
+
return 0
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/** The server's Certificate (§4.4.2): its request context must be empty. The chain is copied out, and what CertificateVerify signs computed. */
|
|
888
|
+
handleCertificate(length: i32): i32 {
|
|
889
|
+
const view: TlsServerFlightView = this.view
|
|
890
|
+
tlsReadCertificate(view, this.input, 4, length)
|
|
891
|
+
if (view.alert !== 0) {
|
|
892
|
+
return view.alert
|
|
893
|
+
}
|
|
894
|
+
if (view.extensionAlert !== 0) {
|
|
895
|
+
return view.extensionAlert
|
|
896
|
+
}
|
|
897
|
+
if (view.contextLength !== 0) {
|
|
898
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
899
|
+
}
|
|
900
|
+
this.certificates = tlsCopyCertificates(view)
|
|
901
|
+
this.transcript.update(this.input, TLS_CLIENT_FROM, 4 + length)
|
|
902
|
+
this.content = tlsCertificateVerifyContent(this.transcript.hash())
|
|
903
|
+
this.state = TLS_CLIENT_STATE_WAIT_CERTIFICATE_VERIFY
|
|
904
|
+
return 0
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/** CertificateVerify (§4.4.3): a scheme the client offered. The signature is kept, unchecked, for the caller; the client then waits on it. */
|
|
908
|
+
handleCertificateVerify(length: i32): i32 {
|
|
909
|
+
const view: TlsServerFlightView = this.view
|
|
910
|
+
tlsReadCertificateVerify(view, this.input, 4, length)
|
|
911
|
+
if (view.alert !== 0) {
|
|
912
|
+
return view.alert
|
|
913
|
+
}
|
|
914
|
+
if (!tlsClientOffersScheme(view.scheme)) {
|
|
915
|
+
return TLS_ALERT_ILLEGAL_PARAMETER
|
|
916
|
+
}
|
|
917
|
+
this.scheme = view.scheme
|
|
918
|
+
this.signature = tlsCopyWindow(view.data, view.signatureAt, view.signatureLength)
|
|
919
|
+
this.transcript.update(this.input, TLS_CLIENT_FROM, 4 + length)
|
|
920
|
+
this.state = TLS_CLIENT_STATE_WAIT_AUTH
|
|
921
|
+
return 0
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* The server's Finished (§4.4.4), checked with `timingSafeEqualAt` against the one
|
|
926
|
+
* its handshake secret gives (`decrypt_error` if it differs). Then the
|
|
927
|
+
* master secret, the application and exporter secrets, and the client's
|
|
928
|
+
* Finished at the Handshake level; the handshake secret is wiped.
|
|
929
|
+
*/
|
|
930
|
+
handleFinished(length: i32): i32 {
|
|
931
|
+
const h: i32 = this.hashLength
|
|
932
|
+
if (length !== h) {
|
|
933
|
+
return TLS_ALERT_DECODE_ERROR
|
|
934
|
+
}
|
|
935
|
+
const pool: TlsSecretPool = this.secrets()
|
|
936
|
+
this.transcript.hashInto(this.hashes, TLS_CLIENT_FROM)
|
|
937
|
+
const expected: u8[] = tlsFinishedVerifyDataHeld(h, pool.serverHandshake, this.hashes, TLS_CLIENT_FROM)
|
|
938
|
+
const matches: boolean = timingSafeEqualAt(this.input, 4, expected, TLS_CLIENT_FROM, h)
|
|
939
|
+
secureZero(expected)
|
|
940
|
+
if (!matches) {
|
|
941
|
+
return TLS_ALERT_DECRYPT_ERROR
|
|
942
|
+
}
|
|
943
|
+
this.transcript.update(this.input, TLS_CLIENT_FROM, 4 + length)
|
|
944
|
+
this.transcript.hashInto(this.hashes, TLS_CLIENT_FROM)
|
|
945
|
+
// The master secret is derived over the handshake secret, which has no
|
|
946
|
+
// other use left.
|
|
947
|
+
tlsMasterSecretHeld(h, pool.handshake, pool.handshake)
|
|
948
|
+
tlsDeriveSecretHeld(
|
|
949
|
+
h,
|
|
950
|
+
pool.handshake,
|
|
951
|
+
"c ap traffic",
|
|
952
|
+
this.hashes,
|
|
953
|
+
TLS_CLIENT_FROM,
|
|
954
|
+
pool.clientApplication
|
|
955
|
+
)
|
|
956
|
+
tlsDeriveSecretHeld(
|
|
957
|
+
h,
|
|
958
|
+
pool.handshake,
|
|
959
|
+
"s ap traffic",
|
|
960
|
+
this.hashes,
|
|
961
|
+
TLS_CLIENT_FROM,
|
|
962
|
+
pool.serverApplication
|
|
963
|
+
)
|
|
964
|
+
tlsDeriveSecretHeld(h, pool.handshake, "exp master", this.hashes, TLS_CLIENT_FROM, pool.exporter)
|
|
965
|
+
pool.zero(TLS_SECRET_HANDSHAKE)
|
|
966
|
+
const verifyData: u8[] = tlsFinishedVerifyDataHeld(h, pool.clientHandshake, this.hashes, TLS_CLIENT_FROM)
|
|
967
|
+
const at: i32 = this.outputHandshakeLength
|
|
968
|
+
const end: i32 = tlsWriteFinished(this.outputHandshake, at, verifyData, TLS_CLIENT_FROM, h)
|
|
969
|
+
secureZero(verifyData)
|
|
970
|
+
this.sent(TLS_LEVEL_HANDSHAKE, at, end)
|
|
971
|
+
this.held =
|
|
972
|
+
(this.held |
|
|
973
|
+
(1 << TLS_SECRET_CLIENT_APPLICATION) |
|
|
974
|
+
(1 << TLS_SECRET_SERVER_APPLICATION) |
|
|
975
|
+
(1 << TLS_SECRET_EXPORTER)) &
|
|
976
|
+
~(1 << TLS_SECRET_HANDSHAKE)
|
|
977
|
+
this.state = TLS_CLIENT_STATE_CONNECTED
|
|
978
|
+
return 0
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/** A NewSessionTicket (§4.6.1) once connected: checked for its structure and dropped, since this client does not resume. */
|
|
982
|
+
handleTicket(length: i32): i32 {
|
|
983
|
+
tlsReadNewSessionTicket(this.view, this.input, 4, length)
|
|
984
|
+
return this.view.alert
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/** The server's DER certificates, leaf first; empty before its Certificate is read. */
|
|
988
|
+
peerCertificates(): u8[][] {
|
|
989
|
+
return this.certificates
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/** The CertificateVerify scheme (`TLS_SIGNATURE_*`); 0 before it is read. */
|
|
993
|
+
verifyScheme(): i32 {
|
|
994
|
+
return this.scheme
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/** What the CertificateVerify signature is over (§4.4.3): 64 spaces, the server's context string, a zero byte and the transcript hash. */
|
|
998
|
+
verifyContent(): u8[] {
|
|
999
|
+
return this.content
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/** The CertificateVerify signature as sent: DER for ECDSA, raw for RSA-PSS. */
|
|
1003
|
+
verifySignature(): u8[] {
|
|
1004
|
+
return this.signature
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/** The ALPN protocol the server chose, or empty when it chose none. */
|
|
1008
|
+
alpn(): string {
|
|
1009
|
+
return this.alpnChosen
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* The caller's verdict on the server's chain and signature, at
|
|
1014
|
+
* `TLS_CLIENT_STATE_WAIT_AUTH`: `true` goes on to the server's Finished,
|
|
1015
|
+
* reading what was held meanwhile; `false` is `reject(bad_certificate)`.
|
|
1016
|
+
* Answers 0 or the alert; in any other state the client fails with
|
|
1017
|
+
* `internal_error`.
|
|
1018
|
+
*/
|
|
1019
|
+
authenticate(ok: boolean): i32 {
|
|
1020
|
+
if (!ok) {
|
|
1021
|
+
return this.reject(TLS_ALERT_BAD_CERTIFICATE)
|
|
1022
|
+
}
|
|
1023
|
+
if (this.state === TLS_CLIENT_STATE_FAILED) {
|
|
1024
|
+
return this.alert
|
|
1025
|
+
}
|
|
1026
|
+
if (this.state !== TLS_CLIENT_STATE_WAIT_AUTH) {
|
|
1027
|
+
return this.fail(TLS_ALERT_INTERNAL_ERROR)
|
|
1028
|
+
}
|
|
1029
|
+
this.state = TLS_CLIENT_STATE_WAIT_FINISHED
|
|
1030
|
+
const alert: i32 = this.drain()
|
|
1031
|
+
if (alert !== 0) {
|
|
1032
|
+
return this.fail(alert)
|
|
1033
|
+
}
|
|
1034
|
+
return 0
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Refuses the server at `TLS_CLIENT_STATE_WAIT_AUTH` with `alert`, the
|
|
1039
|
+
* caller's reason (`bad_certificate`, `certificate_expired`,
|
|
1040
|
+
* `unknown_ca`, `decrypt_error`…): the client fails with it and answers it.
|
|
1041
|
+
* An alert outside 1 to 255 is taken as `bad_certificate`. In any other
|
|
1042
|
+
* state the client fails with `internal_error`.
|
|
1043
|
+
*/
|
|
1044
|
+
reject(alert: i32): i32 {
|
|
1045
|
+
if (this.state === TLS_CLIENT_STATE_FAILED) {
|
|
1046
|
+
return this.alert
|
|
1047
|
+
}
|
|
1048
|
+
if (this.state !== TLS_CLIENT_STATE_WAIT_AUTH) {
|
|
1049
|
+
return this.fail(TLS_ALERT_INTERNAL_ERROR)
|
|
1050
|
+
}
|
|
1051
|
+
return this.fail(alert >= 1 && alert <= 255 ? alert : TLS_ALERT_BAD_CERTIFICATE)
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/** A message the client wrote at `level`, `output[at .. end)`, goes into the transcript once its hash is known and stays on the output. */
|
|
1055
|
+
sent(level: i32, at: i32, end: i32): void {
|
|
1056
|
+
if (level === TLS_LEVEL_INITIAL) {
|
|
1057
|
+
if (this.hashLength !== 0) {
|
|
1058
|
+
this.transcript.update(this.outputInitial, at, end - at)
|
|
1059
|
+
}
|
|
1060
|
+
this.outputInitialLength = end
|
|
1061
|
+
} else {
|
|
1062
|
+
this.transcript.update(this.outputHandshake, at, end - at)
|
|
1063
|
+
this.outputHandshakeLength = end
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/** The handshake bytes written at `level` since the last call, in a fresh array, and forgets them. */
|
|
1068
|
+
takeOutput(level: i32): u8[] {
|
|
1069
|
+
const none: u8[] = []
|
|
1070
|
+
switch (level) {
|
|
1071
|
+
case TLS_LEVEL_INITIAL: {
|
|
1072
|
+
const out: u8[] = tlsCopyWindow(this.outputInitial, TLS_CLIENT_FROM, this.outputInitialLength)
|
|
1073
|
+
this.outputInitialLength = 0
|
|
1074
|
+
return out
|
|
1075
|
+
}
|
|
1076
|
+
case TLS_LEVEL_HANDSHAKE: {
|
|
1077
|
+
const out: u8[] = tlsCopyWindow(this.outputHandshake, TLS_CLIENT_FROM, this.outputHandshakeLength)
|
|
1078
|
+
this.outputHandshakeLength = 0
|
|
1079
|
+
return out
|
|
1080
|
+
}
|
|
1081
|
+
default:
|
|
1082
|
+
return none
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/** Forgets the handshake bytes written at `level`, which a carrier has read out of the buffer itself. */
|
|
1087
|
+
clearOutput(level: i32): void {
|
|
1088
|
+
if (level === TLS_LEVEL_INITIAL) {
|
|
1089
|
+
this.outputInitialLength = 0
|
|
1090
|
+
} else if (level === TLS_LEVEL_HANDSHAKE) {
|
|
1091
|
+
this.outputHandshakeLength = 0
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|