@amritk/nish-x86_64-linux 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +30 -7
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-wasm.c +1 -0
- package/runtime/runtime.c +46 -8
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +166 -35
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +148 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +905 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +392 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +653 -0
- package/std/net/quic-packet.ts +1474 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1603 -0
- package/std/net/quic.ts +3214 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +410 -0
- package/std/net/tls.ts +1368 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/tls/codec` — the TLS 1.3 handshake messages a server reads and
|
|
3
|
+
* writes (RFC 8446 §4), as bytes.
|
|
4
|
+
*
|
|
5
|
+
* One reader and six writers. `tlsParseClientHello` reads a ClientHello body
|
|
6
|
+
* into a `TlsClientHello`, checking its structure: every length against the
|
|
7
|
+
* bytes it claims, every vector against its RFC 8446 bounds, and no extension
|
|
8
|
+
* twice (§4.2). A message that breaks one answers the alert a server sends for
|
|
9
|
+
* it in the hello's `alert` field (`decode_error` for a malformed or truncated
|
|
10
|
+
* one, `illegal_parameter` for a duplicate), never a panic, since every byte
|
|
11
|
+
* of it is the peer's. What the fields *mean* — which version, which suite,
|
|
12
|
+
* whether a share is missing — is the server's to decide, in `nish/net/tls`.
|
|
13
|
+
*
|
|
14
|
+
* `tlsReadClientHello` is the same reader for a server that keeps its state
|
|
15
|
+
* in a slot: it reads into a `TlsClientHelloView` made once, whose fields are
|
|
16
|
+
* windows into the message rather than copies of it, so a ClientHello read
|
|
17
|
+
* allocates nothing, and `tlsParseClientHello` copies a view's windows out.
|
|
18
|
+
*
|
|
19
|
+
* The writers each answer one whole handshake message, header included:
|
|
20
|
+
* ServerHello, HelloRetryRequest, EncryptedExtensions, Certificate,
|
|
21
|
+
* CertificateVerify and Finished. Their inputs come from the server and the
|
|
22
|
+
* caller's configuration rather than from the peer, so they check nothing.
|
|
23
|
+
* Each is a `tlsWrite*` function that writes into the caller's array at an
|
|
24
|
+
* offset — run over an empty array, it measures — and a `tlsEncode*` one
|
|
25
|
+
* that answers the message in a fresh array.
|
|
26
|
+
*
|
|
27
|
+
* The extensions read are `server_name` (RFC 6066 §3), `supported_groups`,
|
|
28
|
+
* `signature_algorithms`, `supported_versions` and `key_share` (RFC 8446
|
|
29
|
+
* §4.2), `application_layer_protocol_negotiation` (RFC 7301 §3.1) and
|
|
30
|
+
* `quic_transport_parameters` (RFC 9001 §8.2). Every other extension is
|
|
31
|
+
* skipped, which is what RFC 8446 §4.2 asks of a server that does not know it.
|
|
32
|
+
*
|
|
33
|
+
* Written from the RFCs, not ported from another implementation. Private
|
|
34
|
+
* names carry the `tls` prefix because a `std/` module's private functions
|
|
35
|
+
* share the importing program's flat symbol namespace (`docs/wp26-stdlib.md`
|
|
36
|
+
* §3e).
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
// ---- Handshake message types (RFC 8446 §4) --------------------------------
|
|
40
|
+
|
|
41
|
+
export const TLS_HANDSHAKE_CLIENT_HELLO: i32 = 1
|
|
42
|
+
export const TLS_HANDSHAKE_SERVER_HELLO: i32 = 2
|
|
43
|
+
export const TLS_HANDSHAKE_ENCRYPTED_EXTENSIONS: i32 = 8
|
|
44
|
+
export const TLS_HANDSHAKE_CERTIFICATE: i32 = 11
|
|
45
|
+
export const TLS_HANDSHAKE_CERTIFICATE_VERIFY: i32 = 15
|
|
46
|
+
export const TLS_HANDSHAKE_FINISHED: i32 = 20
|
|
47
|
+
/** The synthetic message a HelloRetryRequest puts in the transcript in place of the first ClientHello (§4.4.1). */
|
|
48
|
+
export const TLS_HANDSHAKE_MESSAGE_HASH: i32 = 254
|
|
49
|
+
|
|
50
|
+
// ---- Extension types (RFC 8446 §4.2, RFC 7301, RFC 9001) ------------------
|
|
51
|
+
|
|
52
|
+
export const TLS_EXT_SERVER_NAME: i32 = 0
|
|
53
|
+
/** `pre_shared_key` (§4.2.11): ignored here, but it must be the last extension. */
|
|
54
|
+
export const TLS_EXT_PRE_SHARED_KEY: i32 = 41
|
|
55
|
+
export const TLS_EXT_SUPPORTED_GROUPS: i32 = 10
|
|
56
|
+
export const TLS_EXT_SIGNATURE_ALGORITHMS: i32 = 13
|
|
57
|
+
export const TLS_EXT_ALPN: i32 = 16
|
|
58
|
+
export const TLS_EXT_SUPPORTED_VERSIONS: i32 = 43
|
|
59
|
+
export const TLS_EXT_KEY_SHARE: i32 = 51
|
|
60
|
+
export const TLS_EXT_QUIC_TRANSPORT_PARAMETERS: i32 = 57
|
|
61
|
+
|
|
62
|
+
// ---- Versions, groups and signature schemes -------------------------------
|
|
63
|
+
|
|
64
|
+
/** `legacy_version` in every TLS 1.3 hello (§4.1.2). */
|
|
65
|
+
export const TLS_LEGACY_VERSION: i32 = 0x0303
|
|
66
|
+
/** TLS 1.3 in `supported_versions` (§4.2.1). */
|
|
67
|
+
export const TLS_VERSION_13: i32 = 0x0304
|
|
68
|
+
/** x25519 (§4.2.7), the one group `nish/net/tls` agrees to. */
|
|
69
|
+
export const TLS_GROUP_X25519: i32 = 0x001d
|
|
70
|
+
/** The length of an x25519 key share (RFC 8446 §4.2.8.2). */
|
|
71
|
+
export const TLS_X25519_SHARE_SIZE: i32 = 32
|
|
72
|
+
/** `ecdsa_secp256r1_sha256` (§4.2.3), what a production server signs with. */
|
|
73
|
+
export const TLS_SIGNATURE_ECDSA_SECP256R1_SHA256: i32 = 0x0403
|
|
74
|
+
/** `rsa_pss_rsae_sha256` (§4.2.3), what RFC 8448's server signs with. */
|
|
75
|
+
export const TLS_SIGNATURE_RSA_PSS_RSAE_SHA256: i32 = 0x0804
|
|
76
|
+
|
|
77
|
+
// ---- Alert descriptions (RFC 8446 §6) --------------------------------------
|
|
78
|
+
|
|
79
|
+
/** A message arrived that the handshake's state does not allow. */
|
|
80
|
+
export const TLS_ALERT_UNEXPECTED_MESSAGE: i32 = 10
|
|
81
|
+
/** No acceptable set of parameters: no common suite, group or signature scheme. */
|
|
82
|
+
export const TLS_ALERT_HANDSHAKE_FAILURE: i32 = 40
|
|
83
|
+
/** A field well formed but wrong: a duplicate, a bad share, a second retry without the share. */
|
|
84
|
+
export const TLS_ALERT_ILLEGAL_PARAMETER: i32 = 47
|
|
85
|
+
/** A message that does not parse: a length past its bytes, a vector out of its bounds, trailing bytes. */
|
|
86
|
+
export const TLS_ALERT_DECODE_ERROR: i32 = 50
|
|
87
|
+
/** A Finished that does not verify. */
|
|
88
|
+
export const TLS_ALERT_DECRYPT_ERROR: i32 = 51
|
|
89
|
+
/** A peer that does not offer TLS 1.3. */
|
|
90
|
+
export const TLS_ALERT_PROTOCOL_VERSION: i32 = 70
|
|
91
|
+
/** A failure of this side's own: a bad configuration, randomness or signature, or a call out of order. */
|
|
92
|
+
export const TLS_ALERT_INTERNAL_ERROR: i32 = 80
|
|
93
|
+
/** An extension the handshake needs is absent (§9.2, RFC 9001 §8.2). */
|
|
94
|
+
export const TLS_ALERT_MISSING_EXTENSION: i32 = 109
|
|
95
|
+
/** ALPN offered no protocol the server accepts (RFC 7301 §3.2, RFC 9001 §8.1). */
|
|
96
|
+
export const TLS_ALERT_NO_APPLICATION_PROTOCOL: i32 = 120
|
|
97
|
+
|
|
98
|
+
/** The size of a hello's `random` (§4.1.2). */
|
|
99
|
+
export const TLS_RANDOM_SIZE: i32 = 32
|
|
100
|
+
|
|
101
|
+
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
102
|
+
const TLS_CODEC_FROM: i32 = 0
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A cursor over `data[at .. end)` for the `tlsRead*` functions below, which
|
|
106
|
+
* read big-endian integers and the length prefixes of TLS vectors (RFC 8446
|
|
107
|
+
* §3.4). They are functions rather than methods so that a literal argument
|
|
108
|
+
* takes its parameter's `i32` under `--number-mode f64`.
|
|
109
|
+
*
|
|
110
|
+
* A read past `end`, or a vector whose length breaks its bounds, sets
|
|
111
|
+
* `failed` and answers 0, and every later read then fails too. So a parser
|
|
112
|
+
* can read a whole structure and test `failed` once, and a truncated message
|
|
113
|
+
* cannot read a byte that is not its own: `end` never passes the window the
|
|
114
|
+
* reader was opened on.
|
|
115
|
+
*/
|
|
116
|
+
class TlsReader {
|
|
117
|
+
data: u8[]
|
|
118
|
+
at: i32
|
|
119
|
+
end: i32
|
|
120
|
+
failed: boolean = false
|
|
121
|
+
|
|
122
|
+
constructor(data: u8[], at: i32, end: i32) {
|
|
123
|
+
this.data = data
|
|
124
|
+
this.at = at
|
|
125
|
+
this.end = end
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The next byte, or 0 and a failed reader at the end of the window. */
|
|
130
|
+
const tlsReadU8 = (r: TlsReader): i32 => {
|
|
131
|
+
const at: i32 = r.at
|
|
132
|
+
if (r.failed || at < 0 || at >= r.end || at >= toI32(r.data.length)) {
|
|
133
|
+
r.failed = true
|
|
134
|
+
return 0
|
|
135
|
+
}
|
|
136
|
+
r.at = at + 1
|
|
137
|
+
return toI32(r.data[at])
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The next two bytes, big-endian. */
|
|
141
|
+
const tlsReadU16 = (r: TlsReader): i32 => {
|
|
142
|
+
const hi: i32 = tlsReadU8(r)
|
|
143
|
+
return (hi << 8) | tlsReadU8(r)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Reads a `width`-byte length (1, 2 or 3) and answers where the vector it
|
|
148
|
+
* opens ends. A length below `min`, above `max` or past `end` fails, and
|
|
149
|
+
* answers the current position so that the vector reads as empty.
|
|
150
|
+
*/
|
|
151
|
+
const tlsReadVector = (r: TlsReader, width: i32, min: i32, max: i32): i32 => {
|
|
152
|
+
let n: i32 = 0
|
|
153
|
+
for (let k: i32 = 0; k < width; k++) {
|
|
154
|
+
n = (n << 8) | tlsReadU8(r)
|
|
155
|
+
}
|
|
156
|
+
if (r.failed || n < min || n > max || n > r.end - r.at) {
|
|
157
|
+
r.failed = true
|
|
158
|
+
return r.at
|
|
159
|
+
}
|
|
160
|
+
return r.at + n
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Narrows the reader to a list that ends at `listEnd`, which a vector read just
|
|
165
|
+
* opened, and answers the end to restore (`r.end = outer`) once its entries
|
|
166
|
+
* are read. Every entry of a TLS list must lie inside the list's declared
|
|
167
|
+
* length, not merely inside the message or the extension around it, so an
|
|
168
|
+
* entry whose own length runs past the list fails the reader.
|
|
169
|
+
*/
|
|
170
|
+
const tlsReadList = (r: TlsReader, listEnd: i32): i32 => {
|
|
171
|
+
const outer: i32 = r.end
|
|
172
|
+
r.end = listEnd
|
|
173
|
+
return outer
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Moves to `to`, the end of a vector this reader opened. */
|
|
177
|
+
const tlsReadSkipTo = (r: TlsReader, to: i32): void => {
|
|
178
|
+
if (to < r.at || to > r.end) {
|
|
179
|
+
r.failed = true
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
r.at = to
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* A ClientHello as `tlsReadClientHello` read it, without a byte copied: every
|
|
187
|
+
* field the server needs is a window `(…At, …Length)` into `data`, the buffer
|
|
188
|
+
* the hello was read from, and the rest are numbers and flags. A field whose
|
|
189
|
+
* extension was absent keeps an empty window, and its `has*` flag says so,
|
|
190
|
+
* since an empty list and a missing extension mean different things to the
|
|
191
|
+
* server (§9.2). Make one per connection and read every hello into it: it
|
|
192
|
+
* allocates nothing after its constructor, so a handshake that keeps its state
|
|
193
|
+
* in a slot reads its ClientHellos without moving the arena.
|
|
194
|
+
*/
|
|
195
|
+
export class TlsClientHelloView {
|
|
196
|
+
/** The buffer the windows point into: the one the last hello was read from. */
|
|
197
|
+
data: u8[]
|
|
198
|
+
/** 0 when the message parsed, or the alert it earns: `decode_error` or `illegal_parameter`. */
|
|
199
|
+
alert: i32 = 0
|
|
200
|
+
legacyVersion: i32 = 0
|
|
201
|
+
randomAt: i32 = 0
|
|
202
|
+
/** `legacy_session_id`, which a server echoes (§4.1.3). */
|
|
203
|
+
sessionIdAt: i32 = 0
|
|
204
|
+
sessionIdLength: i32 = 0
|
|
205
|
+
/** `cipher_suites`, 16-bit values in the client's order. */
|
|
206
|
+
suitesAt: i32 = 0
|
|
207
|
+
suitesLength: i32 = 0
|
|
208
|
+
/** `supported_versions`, 16-bit values. */
|
|
209
|
+
versionsAt: i32 = 0
|
|
210
|
+
versionsLength: i32 = 0
|
|
211
|
+
/** `supported_groups`, 16-bit values. */
|
|
212
|
+
groupsAt: i32 = 0
|
|
213
|
+
groupsLength: i32 = 0
|
|
214
|
+
/** `signature_algorithms`, 16-bit values. */
|
|
215
|
+
schemesAt: i32 = 0
|
|
216
|
+
schemesLength: i32 = 0
|
|
217
|
+
/** The `client_shares` list of `key_share`, every entry in it. */
|
|
218
|
+
sharesAt: i32 = 0
|
|
219
|
+
sharesLength: i32 = 0
|
|
220
|
+
/** The x25519 share's key_exchange, as sent; its length is the server's to check. */
|
|
221
|
+
x25519ShareAt: i32 = 0
|
|
222
|
+
x25519ShareLength: i32 = 0
|
|
223
|
+
/** The `host_name` of `server_name`, or empty. */
|
|
224
|
+
serverNameAt: i32 = 0
|
|
225
|
+
serverNameLength: i32 = 0
|
|
226
|
+
/** ALPN's `protocol_name_list`, each name a length byte and its bytes (RFC 7301 §3.1). */
|
|
227
|
+
alpnAt: i32 = 0
|
|
228
|
+
alpnLength: i32 = 0
|
|
229
|
+
/** The client's `quic_transport_parameters`, opaque (RFC 9001 §8.2). */
|
|
230
|
+
quicAt: i32 = 0
|
|
231
|
+
quicLength: i32 = 0
|
|
232
|
+
/** The extensions block, every extension in it. */
|
|
233
|
+
extensionsAt: i32 = 0
|
|
234
|
+
extensionsLength: i32 = 0
|
|
235
|
+
/** Whether `legacy_compression_methods` is the single null method TLS 1.3 requires (§4.1.2). */
|
|
236
|
+
nullCompressionOnly: boolean = false
|
|
237
|
+
hasSupportedVersions: boolean = false
|
|
238
|
+
hasSupportedGroups: boolean = false
|
|
239
|
+
hasKeyShare: boolean = false
|
|
240
|
+
hasX25519Share: boolean = false
|
|
241
|
+
hasSignatureAlgorithms: boolean = false
|
|
242
|
+
hasServerName: boolean = false
|
|
243
|
+
hasAlpn: boolean = false
|
|
244
|
+
hasQuicTransportParameters: boolean = false
|
|
245
|
+
/** One bit per extension type read, for the duplicate check (§4.2); cleared by each read. */
|
|
246
|
+
extensionSeen: u32[]
|
|
247
|
+
/** One bit per group a share was offered for, for the duplicate check (§4.2.8). */
|
|
248
|
+
shareSeen: u32[]
|
|
249
|
+
/** The reader each read runs, kept so that a read allocates none. */
|
|
250
|
+
reader: TlsReader
|
|
251
|
+
|
|
252
|
+
constructor() {
|
|
253
|
+
this.data = []
|
|
254
|
+
this.extensionSeen = new Array<u32>(TLS_U16_BITMAP_WORDS)
|
|
255
|
+
this.shareSeen = new Array<u32>(TLS_U16_BITMAP_WORDS)
|
|
256
|
+
this.reader = new TlsReader(this.data, TLS_CODEC_FROM, TLS_CODEC_FROM)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Forgets the last hello: every window empty, every flag down, both bitmaps clear. */
|
|
260
|
+
clear(): void {
|
|
261
|
+
this.alert = 0
|
|
262
|
+
this.legacyVersion = 0
|
|
263
|
+
this.randomAt = 0
|
|
264
|
+
this.sessionIdAt = 0
|
|
265
|
+
this.sessionIdLength = 0
|
|
266
|
+
this.suitesAt = 0
|
|
267
|
+
this.suitesLength = 0
|
|
268
|
+
this.versionsAt = 0
|
|
269
|
+
this.versionsLength = 0
|
|
270
|
+
this.groupsAt = 0
|
|
271
|
+
this.groupsLength = 0
|
|
272
|
+
this.schemesAt = 0
|
|
273
|
+
this.schemesLength = 0
|
|
274
|
+
this.sharesAt = 0
|
|
275
|
+
this.sharesLength = 0
|
|
276
|
+
this.x25519ShareAt = 0
|
|
277
|
+
this.x25519ShareLength = 0
|
|
278
|
+
this.serverNameAt = 0
|
|
279
|
+
this.serverNameLength = 0
|
|
280
|
+
this.alpnAt = 0
|
|
281
|
+
this.alpnLength = 0
|
|
282
|
+
this.quicAt = 0
|
|
283
|
+
this.quicLength = 0
|
|
284
|
+
this.extensionsAt = 0
|
|
285
|
+
this.extensionsLength = 0
|
|
286
|
+
this.nullCompressionOnly = false
|
|
287
|
+
this.hasSupportedVersions = false
|
|
288
|
+
this.hasSupportedGroups = false
|
|
289
|
+
this.hasKeyShare = false
|
|
290
|
+
this.hasX25519Share = false
|
|
291
|
+
this.hasSignatureAlgorithms = false
|
|
292
|
+
this.hasServerName = false
|
|
293
|
+
this.hasAlpn = false
|
|
294
|
+
this.hasQuicTransportParameters = false
|
|
295
|
+
for (let k: i32 = 0; k < toI32(this.extensionSeen.length); k++) {
|
|
296
|
+
this.extensionSeen[k] = toU32(0)
|
|
297
|
+
}
|
|
298
|
+
for (let k: i32 = 0; k < toI32(this.shareSeen.length); k++) {
|
|
299
|
+
this.shareSeen[k] = toU32(0)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Enough 32-bit words for one bit per 16-bit value. */
|
|
305
|
+
const TLS_U16_BITMAP_WORDS: i32 = 2048
|
|
306
|
+
|
|
307
|
+
/** Sets `value`'s bit in `bits` and answers whether it was already set: the duplicate checks of §4.2 and §4.2.8. */
|
|
308
|
+
const tlsMarkSeen = (bits: u32[], value: i32): boolean => {
|
|
309
|
+
const word: i32 = (value >> 5) & (TLS_U16_BITMAP_WORDS - 1)
|
|
310
|
+
const bit: u32 = toU32(1) << toU32(value & 31)
|
|
311
|
+
if (word < 0 || word >= toI32(bits.length)) {
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
const seen: boolean = (bits[word] & bit) !== toU32(0)
|
|
315
|
+
bits[word] = bits[word] | bit
|
|
316
|
+
return seen
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Records `alert` on `hello` unless an earlier one is there: the first fault is the one reported. */
|
|
320
|
+
const tlsHelloRefuse = (hello: TlsClientHelloView, alert: i32): void => {
|
|
321
|
+
if (hello.alert === 0) {
|
|
322
|
+
hello.alert = alert
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Reads a vector of 16-bit values (a `width`-byte length within `[min, max]`)
|
|
328
|
+
* and answers where it ends; an odd byte count fails the reader. The values
|
|
329
|
+
* are the window from `r.at` before the call to the answer.
|
|
330
|
+
*/
|
|
331
|
+
const tlsReadU16Vector = (r: TlsReader, width: i32, min: i32, max: i32): i32 => {
|
|
332
|
+
const end: i32 = tlsReadVector(r, width, min, max)
|
|
333
|
+
if ((end - r.at) % 2 !== 0) {
|
|
334
|
+
r.failed = true
|
|
335
|
+
return r.at
|
|
336
|
+
}
|
|
337
|
+
tlsReadSkipTo(r, end)
|
|
338
|
+
return end
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Whether `list` holds `value`. */
|
|
342
|
+
export const tlsListHas = (list: i32[], value: i32): boolean => list.indexOf(value) >= 0
|
|
343
|
+
|
|
344
|
+
/** Whether the 16-bit values `data[at .. at + length)` hold `value`. */
|
|
345
|
+
export const tlsWindowHasU16 = (data: u8[], at: i32, length: i32, value: i32): boolean => {
|
|
346
|
+
for (
|
|
347
|
+
let k: i32 = at;
|
|
348
|
+
k + 1 < at + length && k >= 0 && k < toI32(data.length) && k + 1 < toI32(data.length);
|
|
349
|
+
k += 2
|
|
350
|
+
) {
|
|
351
|
+
if (((toI32(data[k]) << 8) | toI32(data[k + 1])) === value) {
|
|
352
|
+
return true
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return false
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* `server_name` (RFC 6066 §3): a list of names, of which a `host_name` (type
|
|
360
|
+
* 0) is kept. A second `host_name` is `illegal_parameter`, as §3 says. So is a
|
|
361
|
+
* host name with a byte outside printable ASCII, because a DNS host name sent
|
|
362
|
+
* here is ASCII (an internationalised one in its A-label form) and the server
|
|
363
|
+
* exposes it as a string.
|
|
364
|
+
*/
|
|
365
|
+
const tlsReadServerName = (r: TlsReader, hello: TlsClientHelloView): void => {
|
|
366
|
+
const listEnd: i32 = tlsReadVector(r, 2, 1, 65535)
|
|
367
|
+
// The list's own end bounds every entry in it (see `tlsReadList`).
|
|
368
|
+
const outerEnd: i32 = tlsReadList(r, listEnd)
|
|
369
|
+
while (!r.failed && r.at < listEnd) {
|
|
370
|
+
const nameType: i32 = tlsReadU8(r)
|
|
371
|
+
const nameEnd: i32 = tlsReadVector(r, 2, 1, 65535)
|
|
372
|
+
if (nameType === 0) {
|
|
373
|
+
if (hello.hasServerName) {
|
|
374
|
+
tlsHelloRefuse(hello, TLS_ALERT_ILLEGAL_PARAMETER)
|
|
375
|
+
}
|
|
376
|
+
hello.hasServerName = true
|
|
377
|
+
hello.serverNameAt = r.at
|
|
378
|
+
hello.serverNameLength = nameEnd - r.at
|
|
379
|
+
while (!r.failed && r.at < nameEnd) {
|
|
380
|
+
const c: i32 = tlsReadU8(r)
|
|
381
|
+
if (c < 0x21 || c > 0x7e) {
|
|
382
|
+
tlsHelloRefuse(hello, TLS_ALERT_ILLEGAL_PARAMETER)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
tlsReadSkipTo(r, nameEnd)
|
|
387
|
+
}
|
|
388
|
+
r.end = outerEnd
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* `key_share` (RFC 8446 §4.2.8): the client's shares, each a group and its
|
|
393
|
+
* key_exchange<1..2^16-1>. Two shares for one group are `illegal_parameter`
|
|
394
|
+
* (§4.2.8, "Clients MUST NOT offer multiple KeyShareEntry values for the same
|
|
395
|
+
* group").
|
|
396
|
+
*/
|
|
397
|
+
const tlsReadKeyShare = (r: TlsReader, hello: TlsClientHelloView): void => {
|
|
398
|
+
hello.hasKeyShare = true
|
|
399
|
+
const listEnd: i32 = tlsReadVector(r, 2, 0, 65535)
|
|
400
|
+
hello.sharesAt = r.at
|
|
401
|
+
hello.sharesLength = listEnd - r.at
|
|
402
|
+
// The list's own end bounds every entry in it (see `tlsReadList`).
|
|
403
|
+
const outerEnd: i32 = tlsReadList(r, listEnd)
|
|
404
|
+
while (!r.failed && r.at < listEnd) {
|
|
405
|
+
const group: i32 = tlsReadU16(r)
|
|
406
|
+
const shareEnd: i32 = tlsReadVector(r, 2, 1, 65535)
|
|
407
|
+
if (tlsMarkSeen(hello.shareSeen, group)) {
|
|
408
|
+
tlsHelloRefuse(hello, TLS_ALERT_ILLEGAL_PARAMETER)
|
|
409
|
+
}
|
|
410
|
+
if (group === TLS_GROUP_X25519 && !r.failed) {
|
|
411
|
+
hello.hasX25519Share = true
|
|
412
|
+
hello.x25519ShareAt = r.at
|
|
413
|
+
hello.x25519ShareLength = shareEnd - r.at
|
|
414
|
+
}
|
|
415
|
+
tlsReadSkipTo(r, shareEnd)
|
|
416
|
+
}
|
|
417
|
+
r.end = outerEnd
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** `application_layer_protocol_negotiation` (RFC 7301 §3.1): ProtocolName<1..2^8-1> names in a list<2..2^16-1>. */
|
|
421
|
+
const tlsReadAlpn = (r: TlsReader, hello: TlsClientHelloView): void => {
|
|
422
|
+
hello.hasAlpn = true
|
|
423
|
+
const listEnd: i32 = tlsReadVector(r, 2, 2, 65535)
|
|
424
|
+
hello.alpnAt = r.at
|
|
425
|
+
hello.alpnLength = listEnd - r.at
|
|
426
|
+
// The list's own end bounds every entry in it (see `tlsReadList`).
|
|
427
|
+
const outerEnd: i32 = tlsReadList(r, listEnd)
|
|
428
|
+
while (!r.failed && r.at < listEnd) {
|
|
429
|
+
const nameEnd: i32 = tlsReadVector(r, 1, 1, 255)
|
|
430
|
+
tlsReadSkipTo(r, nameEnd)
|
|
431
|
+
}
|
|
432
|
+
r.end = outerEnd
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Reads one extension body, `r.at .. end`, of type `type`; an unknown type is skipped. */
|
|
436
|
+
const tlsReadExtension = (r: TlsReader, type: i32, end: i32, hello: TlsClientHelloView): void => {
|
|
437
|
+
switch (type) {
|
|
438
|
+
case TLS_EXT_SERVER_NAME:
|
|
439
|
+
tlsReadServerName(r, hello)
|
|
440
|
+
break
|
|
441
|
+
case TLS_EXT_SUPPORTED_GROUPS:
|
|
442
|
+
hello.hasSupportedGroups = true
|
|
443
|
+
hello.groupsAt = r.at + 2
|
|
444
|
+
hello.groupsLength = tlsReadU16Vector(r, 2, 2, 65535) - hello.groupsAt
|
|
445
|
+
break
|
|
446
|
+
case TLS_EXT_SIGNATURE_ALGORITHMS:
|
|
447
|
+
hello.hasSignatureAlgorithms = true
|
|
448
|
+
hello.schemesAt = r.at + 2
|
|
449
|
+
hello.schemesLength = tlsReadU16Vector(r, 2, 2, 65534) - hello.schemesAt
|
|
450
|
+
break
|
|
451
|
+
case TLS_EXT_ALPN:
|
|
452
|
+
tlsReadAlpn(r, hello)
|
|
453
|
+
break
|
|
454
|
+
case TLS_EXT_SUPPORTED_VERSIONS:
|
|
455
|
+
hello.hasSupportedVersions = true
|
|
456
|
+
hello.versionsAt = r.at + 1
|
|
457
|
+
hello.versionsLength = tlsReadU16Vector(r, 1, 2, 254) - hello.versionsAt
|
|
458
|
+
break
|
|
459
|
+
case TLS_EXT_KEY_SHARE:
|
|
460
|
+
tlsReadKeyShare(r, hello)
|
|
461
|
+
break
|
|
462
|
+
case TLS_EXT_QUIC_TRANSPORT_PARAMETERS:
|
|
463
|
+
hello.hasQuicTransportParameters = true
|
|
464
|
+
hello.quicAt = r.at
|
|
465
|
+
hello.quicLength = end - r.at
|
|
466
|
+
tlsReadSkipTo(r, end)
|
|
467
|
+
break
|
|
468
|
+
default:
|
|
469
|
+
tlsReadSkipTo(r, end)
|
|
470
|
+
break
|
|
471
|
+
}
|
|
472
|
+
// Every reader above stops where its own vectors end, and none can pass
|
|
473
|
+
// the extension's end, which is the reader's while it runs; an extension
|
|
474
|
+
// body with bytes left after them is malformed (RFC 8446 §4.2), so this is
|
|
475
|
+
// where trailing bytes are refused.
|
|
476
|
+
if (r.at !== end) {
|
|
477
|
+
r.failed = true
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Reads the ClientHello body `data[off .. off + len)` — the message without
|
|
483
|
+
* its four-byte handshake header — into `hello`, which forgets the last one
|
|
484
|
+
* first. Its `alert` is 0 when the body parsed; otherwise the first fault
|
|
485
|
+
* decides it: `decode_error` for a length past its bytes, a vector out of its
|
|
486
|
+
* bounds or bytes left over, and `illegal_parameter` for an extension sent
|
|
487
|
+
* twice (§4.2), a `pre_shared_key` that is not the last extension (§4.2.11),
|
|
488
|
+
* two shares for one group, or a bad `server_name`. A window outside `data`
|
|
489
|
+
* is `decode_error` too, rather than a panic. Nothing is copied: the windows
|
|
490
|
+
* point into `data`, which must outlive the use made of them.
|
|
491
|
+
*/
|
|
492
|
+
export const tlsReadClientHello = (hello: TlsClientHelloView, data: u8[], off: i32, len: i32): void => {
|
|
493
|
+
hello.clear()
|
|
494
|
+
hello.data = data
|
|
495
|
+
if (off < 0 || len < 0 || off > toI32(data.length) - len) {
|
|
496
|
+
hello.alert = TLS_ALERT_DECODE_ERROR
|
|
497
|
+
return
|
|
498
|
+
}
|
|
499
|
+
const end: i32 = off + len
|
|
500
|
+
const r: TlsReader = hello.reader
|
|
501
|
+
r.data = data
|
|
502
|
+
r.at = off
|
|
503
|
+
r.end = end
|
|
504
|
+
r.failed = false
|
|
505
|
+
hello.legacyVersion = tlsReadU16(r)
|
|
506
|
+
hello.randomAt = r.at
|
|
507
|
+
tlsReadSkipTo(r, r.at + TLS_RANDOM_SIZE)
|
|
508
|
+
const sessionEnd: i32 = tlsReadVector(r, 1, 0, 32)
|
|
509
|
+
hello.sessionIdAt = r.at
|
|
510
|
+
hello.sessionIdLength = sessionEnd - r.at
|
|
511
|
+
tlsReadSkipTo(r, sessionEnd)
|
|
512
|
+
hello.suitesAt = r.at + 2
|
|
513
|
+
hello.suitesLength = tlsReadU16Vector(r, 2, 2, 65534) - hello.suitesAt
|
|
514
|
+
const compressionEnd: i32 = tlsReadVector(r, 1, 1, 255)
|
|
515
|
+
hello.nullCompressionOnly = compressionEnd - r.at === 1 && tlsReadU8(r) === 0
|
|
516
|
+
tlsReadSkipTo(r, compressionEnd)
|
|
517
|
+
// A hello with no extensions block is well formed (§4.1.2 keeps it for
|
|
518
|
+
// TLS 1.2 and earlier); it offers no TLS 1.3, which the server refuses.
|
|
519
|
+
if (!r.failed && r.at < end) {
|
|
520
|
+
const extensionsEnd: i32 = tlsReadVector(r, 2, 0, 65535)
|
|
521
|
+
hello.extensionsAt = r.at
|
|
522
|
+
hello.extensionsLength = extensionsEnd - r.at
|
|
523
|
+
// The extensions vector's own end bounds every extension in it: one whose
|
|
524
|
+
// length runs past it is malformed even when the message's length still
|
|
525
|
+
// balances.
|
|
526
|
+
const messageEnd: i32 = tlsReadList(r, extensionsEnd)
|
|
527
|
+
while (!r.failed && r.at < extensionsEnd) {
|
|
528
|
+
const type: i32 = tlsReadU16(r)
|
|
529
|
+
const bodyEnd: i32 = tlsReadVector(r, 2, 0, 65535)
|
|
530
|
+
if (r.failed) {
|
|
531
|
+
break
|
|
532
|
+
}
|
|
533
|
+
if (tlsMarkSeen(hello.extensionSeen, type)) {
|
|
534
|
+
tlsHelloRefuse(hello, TLS_ALERT_ILLEGAL_PARAMETER)
|
|
535
|
+
}
|
|
536
|
+
// §4.2.11: `pre_shared_key` must be the last extension, and a server
|
|
537
|
+
// checks that even though it does not take the PSK.
|
|
538
|
+
if (type === TLS_EXT_PRE_SHARED_KEY && bodyEnd !== extensionsEnd) {
|
|
539
|
+
tlsHelloRefuse(hello, TLS_ALERT_ILLEGAL_PARAMETER)
|
|
540
|
+
}
|
|
541
|
+
// The extension's own end bounds every read inside it, so a vector in
|
|
542
|
+
// one extension cannot run on into the next.
|
|
543
|
+
const outerEnd: i32 = tlsReadList(r, bodyEnd)
|
|
544
|
+
tlsReadExtension(r, type, bodyEnd, hello)
|
|
545
|
+
r.end = outerEnd
|
|
546
|
+
}
|
|
547
|
+
r.end = messageEnd
|
|
548
|
+
}
|
|
549
|
+
if (r.failed || r.at !== end) {
|
|
550
|
+
tlsHelloRefuse(hello, TLS_ALERT_DECODE_ERROR)
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* A ClientHello as `tlsParseClientHello` read it (RFC 8446 §4.1.2), each field
|
|
556
|
+
* copied out of the message into an array of its own: the same hello as a
|
|
557
|
+
* `TlsClientHelloView`, for a caller that wants values rather than windows. A
|
|
558
|
+
* field whose extension was absent keeps its empty value, and its `has*` flag
|
|
559
|
+
* says so.
|
|
560
|
+
*/
|
|
561
|
+
export class TlsClientHello {
|
|
562
|
+
random: u8[]
|
|
563
|
+
/** `legacy_session_id`, which a server echoes (§4.1.3). */
|
|
564
|
+
sessionId: u8[]
|
|
565
|
+
cipherSuites: i32[]
|
|
566
|
+
/** `supported_versions`, in the client's order. */
|
|
567
|
+
versions: i32[]
|
|
568
|
+
/** `supported_groups`, in the client's order. */
|
|
569
|
+
groups: i32[]
|
|
570
|
+
/** Every group a key share was offered for, in order. */
|
|
571
|
+
shareGroups: i32[]
|
|
572
|
+
/** The x25519 share's key_exchange, as sent; its length is the server's to check. */
|
|
573
|
+
x25519Share: u8[]
|
|
574
|
+
/** `signature_algorithms`, in the client's order. */
|
|
575
|
+
signatureSchemes: i32[]
|
|
576
|
+
/** The `host_name` of `server_name`, or empty. */
|
|
577
|
+
serverName: string = ""
|
|
578
|
+
/** The ALPN protocol names offered, each as its bytes (RFC 7301 §3.1). */
|
|
579
|
+
alpn: u8[][]
|
|
580
|
+
/** The client's `quic_transport_parameters`, opaque (RFC 9001 §8.2). */
|
|
581
|
+
quicTransportParameters: u8[]
|
|
582
|
+
/** Every extension type read, in order. */
|
|
583
|
+
extensionTypes: i32[]
|
|
584
|
+
/** 0 when the message parsed, or the alert it earns: `decode_error` or `illegal_parameter`. */
|
|
585
|
+
alert: i32 = 0
|
|
586
|
+
legacyVersion: i32 = 0
|
|
587
|
+
/** Whether `legacy_compression_methods` is the single null method TLS 1.3 requires (§4.1.2). */
|
|
588
|
+
nullCompressionOnly: boolean = false
|
|
589
|
+
hasSupportedVersions: boolean = false
|
|
590
|
+
hasSupportedGroups: boolean = false
|
|
591
|
+
hasKeyShare: boolean = false
|
|
592
|
+
hasX25519Share: boolean = false
|
|
593
|
+
hasSignatureAlgorithms: boolean = false
|
|
594
|
+
hasServerName: boolean = false
|
|
595
|
+
hasAlpn: boolean = false
|
|
596
|
+
hasQuicTransportParameters: boolean = false
|
|
597
|
+
|
|
598
|
+
constructor() {
|
|
599
|
+
this.random = []
|
|
600
|
+
this.sessionId = []
|
|
601
|
+
this.cipherSuites = []
|
|
602
|
+
this.versions = []
|
|
603
|
+
this.groups = []
|
|
604
|
+
this.shareGroups = []
|
|
605
|
+
this.x25519Share = []
|
|
606
|
+
this.signatureSchemes = []
|
|
607
|
+
this.alpn = []
|
|
608
|
+
this.quicTransportParameters = []
|
|
609
|
+
this.extensionTypes = []
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** A copy of `data[at .. at + length)`. */
|
|
614
|
+
const tlsCopyWindow = (data: u8[], at: i32, length: i32): u8[] => {
|
|
615
|
+
const out: u8[] = new Array<u8>(length < 0 ? TLS_CODEC_FROM : length)
|
|
616
|
+
for (let k: i32 = 0; k < toI32(out.length) && at + k >= 0 && at + k < toI32(data.length); k++) {
|
|
617
|
+
out[k] = data[at + k]
|
|
618
|
+
}
|
|
619
|
+
return out
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** The 16-bit values of `data[at .. at + length)`, in a fresh list. */
|
|
623
|
+
const tlsCopyU16Window = (data: u8[], at: i32, length: i32): i32[] => {
|
|
624
|
+
const out: i32[] = []
|
|
625
|
+
for (
|
|
626
|
+
let k: i32 = at;
|
|
627
|
+
k + 1 < at + length && k >= 0 && k < toI32(data.length) && k + 1 < toI32(data.length);
|
|
628
|
+
k += 2
|
|
629
|
+
) {
|
|
630
|
+
out.push((toI32(data[k]) << 8) | toI32(data[k + 1]))
|
|
631
|
+
}
|
|
632
|
+
return out
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** The bytes of `data[at .. at + length)` as a string of one character a byte: ASCII here, a host name. */
|
|
636
|
+
export const tlsWindowString = (data: u8[], at: i32, length: i32): string => {
|
|
637
|
+
const parts: string[] = []
|
|
638
|
+
for (let k: i32 = at; k < at + length && k >= 0 && k < toI32(data.length); k++) {
|
|
639
|
+
parts.push(String.fromCharCode(toI32(data[k])))
|
|
640
|
+
}
|
|
641
|
+
return parts.join("")
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Reads the ClientHello body `data[off .. off + len)` — the message without
|
|
646
|
+
* its four-byte handshake header — into a fresh `TlsClientHello`, with
|
|
647
|
+
* `tlsReadClientHello`'s checks and its `alert`, every field copied out.
|
|
648
|
+
*/
|
|
649
|
+
export const tlsParseClientHello = (data: u8[], off: i32, len: i32): TlsClientHello => {
|
|
650
|
+
const view = new TlsClientHelloView()
|
|
651
|
+
tlsReadClientHello(view, data, off, len)
|
|
652
|
+
const hello = new TlsClientHello()
|
|
653
|
+
hello.alert = view.alert
|
|
654
|
+
if (off < 0 || len < 0 || off > toI32(data.length) - len) {
|
|
655
|
+
return hello
|
|
656
|
+
}
|
|
657
|
+
hello.legacyVersion = view.legacyVersion
|
|
658
|
+
hello.random = tlsCopyWindow(data, view.randomAt, TLS_RANDOM_SIZE)
|
|
659
|
+
hello.sessionId = tlsCopyWindow(data, view.sessionIdAt, view.sessionIdLength)
|
|
660
|
+
hello.cipherSuites = tlsCopyU16Window(data, view.suitesAt, view.suitesLength)
|
|
661
|
+
hello.versions = tlsCopyU16Window(data, view.versionsAt, view.versionsLength)
|
|
662
|
+
hello.groups = tlsCopyU16Window(data, view.groupsAt, view.groupsLength)
|
|
663
|
+
hello.signatureSchemes = tlsCopyU16Window(data, view.schemesAt, view.schemesLength)
|
|
664
|
+
hello.x25519Share = tlsCopyWindow(data, view.x25519ShareAt, view.x25519ShareLength)
|
|
665
|
+
hello.serverName = tlsWindowString(data, view.serverNameAt, view.serverNameLength)
|
|
666
|
+
hello.quicTransportParameters = tlsCopyWindow(data, view.quicAt, view.quicLength)
|
|
667
|
+
// The key shares, ALPN names and extension types, walked again; the read
|
|
668
|
+
// above has checked every length these loops follow.
|
|
669
|
+
let at: i32 = view.sharesAt
|
|
670
|
+
while (
|
|
671
|
+
at + 4 <= view.sharesAt + view.sharesLength &&
|
|
672
|
+
at >= 0 &&
|
|
673
|
+
at < toI32(data.length) &&
|
|
674
|
+
at + 3 < toI32(data.length)
|
|
675
|
+
) {
|
|
676
|
+
hello.shareGroups.push((toI32(data[at]) << 8) | toI32(data[at + 1]))
|
|
677
|
+
at = at + 4 + ((toI32(data[at + 2]) << 8) | toI32(data[at + 3]))
|
|
678
|
+
}
|
|
679
|
+
at = view.alpnAt
|
|
680
|
+
while (at < view.alpnAt + view.alpnLength && at >= 0 && at < toI32(data.length)) {
|
|
681
|
+
const n: i32 = toI32(data[at])
|
|
682
|
+
hello.alpn.push(tlsCopyWindow(data, at + 1, n))
|
|
683
|
+
at = at + 1 + n
|
|
684
|
+
}
|
|
685
|
+
at = view.extensionsAt
|
|
686
|
+
while (
|
|
687
|
+
at + 4 <= view.extensionsAt + view.extensionsLength &&
|
|
688
|
+
at >= 0 &&
|
|
689
|
+
at < toI32(data.length) &&
|
|
690
|
+
at + 3 < toI32(data.length)
|
|
691
|
+
) {
|
|
692
|
+
hello.extensionTypes.push((toI32(data[at]) << 8) | toI32(data[at + 1]))
|
|
693
|
+
at = at + 4 + ((toI32(data[at + 2]) << 8) | toI32(data[at + 3]))
|
|
694
|
+
}
|
|
695
|
+
hello.nullCompressionOnly = view.nullCompressionOnly
|
|
696
|
+
hello.hasSupportedVersions = view.hasSupportedVersions
|
|
697
|
+
hello.hasSupportedGroups = view.hasSupportedGroups
|
|
698
|
+
hello.hasKeyShare = view.hasKeyShare
|
|
699
|
+
hello.hasX25519Share = view.hasX25519Share
|
|
700
|
+
hello.hasSignatureAlgorithms = view.hasSignatureAlgorithms
|
|
701
|
+
hello.hasServerName = view.hasServerName
|
|
702
|
+
hello.hasAlpn = view.hasAlpn
|
|
703
|
+
hello.hasQuicTransportParameters = view.hasQuicTransportParameters
|
|
704
|
+
return hello
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* The writers below put a handshake message into the caller's array at an
|
|
709
|
+
* offset and answer where it ends. A byte that would land outside the array
|
|
710
|
+
* is dropped rather than written, and the answer is still where the message
|
|
711
|
+
* ends, so a writer run over an empty array is how long its message is: the
|
|
712
|
+
* `tlsEncode*` functions measure that way, allocate the message's array and
|
|
713
|
+
* write it again, and a server that keeps its output in a buffer of its own
|
|
714
|
+
* makes room first and writes in place. The `tlsPut*` helpers are the same
|
|
715
|
+
* shape: they take the position and answer the next.
|
|
716
|
+
*/
|
|
717
|
+
const tlsPutU8 = (out: u8[], at: i32, v: i32): i32 => {
|
|
718
|
+
if (at >= 0 && at < toI32(out.length)) {
|
|
719
|
+
out[at] = toU8(v & 255)
|
|
720
|
+
}
|
|
721
|
+
return at + 1
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const tlsPutU16 = (out: u8[], at: i32, v: i32): i32 => tlsPutU8(out, tlsPutU8(out, at, v >> 8), v)
|
|
725
|
+
|
|
726
|
+
/** `bytes[off .. off + len)`, at `out[at]`. */
|
|
727
|
+
const tlsPutWindow = (out: u8[], at: i32, bytes: u8[], off: i32, len: i32): i32 => {
|
|
728
|
+
// Measuring over an empty array: nothing would land, so nothing is copied.
|
|
729
|
+
if (toI32(out.length) === 0) {
|
|
730
|
+
return at + len
|
|
731
|
+
}
|
|
732
|
+
for (let k: i32 = 0; k < len && off + k >= 0 && off + k < toI32(bytes.length); k++) {
|
|
733
|
+
tlsPutU8(out, at + k, toI32(bytes[off + k]))
|
|
734
|
+
}
|
|
735
|
+
return at + len
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const tlsPutBytes = (out: u8[], at: i32, b: u8[]): i32 =>
|
|
739
|
+
tlsPutWindow(out, at, b, TLS_CODEC_FROM, toI32(b.length))
|
|
740
|
+
|
|
741
|
+
/** The bytes of `text`, one a character: ASCII here, an ALPN protocol id. */
|
|
742
|
+
const tlsPutAscii = (out: u8[], at: i32, text: string): i32 => {
|
|
743
|
+
const length: i32 = toI32(text.length)
|
|
744
|
+
for (let k: i32 = 0; k < length; k++) {
|
|
745
|
+
tlsPutU8(out, at + k, toI32(text.charCodeAt(k)))
|
|
746
|
+
}
|
|
747
|
+
return at + length
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Writes the `width`-byte length of the vector whose contents run from
|
|
752
|
+
* `start` to `end`, into the `width` bytes before `start` that the caller
|
|
753
|
+
* left for it.
|
|
754
|
+
*/
|
|
755
|
+
const tlsPutLength = (out: u8[], start: i32, end: i32, width: i32): void => {
|
|
756
|
+
let n: i32 = end - start
|
|
757
|
+
for (let at: i32 = start - 1; at >= start - width; at--) {
|
|
758
|
+
tlsPutU8(out, at, n)
|
|
759
|
+
n = n >> 8
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/** The handshake header (§4): `type`, then a 24-bit length of the body that follows it to `end`. */
|
|
764
|
+
const tlsPutHeader = (out: u8[], at: i32, type: i32, end: i32): void => {
|
|
765
|
+
tlsPutU8(out, at, type)
|
|
766
|
+
tlsPutLength(out, at + 4, end, 3)
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** The fixed `random` that marks a ServerHello as a HelloRetryRequest: SHA-256 of "HelloRetryRequest" (§4.1.3). */
|
|
770
|
+
const TLS_HELLO_RETRY_RANDOM: string = "cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c"
|
|
771
|
+
|
|
772
|
+
/** The value of one lowercase hex digit. */
|
|
773
|
+
const tlsHexNibble = (c: i32): i32 => (c <= 57 ? c - 48 : c - 87)
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Writes the bytes the lowercase hex constant `text` spells at `out[at]` and
|
|
777
|
+
* answers where they end: how this stack's fixed values, written out where
|
|
778
|
+
* they are defined, become bytes without an array of their own.
|
|
779
|
+
*/
|
|
780
|
+
export const tlsHexInto = (text: string, out: u8[], at: i32): i32 => {
|
|
781
|
+
const length: i32 = toI32(text.length) >> 1
|
|
782
|
+
for (let k: i32 = 0; k < length; k++) {
|
|
783
|
+
const hi: i32 = tlsHexNibble(toI32(text.charCodeAt(2 * k)))
|
|
784
|
+
const lo: i32 = tlsHexNibble(toI32(text.charCodeAt(2 * k + 1)))
|
|
785
|
+
tlsPutU8(out, at + k, (hi << 4) | lo)
|
|
786
|
+
}
|
|
787
|
+
return at + length
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/** The `supported_versions` a server hello carries: TLS 1.3 alone (§4.2.1). */
|
|
791
|
+
const tlsPutSelectedVersion = (out: u8[], at: i32): i32 =>
|
|
792
|
+
tlsPutU16(out, tlsPutU16(out, tlsPutU16(out, at, TLS_EXT_SUPPORTED_VERSIONS), 2), TLS_VERSION_13)
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* What a ServerHello and a HelloRetryRequest open with (§4.1.3), after the
|
|
796
|
+
* header: the legacy version, the random (`retry` for the HelloRetryRequest's
|
|
797
|
+
* fixed one), the echoed session id, the suite and null compression.
|
|
798
|
+
*/
|
|
799
|
+
const tlsPutHelloHead = (
|
|
800
|
+
out: u8[],
|
|
801
|
+
at: i32,
|
|
802
|
+
random: u8[],
|
|
803
|
+
retry: boolean,
|
|
804
|
+
sessionId: u8[],
|
|
805
|
+
sessionIdOff: i32,
|
|
806
|
+
sessionIdLength: i32,
|
|
807
|
+
suite: i32
|
|
808
|
+
): i32 => {
|
|
809
|
+
let p: i32 = tlsPutU16(out, at, TLS_LEGACY_VERSION)
|
|
810
|
+
p = retry ? tlsHexInto(TLS_HELLO_RETRY_RANDOM, out, p) : tlsPutBytes(out, p, random)
|
|
811
|
+
p = tlsPutU8(out, p, sessionIdLength)
|
|
812
|
+
p = tlsPutWindow(out, p, sessionId, sessionIdOff, sessionIdLength)
|
|
813
|
+
p = tlsPutU16(out, p, suite)
|
|
814
|
+
return tlsPutU8(out, p, 0)
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Writes a ServerHello (§4.1.3) at `out[at]` and answers where it ends:
|
|
819
|
+
* choosing `suite` and TLS 1.3, echoing the session id
|
|
820
|
+
* `sessionId[sessionIdOff .. sessionIdOff + sessionIdLength)`, and carrying
|
|
821
|
+
* the server's x25519 share. The extensions are `key_share` then
|
|
822
|
+
* `supported_versions`, the order RFC 8448 §3 shows.
|
|
823
|
+
*/
|
|
824
|
+
export const tlsWriteServerHello = (
|
|
825
|
+
out: u8[],
|
|
826
|
+
at: i32,
|
|
827
|
+
random: u8[],
|
|
828
|
+
sessionId: u8[],
|
|
829
|
+
sessionIdOff: i32,
|
|
830
|
+
sessionIdLength: i32,
|
|
831
|
+
suite: i32,
|
|
832
|
+
x25519Public: u8[]
|
|
833
|
+
): i32 => {
|
|
834
|
+
let p: i32 = tlsPutHelloHead(out, at + 4, random, false, sessionId, sessionIdOff, sessionIdLength, suite)
|
|
835
|
+
const extensions: i32 = p + 2
|
|
836
|
+
p = tlsPutU16(out, extensions, TLS_EXT_KEY_SHARE)
|
|
837
|
+
const share: i32 = p + 2
|
|
838
|
+
p = tlsPutU16(out, share, TLS_GROUP_X25519)
|
|
839
|
+
const key: i32 = p + 2
|
|
840
|
+
p = tlsPutBytes(out, key, x25519Public)
|
|
841
|
+
tlsPutLength(out, key, p, 2)
|
|
842
|
+
tlsPutLength(out, share, p, 2)
|
|
843
|
+
p = tlsPutSelectedVersion(out, p)
|
|
844
|
+
tlsPutLength(out, extensions, p, 2)
|
|
845
|
+
tlsPutHeader(out, at, TLS_HANDSHAKE_SERVER_HELLO, p)
|
|
846
|
+
return p
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Writes a HelloRetryRequest (§4.1.4) at `out[at]` and answers where it ends:
|
|
851
|
+
* a ServerHello whose `random` is the fixed HelloRetryRequest value, choosing
|
|
852
|
+
* `suite`, echoing the session id, and asking for a share of `group` in
|
|
853
|
+
* `key_share`. No cookie is sent.
|
|
854
|
+
*/
|
|
855
|
+
export const tlsWriteHelloRetryRequest = (
|
|
856
|
+
out: u8[],
|
|
857
|
+
at: i32,
|
|
858
|
+
sessionId: u8[],
|
|
859
|
+
sessionIdOff: i32,
|
|
860
|
+
sessionIdLength: i32,
|
|
861
|
+
suite: i32,
|
|
862
|
+
group: i32
|
|
863
|
+
): i32 => {
|
|
864
|
+
const none: u8[] = []
|
|
865
|
+
let p: i32 = tlsPutHelloHead(out, at + 4, none, true, sessionId, sessionIdOff, sessionIdLength, suite)
|
|
866
|
+
const extensions: i32 = p + 2
|
|
867
|
+
p = tlsPutU16(out, extensions, TLS_EXT_KEY_SHARE)
|
|
868
|
+
p = tlsPutU16(out, p, 2)
|
|
869
|
+
p = tlsPutU16(out, p, group)
|
|
870
|
+
p = tlsPutSelectedVersion(out, p)
|
|
871
|
+
tlsPutLength(out, extensions, p, 2)
|
|
872
|
+
tlsPutHeader(out, at, TLS_HANDSHAKE_SERVER_HELLO, p)
|
|
873
|
+
return p
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Writes EncryptedExtensions (§4.3.1) at `out[at]` and answers where it ends.
|
|
878
|
+
* `extra` is extensions the caller encodes itself, written first and as they
|
|
879
|
+
* are; then the chosen ALPN protocol when `alpn` is not empty (RFC 7301 §3.1,
|
|
880
|
+
* a list of exactly one), an empty `server_name` when `acknowledgeServerName`
|
|
881
|
+
* (RFC 6066 §3's acknowledgement), and the server's
|
|
882
|
+
* `quic_transport_parameters` when `quicTransportParameters` is not `null`.
|
|
883
|
+
*/
|
|
884
|
+
export const tlsWriteEncryptedExtensions = (
|
|
885
|
+
out: u8[],
|
|
886
|
+
at: i32,
|
|
887
|
+
extra: u8[],
|
|
888
|
+
alpn: string,
|
|
889
|
+
acknowledgeServerName: boolean,
|
|
890
|
+
quicTransportParameters: u8[] | null
|
|
891
|
+
): i32 => {
|
|
892
|
+
const extensions: i32 = at + 6
|
|
893
|
+
let p: i32 = tlsPutBytes(out, extensions, extra)
|
|
894
|
+
if (toI32(alpn.length) > 0) {
|
|
895
|
+
p = tlsPutU16(out, p, TLS_EXT_ALPN)
|
|
896
|
+
const body: i32 = p + 2
|
|
897
|
+
const list: i32 = body + 2
|
|
898
|
+
const name: i32 = list + 1
|
|
899
|
+
p = tlsPutAscii(out, name, alpn)
|
|
900
|
+
tlsPutLength(out, name, p, 1)
|
|
901
|
+
tlsPutLength(out, list, p, 2)
|
|
902
|
+
tlsPutLength(out, body, p, 2)
|
|
903
|
+
}
|
|
904
|
+
if (acknowledgeServerName) {
|
|
905
|
+
p = tlsPutU16(out, tlsPutU16(out, p, TLS_EXT_SERVER_NAME), 0)
|
|
906
|
+
}
|
|
907
|
+
if (quicTransportParameters !== null) {
|
|
908
|
+
p = tlsPutU16(out, p, TLS_EXT_QUIC_TRANSPORT_PARAMETERS)
|
|
909
|
+
const body: i32 = p + 2
|
|
910
|
+
p = tlsPutBytes(out, body, quicTransportParameters)
|
|
911
|
+
tlsPutLength(out, body, p, 2)
|
|
912
|
+
}
|
|
913
|
+
tlsPutLength(out, extensions, p, 2)
|
|
914
|
+
tlsPutHeader(out, at, TLS_HANDSHAKE_ENCRYPTED_EXTENSIONS, p)
|
|
915
|
+
return p
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Writes a server's Certificate (§4.4.2) at `out[at]` and answers where it
|
|
920
|
+
* ends: an empty request context, then each DER certificate of `chain`, leaf
|
|
921
|
+
* first, with no extensions of its own.
|
|
922
|
+
*/
|
|
923
|
+
export const tlsWriteCertificate = (out: u8[], at: i32, chain: u8[][]): i32 => {
|
|
924
|
+
const list: i32 = tlsPutU8(out, at + 4, 0) + 3
|
|
925
|
+
let p: i32 = list
|
|
926
|
+
for (const certificate of chain) {
|
|
927
|
+
const entry: i32 = p + 3
|
|
928
|
+
p = tlsPutBytes(out, entry, certificate)
|
|
929
|
+
tlsPutLength(out, entry, p, 3)
|
|
930
|
+
p = tlsPutU16(out, p, 0)
|
|
931
|
+
}
|
|
932
|
+
tlsPutLength(out, list, p, 3)
|
|
933
|
+
tlsPutHeader(out, at, TLS_HANDSHAKE_CERTIFICATE, p)
|
|
934
|
+
return p
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/** Writes a CertificateVerify (§4.4.3) at `out[at]`: the signature scheme and the signature as it goes on the wire. */
|
|
938
|
+
export const tlsWriteCertificateVerify = (out: u8[], at: i32, scheme: i32, signature: u8[]): i32 => {
|
|
939
|
+
const body: i32 = tlsPutU16(out, at + 4, scheme) + 2
|
|
940
|
+
const p: i32 = tlsPutBytes(out, body, signature)
|
|
941
|
+
tlsPutLength(out, body, p, 2)
|
|
942
|
+
tlsPutHeader(out, at, TLS_HANDSHAKE_CERTIFICATE_VERIFY, p)
|
|
943
|
+
return p
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/** Writes a Finished (§4.4.4) at `out[at]`, carrying `verifyData[off .. off + len)`. */
|
|
947
|
+
export const tlsWriteFinished = (out: u8[], at: i32, verifyData: u8[], off: i32, len: i32): i32 => {
|
|
948
|
+
const p: i32 = tlsPutWindow(out, at + 4, verifyData, off, len)
|
|
949
|
+
tlsPutHeader(out, at, TLS_HANDSHAKE_FINISHED, p)
|
|
950
|
+
return p
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Writes what a server's CertificateVerify signs (§4.4.3) at `out[at]`: 64
|
|
955
|
+
* spaces, the context string "TLS 1.3, server CertificateVerify", a zero byte,
|
|
956
|
+
* and the transcript hash `transcriptHash[off .. off + len)` through
|
|
957
|
+
* Certificate.
|
|
958
|
+
*/
|
|
959
|
+
export const tlsWriteCertificateVerifyContent = (
|
|
960
|
+
out: u8[],
|
|
961
|
+
at: i32,
|
|
962
|
+
transcriptHash: u8[],
|
|
963
|
+
off: i32,
|
|
964
|
+
len: i32
|
|
965
|
+
): i32 => {
|
|
966
|
+
let p: i32 = at
|
|
967
|
+
for (let k: i32 = 0; k < 64; k++) {
|
|
968
|
+
p = tlsPutU8(out, p, 0x20)
|
|
969
|
+
}
|
|
970
|
+
p = tlsPutAscii(out, p, "TLS 1.3, server CertificateVerify")
|
|
971
|
+
p = tlsPutU8(out, p, 0)
|
|
972
|
+
return tlsPutWindow(out, p, transcriptHash, off, len)
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/** The bytes of `name` as a string of one character a byte, which `tlsWriteEncryptedExtensions` writes back as they were. */
|
|
976
|
+
const tlsBytesString = (name: u8[]): string => tlsWindowString(name, TLS_CODEC_FROM, toI32(name.length))
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* A ServerHello (§4.1.3) choosing `suite` and TLS 1.3, echoing the client's
|
|
980
|
+
* `sessionId`, and carrying the server's x25519 share, in a fresh array
|
|
981
|
+
* (`tlsWriteServerHello`).
|
|
982
|
+
*/
|
|
983
|
+
export const tlsEncodeServerHello = (random: u8[], sessionId: u8[], suite: i32, x25519Public: u8[]): u8[] => {
|
|
984
|
+
const none: u8[] = []
|
|
985
|
+
const sessionIdLength: i32 = toI32(sessionId.length)
|
|
986
|
+
const out: u8[] = new Array<u8>(
|
|
987
|
+
tlsWriteServerHello(
|
|
988
|
+
none,
|
|
989
|
+
TLS_CODEC_FROM,
|
|
990
|
+
random,
|
|
991
|
+
sessionId,
|
|
992
|
+
TLS_CODEC_FROM,
|
|
993
|
+
sessionIdLength,
|
|
994
|
+
suite,
|
|
995
|
+
x25519Public
|
|
996
|
+
)
|
|
997
|
+
)
|
|
998
|
+
tlsWriteServerHello(
|
|
999
|
+
out,
|
|
1000
|
+
TLS_CODEC_FROM,
|
|
1001
|
+
random,
|
|
1002
|
+
sessionId,
|
|
1003
|
+
TLS_CODEC_FROM,
|
|
1004
|
+
sessionIdLength,
|
|
1005
|
+
suite,
|
|
1006
|
+
x25519Public
|
|
1007
|
+
)
|
|
1008
|
+
return out
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/** A HelloRetryRequest (§4.1.4) choosing `suite`, echoing `sessionId` and asking for `group`, in a fresh array (`tlsWriteHelloRetryRequest`). */
|
|
1012
|
+
export const tlsEncodeHelloRetryRequest = (sessionId: u8[], suite: i32, group: i32): u8[] => {
|
|
1013
|
+
const none: u8[] = []
|
|
1014
|
+
const sessionIdLength: i32 = toI32(sessionId.length)
|
|
1015
|
+
const out: u8[] = new Array<u8>(
|
|
1016
|
+
tlsWriteHelloRetryRequest(none, TLS_CODEC_FROM, sessionId, TLS_CODEC_FROM, sessionIdLength, suite, group)
|
|
1017
|
+
)
|
|
1018
|
+
tlsWriteHelloRetryRequest(out, TLS_CODEC_FROM, sessionId, TLS_CODEC_FROM, sessionIdLength, suite, group)
|
|
1019
|
+
return out
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* EncryptedExtensions (§4.3.1) in a fresh array (`tlsWriteEncryptedExtensions`),
|
|
1024
|
+
* the ALPN protocol given as its bytes.
|
|
1025
|
+
*/
|
|
1026
|
+
export const tlsEncodeEncryptedExtensions = (
|
|
1027
|
+
extra: u8[],
|
|
1028
|
+
alpn: u8[],
|
|
1029
|
+
acknowledgeServerName: boolean,
|
|
1030
|
+
quicTransportParameters: u8[] | null
|
|
1031
|
+
): u8[] => {
|
|
1032
|
+
const none: u8[] = []
|
|
1033
|
+
const protocol: string = tlsBytesString(alpn)
|
|
1034
|
+
const out: u8[] = new Array<u8>(
|
|
1035
|
+
tlsWriteEncryptedExtensions(
|
|
1036
|
+
none,
|
|
1037
|
+
TLS_CODEC_FROM,
|
|
1038
|
+
extra,
|
|
1039
|
+
protocol,
|
|
1040
|
+
acknowledgeServerName,
|
|
1041
|
+
quicTransportParameters
|
|
1042
|
+
)
|
|
1043
|
+
)
|
|
1044
|
+
tlsWriteEncryptedExtensions(
|
|
1045
|
+
out,
|
|
1046
|
+
TLS_CODEC_FROM,
|
|
1047
|
+
extra,
|
|
1048
|
+
protocol,
|
|
1049
|
+
acknowledgeServerName,
|
|
1050
|
+
quicTransportParameters
|
|
1051
|
+
)
|
|
1052
|
+
return out
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/** A server's Certificate (§4.4.2) in a fresh array (`tlsWriteCertificate`). */
|
|
1056
|
+
export const tlsEncodeCertificate = (chain: u8[][]): u8[] => {
|
|
1057
|
+
const none: u8[] = []
|
|
1058
|
+
const out: u8[] = new Array<u8>(tlsWriteCertificate(none, TLS_CODEC_FROM, chain))
|
|
1059
|
+
tlsWriteCertificate(out, TLS_CODEC_FROM, chain)
|
|
1060
|
+
return out
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/** A CertificateVerify (§4.4.3) in a fresh array (`tlsWriteCertificateVerify`). */
|
|
1064
|
+
export const tlsEncodeCertificateVerify = (scheme: i32, signature: u8[]): u8[] => {
|
|
1065
|
+
const none: u8[] = []
|
|
1066
|
+
const out: u8[] = new Array<u8>(tlsWriteCertificateVerify(none, TLS_CODEC_FROM, scheme, signature))
|
|
1067
|
+
tlsWriteCertificateVerify(out, TLS_CODEC_FROM, scheme, signature)
|
|
1068
|
+
return out
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/** A Finished (§4.4.4) carrying `verifyData`, in a fresh array (`tlsWriteFinished`). */
|
|
1072
|
+
export const tlsEncodeFinished = (verifyData: u8[]): u8[] => {
|
|
1073
|
+
const none: u8[] = []
|
|
1074
|
+
const length: i32 = toI32(verifyData.length)
|
|
1075
|
+
const out: u8[] = new Array<u8>(tlsWriteFinished(none, TLS_CODEC_FROM, verifyData, TLS_CODEC_FROM, length))
|
|
1076
|
+
tlsWriteFinished(out, TLS_CODEC_FROM, verifyData, TLS_CODEC_FROM, length)
|
|
1077
|
+
return out
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/** What a server's CertificateVerify signs (§4.4.3), in a fresh array (`tlsWriteCertificateVerifyContent`). */
|
|
1081
|
+
export const tlsCertificateVerifyContent = (transcriptHash: u8[]): u8[] => {
|
|
1082
|
+
const none: u8[] = []
|
|
1083
|
+
const length: i32 = toI32(transcriptHash.length)
|
|
1084
|
+
const out: u8[] = new Array<u8>(
|
|
1085
|
+
tlsWriteCertificateVerifyContent(none, TLS_CODEC_FROM, transcriptHash, TLS_CODEC_FROM, length)
|
|
1086
|
+
)
|
|
1087
|
+
tlsWriteCertificateVerifyContent(out, TLS_CODEC_FROM, transcriptHash, TLS_CODEC_FROM, length)
|
|
1088
|
+
return out
|
|
1089
|
+
}
|