@amritk/nish-x86_64-linux 0.15.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 +58 -40
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +67 -10
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +72 -26
- package/runtime/runtime-net.c +72 -19
- package/runtime/runtime-os.c +127 -45
- package/runtime/runtime-parallel.c +5 -1
- package/runtime/runtime-wasm.c +26 -2
- package/runtime/runtime.c +86 -14
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +192 -36
- package/std/crypto/aes.ts +104 -21
- package/std/crypto/base64url.ts +43 -17
- package/std/crypto/chacha20poly1305.ts +52 -31
- package/std/crypto/ct.ts +14 -3
- package/std/crypto/hkdf.ts +385 -8
- package/std/crypto/hmac.ts +325 -9
- package/std/crypto/p256.ts +153 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +15 -3
- package/std/crypto/sha512.ts +16 -2
- package/std/crypto/x25519.ts +90 -37
- package/std/crypto/x509.ts +265 -65
- 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,621 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/tls/record` — the TLS 1.3 record layer (RFC 8446 §5): the
|
|
3
|
+
* framing of a byte stream into records, the protection of one direction of
|
|
4
|
+
* it under a traffic secret, and the next secret a KeyUpdate moves to.
|
|
5
|
+
*
|
|
6
|
+
* import { TlsRecordProtection, TlsRecordReader } from "nish/net/tls/record";
|
|
7
|
+
*
|
|
8
|
+
* const write = new TlsRecordProtection();
|
|
9
|
+
* write.install(TLS_AES_128_GCM_SHA256, serverApplicationSecret);
|
|
10
|
+
* const n: i32 = write.seal(TLS_CONTENT_APPLICATION_DATA, data, 0, len, 0, out, at);
|
|
11
|
+
*
|
|
12
|
+
* **Framing.** A record is a five-byte header — the content type, a legacy
|
|
13
|
+
* version and a 16-bit length — and that many bytes. `TlsRecordReader` holds
|
|
14
|
+
* the bytes of a stream that are not yet a whole record, in a buffer allocated
|
|
15
|
+
* once and big enough for the largest record the RFC allows (2^14 + 256 bytes
|
|
16
|
+
* of body), and says as soon as a header is in whether the record is one this
|
|
17
|
+
* layer will read: a content type it does not know is `unexpected_message`,
|
|
18
|
+
* and a length past the limit is `record_overflow`, before any of the body is
|
|
19
|
+
* waited for. The legacy version is ignored, as §5.1 says it must be.
|
|
20
|
+
*
|
|
21
|
+
* **Protection.** `TlsRecordProtection` is one direction of a connection:
|
|
22
|
+
* cleartext until `install` gives it a suite and a traffic secret, then
|
|
23
|
+
* TLSCiphertext (§5.2) under the AEAD the suite names. Each record's nonce is
|
|
24
|
+
* the traffic IV XORed with the 64-bit record sequence number (§5.3), and its
|
|
25
|
+
* additional data is the record's own header. Inside, the content is followed
|
|
26
|
+
* by its real type and by zero padding (§5.4); `open` finds the type by
|
|
27
|
+
* reading every byte of the decrypted record whatever the padding is, so how
|
|
28
|
+
* much padding there was is not in its timing, and refuses a record that is
|
|
29
|
+
* padding and nothing else. Both `seal` and `open` write into a buffer the
|
|
30
|
+
* caller owns and answer a count, and what they allocate on the way dies with
|
|
31
|
+
* the call (docs/LANGUAGE.md, "Memory model"), so a connection that has
|
|
32
|
+
* finished its handshake moves records without moving the arena.
|
|
33
|
+
*
|
|
34
|
+
* **Refusals are alerts.** Every function that can refuse answers the alert to
|
|
35
|
+
* send, negated, as a negative count: `-TLS_ALERT_BAD_RECORD_MAC` for a record
|
|
36
|
+
* that does not authenticate, `-TLS_ALERT_RECORD_OVERFLOW` for one past the
|
|
37
|
+
* limits, `-TLS_ALERT_UNEXPECTED_MESSAGE` for a type that cannot be here, and
|
|
38
|
+
* `-TLS_ALERT_INTERNAL_ERROR` for a mistake of the caller's — a window outside
|
|
39
|
+
* its array, an output too small, content over 2^14 bytes. Nothing the peer
|
|
40
|
+
* sends can make it panic.
|
|
41
|
+
*
|
|
42
|
+
* **Secrets.** A traffic secret arrives as plain bytes, since that is how
|
|
43
|
+
* `nish/net/tls` hands it over. The key, the IV and the AES key schedule
|
|
44
|
+
* derived from it are held in the protection's fields, because a `Secret` may
|
|
45
|
+
* not be one (NL2430), in arrays the protection reuses for every key it is
|
|
46
|
+
* given, and are zeroed when it is installed again or cleared — the key and
|
|
47
|
+
* IV with `secureZero`. The key and IV are derived straight into place, in an
|
|
48
|
+
* HKDF scratch that is wiped after each derivation, and the schedule is
|
|
49
|
+
* expanded into place by `aesKeyInto`. The schedule is `u64` words, which
|
|
50
|
+
* `secureZero` does not take, and are zeroed by ordinary stores that stand
|
|
51
|
+
* because it stays reachable (TLS-2 in `docs/security/tls.md`).
|
|
52
|
+
*
|
|
53
|
+
* The record layer is sans-IO: `nish/net/tls/record-server` runs it over a
|
|
54
|
+
* stream for `TlsServer`, and `nish/net/tls-tcp` puts that on a socket.
|
|
55
|
+
* Written from RFC 8446 §5, not ported from another implementation.
|
|
56
|
+
*/
|
|
57
|
+
import { AesKey, aesGcmOpen, aesGcmSeal, aesKeyInto } from "nish/crypto/aes"
|
|
58
|
+
import { chacha20Poly1305Open, chacha20Poly1305Seal } from "nish/crypto/chacha20poly1305"
|
|
59
|
+
import {
|
|
60
|
+
TLS_ALERT_INTERNAL_ERROR,
|
|
61
|
+
TLS_ALERT_UNEXPECTED_MESSAGE,
|
|
62
|
+
TLS_LEGACY_VERSION,
|
|
63
|
+
} from "nish/net/tls/codec"
|
|
64
|
+
import { HkdfScratch, hkdfExpandLabelInto } from "nish/crypto/hkdf"
|
|
65
|
+
import { SHA384_SIZE } from "nish/crypto/sha512"
|
|
66
|
+
import {
|
|
67
|
+
TLS_CHACHA20_POLY1305_SHA256,
|
|
68
|
+
TLS_IV_SIZE,
|
|
69
|
+
tlsExpandLabel,
|
|
70
|
+
tlsSuiteHashLength,
|
|
71
|
+
tlsSuiteKeyLength,
|
|
72
|
+
tlsTrafficKeysInto,
|
|
73
|
+
} from "nish/net/tls/schedule"
|
|
74
|
+
|
|
75
|
+
// ---- The wire's constants ------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/** `change_cipher_spec`: the compatibility record of RFC 8446 §D.4, one byte of 1. */
|
|
78
|
+
export const TLS_CONTENT_CHANGE_CIPHER_SPEC: i32 = 20
|
|
79
|
+
/** `alert` (§6): a level and a description. */
|
|
80
|
+
export const TLS_CONTENT_ALERT: i32 = 21
|
|
81
|
+
/** `handshake` (§4). */
|
|
82
|
+
export const TLS_CONTENT_HANDSHAKE: i32 = 22
|
|
83
|
+
/** `application_data`, and the outer type of every protected record. */
|
|
84
|
+
export const TLS_CONTENT_APPLICATION_DATA: i32 = 23
|
|
85
|
+
|
|
86
|
+
/** The bytes before a record's body: its type, a legacy version and a 16-bit length. */
|
|
87
|
+
export const TLS_RECORD_HEADER_SIZE: i32 = 5
|
|
88
|
+
/** The most content one record carries: 2^14 bytes (§5.1). */
|
|
89
|
+
export const TLS_MAX_PLAINTEXT: i32 = 16384
|
|
90
|
+
/** The longest body a protected record may have: 2^14 + 256 bytes (§5.2). */
|
|
91
|
+
export const TLS_MAX_CIPHERTEXT: i32 = 16640
|
|
92
|
+
/** The longest whole record, header included. */
|
|
93
|
+
export const TLS_MAX_RECORD: i32 = 16645
|
|
94
|
+
/** The AEAD tag every protected record ends with; all three suites' is 16 bytes. */
|
|
95
|
+
export const TLS_RECORD_TAG_SIZE: i32 = 16
|
|
96
|
+
/** The longest a decrypted record may be: the content, its type, and its padding (§5.4). */
|
|
97
|
+
const TLS_MAX_INNER_PLAINTEXT: i32 = 16385
|
|
98
|
+
|
|
99
|
+
/** The KeyUpdate handshake message (§4.6.3). */
|
|
100
|
+
export const TLS_HANDSHAKE_KEY_UPDATE: i32 = 24
|
|
101
|
+
|
|
102
|
+
/** `close_notify` (§6.1): the sender writes nothing more. */
|
|
103
|
+
export const TLS_ALERT_CLOSE_NOTIFY: i32 = 0
|
|
104
|
+
/** `bad_record_mac`: a protected record did not authenticate. */
|
|
105
|
+
export const TLS_ALERT_BAD_RECORD_MAC: i32 = 20
|
|
106
|
+
/** `record_overflow`: a record longer than the limits allow. */
|
|
107
|
+
export const TLS_ALERT_RECORD_OVERFLOW: i32 = 22
|
|
108
|
+
/** `user_canceled` (§6.1): a warning that the peer is abandoning the handshake; a close follows. */
|
|
109
|
+
export const TLS_ALERT_USER_CANCELED: i32 = 90
|
|
110
|
+
|
|
111
|
+
/** An alert's level byte for `close_notify` and `user_canceled`. TLS 1.3 reads nothing into it (§6). */
|
|
112
|
+
export const TLS_ALERT_LEVEL_WARNING: i32 = 1
|
|
113
|
+
/** An alert's level byte for every error alert. */
|
|
114
|
+
export const TLS_ALERT_LEVEL_FATAL: i32 = 2
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* How many records one key protects before a writer moves to the next with a
|
|
118
|
+
* KeyUpdate: 2^24. AES-GCM's limit is 2^24.5 full-size records per key (RFC
|
|
119
|
+
* 8446 §5.5), and this is under it with room; ChaCha20-Poly1305's is far past
|
|
120
|
+
* it, and one rule for all three suites keeps the writer simple.
|
|
121
|
+
*/
|
|
122
|
+
export const TLS_KEY_UPDATE_RECORDS: i64 = 16777216
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The sequence number no record is sealed or opened at: 2^53 − 1. The counter
|
|
126
|
+
* must never wrap (§5.3), and stopping here, at a number no connection
|
|
127
|
+
* reaches, keeps the `i64` it is held in far from its own overflow.
|
|
128
|
+
*/
|
|
129
|
+
export const TLS_LAST_SEQUENCE: i64 = 9007199254740991
|
|
130
|
+
|
|
131
|
+
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
132
|
+
const TLS_RECORD_FROM: i32 = 0
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The traffic secret a KeyUpdate moves one direction to (RFC 8446 §7.2):
|
|
136
|
+
* `HKDF-Expand-Label(secret, "traffic upd", "", Hash.length)` over the hash
|
|
137
|
+
* `hashLength` names. The old secret is the caller's to wipe.
|
|
138
|
+
*/
|
|
139
|
+
export const tlsNextTrafficSecret = (hashLength: i32, secret: u8[]): u8[] => {
|
|
140
|
+
const none: u8[] = []
|
|
141
|
+
return tlsExpandLabel(hashLength, secret, "traffic upd", none, hashLength)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Whether `[off, off + len)` is a window inside an array of `length` elements: every caller's range check. */
|
|
145
|
+
export const tlsRecordWindowFits = (length: i32, off: i32, len: i32): boolean =>
|
|
146
|
+
off >= 0 && len >= 0 && off <= length - len
|
|
147
|
+
|
|
148
|
+
/** The five header bytes of a record of `type` and body length `len`, written at `out[at]`. */
|
|
149
|
+
const tlsWriteRecordHeader = (out: u8[], at: i32, type: i32, len: i32): void => {
|
|
150
|
+
if (at < 0 || at > toI32(out.length) - TLS_RECORD_HEADER_SIZE) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
out[at] = toU8(type)
|
|
154
|
+
out[at + 1] = toU8(TLS_LEGACY_VERSION >> 8)
|
|
155
|
+
out[at + 2] = toU8(TLS_LEGACY_VERSION & 255)
|
|
156
|
+
out[at + 3] = toU8(len >> 8)
|
|
157
|
+
out[at + 4] = toU8(len & 255)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The length of the record whose header starts at `buf[off]`, header
|
|
162
|
+
* included, read from the `avail` bytes there: 0 while fewer than five are
|
|
163
|
+
* in, or the alert negated when the header announces a type this layer does
|
|
164
|
+
* not know (`unexpected_message`) or a body past 2^14 + 256 bytes
|
|
165
|
+
* (`record_overflow`, §5.2). The legacy version is not read (§5.1).
|
|
166
|
+
*/
|
|
167
|
+
export const tlsRecordLength = (buf: u8[], off: i32, avail: i32): i32 => {
|
|
168
|
+
if (!tlsRecordWindowFits(toI32(buf.length), off, avail)) {
|
|
169
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
170
|
+
}
|
|
171
|
+
if (avail < TLS_RECORD_HEADER_SIZE) {
|
|
172
|
+
return 0
|
|
173
|
+
}
|
|
174
|
+
const type: i32 = toI32(buf[off])
|
|
175
|
+
if (type < TLS_CONTENT_CHANGE_CIPHER_SPEC || type > TLS_CONTENT_APPLICATION_DATA) {
|
|
176
|
+
return -TLS_ALERT_UNEXPECTED_MESSAGE
|
|
177
|
+
}
|
|
178
|
+
const length: i32 = (toI32(buf[off + 3]) << 8) | toI32(buf[off + 4])
|
|
179
|
+
if (length > TLS_MAX_CIPHERTEXT) {
|
|
180
|
+
return -TLS_ALERT_RECORD_OVERFLOW
|
|
181
|
+
}
|
|
182
|
+
return TLS_RECORD_HEADER_SIZE + length
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Zeroes an AES key schedule's round keys and H with ordinary stores, which
|
|
187
|
+
* stand because the schedule stays reachable for the next key (TLS-2).
|
|
188
|
+
*/
|
|
189
|
+
const tlsClearAesKey = (aes: AesKey): void => {
|
|
190
|
+
for (let k: i32 = 0; k < toI32(aes.roundKeys.length); k++) {
|
|
191
|
+
aes.roundKeys[k] = toU64(0)
|
|
192
|
+
}
|
|
193
|
+
aes.hHi = toU64(0)
|
|
194
|
+
aes.hLo = toU64(0)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The bytes of a stream that are not yet a whole record. The buffer is
|
|
199
|
+
* allocated once, `TLS_MAX_RECORD` bytes, so a reader never grows: `push`
|
|
200
|
+
* takes what fits, and the bytes of a record stay until `consume` lets them
|
|
201
|
+
* go. The caller reads `buffer[start .. start + next())` for the record.
|
|
202
|
+
*/
|
|
203
|
+
export class TlsRecordReader {
|
|
204
|
+
buffer: u8[]
|
|
205
|
+
/** The first byte not yet consumed. */
|
|
206
|
+
start: i32 = 0
|
|
207
|
+
/** One past the last byte pushed. */
|
|
208
|
+
end: i32 = 0
|
|
209
|
+
|
|
210
|
+
constructor() {
|
|
211
|
+
this.buffer = new Array<u8>(TLS_MAX_RECORD)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Forgets every byte, for a connection that starts again in the same slot. */
|
|
215
|
+
reset(): void {
|
|
216
|
+
this.start = 0
|
|
217
|
+
this.end = 0
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** How many bytes `push` would take now. */
|
|
221
|
+
space(): i32 {
|
|
222
|
+
return toI32(this.buffer.length) - (this.end - this.start)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Copies as many of `data[off .. off + len)` as fit and answers how many,
|
|
227
|
+
* after moving what is held to the front. A window outside `data` is
|
|
228
|
+
* `-TLS_ALERT_INTERNAL_ERROR`, and nothing is copied.
|
|
229
|
+
*/
|
|
230
|
+
push(data: u8[], off: i32, len: i32): i32 {
|
|
231
|
+
if (!tlsRecordWindowFits(toI32(data.length), off, len)) {
|
|
232
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
233
|
+
}
|
|
234
|
+
const held: i32 = this.end - this.start
|
|
235
|
+
// The held bytes move to the front only when the new ones would not fit after them.
|
|
236
|
+
if (this.start > 0 && toI32(this.buffer.length) - this.end < len) {
|
|
237
|
+
for (let k: i32 = 0; k < held; k++) {
|
|
238
|
+
this.buffer[k] = this.buffer[this.start + k]
|
|
239
|
+
}
|
|
240
|
+
this.start = 0
|
|
241
|
+
this.end = held
|
|
242
|
+
}
|
|
243
|
+
const room: i32 = toI32(this.buffer.length) - this.end
|
|
244
|
+
const take: i32 = len < room ? len : room
|
|
245
|
+
for (let k: i32 = 0; k < take; k++) {
|
|
246
|
+
this.buffer[this.end + k] = data[off + k]
|
|
247
|
+
}
|
|
248
|
+
this.end = this.end + take
|
|
249
|
+
return take
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* The length of the whole record at `start`, header included, once every
|
|
254
|
+
* byte of it is in; 0 before; or the alert negated for a header this layer
|
|
255
|
+
* refuses, as `tlsRecordLength` says.
|
|
256
|
+
*/
|
|
257
|
+
next(): i32 {
|
|
258
|
+
const avail: i32 = this.end - this.start
|
|
259
|
+
const length: i32 = tlsRecordLength(this.buffer, this.start, avail)
|
|
260
|
+
return length > avail ? 0 : length
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Lets the first `n` held bytes go: the record `next` answered, once it is handled. */
|
|
264
|
+
consume(n: i32): void {
|
|
265
|
+
const held: i32 = this.end - this.start
|
|
266
|
+
let take: i32 = n < held ? n : held
|
|
267
|
+
if (take < 0) {
|
|
268
|
+
take = 0
|
|
269
|
+
}
|
|
270
|
+
this.start = this.start + take
|
|
271
|
+
if (this.start === this.end) {
|
|
272
|
+
this.start = 0
|
|
273
|
+
this.end = 0
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* One direction of a connection's record protection. Cleartext (`suite` 0)
|
|
280
|
+
* until `install`; then every record is sealed or opened under the suite's
|
|
281
|
+
* AEAD with the key and IV of the traffic secret it was given, and its
|
|
282
|
+
* sequence number counts from 0. `contentType` is the type the last `open`
|
|
283
|
+
* found inside a protected record, or the header's type for a cleartext one.
|
|
284
|
+
*
|
|
285
|
+
* Every array it fills is made by the constructor: a key of each length, an
|
|
286
|
+
* AES key schedule of each size, the IV, the next traffic secret of each
|
|
287
|
+
* hash, and the HKDF scratch the keys are derived in. `key` and `aes` point
|
|
288
|
+
* at the ones the installed suite uses, so changing suites allocates nothing.
|
|
289
|
+
*/
|
|
290
|
+
export class TlsRecordProtection {
|
|
291
|
+
/** The AEAD key (§7.3): the suite's key length, zeroed while the direction is cleartext. */
|
|
292
|
+
key: u8[]
|
|
293
|
+
/** The 12-byte traffic IV (§7.3), zeroed while the direction is cleartext. */
|
|
294
|
+
iv: u8[]
|
|
295
|
+
/** The AES key schedule for the two AES-GCM suites, the one of the installed key's size. */
|
|
296
|
+
aes: AesKey | null = null
|
|
297
|
+
/** The next record's sequence number (§5.3). */
|
|
298
|
+
sequence: i64 = 0
|
|
299
|
+
/**
|
|
300
|
+
* The sequence number at which a writer moves to the next key. Readable and
|
|
301
|
+
* writable, so a test can bring the key update forward; a reader ignores it.
|
|
302
|
+
*/
|
|
303
|
+
recordLimit: i64 = 0
|
|
304
|
+
/** The negotiated suite, or 0 while the direction is cleartext. */
|
|
305
|
+
suite: i32 = 0
|
|
306
|
+
/** The real content type of the last record `open` answered. */
|
|
307
|
+
contentType: i32 = 0
|
|
308
|
+
/** The arrays `key` and `aes` point into: AES-128's and ChaCha20's or AES-256's. */
|
|
309
|
+
key16: u8[]
|
|
310
|
+
key32: u8[]
|
|
311
|
+
aes128: AesKey
|
|
312
|
+
aes256: AesKey
|
|
313
|
+
/** Where `advance` derives the next traffic secret: HashLen bytes of it. */
|
|
314
|
+
next: u8[]
|
|
315
|
+
/** Where the keys are derived. */
|
|
316
|
+
kdf: HkdfScratch
|
|
317
|
+
|
|
318
|
+
constructor() {
|
|
319
|
+
this.key16 = new Array<u8>(16)
|
|
320
|
+
this.key32 = new Array<u8>(32)
|
|
321
|
+
this.key = this.key16
|
|
322
|
+
this.iv = new Array<u8>(TLS_IV_SIZE)
|
|
323
|
+
this.aes128 = new AesKey(10, new Array<u64>(88))
|
|
324
|
+
this.aes256 = new AesKey(14, new Array<u64>(120))
|
|
325
|
+
this.next = new Array<u8>(SHA384_SIZE)
|
|
326
|
+
this.kdf = new HkdfScratch()
|
|
327
|
+
this.recordLimit = TLS_KEY_UPDATE_RECORDS
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Whether records in this direction are protected. */
|
|
331
|
+
isProtected(): boolean {
|
|
332
|
+
return this.suite !== 0
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Back to cleartext, with the sequence number at 0 and both keys, the IV
|
|
337
|
+
* and both AES key schedules zeroed: the keys and IV with `secureZero`,
|
|
338
|
+
* and the schedules' `u64` words with ordinary stores, which stand because
|
|
339
|
+
* the schedules stay reachable for the next `install` to fill (TLS-2).
|
|
340
|
+
*/
|
|
341
|
+
clear(): void {
|
|
342
|
+
secureZero(this.key16)
|
|
343
|
+
secureZero(this.key32)
|
|
344
|
+
secureZero(this.iv)
|
|
345
|
+
tlsClearAesKey(this.aes128)
|
|
346
|
+
tlsClearAesKey(this.aes256)
|
|
347
|
+
this.suite = 0
|
|
348
|
+
this.sequence = 0
|
|
349
|
+
this.contentType = 0
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Protects this direction under `secret` from the next record on: the
|
|
354
|
+
* suite's key and IV (§7.3), the sequence number back to 0, and what this
|
|
355
|
+
* replaces zeroed by `clear`. `false`, leaving the direction cleartext, for
|
|
356
|
+
* a suite `nish/net/tls/schedule` does not know or a secret that is not its
|
|
357
|
+
* hash's length.
|
|
358
|
+
*
|
|
359
|
+
* The key, the IV and the AES key schedule are written into the arrays this
|
|
360
|
+
* direction made when it was constructed, so an install allocates nothing
|
|
361
|
+
* that outlives it (TLS-3).
|
|
362
|
+
*/
|
|
363
|
+
install(suite: i32, secret: u8[]): boolean {
|
|
364
|
+
this.clear()
|
|
365
|
+
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
366
|
+
if (hashLength === 0 || toI32(secret.length) !== hashLength) {
|
|
367
|
+
return false
|
|
368
|
+
}
|
|
369
|
+
this.reserve(suite)
|
|
370
|
+
this.derive(suite, secret)
|
|
371
|
+
this.suite = suite
|
|
372
|
+
return true
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Points `key`, and for AES `aes`, at the arrays of the size `suite` needs. */
|
|
376
|
+
reserve(suite: i32): void {
|
|
377
|
+
this.key = tlsSuiteKeyLength(suite) === 16 ? this.key16 : this.key32
|
|
378
|
+
if (suite !== TLS_CHACHA20_POLY1305_SHA256) {
|
|
379
|
+
this.aes = tlsSuiteKeyLength(suite) === 16 ? this.aes128 : this.aes256
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Fills the key, the IV and the AES key schedule from `secret`, into the
|
|
385
|
+
* arrays `reserve` chose: the key and IV derived straight into place in the
|
|
386
|
+
* direction's own HKDF scratch, which is wiped after, and the schedule
|
|
387
|
+
* expanded into this direction's `AesKey` by `aesKeyInto`. All of it runs
|
|
388
|
+
* inside an arena block, so a key install leaves the arena where it was.
|
|
389
|
+
* ChaCha20 has no schedule.
|
|
390
|
+
*/
|
|
391
|
+
derive(suite: i32, secret: u8[]): void {
|
|
392
|
+
using _scope = arena()
|
|
393
|
+
tlsTrafficKeysInto(this.kdf, suite, secret, this.key, this.iv)
|
|
394
|
+
const aes: AesKey | null = this.aes
|
|
395
|
+
if (aes !== null && suite !== TLS_CHACHA20_POLY1305_SHA256) {
|
|
396
|
+
aesKeyInto(this.key, aes)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* The AES key schedule when the suite is an AES-GCM one, `null` for
|
|
402
|
+
* ChaCha20: a protection that carried AES keeps its schedule for the next
|
|
403
|
+
* AES key, and must not use it for ChaCha20.
|
|
404
|
+
*/
|
|
405
|
+
aesForSuite(): AesKey | null {
|
|
406
|
+
return this.suite === TLS_CHACHA20_POLY1305_SHA256 ? null : this.aes
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Moves this direction to the next traffic secret (§7.2), the KeyUpdate
|
|
411
|
+
* step: `secret`, the current one, is overwritten in place by the next,
|
|
412
|
+
* and the keys are installed from it; the next secret's own copy is wiped.
|
|
413
|
+
* The next secret is derived in place inside an arena block, as `derive`'s
|
|
414
|
+
* keys are. False, as `install` answers, when the keys do not install.
|
|
415
|
+
*/
|
|
416
|
+
advance(secret: u8[]): boolean {
|
|
417
|
+
const suite: i32 = this.suite
|
|
418
|
+
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
419
|
+
if (hashLength === 0) {
|
|
420
|
+
return false
|
|
421
|
+
}
|
|
422
|
+
const next: u8[] = this.next
|
|
423
|
+
{
|
|
424
|
+
using _scope = arena()
|
|
425
|
+
const none: u8[] = []
|
|
426
|
+
hkdfExpandLabelInto(
|
|
427
|
+
this.kdf,
|
|
428
|
+
hashLength,
|
|
429
|
+
secret,
|
|
430
|
+
"traffic upd",
|
|
431
|
+
none,
|
|
432
|
+
TLS_RECORD_FROM,
|
|
433
|
+
TLS_RECORD_FROM,
|
|
434
|
+
next,
|
|
435
|
+
TLS_RECORD_FROM,
|
|
436
|
+
hashLength
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
for (let k: i32 = 0; k < toI32(secret.length) && k < hashLength && k < toI32(next.length); k++) {
|
|
440
|
+
secret[k] = next[k]
|
|
441
|
+
}
|
|
442
|
+
secureZero(next)
|
|
443
|
+
return this.install(suite, secret)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** The per-record nonce (§5.3): the IV with the sequence number XORed into its last eight bytes, big-endian. */
|
|
447
|
+
nonceInto(nonce: u8[]): void {
|
|
448
|
+
for (let k: i32 = 0; k < TLS_IV_SIZE && k < toI32(nonce.length) && k < toI32(this.iv.length); k++) {
|
|
449
|
+
const shift: i32 = (TLS_IV_SIZE - 1 - k) << 3
|
|
450
|
+
const byte: i32 = shift < 64 ? toI32((this.sequence >> toI64(shift)) & toI64(255)) : 0
|
|
451
|
+
nonce[k] = toU8(toI32(this.iv[k]) ^ byte)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Writes one record of content type `type` holding `data[off .. off + len)`
|
|
457
|
+
* at `out[at]` and answers its length, header included. Cleartext, it is a
|
|
458
|
+
* TLSPlaintext, `padding` must be 0 and `type` may not be
|
|
459
|
+
* `application_data`; protected, it is a TLSCiphertext
|
|
460
|
+
* whose inner plaintext carries `type` and then `padding` zero bytes
|
|
461
|
+
* (§5.4), and the sequence number moves on. `-TLS_ALERT_INTERNAL_ERROR`,
|
|
462
|
+
* with nothing written, for content over 2^14 bytes, a padding that would
|
|
463
|
+
* take the inner plaintext past 2^14 + 1, a window outside `data`, too
|
|
464
|
+
* little room at `out[at]`, or a sequence number that would wrap.
|
|
465
|
+
*/
|
|
466
|
+
seal(type: i32, data: u8[], off: i32, len: i32, padding: i32, out: u8[], at: i32): i32 {
|
|
467
|
+
if (!tlsRecordWindowFits(toI32(data.length), off, len) || len > TLS_MAX_PLAINTEXT || padding < 0) {
|
|
468
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
469
|
+
}
|
|
470
|
+
if (this.suite === 0) {
|
|
471
|
+
// Application data is never sent in the clear, as `open` never reads
|
|
472
|
+
// it so: a cleartext direction asked for some has lost its keys.
|
|
473
|
+
if (
|
|
474
|
+
type === TLS_CONTENT_APPLICATION_DATA ||
|
|
475
|
+
padding !== 0 ||
|
|
476
|
+
!tlsRecordWindowFits(toI32(out.length), at, TLS_RECORD_HEADER_SIZE + len)
|
|
477
|
+
) {
|
|
478
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
479
|
+
}
|
|
480
|
+
tlsWriteRecordHeader(out, at, type, len)
|
|
481
|
+
for (let k: i32 = 0; k < len; k++) {
|
|
482
|
+
out[at + TLS_RECORD_HEADER_SIZE + k] = data[off + k]
|
|
483
|
+
}
|
|
484
|
+
return TLS_RECORD_HEADER_SIZE + len
|
|
485
|
+
}
|
|
486
|
+
if (padding > TLS_MAX_INNER_PLAINTEXT - 1 - len || this.sequence === TLS_LAST_SEQUENCE) {
|
|
487
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
488
|
+
}
|
|
489
|
+
const inner: i32 = len + 1 + padding
|
|
490
|
+
const body: i32 = inner + TLS_RECORD_TAG_SIZE
|
|
491
|
+
if (!tlsRecordWindowFits(toI32(out.length), at, TLS_RECORD_HEADER_SIZE + body)) {
|
|
492
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
493
|
+
}
|
|
494
|
+
const plaintext: u8[] = new Array<u8>(inner)
|
|
495
|
+
for (let k: i32 = 0; k < len && k < toI32(plaintext.length); k++) {
|
|
496
|
+
plaintext[k] = data[off + k]
|
|
497
|
+
}
|
|
498
|
+
plaintext[len] = toU8(type)
|
|
499
|
+
const aad: u8[] = new Array<u8>(5)
|
|
500
|
+
tlsWriteRecordHeader(aad, TLS_RECORD_FROM, TLS_CONTENT_APPLICATION_DATA, body)
|
|
501
|
+
const nonce: u8[] = new Array<u8>(12)
|
|
502
|
+
this.nonceInto(nonce)
|
|
503
|
+
const aes: AesKey | null = this.aesForSuite()
|
|
504
|
+
const sealed: u8[] | null =
|
|
505
|
+
aes !== null
|
|
506
|
+
? aesGcmSeal(aes, nonce, aad, plaintext)
|
|
507
|
+
: chacha20Poly1305Seal(this.key, nonce, aad, plaintext)
|
|
508
|
+
// The nonce is the IV under a public sequence number: wiped as the IV is.
|
|
509
|
+
secureZero(nonce)
|
|
510
|
+
if (sealed === null || toI32(sealed.length) !== body) {
|
|
511
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
512
|
+
}
|
|
513
|
+
tlsWriteRecordHeader(out, at, TLS_CONTENT_APPLICATION_DATA, body)
|
|
514
|
+
for (let k: i32 = 0; k < body && k < toI32(sealed.length); k++) {
|
|
515
|
+
out[at + TLS_RECORD_HEADER_SIZE + k] = sealed[k]
|
|
516
|
+
}
|
|
517
|
+
this.sequence = this.sequence + toI64(1)
|
|
518
|
+
return TLS_RECORD_HEADER_SIZE + body
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Reads the whole record `record[off .. off + len)`, header included, and
|
|
523
|
+
* writes its content at `out[at]`: answers the content's length and sets
|
|
524
|
+
* `contentType`. Cleartext, the content is the body and the type the
|
|
525
|
+
* header's: `application_data` is `unexpected_message`, since it is never
|
|
526
|
+
* sent in the clear, and a body over 2^14 bytes is `record_overflow`.
|
|
527
|
+
* Protected, the header's type must be `application_data`
|
|
528
|
+
* (`unexpected_message` otherwise), the body must authenticate under this
|
|
529
|
+
* record's nonce with the header as additional data (`bad_record_mac`), the
|
|
530
|
+
* decrypted record may be no longer than 2^14 + 1 bytes
|
|
531
|
+
* (`record_overflow`), and its last non-zero byte is the real type — one
|
|
532
|
+
* that is all zeros is `unexpected_message` (§5.4). Every refusal is the
|
|
533
|
+
* alert negated; a window outside `record`, a `len` that is not the
|
|
534
|
+
* header's, or too little room at `out[at]` is `-TLS_ALERT_INTERNAL_ERROR`.
|
|
535
|
+
*/
|
|
536
|
+
open(record: u8[], off: i32, len: i32, out: u8[], at: i32): i32 {
|
|
537
|
+
if (!tlsRecordWindowFits(toI32(record.length), off, len) || len < TLS_RECORD_HEADER_SIZE) {
|
|
538
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
539
|
+
}
|
|
540
|
+
const declared: i32 = tlsRecordLength(record, off, len)
|
|
541
|
+
if (declared < 0) {
|
|
542
|
+
return declared
|
|
543
|
+
}
|
|
544
|
+
if (declared !== len) {
|
|
545
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
546
|
+
}
|
|
547
|
+
const type: i32 = toI32(record[off])
|
|
548
|
+
const body: i32 = len - TLS_RECORD_HEADER_SIZE
|
|
549
|
+
if (this.suite === 0) {
|
|
550
|
+
if (type === TLS_CONTENT_APPLICATION_DATA) {
|
|
551
|
+
return -TLS_ALERT_UNEXPECTED_MESSAGE
|
|
552
|
+
}
|
|
553
|
+
if (body > TLS_MAX_PLAINTEXT) {
|
|
554
|
+
return -TLS_ALERT_RECORD_OVERFLOW
|
|
555
|
+
}
|
|
556
|
+
if (!tlsRecordWindowFits(toI32(out.length), at, body)) {
|
|
557
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
558
|
+
}
|
|
559
|
+
for (let k: i32 = 0; k < body; k++) {
|
|
560
|
+
out[at + k] = record[off + TLS_RECORD_HEADER_SIZE + k]
|
|
561
|
+
}
|
|
562
|
+
this.contentType = type
|
|
563
|
+
return body
|
|
564
|
+
}
|
|
565
|
+
if (type !== TLS_CONTENT_APPLICATION_DATA) {
|
|
566
|
+
return -TLS_ALERT_UNEXPECTED_MESSAGE
|
|
567
|
+
}
|
|
568
|
+
if (body < TLS_RECORD_TAG_SIZE + 1) {
|
|
569
|
+
return -TLS_ALERT_BAD_RECORD_MAC
|
|
570
|
+
}
|
|
571
|
+
if (this.sequence === TLS_LAST_SEQUENCE) {
|
|
572
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
573
|
+
}
|
|
574
|
+
const sealed: u8[] = new Array<u8>(body)
|
|
575
|
+
for (let k: i32 = 0; k < body && k < toI32(sealed.length); k++) {
|
|
576
|
+
sealed[k] = record[off + TLS_RECORD_HEADER_SIZE + k]
|
|
577
|
+
}
|
|
578
|
+
const aad: u8[] = new Array<u8>(5)
|
|
579
|
+
for (let k: i32 = 0; k < TLS_RECORD_HEADER_SIZE && k < toI32(aad.length); k++) {
|
|
580
|
+
aad[k] = record[off + k]
|
|
581
|
+
}
|
|
582
|
+
const nonce: u8[] = new Array<u8>(12)
|
|
583
|
+
this.nonceInto(nonce)
|
|
584
|
+
const aes: AesKey | null = this.aesForSuite()
|
|
585
|
+
const inner: u8[] | null =
|
|
586
|
+
aes !== null ? aesGcmOpen(aes, nonce, aad, sealed) : chacha20Poly1305Open(this.key, nonce, aad, sealed)
|
|
587
|
+
secureZero(nonce)
|
|
588
|
+
if (inner === null) {
|
|
589
|
+
return -TLS_ALERT_BAD_RECORD_MAC
|
|
590
|
+
}
|
|
591
|
+
// The record authenticated, so it is the peer's, and its number is spent
|
|
592
|
+
// whatever its content turns out to be.
|
|
593
|
+
this.sequence = this.sequence + toI64(1)
|
|
594
|
+
const innerLength: i32 = toI32(inner.length)
|
|
595
|
+
if (innerLength > TLS_MAX_INNER_PLAINTEXT) {
|
|
596
|
+
return -TLS_ALERT_RECORD_OVERFLOW
|
|
597
|
+
}
|
|
598
|
+
// The last non-zero byte and where it is, found by reading every byte
|
|
599
|
+
// and keeping each non-zero one by mask, so neither the time nor the
|
|
600
|
+
// addresses read depend on how much of the record was padding.
|
|
601
|
+
let last: i32 = -1
|
|
602
|
+
let found: i32 = 0
|
|
603
|
+
for (let k: i32 = 0; k < innerLength; k++) {
|
|
604
|
+
const b: i32 = toI32(inner[k])
|
|
605
|
+
const keep: i32 = (0 - b) >> 31
|
|
606
|
+
last = (k & keep) | (last & ~keep)
|
|
607
|
+
found = (b & keep) | (found & ~keep)
|
|
608
|
+
}
|
|
609
|
+
if (last < 0) {
|
|
610
|
+
return -TLS_ALERT_UNEXPECTED_MESSAGE
|
|
611
|
+
}
|
|
612
|
+
if (!tlsRecordWindowFits(toI32(out.length), at, last)) {
|
|
613
|
+
return -TLS_ALERT_INTERNAL_ERROR
|
|
614
|
+
}
|
|
615
|
+
for (let k: i32 = 0; k < last && k < innerLength; k++) {
|
|
616
|
+
out[at + k] = inner[k]
|
|
617
|
+
}
|
|
618
|
+
this.contentType = found
|
|
619
|
+
return last
|
|
620
|
+
}
|
|
621
|
+
}
|