@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.
Files changed (59) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +215 -18
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +4 -4
  14. package/runtime/runtime-wasm.c +5 -3
  15. package/runtime/runtime.c +69 -13
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +223 -22
  18. package/std/README.md +81 -51
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +932 -61
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1120 -34
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +211 -31
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-listener.ts +88 -7
  45. package/std/net/quic-packet.ts +243 -82
  46. package/std/net/quic-recovery.ts +361 -30
  47. package/std/net/quic-stream.ts +424 -24
  48. package/std/net/quic.ts +519 -167
  49. package/std/net/router.ts +676 -0
  50. package/std/net/tls/codec.ts +426 -27
  51. package/std/net/tls/record-server.ts +478 -132
  52. package/std/net/tls/record.ts +133 -42
  53. package/std/net/tls/schedule.ts +464 -55
  54. package/std/net/tls/ticket.ts +610 -0
  55. package/std/net/tls-tcp.ts +196 -53
  56. package/std/net/tls.ts +1018 -260
  57. package/std/net/url.ts +571 -0
  58. package/std/random.ts +94 -0
  59. package/std/threads.ts +438 -14
@@ -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
- * **Secrets are not wiped.** Every function here answers or holds a secret as
29
- * plain bytes, and each stays in arena memory until that memory is reused.
30
- * `secureZero`, the store no optimiser removes, is on `main`, and `std/` may
31
- * call it once a release ships it (CLAUDE.md §Security, TLS-1 in
32
- * `docs/security/tls.md`).
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
- hkdfExpandLabelSha256,
40
- hkdfExpandLabelSha384,
64
+ hkdfExpandLabelSha256Held,
65
+ hkdfExpandLabelSha256Secret,
66
+ hkdfExpandLabelSha384Held,
67
+ hkdfExpandLabelSha384Secret,
41
68
  hkdfExtractInto,
42
- hkdfExtractSha256,
43
- hkdfExtractSha384,
44
69
  } from "nish/crypto/hkdf"
45
- import { hmacCopySha256, hmacCopySha384, hmacSha256, hmacSha384 } from "nish/crypto/hmac"
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 `secret`, the label (without its `"tls13 "` prefix, which is
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
- secret: u8[],
136
+ prk: Secret<u8[]>,
103
137
  label: string,
104
138
  context: u8[],
105
139
  length: i32
106
- ): u8[] =>
107
- hashLength === SHA384_SIZE
108
- ? hkdfExpandLabelSha384(secret, label, context, length)
109
- : hkdfExpandLabelSha256(secret, label, context, length)
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
- /** HKDF-Extract (RFC 5869 §2.2) over the hash `hashLength` names; an empty salt is HashLen zeros. */
112
- export const tlsExtract = (hashLength: i32, salt: u8[], ikm: u8[]): u8[] =>
113
- hashLength === SHA384_SIZE ? hkdfExtractSha384(salt, ikm) : hkdfExtractSha256(salt, ikm)
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 `secret` with `label` and a transcript hash, HashLen bytes. */
122
- export const tlsDeriveSecret = (hashLength: i32, secret: u8[], label: string, transcriptHash: u8[]): u8[] =>
123
- tlsExpandLabel(hashLength, secret, label, transcriptHash, hashLength)
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 none: u8[] = []
128
- return tlsExtract(hashLength, none, new Array<u8>(hashLength))
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
- /** The handshake secret: HKDF-Extract of the ECDHE secret under `Derive-Secret(early, "derived", "")`. */
132
- export const tlsHandshakeSecret = (hashLength: i32, earlySecret: u8[], ecdhe: u8[]): u8[] =>
133
- tlsExtract(hashLength, tlsDeriveSecret(hashLength, earlySecret, "derived", tlsEmptyHash(hashLength)), ecdhe)
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
- /** The master secret: HKDF-Extract of HashLen zeros under `Derive-Secret(handshake, "derived", "")`. */
136
- export const tlsMasterSecret = (hashLength: i32, handshakeSecret: u8[]): u8[] =>
137
- tlsExtract(
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
- tlsDeriveSecret(hashLength, handshakeSecret, "derived", tlsEmptyHash(hashLength)),
140
- new Array<u8>(hashLength)
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[], transcriptHash: u8[]): 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
- return hashLength === SHA384_SIZE
152
- ? hmacSha384(finishedKey, transcriptHash)
153
- : hmacSha256(finishedKey, transcriptHash)
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
- /** The AEAD key a traffic secret gives `suite`'s record protection (RFC 8446 §7.3); empty for a suite this module does not negotiate. */
157
- export const tlsTrafficKey = (suite: i32, secret: u8[]): u8[] => {
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 none
293
+ return secret(new Array<u8>(0))
162
294
  }
163
- return tlsExpandLabel(hashLength, secret, "key", none, tlsSuiteKeyLength(suite))
295
+ return tlsExpandLabel(hashLength, trafficSecret, "key", none, tlsSuiteKeyLength(suite))
164
296
  }
165
297
 
166
- /** The 12-byte IV a traffic secret gives `suite`'s record protection (RFC 8446 §7.3); empty for a suite this module does not negotiate. */
167
- export const tlsTrafficIv = (suite: i32, secret: u8[]): u8[] => {
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 none
306
+ return secret(new Array<u8>(0))
172
307
  }
173
- return tlsExpandLabel(hashLength, secret, "iv", none, TLS_IV_SIZE)
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 `secret` with
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
- secret: u8[],
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
- secret,
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
- secret: u8[],
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(secret.length) !== hashLength ||
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
- secret,
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
- secret,
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
+ }