@amritk/nish-x86_64-linux 0.18.0 → 0.20.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 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +215 -18
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1120 -34
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/threads.ts +438 -14
package/std/net/tls/schedule.ts
CHANGED
|
@@ -13,6 +13,17 @@
|
|
|
13
13
|
* master = HKDF-Extract(Derive-Secret(handshake, "derived", ""), 0)
|
|
14
14
|
* c/s ap traffic, exporter = Derive-Secret(master, "c ap traffic" / "s ap traffic" / "exp master", CH..server Finished)
|
|
15
15
|
*
|
|
16
|
+
* A resumed handshake (RFC 8446 §2.2) starts from the ticket's PSK instead,
|
|
17
|
+
* and binds the ClientHello to it before anything else is derived:
|
|
18
|
+
*
|
|
19
|
+
* PSK = HKDF-Expand-Label(resumption master, "resumption", ticket_nonce, HashLen)
|
|
20
|
+
* early = HKDF-Extract(0, PSK)
|
|
21
|
+
* binder key = Derive-Secret(early, "res binder", "")
|
|
22
|
+
* binder = HMAC(Expand-Label(binder key, "finished", "", HashLen), Hash(truncated ClientHello))
|
|
23
|
+
* handshake = HKDF-Extract(Derive-Secret(early, "derived", ""), ECDHE)
|
|
24
|
+
*
|
|
25
|
+
* and goes on from the handshake secret as the full handshake does.
|
|
26
|
+
*
|
|
16
27
|
* Every function takes the hash length (32 or 48) rather than a suite, because
|
|
17
28
|
* that is all that changes between them; `tlsSuiteHashLength` turns a suite
|
|
18
29
|
* into one. The traffic key and IV are here too, because T2's record layer
|
|
@@ -25,27 +36,50 @@
|
|
|
25
36
|
* schedule inside `using a = arena()` blocks and leave the arena where it was
|
|
26
37
|
* (TLS-3 in `docs/security/tls.md`). The scratch is wiped by every step.
|
|
27
38
|
*
|
|
28
|
-
* **
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* `
|
|
39
|
+
* **Held.** Each step also has a `*Held` form that reads its secrets from
|
|
40
|
+
* `Secret`s the caller holds and writes the secret it derives over the bytes
|
|
41
|
+
* of one, in place, so a connection keeps each stage of the schedule in one
|
|
42
|
+
* `Secret` for its life and ratchets it: `tlsDeriveSecretHeld`,
|
|
43
|
+
* `tlsHandshakeSecretHeld`, `tlsMasterSecretHeld`, and the two that answer
|
|
44
|
+
* plain bytes, `tlsFinishedVerifyDataHeld` (`verify_data` goes on the wire)
|
|
45
|
+
* and `tlsTrafficKeysFromHeld` (the cipher reads the key and IV). Every
|
|
46
|
+
* plain intermediate is wiped before it returns.
|
|
47
|
+
*
|
|
48
|
+
* **Secrets are `Secret`s.** The one-shot functions take every secret they
|
|
49
|
+
* read (a traffic secret, the ECDHE secret, a stage of the schedule) as a
|
|
50
|
+
* `Secret<u8[]>` (`nish:secret`) and answer every secret they derive as one;
|
|
51
|
+
* each reads its key only inside `exposeWith` and wipes the intermediates it
|
|
52
|
+
* made (the "derived" salts, the `finished_key`, the zero inputs) before it
|
|
53
|
+
* returns, and HMAC wipes its key blocks. The caller owns what it is handed
|
|
54
|
+
* and wipes it. What a `Secret` cannot reach is TLS-1 in
|
|
55
|
+
* `docs/security/tls.md`. The transcript hashes, the labels and a Finished
|
|
56
|
+
* message's `verify_data` are public and stay plain.
|
|
33
57
|
*
|
|
34
58
|
* Written from RFC 8446, not ported from another implementation.
|
|
35
59
|
*/
|
|
36
60
|
import {
|
|
37
61
|
HkdfScratch,
|
|
62
|
+
hkdfExpandLabelHeldInto,
|
|
38
63
|
hkdfExpandLabelInto,
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
hkdfExpandLabelSha256Held,
|
|
65
|
+
hkdfExpandLabelSha256Secret,
|
|
66
|
+
hkdfExpandLabelSha384Held,
|
|
67
|
+
hkdfExpandLabelSha384Secret,
|
|
41
68
|
hkdfExtractInto,
|
|
42
|
-
hkdfExtractSha256,
|
|
43
|
-
hkdfExtractSha384,
|
|
44
69
|
} from "nish/crypto/hkdf"
|
|
45
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
hmacCopySha256,
|
|
72
|
+
hmacCopySha384,
|
|
73
|
+
hmacSha256,
|
|
74
|
+
hmacSha256Secret,
|
|
75
|
+
hmacSha384,
|
|
76
|
+
hmacSha384Secret,
|
|
77
|
+
} from "nish/crypto/hmac"
|
|
46
78
|
import { tlsHexInto } from "nish/net/tls/codec"
|
|
79
|
+
import { timingSafeEqualAt } from "nish/crypto/ct"
|
|
47
80
|
import { SHA256_SIZE, Sha256, sha256 } from "nish/crypto/sha256"
|
|
48
81
|
import { SHA384_SIZE, Sha384, sha384 } from "nish/crypto/sha512"
|
|
82
|
+
import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
|
|
49
83
|
|
|
50
84
|
/** `TLS_AES_128_GCM_SHA256` (RFC 8446 §B.4). */
|
|
51
85
|
export const TLS_AES_128_GCM_SHA256: i32 = 0x1301
|
|
@@ -94,23 +128,50 @@ export const tlsSuiteKeyLength = (suite: i32): i32 => {
|
|
|
94
128
|
|
|
95
129
|
/**
|
|
96
130
|
* HKDF-Expand-Label (RFC 8446 §7.1) over the hash `hashLength` names: `length`
|
|
97
|
-
* bytes from `
|
|
98
|
-
* added) and `context`.
|
|
131
|
+
* bytes from `prk` (RFC 8446's Secret), the label (without its `"tls13 "`
|
|
132
|
+
* prefix, which is added) and `context`, held as a `Secret`.
|
|
99
133
|
*/
|
|
100
134
|
export const tlsExpandLabel = (
|
|
101
135
|
hashLength: i32,
|
|
102
|
-
|
|
136
|
+
prk: Secret<u8[]>,
|
|
103
137
|
label: string,
|
|
104
138
|
context: u8[],
|
|
105
139
|
length: i32
|
|
106
|
-
): u8[] =>
|
|
107
|
-
hashLength === SHA384_SIZE
|
|
108
|
-
|
|
109
|
-
|
|
140
|
+
): Secret<u8[]> => {
|
|
141
|
+
if (hashLength === SHA384_SIZE) {
|
|
142
|
+
return hkdfExpandLabelSha384Secret(prk, label, context, length)
|
|
143
|
+
}
|
|
144
|
+
return hkdfExpandLabelSha256Secret(prk, label, context, length)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** HMAC-SHA-256 keyed with the plain salt over the plain IKM, for the inner `exposeWith` of `tlsExtract` to run. */
|
|
148
|
+
const tlsExtractSha256Exposed = (ikm: u8[], salt: u8[]): u8[] => hmacSha256(salt, ikm)
|
|
149
|
+
|
|
150
|
+
/** HMAC-SHA-384 keyed with the plain salt over the plain IKM, as the SHA-256 one is. */
|
|
151
|
+
const tlsExtractSha384Exposed = (ikm: u8[], salt: u8[]): u8[] => hmacSha384(salt, ikm)
|
|
110
152
|
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
153
|
+
/** Exposes the IKM beside the already exposed salt: the outer `exposeWith` of `tlsExtract` over SHA-256. */
|
|
154
|
+
const tlsExtractSha256Salted = (salt: u8[], ikm: Secret<u8[]>): u8[] =>
|
|
155
|
+
exposeWith(ikm, salt, tlsExtractSha256Exposed)
|
|
156
|
+
|
|
157
|
+
/** Exposes the IKM beside the already exposed salt, as the SHA-256 one does. */
|
|
158
|
+
const tlsExtractSha384Salted = (salt: u8[], ikm: Secret<u8[]>): u8[] =>
|
|
159
|
+
exposeWith(ikm, salt, tlsExtractSha384Exposed)
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* HKDF-Extract (RFC 5869 §2.2) over the hash `hashLength` names, held as a
|
|
163
|
+
* `Secret`. Both inputs are `Secret`s, since the schedule salts every extract
|
|
164
|
+
* after the first with a derived secret; an empty salt keys HMAC as HashLen
|
|
165
|
+
* zeros do.
|
|
166
|
+
*/
|
|
167
|
+
export const tlsExtract = (hashLength: i32, salt: Secret<u8[]>, ikm: Secret<u8[]>): Secret<u8[]> => {
|
|
168
|
+
if (hashLength === SHA384_SIZE) {
|
|
169
|
+
const prk384: u8[] = exposeWith(salt, ikm, tlsExtractSha384Salted)
|
|
170
|
+
return secret(prk384)
|
|
171
|
+
}
|
|
172
|
+
const prk: u8[] = exposeWith(salt, ikm, tlsExtractSha256Salted)
|
|
173
|
+
return secret(prk)
|
|
174
|
+
}
|
|
114
175
|
|
|
115
176
|
/** The hash of the empty string, the transcript `Derive-Secret(., "derived", "")` hashes. */
|
|
116
177
|
export const tlsEmptyHash = (hashLength: i32): u8[] => {
|
|
@@ -118,59 +179,133 @@ export const tlsEmptyHash = (hashLength: i32): u8[] => {
|
|
|
118
179
|
return hashLength === SHA384_SIZE ? sha384(none) : sha256(none)
|
|
119
180
|
}
|
|
120
181
|
|
|
121
|
-
/** Derive-Secret (RFC 8446 §7.1): Expand-Label of `
|
|
122
|
-
export const tlsDeriveSecret = (
|
|
123
|
-
|
|
182
|
+
/** Derive-Secret (RFC 8446 §7.1): Expand-Label of `prk` with `label` and a transcript hash, HashLen bytes held as a `Secret`. */
|
|
183
|
+
export const tlsDeriveSecret = (
|
|
184
|
+
hashLength: i32,
|
|
185
|
+
prk: Secret<u8[]>,
|
|
186
|
+
label: string,
|
|
187
|
+
transcriptHash: u8[]
|
|
188
|
+
): Secret<u8[]> => tlsExpandLabel(hashLength, prk, label, transcriptHash, hashLength)
|
|
124
189
|
|
|
125
190
|
/** The early secret with no PSK: HKDF-Extract of HashLen zeros under a zero salt (RFC 8446 §7.1). */
|
|
126
|
-
export const tlsEarlySecret = (hashLength: i32): u8[] => {
|
|
127
|
-
const
|
|
128
|
-
|
|
191
|
+
export const tlsEarlySecret = (hashLength: i32): Secret<u8[]> => {
|
|
192
|
+
const salt: Secret<u8[]> = secret(new Array<u8>(hashLength))
|
|
193
|
+
const ikm: Secret<u8[]> = secret(new Array<u8>(hashLength))
|
|
194
|
+
const early: Secret<u8[]> = tlsExtract(hashLength, salt, ikm)
|
|
195
|
+
wipe(salt)
|
|
196
|
+
wipe(ikm)
|
|
197
|
+
return early
|
|
129
198
|
}
|
|
130
199
|
|
|
131
|
-
/**
|
|
132
|
-
|
|
133
|
-
|
|
200
|
+
/**
|
|
201
|
+
* The early secret of a resumed handshake: HKDF-Extract of the PSK under a
|
|
202
|
+
* zero salt (RFC 8446 §7.1). The zero salt is wiped before it returns; the
|
|
203
|
+
* PSK is the caller's.
|
|
204
|
+
*/
|
|
205
|
+
export const tlsPskEarlySecret = (hashLength: i32, psk: Secret<u8[]>): Secret<u8[]> => {
|
|
206
|
+
const salt: Secret<u8[]> = secret(new Array<u8>(hashLength))
|
|
207
|
+
const early: Secret<u8[]> = tlsExtract(hashLength, salt, psk)
|
|
208
|
+
wipe(salt)
|
|
209
|
+
return early
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** The binder key of a resumption PSK: `Derive-Secret(early, "res binder", "")` (RFC 8446 §7.1). */
|
|
213
|
+
export const tlsBinderKey = (hashLength: i32, earlySecret: Secret<u8[]>): Secret<u8[]> =>
|
|
214
|
+
tlsDeriveSecret(hashLength, earlySecret, "res binder", tlsEmptyHash(hashLength))
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The salt the handshake secret is extracted under, `Derive-Secret(early,
|
|
218
|
+
* "derived", "")` (RFC 8446 §7.1): what `tlsHandshakeSecret` derives and
|
|
219
|
+
* wipes itself, answered for a caller that extracts in place.
|
|
220
|
+
*/
|
|
221
|
+
export const tlsHandshakeSalt = (hashLength: i32, earlySecret: Secret<u8[]>): Secret<u8[]> =>
|
|
222
|
+
tlsDeriveSecret(hashLength, earlySecret, "derived", tlsEmptyHash(hashLength))
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The PSK a ticket stands for: `HKDF-Expand-Label(resumption_master_secret,
|
|
226
|
+
* "resumption", ticket_nonce, HashLen)` (RFC 8446 §4.6.1).
|
|
227
|
+
*/
|
|
228
|
+
export const tlsResumptionPsk = (
|
|
229
|
+
hashLength: i32,
|
|
230
|
+
resumptionMasterSecret: Secret<u8[]>,
|
|
231
|
+
ticketNonce: u8[]
|
|
232
|
+
): Secret<u8[]> => tlsExpandLabel(hashLength, resumptionMasterSecret, "resumption", ticketNonce, hashLength)
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The handshake secret: HKDF-Extract of the ECDHE secret under
|
|
236
|
+
* `Derive-Secret(early, "derived", "")`, which is wiped before it returns.
|
|
237
|
+
*/
|
|
238
|
+
export const tlsHandshakeSecret = (
|
|
239
|
+
hashLength: i32,
|
|
240
|
+
earlySecret: Secret<u8[]>,
|
|
241
|
+
ecdhe: Secret<u8[]>
|
|
242
|
+
): Secret<u8[]> => {
|
|
243
|
+
const derived: Secret<u8[]> = tlsDeriveSecret(hashLength, earlySecret, "derived", tlsEmptyHash(hashLength))
|
|
244
|
+
const handshake: Secret<u8[]> = tlsExtract(hashLength, derived, ecdhe)
|
|
245
|
+
wipe(derived)
|
|
246
|
+
return handshake
|
|
247
|
+
}
|
|
134
248
|
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
249
|
+
/**
|
|
250
|
+
* The master secret: HKDF-Extract of HashLen zeros under
|
|
251
|
+
* `Derive-Secret(handshake, "derived", "")`, which is wiped before it returns.
|
|
252
|
+
*/
|
|
253
|
+
export const tlsMasterSecret = (hashLength: i32, handshakeSecret: Secret<u8[]>): Secret<u8[]> => {
|
|
254
|
+
const derived: Secret<u8[]> = tlsDeriveSecret(
|
|
138
255
|
hashLength,
|
|
139
|
-
|
|
140
|
-
|
|
256
|
+
handshakeSecret,
|
|
257
|
+
"derived",
|
|
258
|
+
tlsEmptyHash(hashLength)
|
|
141
259
|
)
|
|
260
|
+
const zeros: Secret<u8[]> = secret(new Array<u8>(hashLength))
|
|
261
|
+
const master: Secret<u8[]> = tlsExtract(hashLength, derived, zeros)
|
|
262
|
+
wipe(derived)
|
|
263
|
+
wipe(zeros)
|
|
264
|
+
return master
|
|
265
|
+
}
|
|
142
266
|
|
|
143
267
|
/**
|
|
144
268
|
* A Finished message's `verify_data` (RFC 8446 §4.4.4): the HMAC of the
|
|
145
269
|
* transcript hash under `finished_key = Expand-Label(baseKey, "finished", "",
|
|
146
|
-
* HashLen)`, where `baseKey` is the sender's handshake traffic secret.
|
|
270
|
+
* HashLen)`, where `baseKey` is the sender's handshake traffic secret. The
|
|
271
|
+
* `finished_key` is wiped before it returns; `verify_data` goes on the wire,
|
|
272
|
+
* so it is plain.
|
|
147
273
|
*/
|
|
148
|
-
export const tlsFinishedVerifyData = (hashLength: i32, baseKey: u8[]
|
|
274
|
+
export const tlsFinishedVerifyData = (hashLength: i32, baseKey: Secret<u8[]>, transcriptHash: u8[]): u8[] => {
|
|
149
275
|
const none: u8[] = []
|
|
150
|
-
const finishedKey: u8[] = tlsExpandLabel(hashLength, baseKey, "finished", none, hashLength)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
276
|
+
const finishedKey: Secret<u8[]> = tlsExpandLabel(hashLength, baseKey, "finished", none, hashLength)
|
|
277
|
+
const verifyData: u8[] =
|
|
278
|
+
hashLength === SHA384_SIZE
|
|
279
|
+
? hmacSha384Secret(finishedKey, transcriptHash)
|
|
280
|
+
: hmacSha256Secret(finishedKey, transcriptHash)
|
|
281
|
+
wipe(finishedKey)
|
|
282
|
+
return verifyData
|
|
154
283
|
}
|
|
155
284
|
|
|
156
|
-
/**
|
|
157
|
-
|
|
285
|
+
/**
|
|
286
|
+
* The AEAD key a traffic secret gives `suite`'s record protection (RFC 8446
|
|
287
|
+
* §7.3), held as a `Secret`; empty for a suite this module does not negotiate.
|
|
288
|
+
*/
|
|
289
|
+
export const tlsTrafficKey = (suite: i32, trafficSecret: Secret<u8[]>): Secret<u8[]> => {
|
|
158
290
|
const none: u8[] = []
|
|
159
291
|
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
160
292
|
if (hashLength === 0) {
|
|
161
|
-
return
|
|
293
|
+
return secret(new Array<u8>(0))
|
|
162
294
|
}
|
|
163
|
-
return tlsExpandLabel(hashLength,
|
|
295
|
+
return tlsExpandLabel(hashLength, trafficSecret, "key", none, tlsSuiteKeyLength(suite))
|
|
164
296
|
}
|
|
165
297
|
|
|
166
|
-
/**
|
|
167
|
-
|
|
298
|
+
/**
|
|
299
|
+
* The 12-byte IV a traffic secret gives `suite`'s record protection (RFC 8446
|
|
300
|
+
* §7.3), held as a `Secret`; empty for a suite this module does not negotiate.
|
|
301
|
+
*/
|
|
302
|
+
export const tlsTrafficIv = (suite: i32, trafficSecret: Secret<u8[]>): Secret<u8[]> => {
|
|
168
303
|
const none: u8[] = []
|
|
169
304
|
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
170
305
|
if (hashLength === 0) {
|
|
171
|
-
return
|
|
306
|
+
return secret(new Array<u8>(0))
|
|
172
307
|
}
|
|
173
|
-
return tlsExpandLabel(hashLength,
|
|
308
|
+
return tlsExpandLabel(hashLength, trafficSecret, "iv", none, TLS_IV_SIZE)
|
|
174
309
|
}
|
|
175
310
|
|
|
176
311
|
/**
|
|
@@ -288,14 +423,14 @@ const tlsEmptyHashInto = (hashLength: i32, out: u8[]): void => {
|
|
|
288
423
|
}
|
|
289
424
|
|
|
290
425
|
/**
|
|
291
|
-
* `tlsDeriveSecret` in place: Derive-Secret (RFC 8446 §7.1) of `
|
|
426
|
+
* `tlsDeriveSecret` in place: Derive-Secret (RFC 8446 §7.1) of `prk` with
|
|
292
427
|
* `label` and the transcript hash `transcriptHash[hashOff .. hashOff +
|
|
293
428
|
* hashLength)`, its HashLen bytes written at `out[0]`.
|
|
294
429
|
*/
|
|
295
430
|
export const tlsDeriveSecretInto = (
|
|
296
431
|
kdf: HkdfScratch,
|
|
297
432
|
hashLength: i32,
|
|
298
|
-
|
|
433
|
+
prk: u8[],
|
|
299
434
|
label: string,
|
|
300
435
|
transcriptHash: u8[],
|
|
301
436
|
hashOff: i32,
|
|
@@ -304,7 +439,7 @@ export const tlsDeriveSecretInto = (
|
|
|
304
439
|
hkdfExpandLabelInto(
|
|
305
440
|
kdf,
|
|
306
441
|
hashLength,
|
|
307
|
-
|
|
442
|
+
prk,
|
|
308
443
|
label,
|
|
309
444
|
transcriptHash,
|
|
310
445
|
hashOff,
|
|
@@ -376,6 +511,98 @@ export const tlsFinishedVerifyDataInto = (
|
|
|
376
511
|
kdf.wipe()
|
|
377
512
|
}
|
|
378
513
|
|
|
514
|
+
/**
|
|
515
|
+
* `tlsPskEarlySecret` in place: HKDF-Extract of `psk` under a zero salt
|
|
516
|
+
* (RFC 8446 §7.1), written at `out[0]`. The scratch is wiped; the zero salt
|
|
517
|
+
* holds nothing to wipe.
|
|
518
|
+
*/
|
|
519
|
+
export const tlsPskEarlySecretInto = (kdf: HkdfScratch, hashLength: i32, psk: u8[], out: u8[]): void => {
|
|
520
|
+
hkdfExtractInto(kdf, hashLength, new Array<u8>(hashLength), psk, out, TLS_SCHEDULE_FROM)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** `tlsBinderKey` in place: `Derive-Secret(early, "res binder", "")`, written at `out[0]`. */
|
|
524
|
+
export const tlsBinderKeyInto = (kdf: HkdfScratch, hashLength: i32, earlySecret: u8[], out: u8[]): void => {
|
|
525
|
+
const empty: u8[] = new Array<u8>(hashLength)
|
|
526
|
+
tlsEmptyHashInto(hashLength, empty)
|
|
527
|
+
tlsDeriveSecretInto(kdf, hashLength, earlySecret, "res binder", empty, TLS_SCHEDULE_FROM, out)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* `tlsHandshakeSalt` in place: `Derive-Secret(early, "derived", "")`, the
|
|
532
|
+
* salt a resumed handshake extracts its handshake secret under, written at
|
|
533
|
+
* `out[0]`. The caller wipes it once the extract is done.
|
|
534
|
+
*/
|
|
535
|
+
export const tlsHandshakeSaltInto = (
|
|
536
|
+
kdf: HkdfScratch,
|
|
537
|
+
hashLength: i32,
|
|
538
|
+
earlySecret: u8[],
|
|
539
|
+
out: u8[]
|
|
540
|
+
): void => {
|
|
541
|
+
const empty: u8[] = new Array<u8>(hashLength)
|
|
542
|
+
tlsEmptyHashInto(hashLength, empty)
|
|
543
|
+
tlsDeriveSecretInto(kdf, hashLength, earlySecret, "derived", empty, TLS_SCHEDULE_FROM, out)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* `tlsResumptionPsk` in place: `HKDF-Expand-Label(resumption_master_secret,
|
|
548
|
+
* "resumption", ticketNonce[nonceOff .. nonceOff + nonceLength), HashLen)`
|
|
549
|
+
* (RFC 8446 §4.6.1), written at `out[0]`.
|
|
550
|
+
*/
|
|
551
|
+
export const tlsResumptionPskInto = (
|
|
552
|
+
kdf: HkdfScratch,
|
|
553
|
+
hashLength: i32,
|
|
554
|
+
resumptionMasterSecret: u8[],
|
|
555
|
+
ticketNonce: u8[],
|
|
556
|
+
nonceOff: i32,
|
|
557
|
+
nonceLength: i32,
|
|
558
|
+
out: u8[]
|
|
559
|
+
): void => {
|
|
560
|
+
hkdfExpandLabelInto(
|
|
561
|
+
kdf,
|
|
562
|
+
hashLength,
|
|
563
|
+
resumptionMasterSecret,
|
|
564
|
+
"resumption",
|
|
565
|
+
ticketNonce,
|
|
566
|
+
nonceOff,
|
|
567
|
+
nonceLength,
|
|
568
|
+
out,
|
|
569
|
+
TLS_SCHEDULE_FROM,
|
|
570
|
+
hashLength
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Whether `binder[binderOff .. binderOff + binderLength)` is the PSK binder
|
|
576
|
+
* RFC 8446 §4.2.11.2 asks for: the HMAC, under `finished_key =
|
|
577
|
+
* Expand-Label(binderKey, "finished", "", HashLen)`, of
|
|
578
|
+
* `truncatedHash[hashOff .. hashOff + hashLength)`, the transcript hash of
|
|
579
|
+
* the ClientHello up to its binders list. A binder that is not HashLen bytes
|
|
580
|
+
* long, or whose window is not inside `binder`, is `false` (row 9 of
|
|
581
|
+
* "Resumption" in `docs/security/tls.md`). The comparison reads every byte
|
|
582
|
+
* whatever it finds; the binder the server computed is wiped before it
|
|
583
|
+
* returns, as `tlsFinishedVerifyDataInto` wipes the finished key and the
|
|
584
|
+
* scratch.
|
|
585
|
+
*/
|
|
586
|
+
export const tlsBinderVerify = (
|
|
587
|
+
kdf: HkdfScratch,
|
|
588
|
+
hashLength: i32,
|
|
589
|
+
binderKey: u8[],
|
|
590
|
+
truncatedHash: u8[],
|
|
591
|
+
hashOff: i32,
|
|
592
|
+
binder: u8[],
|
|
593
|
+
binderOff: i32,
|
|
594
|
+
binderLength: i32
|
|
595
|
+
): boolean => {
|
|
596
|
+
if (binderLength !== hashLength) {
|
|
597
|
+
return false
|
|
598
|
+
}
|
|
599
|
+
const expected: u8[] = new Array<u8>(hashLength)
|
|
600
|
+
tlsFinishedVerifyDataInto(kdf, hashLength, binderKey, truncatedHash, hashOff, expected, TLS_SCHEDULE_FROM)
|
|
601
|
+
const verified: boolean = timingSafeEqualAt(binder, binderOff, expected, TLS_SCHEDULE_FROM, hashLength)
|
|
602
|
+
secureZero(expected)
|
|
603
|
+
return verified
|
|
604
|
+
}
|
|
605
|
+
|
|
379
606
|
/**
|
|
380
607
|
* `tlsTrafficKey` and `tlsTrafficIv` in place: the AEAD key a traffic secret
|
|
381
608
|
* gives `suite` (RFC 8446 §7.3) written into `key`, which must be the suite's
|
|
@@ -386,7 +613,7 @@ export const tlsFinishedVerifyDataInto = (
|
|
|
386
613
|
export const tlsTrafficKeysInto = (
|
|
387
614
|
kdf: HkdfScratch,
|
|
388
615
|
suite: i32,
|
|
389
|
-
|
|
616
|
+
trafficSecret: u8[],
|
|
390
617
|
key: u8[],
|
|
391
618
|
iv: u8[]
|
|
392
619
|
): boolean => {
|
|
@@ -395,7 +622,7 @@ export const tlsTrafficKeysInto = (
|
|
|
395
622
|
const keyLength: i32 = tlsSuiteKeyLength(suite)
|
|
396
623
|
if (
|
|
397
624
|
hashLength === 0 ||
|
|
398
|
-
toI32(
|
|
625
|
+
toI32(trafficSecret.length) !== hashLength ||
|
|
399
626
|
toI32(key.length) !== keyLength ||
|
|
400
627
|
toI32(iv.length) !== TLS_IV_SIZE
|
|
401
628
|
) {
|
|
@@ -404,7 +631,7 @@ export const tlsTrafficKeysInto = (
|
|
|
404
631
|
hkdfExpandLabelInto(
|
|
405
632
|
kdf,
|
|
406
633
|
hashLength,
|
|
407
|
-
|
|
634
|
+
trafficSecret,
|
|
408
635
|
"key",
|
|
409
636
|
none,
|
|
410
637
|
TLS_SCHEDULE_FROM,
|
|
@@ -416,7 +643,7 @@ export const tlsTrafficKeysInto = (
|
|
|
416
643
|
hkdfExpandLabelInto(
|
|
417
644
|
kdf,
|
|
418
645
|
hashLength,
|
|
419
|
-
|
|
646
|
+
trafficSecret,
|
|
420
647
|
"iv",
|
|
421
648
|
none,
|
|
422
649
|
TLS_SCHEDULE_FROM,
|
|
@@ -427,3 +654,185 @@ export const tlsTrafficKeysInto = (
|
|
|
427
654
|
)
|
|
428
655
|
return true
|
|
429
656
|
}
|
|
657
|
+
|
|
658
|
+
// ---- The schedule into held `Secret`s --------------------------------------
|
|
659
|
+
|
|
660
|
+
/** The length of the bytes a `Secret` holds, which is public, for `expose` to read. */
|
|
661
|
+
const tlsHeldLength = (bytes: u8[]): i32 => toI32(bytes.length)
|
|
662
|
+
|
|
663
|
+
/** Overwrites the bytes a `Secret` holds with `from`, for `exposeWith` to run on the destination. */
|
|
664
|
+
const tlsHeldCopy = (held: u8[], from: u8[]): void => {
|
|
665
|
+
for (let k: i32 = 0; k < toI32(held.length) && k < toI32(from.length); k++) {
|
|
666
|
+
held[k] = from[k]
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** `transcriptHash[hashOff .. hashOff + hashLength)` in a fresh array: the context a held form hands HKDF. */
|
|
671
|
+
const tlsHeldWindow = (hashLength: i32, transcriptHash: u8[], hashOff: i32): u8[] => {
|
|
672
|
+
const window: u8[] = new Array<u8>(hashLength)
|
|
673
|
+
for (let k: i32 = 0; k < toI32(window.length); k++) {
|
|
674
|
+
window[k] = transcriptHash[hashOff + k]
|
|
675
|
+
}
|
|
676
|
+
return window
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Panics naming `caller` unless `hashLength` is 32 or 48 and `held` holds
|
|
681
|
+
* `hashLength` bytes, as every secret of the schedule does.
|
|
682
|
+
*/
|
|
683
|
+
const tlsHeldCheck = (caller: string, hashLength: i32, held: Secret<u8[]>): void => {
|
|
684
|
+
if (hashLength !== SHA256_SIZE && hashLength !== SHA384_SIZE) {
|
|
685
|
+
panic(`${caller}: a hash length of ${hashLength}, not 32 or 48`)
|
|
686
|
+
}
|
|
687
|
+
const length: i32 = expose(held, tlsHeldLength)
|
|
688
|
+
if (length !== hashLength) {
|
|
689
|
+
panic(`${caller}: a secret of ${length} bytes, not ${hashLength}`)
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/** HKDF-Extract of `ikm` under `salt`, both held, written over `dst`'s bytes; the plain PRK is wiped. */
|
|
694
|
+
const tlsExtractHeld = (hashLength: i32, salt: Secret<u8[]>, ikm: Secret<u8[]>, dst: Secret<u8[]>): void => {
|
|
695
|
+
const prk: u8[] =
|
|
696
|
+
hashLength === SHA384_SIZE
|
|
697
|
+
? exposeWith(salt, ikm, tlsExtractSha384Salted)
|
|
698
|
+
: exposeWith(salt, ikm, tlsExtractSha256Salted)
|
|
699
|
+
exposeWith(dst, prk, tlsHeldCopy)
|
|
700
|
+
wipe(prk)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* `tlsDeriveSecret` into a held `Secret`: Derive-Secret (RFC 8446 §7.1) of
|
|
705
|
+
* `prk` with `label` and the transcript hash `transcriptHash[hashOff ..
|
|
706
|
+
* hashOff + hashLength)`, written over the HashLen bytes `dst` holds. `dst`
|
|
707
|
+
* may be `prk`. Panics unless `hashLength` is 32 or 48 and both hold
|
|
708
|
+
* HashLen bytes.
|
|
709
|
+
*/
|
|
710
|
+
export const tlsDeriveSecretHeld = (
|
|
711
|
+
hashLength: i32,
|
|
712
|
+
prk: Secret<u8[]>,
|
|
713
|
+
label: string,
|
|
714
|
+
transcriptHash: u8[],
|
|
715
|
+
hashOff: i32,
|
|
716
|
+
dst: Secret<u8[]>
|
|
717
|
+
): void => {
|
|
718
|
+
tlsHeldCheck("tlsDeriveSecretHeld", hashLength, prk)
|
|
719
|
+
tlsHeldCheck("tlsDeriveSecretHeld", hashLength, dst)
|
|
720
|
+
const context: u8[] = tlsHeldWindow(hashLength, transcriptHash, hashOff)
|
|
721
|
+
if (hashLength === SHA384_SIZE) {
|
|
722
|
+
hkdfExpandLabelSha384Held(dst, prk, label, context)
|
|
723
|
+
} else {
|
|
724
|
+
hkdfExpandLabelSha256Held(dst, prk, label, context)
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* `tlsHandshakeSecret` into a held `Secret`: HKDF-Extract of `ecdhe` under
|
|
730
|
+
* `Derive-Secret(early, "derived", "")`, written over `dst`'s HashLen bytes.
|
|
731
|
+
* The salt is wiped before it returns; `dst` may be `earlySecret`.
|
|
732
|
+
*/
|
|
733
|
+
export const tlsHandshakeSecretHeld = (
|
|
734
|
+
hashLength: i32,
|
|
735
|
+
earlySecret: Secret<u8[]>,
|
|
736
|
+
ecdhe: Secret<u8[]>,
|
|
737
|
+
dst: Secret<u8[]>
|
|
738
|
+
): void => {
|
|
739
|
+
tlsHeldCheck("tlsHandshakeSecretHeld", hashLength, dst)
|
|
740
|
+
const derived: Secret<u8[]> = secret(new Array<u8>(hashLength))
|
|
741
|
+
tlsDeriveSecretHeld(
|
|
742
|
+
hashLength,
|
|
743
|
+
earlySecret,
|
|
744
|
+
"derived",
|
|
745
|
+
tlsEmptyHash(hashLength),
|
|
746
|
+
TLS_SCHEDULE_FROM,
|
|
747
|
+
derived
|
|
748
|
+
)
|
|
749
|
+
tlsExtractHeld(hashLength, derived, ecdhe, dst)
|
|
750
|
+
wipe(derived)
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* `tlsMasterSecret` into a held `Secret`: HKDF-Extract of HashLen zeros under
|
|
755
|
+
* `Derive-Secret(handshake, "derived", "")`, written over `dst`'s HashLen
|
|
756
|
+
* bytes. The salt and the zeros are wiped before it returns; `dst` may be
|
|
757
|
+
* `handshakeSecret`.
|
|
758
|
+
*/
|
|
759
|
+
export const tlsMasterSecretHeld = (
|
|
760
|
+
hashLength: i32,
|
|
761
|
+
handshakeSecret: Secret<u8[]>,
|
|
762
|
+
dst: Secret<u8[]>
|
|
763
|
+
): void => {
|
|
764
|
+
tlsHeldCheck("tlsMasterSecretHeld", hashLength, dst)
|
|
765
|
+
const derived: Secret<u8[]> = secret(new Array<u8>(hashLength))
|
|
766
|
+
tlsDeriveSecretHeld(
|
|
767
|
+
hashLength,
|
|
768
|
+
handshakeSecret,
|
|
769
|
+
"derived",
|
|
770
|
+
tlsEmptyHash(hashLength),
|
|
771
|
+
TLS_SCHEDULE_FROM,
|
|
772
|
+
derived
|
|
773
|
+
)
|
|
774
|
+
const zeros: Secret<u8[]> = secret(new Array<u8>(hashLength))
|
|
775
|
+
tlsExtractHeld(hashLength, derived, zeros, dst)
|
|
776
|
+
wipe(derived)
|
|
777
|
+
wipe(zeros)
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* `tlsFinishedVerifyData` from a held base key and a window of a transcript:
|
|
782
|
+
* the HMAC of `transcriptHash[hashOff .. hashOff + hashLength)` under
|
|
783
|
+
* `finished_key = Expand-Label(baseKey, "finished", "", HashLen)` (RFC 8446
|
|
784
|
+
* §4.4.4). The finished key is held and wiped before it returns;
|
|
785
|
+
* `verify_data` goes on the wire, so it is plain.
|
|
786
|
+
*/
|
|
787
|
+
export const tlsFinishedVerifyDataHeld = (
|
|
788
|
+
hashLength: i32,
|
|
789
|
+
baseKey: Secret<u8[]>,
|
|
790
|
+
transcriptHash: u8[],
|
|
791
|
+
hashOff: i32
|
|
792
|
+
): u8[] => {
|
|
793
|
+
tlsHeldCheck("tlsFinishedVerifyDataHeld", hashLength, baseKey)
|
|
794
|
+
const none: u8[] = []
|
|
795
|
+
const finishedKey: Secret<u8[]> = secret(new Array<u8>(hashLength))
|
|
796
|
+
if (hashLength === SHA384_SIZE) {
|
|
797
|
+
hkdfExpandLabelSha384Held(finishedKey, baseKey, "finished", none)
|
|
798
|
+
} else {
|
|
799
|
+
hkdfExpandLabelSha256Held(finishedKey, baseKey, "finished", none)
|
|
800
|
+
}
|
|
801
|
+
const transcript: u8[] = tlsHeldWindow(hashLength, transcriptHash, hashOff)
|
|
802
|
+
const verifyData: u8[] =
|
|
803
|
+
hashLength === SHA384_SIZE
|
|
804
|
+
? hmacSha384Secret(finishedKey, transcript)
|
|
805
|
+
: hmacSha256Secret(finishedKey, transcript)
|
|
806
|
+
wipe(finishedKey)
|
|
807
|
+
return verifyData
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* `tlsTrafficKeysInto` from a held traffic secret: the AEAD key it gives
|
|
812
|
+
* `suite` (RFC 8446 §7.3) written into `key`, which must be the suite's key
|
|
813
|
+
* length, and its 12-byte IV into `iv`. Both stay plain, since the cipher
|
|
814
|
+
* reads them. `false`, writing nothing, for a suite this module does not
|
|
815
|
+
* negotiate, a secret that is not its hash's length, or a key or IV array of
|
|
816
|
+
* the wrong length.
|
|
817
|
+
*/
|
|
818
|
+
export const tlsTrafficKeysFromHeld = (
|
|
819
|
+
suite: i32,
|
|
820
|
+
trafficSecret: Secret<u8[]>,
|
|
821
|
+
key: u8[],
|
|
822
|
+
iv: u8[]
|
|
823
|
+
): boolean => {
|
|
824
|
+
const none: u8[] = []
|
|
825
|
+
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
826
|
+
const keyLength: i32 = tlsSuiteKeyLength(suite)
|
|
827
|
+
if (
|
|
828
|
+
hashLength === 0 ||
|
|
829
|
+
expose(trafficSecret, tlsHeldLength) !== hashLength ||
|
|
830
|
+
toI32(key.length) !== keyLength ||
|
|
831
|
+
toI32(iv.length) !== TLS_IV_SIZE
|
|
832
|
+
) {
|
|
833
|
+
return false
|
|
834
|
+
}
|
|
835
|
+
hkdfExpandLabelHeldInto(hashLength, trafficSecret, "key", none, key, TLS_SCHEDULE_FROM, keyLength)
|
|
836
|
+
hkdfExpandLabelHeldInto(hashLength, trafficSecret, "iv", none, iv, TLS_SCHEDULE_FROM, TLS_IV_SIZE)
|
|
837
|
+
return true
|
|
838
|
+
}
|