@amritk/nish-x86_64-linux 0.17.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 (61) 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 +231 -20
  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 +203 -0
  14. package/runtime/runtime-wasm.c +16 -3
  15. package/runtime/runtime.c +89 -23
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +225 -20
  18. package/std/README.md +86 -55
  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 +1376 -42
  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 +1412 -70
  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 +231 -45
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-conn-params.ts +86 -16
  45. package/std/net/quic-listener.ts +576 -219
  46. package/std/net/quic-packet.ts +443 -58
  47. package/std/net/quic-recovery.ts +361 -30
  48. package/std/net/quic-stream.ts +434 -28
  49. package/std/net/quic.ts +922 -206
  50. package/std/net/router.ts +676 -0
  51. package/std/net/tls/codec.ts +426 -27
  52. package/std/net/tls/record-server.ts +478 -132
  53. package/std/net/tls/record.ts +133 -42
  54. package/std/net/tls/schedule.ts +464 -55
  55. package/std/net/tls/ticket.ts +610 -0
  56. package/std/net/tls-tcp.ts +204 -57
  57. package/std/net/tls.ts +1132 -305
  58. package/std/net/url.ts +571 -0
  59. package/std/random.ts +94 -0
  60. package/std/text.ts +77 -0
  61. package/std/threads.ts +438 -14
package/std/net/tls.ts CHANGED
@@ -39,21 +39,38 @@
39
39
  * the share is refused. `server_name` is read and exposed as `serverName`.
40
40
  * ALPN picks the first of the configuration's protocols the client offers.
41
41
  * `quic_transport_parameters` is read and written only when the configuration
42
- * says the carrier is QUIC. PSKs, 0-RTT, NewSessionTicket and client
43
- * authentication are not here: a PSK or early-data offer is ignored, so the
44
- * client falls back to a full handshake.
42
+ * says the carrier is QUIC. 0-RTT and client authentication are not here:
43
+ * an early-data offer is ignored.
44
+ *
45
+ * **Resumption.** A server the carrier calls `enableTickets` on, over TCP
46
+ * only, resumes with a session ticket in `psk_dhe_ke` mode (RFC 8446 §2.2,
47
+ * §4.2.11) and issues one after each handshake. The ticket keys and the
48
+ * strike register are the program's, lent to each call and never kept: a
49
+ * ClientHello that offers a ticket stops at `TLS_STATE_WAIT_TICKET`, and
50
+ * `resume` reads the identities in the client's order, verifies the binder of
51
+ * the one it selects, records its id, and answers a resumed flight — or a full
52
+ * handshake when no identity can be used (`docs/security/tls.md`,
53
+ * "Resumption"). Once connected, `issue` writes one NewSessionTicket at the
54
+ * Application level. Sealing draws the ticket's nonce and `issue` the
55
+ * `ticket_age_add` from the random device; nothing else here does. Over QUIC
56
+ * the hand-off is never called, so a PSK offer gets a full handshake.
45
57
  *
46
58
  * **Refusals are alerts.** `receive` and `sign` answer 0 or a TLS alert
47
59
  * description (`TLS_ALERT_*` in `nish/net/tls/codec`); after one the server
48
60
  * is in `TLS_STATE_FAILED` and answers the same alert to every call. Nothing
49
61
  * the peer sends can make it panic.
50
62
  *
51
- * **Secrets.** The ECDHE secret, and the copy of the ephemeral key it is
52
- * computed with, are `Secret`s (`nish:secret`) wiped on every path. What the
53
- * server keeps in its fields — the caller's ephemeral key bytes, since a
54
- * `Secret` may not be a field, and the handshake, traffic and exporter secrets
55
- * its carrier reads — is wiped by its carrier, and by `restart` before the
56
- * next connection uses the arrays (CLAUDE.md §Security; TLS-1 in
63
+ * **Secrets.** Every secret the server holds is a `Secret` (`nish:secret`):
64
+ * the ECDHE secret and the words it is computed in, wiped on every path; the
65
+ * ephemeral key, copied into the `ephemeral` field by `restart` with the
66
+ * caller's array zeroed; and the handshake, traffic, exporter and resumption
67
+ * secrets, and a resumed handshake's PSK, early secret and binder key, fields
68
+ * of the two `TlsSecretPool`s, read only inside `expose`.
69
+ * `wipe` zeroes them all, and `restart` calls it before the next connection;
70
+ * the carriers call it when a connection ends, and `wipeSecret` and
71
+ * `wipeEphemeral` as each secret's work is done. `fail` wipes the resumption
72
+ * master secret and the PSK's secrets, and `issue` the resumption master
73
+ * secret once the ticket is sealed (CLAUDE.md §Security; TLS-1 in
57
74
  * `docs/security/tls.md`).
58
75
  *
59
76
  * **Memory.** A `TlsServer` keeps its state in buffers it allocates once —
@@ -67,11 +84,12 @@
67
84
  * 8446, RFC 7301, RFC 6066 and RFC 9001, not ported from another
68
85
  * implementation.
69
86
  */
70
- import { Secret, expose, secret, wipe } from "nish:secret"
71
- import { HkdfScratch, hkdfExtractSha256, hkdfExtractSha384 } from "nish/crypto/hkdf"
87
+ import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
88
+ import { HkdfScratch } from "nish/crypto/hkdf"
89
+ import { hmacSha256, hmacSha384 } from "nish/crypto/hmac"
72
90
  import { SHA256_SIZE } from "nish/crypto/sha256"
73
91
  import { SHA384_SIZE } from "nish/crypto/sha512"
74
- import { p256SignSha256 } from "nish/crypto/p256"
92
+ import { P256SignScratch, P256_SIGNATURE_SIZE, p256SignSha256, p256SignSha256Into } from "nish/crypto/p256"
75
93
  import { timingSafeEqual, timingSafeEqualAt } from "nish/crypto/ct"
76
94
  import { X25519_SIZE, x25519, x25519Base } from "nish/crypto/x25519"
77
95
  import {
@@ -100,16 +118,36 @@ import {
100
118
  tlsWriteCertificateVerifyContent,
101
119
  tlsWriteEncryptedExtensions,
102
120
  tlsWriteFinished,
121
+ tlsSelectPskIdentity,
103
122
  tlsWriteHelloRetryRequest,
123
+ tlsWriteNewSessionTicket,
104
124
  tlsWriteServerHello,
125
+ tlsWriteServerHelloPsk,
105
126
  } from "nish/net/tls/codec"
106
127
  import {
128
+ TLS_AES_256_GCM_SHA384,
107
129
  TlsTranscript,
130
+ tlsBinderKeyInto,
131
+ tlsBinderVerify,
108
132
  tlsDeriveSecretInto,
109
133
  tlsFinishedVerifyDataInto,
134
+ tlsHandshakeSaltInto,
110
135
  tlsMasterSecretInto,
136
+ tlsPskEarlySecretInto,
137
+ tlsResumptionPskInto,
111
138
  tlsSuiteHashLength,
112
139
  } from "nish/net/tls/schedule"
140
+ import {
141
+ TLS_TICKET_LIFETIME_SECONDS,
142
+ TLS_TICKET_PSK_MAX,
143
+ TLS_TICKET_SERVER_NAME_MAX,
144
+ TlsTicketKeyInfo,
145
+ TlsTicketState,
146
+ TlsTicketStrikes,
147
+ tlsTicketOpen,
148
+ tlsTicketSeal,
149
+ tlsTicketSize,
150
+ } from "nish/net/tls/ticket"
113
151
 
114
152
  // ---- Levels ------------------------------------------------------------------
115
153
 
@@ -117,7 +155,7 @@ import {
117
155
  export const TLS_LEVEL_INITIAL: i32 = 0
118
156
  /** Under the handshake traffic secrets: EncryptedExtensions through both Finished messages. */
119
157
  export const TLS_LEVEL_HANDSHAKE: i32 = 1
120
- /** Under the application traffic secrets. T1 writes nothing here; the level exists for its secrets. */
158
+ /** Under the application traffic secrets: the one NewSessionTicket `issue` writes. */
121
159
  export const TLS_LEVEL_APPLICATION: i32 = 2
122
160
 
123
161
  // ---- States --------------------------------------------------------------------
@@ -132,6 +170,8 @@ export const TLS_STATE_WAIT_FINISHED: i32 = 2
132
170
  export const TLS_STATE_CONNECTED: i32 = 3
133
171
  /** An alert was answered; `alert` holds it, and every later call answers it again. */
134
172
  export const TLS_STATE_FAILED: i32 = 4
173
+ /** A ClientHello offered a ticket to a server with tickets on; `resume` is due, with the keys and the strike register. */
174
+ export const TLS_STATE_WAIT_TICKET: i32 = 5
135
175
 
136
176
  /**
137
177
  * The longest handshake message accepted, in bytes. A ClientHello carrying a
@@ -238,29 +278,57 @@ const tlsBytesSpell = (data: u8[], at: i32, length: i32, text: string): boolean
238
278
  return true
239
279
  }
240
280
 
241
- /** The minimal DER INTEGER for an unsigned big-endian magnitude: leading zeros dropped, one added when the top bit is set. */
242
- const tlsDerInteger = (magnitude: u8[]): u8[] => {
243
- const body: u8[] = []
244
- let leading: boolean = true
245
- for (const b of magnitude) {
246
- if (leading && toI32(b) === 0) {
247
- continue
248
- }
249
- if (leading && toI32(b) >= 0x80) {
250
- body.push(toU8(0))
251
- }
252
- leading = false
253
- body.push(b)
281
+ /** The shortest and longest DER `ECDSA-Sig-Value` of a P-256 signature: a SEQUENCE of two INTEGERs of 1 to 33 bytes. */
282
+ const TLS_P256_DER_MIN: i32 = 8
283
+ const TLS_P256_DER_MAX: i32 = 72
284
+
285
+ /**
286
+ * The body length of the minimal DER INTEGER for the unsigned big-endian
287
+ * magnitude `rs[at .. at + 32)`: leading zeros dropped, one added when the top
288
+ * bit is set, and zero one zero octet rather than an empty integer (X.690
289
+ * §8.3.1).
290
+ */
291
+ const tlsDerIntegerLength = (rs: u8[], at: i32): i32 => {
292
+ let first: i32 = 0
293
+ while (first < 31 && at + first >= 0 && at + first < toI32(rs.length) && toI32(rs[at + first]) === 0) {
294
+ first++
254
295
  }
255
- // Zero is one zero octet, not an empty integer (X.690 §8.3.1).
256
- if (toI32(body.length) === 0) {
257
- body.push(toU8(0))
296
+ const top: boolean = at + first >= 0 && at + first < toI32(rs.length) && toI32(rs[at + first]) >= 0x80
297
+ return 32 - first + (top ? 1 : 0)
298
+ }
299
+
300
+ /** That INTEGER, of body length `length`, written at `out[to ..)`; answers where it ends. */
301
+ const tlsDerIntegerInto = (out: u8[], to: i32, rs: u8[], at: i32, length: i32): i32 => {
302
+ const end: i32 = to + 2 + length
303
+ if (to < 0 || end > toI32(out.length)) {
304
+ return end
258
305
  }
259
- const out: u8[] = [toU8(0x02), toU8(toI32(body.length))]
260
- for (const b of body) {
261
- out.push(b)
306
+ out[to] = toU8(0x02)
307
+ out[to + 1] = toU8(length)
308
+ for (let k: i32 = 0; k < length; k++) {
309
+ // The body's last `length` bytes are the magnitude's, a zero first when it is 33.
310
+ const from: i32 = at + 32 - length + k
311
+ const j: i32 = to + 2 + k
312
+ if (j >= 0 && j < toI32(out.length)) {
313
+ out[j] = from >= at && from >= 0 && from < toI32(rs.length) ? rs[from] : toU8(0)
314
+ }
262
315
  }
263
- return out
316
+ return end
317
+ }
318
+
319
+ /** The length of `rs`'s DER `ECDSA-Sig-Value`: the SEQUENCE's two bytes and two INTEGERs. */
320
+ const tlsEcdsaDerLength = (rs: u8[]): i32 =>
321
+ 6 + tlsDerIntegerLength(rs, TLS_FROM) + tlsDerIntegerLength(rs, 32)
322
+
323
+ /** `rs`'s DER `ECDSA-Sig-Value` into all of `der`, which is `tlsEcdsaDerLength(rs)` bytes. At most 72, so the length is one short-form byte. */
324
+ const tlsEcdsaDerInto = (der: u8[], rs: u8[]): void => {
325
+ if (toI32(der.length) < 2) {
326
+ return
327
+ }
328
+ der[0] = toU8(0x30)
329
+ der[1] = toU8(toI32(der.length) - 2)
330
+ const r: i32 = tlsDerIntegerLength(rs, TLS_FROM)
331
+ tlsDerIntegerInto(der, tlsDerIntegerInto(der, 2, rs, TLS_FROM, r), rs, 32, tlsDerIntegerLength(rs, 32))
264
332
  }
265
333
 
266
334
  /**
@@ -272,25 +340,9 @@ export const tlsEcdsaDerSignature = (rs: u8[]): u8[] | null => {
272
340
  if (toI32(rs.length) !== 64) {
273
341
  return null
274
342
  }
275
- const r: u8[] = []
276
- const s: u8[] = []
277
- for (let k: i32 = 0; k < toI32(rs.length); k++) {
278
- if (k < 32) {
279
- r.push(rs[k])
280
- } else {
281
- s.push(rs[k])
282
- }
283
- }
284
- const body: u8[] = tlsDerInteger(r)
285
- for (const b of tlsDerInteger(s)) {
286
- body.push(b)
287
- }
288
- // At most 2 * (2 + 33) = 70 bytes, so the length is one short-form byte.
289
- const out: u8[] = [toU8(0x30), toU8(toI32(body.length))]
290
- for (const b of body) {
291
- out.push(b)
292
- }
293
- return out
343
+ const der: u8[] = new Array<u8>(tlsEcdsaDerLength(rs))
344
+ tlsEcdsaDerInto(der, rs)
345
+ return der
294
346
  }
295
347
 
296
348
  /**
@@ -308,45 +360,117 @@ export const tlsSignEcdsaP256 = (priv: Secret<u8[]>, content: u8[]): u8[] | null
308
360
  return tlsEcdsaDerSignature(rs)
309
361
  }
310
362
 
363
+ /**
364
+ * Signs CertificateVerify inputs for `ecdsa_secp256r1_sha256` with nothing
365
+ * kept per signature: `tlsSignEcdsaP256`'s answer, made in a
366
+ * `P256SignScratch` and handed back in one of this signer's own arrays, one
367
+ * per DER length (8 to 72 bytes), since `TlsServer.sign` takes the
368
+ * signature's length from its array and stores what it computes, so a
369
+ * signature it is handed cannot be a temporary. A carrier keeps one signer,
370
+ * not one per slot: signing is synchronous, and the array it answers is read
371
+ * by `sign` before the next signature replaces it.
372
+ */
373
+ export class TlsP256Signer {
374
+ /** The P-256 signer's scratch. */
375
+ scratch: P256SignScratch
376
+ /** `r || s` as `p256SignSha256Into` writes it. */
377
+ rs: u8[]
378
+ /** The DER signatures, index `length - TLS_P256_DER_MIN`. */
379
+ ders: u8[][]
380
+
381
+ constructor() {
382
+ this.scratch = new P256SignScratch()
383
+ this.rs = new Array<u8>(P256_SIGNATURE_SIZE)
384
+ this.ders = []
385
+ for (let length: i32 = TLS_P256_DER_MIN; length <= TLS_P256_DER_MAX; length++) {
386
+ this.ders.push(new Array<u8>(length))
387
+ }
388
+ }
389
+
390
+ /**
391
+ * `tlsSignEcdsaP256(priv, content)`, byte for byte, in the array of this
392
+ * signer's that has its length; `null` for a malformed key. It keeps
393
+ * nothing: the P-256 signature releases what it allocated, and the DER is
394
+ * written in place. The key is borrowed.
395
+ */
396
+ sign(priv: Secret<u8[]>, content: u8[]): u8[] | null {
397
+ if (
398
+ !p256SignSha256Into(this.scratch, priv, content, TLS_FROM, toI32(content.length), this.rs, TLS_FROM)
399
+ ) {
400
+ return null
401
+ }
402
+ const at: i32 = tlsEcdsaDerLength(this.rs) - TLS_P256_DER_MIN
403
+ if (at < 0 || at >= toI32(this.ders.length)) {
404
+ return null
405
+ }
406
+ const der: u8[] = this.ders[at]
407
+ tlsEcdsaDerInto(der, this.rs)
408
+ return der
409
+ }
410
+ }
411
+
311
412
  /** Whether an x25519 shared secret is all zeros, reading every byte (run inside `expose`). */
312
413
  const tlsIsZeroSecret = (shared: u8[]): boolean => timingSafeEqual(shared, new Array<u8>(X25519_SIZE))
313
414
 
314
415
  /**
315
- * `Derive-Secret(early, "derived", "")` with no PSK, the salt the handshake
416
+ * `Derive-Secret(early, "derived", "")` with no PSK, the salt a full handshake's
316
417
  * secret is extracted under: a constant of the hash alone (RFC 8448 §3 prints
317
418
  * the SHA-256 one; the SHA-384 one was computed with Python's hmac and
318
419
  * hashlib). It is written out rather than derived because the functions below
319
420
  * run on the ECDHE secret inside `expose`, and Expand-Label may panic with a
320
- * computed message, which such a function may not reach.
421
+ * computed message, which such a function may not reach. A resumed
422
+ * handshake's salt is derived from the PSK's early secret by `resume`, before
423
+ * the exchange, and reaches it the same way: as words.
321
424
  */
322
425
  const TLS_DERIVED_SALT_SHA256: string = "6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba"
323
426
  const TLS_DERIVED_SALT_SHA384: string =
324
427
  "1591dac5cbbf0330a4a84de9c753330e92d01f0a88214b4464972fd668049e93e52f2b16fad922fdc0584478428f282b"
325
428
 
326
- /** The bytes of a lowercase hex constant of this module, in a fresh array. */
327
- const tlsHexBytes = (text: string): u8[] => {
328
- const out: u8[] = new Array<u8>(toI32(text.length) >> 1)
329
- tlsHexInto(text, out, TLS_FROM)
330
- return out
331
- }
429
+ /**
430
+ * The SHA-256 handshake secret: HKDF-Extract of the ECDHE secret under `salt`
431
+ * (run inside `exposeWith`). The salt is always HashLen bytes, so this is the
432
+ * HMAC itself: `hkdfExtractSha256` would pass it through `hkdfSalt`, which the
433
+ * checker reads as a write through the argument `exposeWith` hands it.
434
+ */
435
+ const tlsHandshakeFromEcdhe256 = (ecdhe: u8[], salt: u8[]): u8[] => hmacSha256(salt, ecdhe)
332
436
 
333
- /** The SHA-256 handshake secret: HKDF-Extract of the ECDHE secret under the derived salt (run inside `expose`). */
334
- const tlsHandshakeFromEcdhe256 = (ecdhe: u8[]): u8[] =>
335
- hkdfExtractSha256(tlsHexBytes(TLS_DERIVED_SALT_SHA256), ecdhe)
437
+ /** The SHA-384 handshake secret: HKDF-Extract of the ECDHE secret under `salt`, as the SHA-256 one is. */
438
+ const tlsHandshakeFromEcdhe384 = (ecdhe: u8[], salt: u8[]): u8[] => hmacSha384(salt, ecdhe)
336
439
 
337
- /** The SHA-384 handshake secret: HKDF-Extract of the ECDHE secret under the derived salt (run inside `expose`). */
338
- const tlsHandshakeFromEcdhe384 = (ecdhe: u8[]): u8[] =>
339
- hkdfExtractSha384(tlsHexBytes(TLS_DERIVED_SALT_SHA384), ecdhe)
440
+ /** Wipes the bytes a `Secret` holds, for `exposeWith` to run on one secret of a holder outside its `wipe()`; answers `answer`. */
441
+ const tlsZeroHeld = (held: u8[], answer: i32): i32 => {
442
+ wipe(held)
443
+ return answer
444
+ }
340
445
 
341
- /** The secret of `level` from one side's pair, or `null` for any other level or one not derived yet (still empty). */
342
- const tlsSecretAt = (level: i32, handshake: u8[], application: u8[]): u8[] | null => {
343
- const chosen: u8[] = level === TLS_LEVEL_APPLICATION ? application : handshake
344
- if ((level !== TLS_LEVEL_HANDSHAKE && level !== TLS_LEVEL_APPLICATION) || toI32(chosen.length) === 0) {
345
- return null
446
+ /** Overwrites the bytes a `Secret` holds with `from`'s, for `exposeWith` to run on the destination; answers 0. */
447
+ export const tlsHeldCopyIn = (held: u8[], from: u8[]): i32 => {
448
+ for (let k: i32 = 0; k < toI32(held.length) && k < toI32(from.length); k++) {
449
+ held[k] = from[k]
450
+ }
451
+ return 0
452
+ }
453
+
454
+ /**
455
+ * A plain copy of a held secret, for a derivation that runs on its bytes
456
+ * inside a `using arena()` block: the copy dies with the block, and its
457
+ * caller `secureZero`s it before the block ends.
458
+ */
459
+ export const tlsHeldClone = (held: u8[]): u8[] => {
460
+ const copy: u8[] = new Array<u8>(held.length)
461
+ for (let k: i32 = 0; k < toI32(copy.length) && k < toI32(held.length); k++) {
462
+ copy[k] = held[k]
346
463
  }
347
- return chosen
464
+ return copy
348
465
  }
349
466
 
467
+ /**
468
+ * Copies the bytes one `Secret` holds over another's, for `exposeWith` to
469
+ * run on the source with the destination as its argument: no plain copy is
470
+ * made on the way. Answers 0.
471
+ */
472
+ export const tlsHeldCopyOver = (from: u8[], to: Secret<u8[]>): i32 => exposeWith(to, from, tlsHeldCopyIn)
473
+
350
474
  // ---- The exchange, in a scope of its own --------------------------------------
351
475
 
352
476
  /**
@@ -366,9 +490,9 @@ const tlsSecretAt = (level: i32, handshake: u8[], application: u8[]): u8[] | nul
366
490
  * `tests/link/net_tls_memory` would notice.
367
491
  *
368
492
  * The words hold secrets — the private key and the handshake secret — in
369
- * fields, where `secureZero` cannot reach, so each is zeroed by an ordinary
370
- * store as soon as it has been read, which stands because the words stay
371
- * reachable from their `TlsServer` (TLS-1 in `docs/security/tls.md`).
493
+ * fields, where `secureZero` cannot reach, so `exchangeKeys` wipes the whole
494
+ * record with `wipe`, a volatile store, once it has unpacked the answers, on
495
+ * the failure path too (TLS-1 in `docs/security/tls.md`).
372
496
  */
373
497
  class TlsExchangeWords {
374
498
  key0: u64 = 0
@@ -389,6 +513,17 @@ class TlsExchangeWords {
389
513
  secret3: u64 = 0
390
514
  secret4: u64 = 0
391
515
  secret5: u64 = 0
516
+ /**
517
+ * The salt the handshake secret is extracted under, `hashLength` bytes of
518
+ * it: the `TLS_DERIVED_SALT_*` constant on a full handshake, the salt
519
+ * derived from the PSK early secret on a resumed one.
520
+ */
521
+ salt0: u64 = 0
522
+ salt1: u64 = 0
523
+ salt2: u64 = 0
524
+ salt3: u64 = 0
525
+ salt4: u64 = 0
526
+ salt5: u64 = 0
392
527
  /** 32 or 48: which handshake secret to extract. */
393
528
  hashLength: i32 = 0
394
529
  }
@@ -413,10 +548,21 @@ const tlsWordInto = (b: u8[], at: i32, v: u64): void => {
413
548
  }
414
549
  }
415
550
 
551
+ /** Unpacks the handshake secret from the exchange's words into the bytes a `Secret` holds, for `exposeWith`. */
552
+ const tlsHandshakeFromWords = (held: u8[], w: TlsExchangeWords): i32 => {
553
+ tlsWordInto(held, 0, w.secret0)
554
+ tlsWordInto(held, 8, w.secret1)
555
+ tlsWordInto(held, 16, w.secret2)
556
+ tlsWordInto(held, 24, w.secret3)
557
+ tlsWordInto(held, 32, w.secret4)
558
+ tlsWordInto(held, 40, w.secret5)
559
+ return 0
560
+ }
561
+
416
562
  /**
417
563
  * The exchange of RFC 8446 §7.4.2 on `w`: the server's public key and the
418
564
  * handshake secret from the ephemeral key and the client's share, both
419
- * written back as words, the key's words zeroed once read. Answers 0,
565
+ * written back as words; the caller wipes the words. Answers 0,
420
566
  * `illegal_parameter` for a share whose shared secret is all zeros (§7.4.2,
421
567
  * RFC 7748 §6.1), or `internal_error` when the exchange answers nothing. The
422
568
  * copy of the key and the ECDHE secret are `Secret`s wiped on every path, and
@@ -426,15 +572,18 @@ const tlsWordInto = (b: u8[], at: i32, v: u64): void => {
426
572
  * returns.
427
573
  */
428
574
  const tlsExchange = (w: TlsExchangeWords): i32 => {
575
+ const salt: u8[] = new Array<u8>(w.hashLength === SHA384_SIZE ? SHA384_SIZE : SHA256_SIZE)
576
+ tlsWordInto(salt, 0, w.salt0)
577
+ tlsWordInto(salt, 8, w.salt1)
578
+ tlsWordInto(salt, 16, w.salt2)
579
+ tlsWordInto(salt, 24, w.salt3)
580
+ tlsWordInto(salt, 32, w.salt4)
581
+ tlsWordInto(salt, 40, w.salt5)
429
582
  const raw: u8[] = new Array<u8>(X25519_SIZE)
430
583
  tlsWordInto(raw, 0, w.key0)
431
584
  tlsWordInto(raw, 8, w.key1)
432
585
  tlsWordInto(raw, 16, w.key2)
433
586
  tlsWordInto(raw, 24, w.key3)
434
- w.key0 = toU64(0)
435
- w.key1 = toU64(0)
436
- w.key2 = toU64(0)
437
- w.key3 = toU64(0)
438
587
  const share: u8[] = new Array<u8>(X25519_SIZE)
439
588
  tlsWordInto(share, 0, w.share0)
440
589
  tlsWordInto(share, 8, w.share1)
@@ -444,33 +593,40 @@ const tlsExchange = (w: TlsExchangeWords): i32 => {
444
593
  const serverPublic: u8[] | null = x25519Base(key)
445
594
  const shared: Secret<u8[]> | null = x25519(key, share)
446
595
  wipe(key)
596
+ let alert: i32 = 0
447
597
  if (shared === null) {
448
- return TLS_ALERT_INTERNAL_ERROR
449
- }
450
- // RFC 7748 §6.1 leaves the all-zero check to the protocol, and RFC 8446
451
- // §7.4.2 makes it: a low-order client share gives the zero secret, which
452
- // anybody can compute. The secret is secret, so the compare reads it all.
453
- if (serverPublic === null || expose(shared, tlsIsZeroSecret)) {
598
+ alert = TLS_ALERT_INTERNAL_ERROR
599
+ } else {
600
+ // RFC 7748 §6.1 leaves the all-zero check to the protocol, and RFC 8446
601
+ // §7.4.2 makes it: a low-order client share gives the zero secret, which
602
+ // anybody can compute. The secret is secret, so the compare reads it all.
603
+ if (serverPublic === null) {
604
+ alert = TLS_ALERT_INTERNAL_ERROR
605
+ } else if (expose(shared, tlsIsZeroSecret)) {
606
+ alert = TLS_ALERT_ILLEGAL_PARAMETER
607
+ } else {
608
+ const handshake: u8[] =
609
+ w.hashLength === SHA384_SIZE
610
+ ? exposeWith(shared, salt, tlsHandshakeFromEcdhe384)
611
+ : exposeWith(shared, salt, tlsHandshakeFromEcdhe256)
612
+ w.public0 = tlsWordAt(serverPublic, 0)
613
+ w.public1 = tlsWordAt(serverPublic, 8)
614
+ w.public2 = tlsWordAt(serverPublic, 16)
615
+ w.public3 = tlsWordAt(serverPublic, 24)
616
+ w.secret0 = tlsWordAt(handshake, 0)
617
+ w.secret1 = tlsWordAt(handshake, 8)
618
+ w.secret2 = tlsWordAt(handshake, 16)
619
+ w.secret3 = tlsWordAt(handshake, 24)
620
+ w.secret4 = tlsWordAt(handshake, 32)
621
+ w.secret5 = tlsWordAt(handshake, 40)
622
+ secureZero(handshake)
623
+ }
454
624
  wipe(shared)
455
- return serverPublic === null ? TLS_ALERT_INTERNAL_ERROR : TLS_ALERT_ILLEGAL_PARAMETER
456
- }
457
- const handshake: u8[] =
458
- w.hashLength === SHA384_SIZE
459
- ? expose(shared, tlsHandshakeFromEcdhe384)
460
- : expose(shared, tlsHandshakeFromEcdhe256)
461
- wipe(shared)
462
- w.public0 = tlsWordAt(serverPublic, 0)
463
- w.public1 = tlsWordAt(serverPublic, 8)
464
- w.public2 = tlsWordAt(serverPublic, 16)
465
- w.public3 = tlsWordAt(serverPublic, 24)
466
- w.secret0 = tlsWordAt(handshake, 0)
467
- w.secret1 = tlsWordAt(handshake, 8)
468
- w.secret2 = tlsWordAt(handshake, 16)
469
- w.secret3 = tlsWordAt(handshake, 24)
470
- w.secret4 = tlsWordAt(handshake, 32)
471
- w.secret5 = tlsWordAt(handshake, 40)
472
- secureZero(handshake)
473
- return 0
625
+ }
626
+ // One wipe every path reaches, before the one return: a wipe on each path
627
+ // is one `opt -O2` may merge (docs/security/tls.md, "What stays in memory").
628
+ secureZero(salt)
629
+ return alert
474
630
  }
475
631
 
476
632
  // ---- Buffers -------------------------------------------------------------------
@@ -481,20 +637,126 @@ const TLS_INPUT_START: i32 = 2048
481
637
  const TLS_INITIAL_OUTPUT_SIZE: i32 = 256
482
638
  /** The longest DER ECDSA P-256 signature, what the flight is first sized for: two 33-byte integers and their headers. */
483
639
  const TLS_ECDSA_SIGNATURE_MAX: i32 = 72
484
- /** Which secret each of a server's secret arrays holds, by index into `secrets256` and `secrets384`. */
485
- const TLS_SECRET_HANDSHAKE: i32 = 0
486
- const TLS_SECRET_CLIENT_HANDSHAKE: i32 = 1
487
- const TLS_SECRET_SERVER_HANDSHAKE: i32 = 2
488
- const TLS_SECRET_CLIENT_APPLICATION: i32 = 3
489
- const TLS_SECRET_SERVER_APPLICATION: i32 = 4
490
- const TLS_SECRET_EXPORTER: i32 = 5
491
- const TLS_SECRET_CLIENT_FINISHED: i32 = 6
640
+ /**
641
+ * Which secret of a `TlsSecretPool` an index names: what `TlsServer.known`,
642
+ * `wipeSecret` and the pool's `zero` take, and the bit of `TlsServer.held`
643
+ * that says the secret is derived (`1 << index`).
644
+ */
645
+ export const TLS_SECRET_HANDSHAKE: i32 = 0
646
+ export const TLS_SECRET_CLIENT_HANDSHAKE: i32 = 1
647
+ export const TLS_SECRET_SERVER_HANDSHAKE: i32 = 2
648
+ export const TLS_SECRET_CLIENT_APPLICATION: i32 = 3
649
+ export const TLS_SECRET_SERVER_APPLICATION: i32 = 4
650
+ export const TLS_SECRET_EXPORTER: i32 = 5
651
+ export const TLS_SECRET_CLIENT_FINISHED: i32 = 6
492
652
  /** The CertificateVerify input sits beside them: 98 bytes and the transcript hash. */
493
- const TLS_SECRET_TO_BE_SIGNED: i32 = 7
494
- const TLS_SECRET_COUNT: i32 = 8
653
+ export const TLS_SECRET_TO_BE_SIGNED: i32 = 7
654
+ export const TLS_SECRET_RESUMPTION: i32 = 8
655
+ /** The PSK a ticket opened to, held from the binder's check to the early secret's extract. */
656
+ export const TLS_SECRET_PSK: i32 = 9
657
+ /** The PSK early secret (RFC 8446 §7.1), held until the handshake secret's salt is derived from it. */
658
+ export const TLS_SECRET_EARLY: i32 = 10
659
+ /** The binder key (§4.2.11.2), held until the binder is compared. */
660
+ export const TLS_SECRET_BINDER_KEY: i32 = 11
495
661
  /** What CertificateVerify signs besides the hash (RFC 8446 §4.4.3): 64 spaces, the 33-byte context and a zero. */
496
662
  const TLS_CERTIFICATE_VERIFY_PREFIX: i32 = 98
497
663
 
664
+ /**
665
+ * The secrets of one connection under one hash, as `TlsServer` holds them
666
+ * in `secrets256` and `secrets384`: an array of exactly `hashLength` bytes
667
+ * for each, allocated once and reused by every connection. The ten the key
668
+ * schedule derives are `Secret`s (`nish:secret`), read only inside `expose`
669
+ * and wiped by `wipe`, so the checker proves no copy of them leaves the
670
+ * pool and that `wipe` reaches every one. The expected client Finished and
671
+ * the CertificateVerify input are what the wire carries, and stay plain;
672
+ * `wipe` zeroes them too (TLS-1).
673
+ */
674
+ export class TlsSecretPool {
675
+ readonly handshake: Secret<u8[]>
676
+ readonly clientHandshake: Secret<u8[]>
677
+ readonly serverHandshake: Secret<u8[]>
678
+ readonly clientApplication: Secret<u8[]>
679
+ readonly serverApplication: Secret<u8[]>
680
+ /** `exporter_master_secret` (RFC 8446 §7.5), for a caller's own exporters. */
681
+ readonly exporter: Secret<u8[]>
682
+ /**
683
+ * `resumption_master_secret` (RFC 8446 §7.1), derived by `sign` and the
684
+ * connection's once the client's Finished is accepted: what a
685
+ * NewSessionTicket's PSK would be derived from.
686
+ */
687
+ readonly resumption: Secret<u8[]>
688
+ /** The PSK the selected ticket opened to, on a resumed handshake only. */
689
+ readonly psk: Secret<u8[]>
690
+ /** The PSK early secret, on a resumed handshake only. */
691
+ readonly early: Secret<u8[]>
692
+ /** The binder key, from the selected ticket's early secret. */
693
+ readonly binderKey: Secret<u8[]>
694
+ /** The client Finished's `verify_data`, known once the server's Finished is written. */
695
+ clientFinished: u8[]
696
+ /** The CertificateVerify input, 98 bytes longer than the rest. */
697
+ toBeSigned: u8[]
698
+
699
+ constructor(hashLength: i32) {
700
+ this.handshake = secret(new Array<u8>(hashLength))
701
+ this.clientHandshake = secret(new Array<u8>(hashLength))
702
+ this.serverHandshake = secret(new Array<u8>(hashLength))
703
+ this.clientApplication = secret(new Array<u8>(hashLength))
704
+ this.serverApplication = secret(new Array<u8>(hashLength))
705
+ this.exporter = secret(new Array<u8>(hashLength))
706
+ this.resumption = secret(new Array<u8>(hashLength))
707
+ this.psk = secret(new Array<u8>(hashLength))
708
+ this.early = secret(new Array<u8>(hashLength))
709
+ this.binderKey = secret(new Array<u8>(hashLength))
710
+ this.clientFinished = new Array<u8>(hashLength)
711
+ this.toBeSigned = new Array<u8>(hashLength + TLS_CERTIFICATE_VERIFY_PREFIX)
712
+ }
713
+
714
+ /** Wipes the one secret `index` names (`TLS_SECRET_*`), leaving the rest; any other index wipes nothing. */
715
+ zero(index: i32): void {
716
+ if (index === TLS_SECRET_HANDSHAKE) {
717
+ exposeWith(this.handshake, TLS_FROM, tlsZeroHeld)
718
+ } else if (index === TLS_SECRET_CLIENT_HANDSHAKE) {
719
+ exposeWith(this.clientHandshake, TLS_FROM, tlsZeroHeld)
720
+ } else if (index === TLS_SECRET_SERVER_HANDSHAKE) {
721
+ exposeWith(this.serverHandshake, TLS_FROM, tlsZeroHeld)
722
+ } else if (index === TLS_SECRET_CLIENT_APPLICATION) {
723
+ exposeWith(this.clientApplication, TLS_FROM, tlsZeroHeld)
724
+ } else if (index === TLS_SECRET_SERVER_APPLICATION) {
725
+ exposeWith(this.serverApplication, TLS_FROM, tlsZeroHeld)
726
+ } else if (index === TLS_SECRET_EXPORTER) {
727
+ exposeWith(this.exporter, TLS_FROM, tlsZeroHeld)
728
+ } else if (index === TLS_SECRET_RESUMPTION) {
729
+ exposeWith(this.resumption, TLS_FROM, tlsZeroHeld)
730
+ } else if (index === TLS_SECRET_PSK) {
731
+ exposeWith(this.psk, TLS_FROM, tlsZeroHeld)
732
+ } else if (index === TLS_SECRET_EARLY) {
733
+ exposeWith(this.early, TLS_FROM, tlsZeroHeld)
734
+ } else if (index === TLS_SECRET_BINDER_KEY) {
735
+ exposeWith(this.binderKey, TLS_FROM, tlsZeroHeld)
736
+ } else if (index === TLS_SECRET_CLIENT_FINISHED) {
737
+ secureZero(this.clientFinished)
738
+ } else if (index === TLS_SECRET_TO_BE_SIGNED) {
739
+ secureZero(this.toBeSigned)
740
+ }
741
+ }
742
+
743
+ /** Wipes every secret in the pool. */
744
+ wipe(): void {
745
+ wipe(this.handshake)
746
+ wipe(this.clientHandshake)
747
+ wipe(this.serverHandshake)
748
+ wipe(this.clientApplication)
749
+ wipe(this.serverApplication)
750
+ wipe(this.exporter)
751
+ wipe(this.resumption)
752
+ wipe(this.psk)
753
+ wipe(this.early)
754
+ wipe(this.binderKey)
755
+ secureZero(this.clientFinished)
756
+ secureZero(this.toBeSigned)
757
+ }
758
+ }
759
+
498
760
  /**
499
761
  * The most the server's Handshake-level flight under `config` takes with an
500
762
  * ECDSA P-256 signature: EncryptedExtensions with every extension it may
@@ -521,6 +783,73 @@ const tlsFlightCapacity = (config: TlsServerConfig): i32 => {
521
783
  return extensions + certificate + 8 + TLS_ECDSA_SIGNATURE_MAX + 4 + SHA384_SIZE
522
784
  }
523
785
 
786
+ // ---- Resumption ------------------------------------------------------------------
787
+
788
+ /** What `tlsServerStrikeProbe` finds for an id: a free entry it could be recorded in. */
789
+ const TLS_SERVER_STRIKE_FREE: i32 = 0
790
+ /** The id is held: recorded and live (row 6 of the record's table). */
791
+ const TLS_SERVER_STRIKE_HELD: i32 = 1
792
+ /** No entry is free (row 7). */
793
+ const TLS_SERVER_STRIKE_FULL: i32 = 2
794
+
795
+ /**
796
+ * What `strikes.take(id, …, now)` would find, without recording anything:
797
+ * the same scan as `TlsTicketStrikes.take`, bounded by `longest` the same
798
+ * way. `resume` asks before the binder is checked, so that an identity
799
+ * already recorded is passed over rather than costing a binder, and records
800
+ * with `take` only once the binder verifies, so that a binder that fails
801
+ * records nothing (row 9).
802
+ */
803
+ const tlsServerStrikeProbe = (strikes: TlsTicketStrikes, id: u8[], now: i64): i32 => {
804
+ const capacity: i32 = strikes.capacity
805
+ const home: i32 = strikes.home(id)
806
+ let free: i32 = -1
807
+ for (let n: i32 = 0; n < capacity; n++) {
808
+ const slot: i32 = (home + n) % capacity
809
+ if (strikes.used[slot] === 0) {
810
+ if (free < 0) {
811
+ free = slot
812
+ }
813
+ break
814
+ }
815
+ if (strikes.expires[slot] < now) {
816
+ if (free < 0) {
817
+ free = slot
818
+ }
819
+ } else if (n <= strikes.longest && strikes.holds(slot, id)) {
820
+ return TLS_SERVER_STRIKE_HELD
821
+ }
822
+ if (free >= 0 && n >= strikes.longest) {
823
+ break
824
+ }
825
+ }
826
+ return free < 0 ? TLS_SERVER_STRIKE_FULL : TLS_SERVER_STRIKE_FREE
827
+ }
828
+
829
+ /** `answerHello`'s index when no ticket was chosen: a full handshake. */
830
+ const TLS_NO_IDENTITY: i32 = -1
831
+
832
+ /**
833
+ * What a NewSessionTicket adds around its ticket: the handshake header (4),
834
+ * `ticket_lifetime` (4), `ticket_age_add` (4), the nonce's length and its one
835
+ * byte (2), the ticket's length (2) and an empty extensions list (2).
836
+ */
837
+ const TLS_SERVER_TICKET_OVERHEAD: i32 = 18
838
+
839
+ /** The bytes the largest ticket takes: a SHA-384 PSK and a 255-byte server name. */
840
+ const tlsServerTicketCapacity = (state: TlsTicketState): i32 => {
841
+ state.suite = TLS_AES_256_GCM_SHA384
842
+ state.pskLength = TLS_TICKET_PSK_MAX
843
+ state.serverNameLength = TLS_TICKET_SERVER_NAME_MAX
844
+ state.lifetime = TLS_TICKET_LIFETIME_SECONDS
845
+ const size: i32 = tlsTicketSize(state)
846
+ state.suite = 0
847
+ state.pskLength = 0
848
+ state.serverNameLength = 0
849
+ state.lifetime = 0
850
+ return size
851
+ }
852
+
524
853
  /**
525
854
  * One TLS 1.3 server handshake. Make one per connection, or one per slot and
526
855
  * `restart` it for each connection the slot takes; feed it what the client
@@ -530,26 +859,30 @@ const tlsFlightCapacity = (config: TlsServerConfig): i32 => {
530
859
  *
531
860
  * The fields are readable: `state` and `alert`, the negotiated `suite`,
532
861
  * `serverName`, `alpn` and the client's `clientTransportParameters`, and the
533
- * traffic secrets. `readSecret` and `writeSecret` answer the last by level.
862
+ * `held` bits of the secrets derived so far. The secrets themselves are
863
+ * `Secret` fields of `secrets()`, read through `expose`; `readKnown` and
864
+ * `writeKnown` say whether the traffic secrets of a level are known yet.
534
865
  *
535
866
  * **What it allocates.** Everything it keeps is allocated by the constructor
536
867
  * and reused by `restart`: the input and output buffers, the transcript's
537
- * hashers, the HKDF scratch, and an array of exactly HashLen bytes for every
538
- * secret under each hash, which the secret fields point at as each is
539
- * derived and away from again when the next connection starts. `receive` and
868
+ * hashers, the HKDF scratch, the ephemeral key's 32 bytes, and HashLen bytes for every secret
869
+ * under each hash, which each derivation writes over in place and `wipe`
870
+ * zeroes before the next connection starts. `receive` and
540
871
  * `sign` do their work inside `using a = arena()` blocks, so the compiler has
541
872
  * proved that what they allocate on the way dies before they return. Four
542
873
  * things grow or are made outside those blocks: the input buffer, doubled up
543
874
  * to the largest message when a ClientHello does not fit (once per slot);
544
875
  * the Handshake-level output, when a signature is longer than an ECDSA one;
545
876
  * the `serverName` string, made when the client's host name differs from the
546
- * last one this server held; and, over QUIC only, a copy of the client's
547
- * transport parameters (TLS-3).
877
+ * last one this server held; and, over QUIC only, the array the client's
878
+ * transport parameters are copied into, which grows only when a client's
879
+ * are longer than any before (TLS-3).
548
880
  */
549
881
  export class TlsServer {
550
882
  config: TlsServerConfig
551
883
  serverRandom: u8[]
552
- ephemeralPrivate: u8[]
884
+ /** The connection's x25519 private key, copied in by `restart` and wiped by the record layer once the ServerHello is written. */
885
+ readonly ephemeral: Secret<u8[]>
553
886
  /** The client's `host_name`, or empty. */
554
887
  serverName: string = ""
555
888
  /** The ALPN protocol chosen, one of the configuration's, or empty. */
@@ -558,6 +891,8 @@ export class TlsServer {
558
891
  lastServerName: string = ""
559
892
  /** The client's `quic_transport_parameters`, opaque; empty unless the carrier is QUIC. */
560
893
  clientTransportParameters: u8[]
894
+ /** The array `clientTransportParameters` points at once read, kept across `restart` and refilled in the room it has. */
895
+ clientParameters: u8[]
561
896
  transcript: TlsTranscript
562
897
  /** The bytes received at the current level and not yet a whole message: `input[0 .. inputLength)`. */
563
898
  input: u8[]
@@ -565,22 +900,11 @@ export class TlsServer {
565
900
  outputInitial: u8[]
566
901
  /** What the server has written at the Handshake level and not handed on: `outputHandshake[0 .. outputHandshakeLength)`. */
567
902
  outputHandshake: u8[]
568
- handshakeSecret: u8[]
569
- clientHandshakeSecret: u8[]
570
- serverHandshakeSecret: u8[]
571
- clientApplicationSecret: u8[]
572
- serverApplicationSecret: u8[]
573
- /** `exporter_master_secret` (RFC 8446 §7.5), for a caller's own exporters. */
574
- exporterSecret: u8[]
575
- /** The CertificateVerify input while `state` is `TLS_STATE_WAIT_SIGNATURE`. */
576
- toBeSigned: u8[]
577
- /** The client Finished's `verify_data`, known once the server's Finished is written. */
578
- expectedClientFinished: u8[]
579
- /** The empty array every secret field points at until its secret is derived. */
903
+ /** An empty array: what `clientTransportParameters` holds until a client's are read. */
580
904
  none: u8[]
581
- /** The arrays the secret fields point into: `TLS_SECRET_COUNT` of each hash's lengths. */
582
- secrets256: u8[][]
583
- secrets384: u8[][]
905
+ /** The secrets under each hash (`TlsSecretPool`); `secrets()` answers the negotiated hash's. */
906
+ secrets256: TlsSecretPool
907
+ secrets384: TlsSecretPool
584
908
  /** The ClientHello last read, as windows into `input`. */
585
909
  hello: TlsClientHelloView
586
910
  /** Where every HKDF and HMAC of the schedule runs. */
@@ -591,6 +915,33 @@ export class TlsServer {
591
915
  serverPublic: u8[]
592
916
  /** A transcript hash on its way into the schedule: HashLen bytes of it. */
593
917
  hashes: u8[]
918
+ /**
919
+ * The salt the handshake secret is extracted under, HashLen bytes of it:
920
+ * the `TLS_DERIVED_SALT_*` constant on a full handshake, derived from the
921
+ * PSK early secret on a resumed one. `exchangeKeys` reads it into the
922
+ * exchange's words and zeroes it.
923
+ */
924
+ salt: u8[]
925
+ /** The transcript hash through the ClientHello's binders prefix, which a binder covers (§4.2.11.2). */
926
+ binderHash: u8[]
927
+ /** What the ticket `resume` is reading opened to; its PSK is zeroed once read. */
928
+ opened: TlsTicketState
929
+ /** What `issue` seals; its PSK is zeroed once sealed. */
930
+ issuing: TlsTicketState
931
+ /** The sealed ticket, sized once for the largest. */
932
+ ticketScratch: u8[]
933
+ /** What the server has written at the Application level and not handed on: one NewSessionTicket. */
934
+ outputApplication: u8[]
935
+ /** The ticket nonce: one ticket a connection, so one byte, the same each time, keeps each PSK distinct (§4.6.1). */
936
+ ticketNonce: u8[]
937
+ /** The ticket's `ticket_age_add`, drawn fresh for each ticket (§4.6.1). */
938
+ ageAddBytes: u8[]
939
+ /**
940
+ * Which of the negotiated pool's secrets the handshake has derived: bit
941
+ * `1 << TLS_SECRET_*` for each. A secret wiped by `wipeSecret` keeps its
942
+ * bit, since its place is still the one it was derived into.
943
+ */
944
+ held: i32 = 0
594
945
  state: i32 = 0
595
946
  /** The alert the server failed with, or 0. */
596
947
  alert: i32 = 0
@@ -600,10 +951,23 @@ export class TlsServer {
600
951
  inputLength: i32 = 0
601
952
  outputInitialLength: i32 = 0
602
953
  outputHandshakeLength: i32 = 0
954
+ outputApplicationLength: i32 = 0
603
955
  /** Whether a HelloRetryRequest was sent, so a second miss is fatal. */
604
956
  retried: boolean = false
605
957
  /** Whether `config` passed `tlsConfigFits`, decided once by the constructor. */
606
958
  configFits: boolean = false
959
+ /** Whether the carrier turned resumption on with `enableTickets`; kept across `restart`. */
960
+ tickets: boolean = false
961
+ /**
962
+ * Whether this connection may be issued a ticket: tickets are on, the
963
+ * client listed `psk_dhe_ke`, the only mode this server resumes in, and its
964
+ * server name, if any, fits a ticket.
965
+ */
966
+ ticketWanted: boolean = false
967
+ /** Whether `issue` has run for this connection. */
968
+ issued: boolean = false
969
+ /** Whether this connection resumed a ticket (row 1 of the record's table). */
970
+ resumed: boolean = false
607
971
 
608
972
  /**
609
973
  * A server for one connection under `config`, with the 32-byte server
@@ -614,49 +978,50 @@ export class TlsServer {
614
978
  constructor(config: TlsServerConfig, serverRandom: u8[], ephemeralPrivate: u8[]) {
615
979
  this.config = config
616
980
  this.serverRandom = serverRandom
617
- this.ephemeralPrivate = ephemeralPrivate
981
+ this.ephemeral = secret(new Array<u8>(X25519_SIZE))
618
982
  this.none = []
619
983
  this.clientTransportParameters = this.none
984
+ this.clientParameters = []
620
985
  this.transcript = new TlsTranscript(0)
621
986
  this.configFits = tlsConfigFits(config)
622
987
  this.input = new Array<u8>(TLS_INPUT_START)
623
988
  this.outputInitial = new Array<u8>(TLS_INITIAL_OUTPUT_SIZE)
624
989
  this.outputHandshake = new Array<u8>(this.configFits ? tlsFlightCapacity(config) : TLS_FROM)
625
- this.secrets256 = []
626
- this.secrets384 = []
627
- for (let k: i32 = 0; k < TLS_SECRET_COUNT; k++) {
628
- const extra: i32 = k === TLS_SECRET_TO_BE_SIGNED ? TLS_CERTIFICATE_VERIFY_PREFIX : 0
629
- this.secrets256.push(new Array<u8>(SHA256_SIZE + extra))
630
- this.secrets384.push(new Array<u8>(SHA384_SIZE + extra))
631
- }
990
+ this.secrets256 = new TlsSecretPool(SHA256_SIZE)
991
+ this.secrets384 = new TlsSecretPool(SHA384_SIZE)
632
992
  this.hello = new TlsClientHelloView()
633
993
  this.kdf = new HkdfScratch()
634
994
  this.exchange = new TlsExchangeWords()
635
995
  this.serverPublic = new Array<u8>(X25519_SIZE)
636
996
  this.hashes = new Array<u8>(SHA384_SIZE)
637
- this.handshakeSecret = this.none
638
- this.clientHandshakeSecret = this.none
639
- this.serverHandshakeSecret = this.none
640
- this.clientApplicationSecret = this.none
641
- this.serverApplicationSecret = this.none
642
- this.exporterSecret = this.none
643
- this.toBeSigned = this.none
644
- this.expectedClientFinished = this.none
997
+ this.salt = new Array<u8>(SHA384_SIZE)
998
+ this.binderHash = new Array<u8>(SHA384_SIZE)
999
+ this.opened = new TlsTicketState()
1000
+ this.issuing = new TlsTicketState()
1001
+ this.ticketScratch = new Array<u8>(tlsServerTicketCapacity(this.issuing))
1002
+ this.outputApplication = new Array<u8>(toI32(this.ticketScratch.length) + TLS_SERVER_TICKET_OVERHEAD)
1003
+ this.ticketNonce = new Array<u8>(1)
1004
+ this.ageAddBytes = new Array<u8>(4)
645
1005
  this.restart(serverRandom, ephemeralPrivate)
646
1006
  }
647
1007
 
648
1008
  /**
649
1009
  * Starts the next connection in this server's buffers, with its own
650
1010
  * 32-byte random and x25519 private key, as a fresh `TlsServer` would:
651
- * every secret the last connection left in them is wiped with `secureZero`
652
- * and every secret field points at nothing again. The two arrays are kept,
653
- * not copied, as the constructor keeps them. A random or key of another
1011
+ * every secret the last connection left is wiped (`wipe`) and none is
1012
+ * held. The random is kept, as the constructor keeps it; the key is copied
1013
+ * into `ephemeral` and the caller's array zeroed with `secureZero`, so the
1014
+ * only copy left is the one the server wipes. A random or key of another
654
1015
  * length, or a configuration `tlsConfigFits` refused, leaves the server
655
1016
  * failed with `internal_error`.
656
1017
  */
657
1018
  restart(serverRandom: u8[], ephemeralPrivate: u8[]): void {
1019
+ const keyLength: i32 = toI32(ephemeralPrivate.length)
1020
+ this.wipe()
1021
+ this.held = 0
1022
+ exposeWith(this.ephemeral, ephemeralPrivate, tlsHeldCopyIn)
1023
+ secureZero(ephemeralPrivate)
658
1024
  this.serverRandom = serverRandom
659
- this.ephemeralPrivate = ephemeralPrivate
660
1025
  this.state = TLS_STATE_WAIT_CLIENT_HELLO
661
1026
  this.alert = 0
662
1027
  this.suite = 0
@@ -668,39 +1033,92 @@ export class TlsServer {
668
1033
  this.inputLength = 0
669
1034
  this.outputInitialLength = 0
670
1035
  this.outputHandshakeLength = 0
671
- for (let k: i32 = 0; k < toI32(this.secrets256.length) && k < toI32(this.secrets384.length); k++) {
672
- secureZero(this.secrets256[k])
673
- secureZero(this.secrets384[k])
674
- }
675
- this.handshakeSecret = this.none
676
- this.clientHandshakeSecret = this.none
677
- this.serverHandshakeSecret = this.none
678
- this.clientApplicationSecret = this.none
679
- this.serverApplicationSecret = this.none
680
- this.exporterSecret = this.none
681
- this.toBeSigned = this.none
682
- this.expectedClientFinished = this.none
683
- if (
684
- toI32(serverRandom.length) !== TLS_RANDOM_SIZE ||
685
- toI32(ephemeralPrivate.length) !== X25519_SIZE ||
686
- !this.configFits
687
- ) {
1036
+ this.outputApplicationLength = 0
1037
+ this.ticketWanted = false
1038
+ this.issued = false
1039
+ this.resumed = false
1040
+ secureZero(this.salt)
1041
+ secureZero(this.opened.psk)
1042
+ secureZero(this.issuing.psk)
1043
+ if (toI32(serverRandom.length) !== TLS_RANDOM_SIZE || keyLength !== X25519_SIZE || !this.configFits) {
688
1044
  this.state = TLS_STATE_FAILED
689
1045
  this.alert = TLS_ALERT_INTERNAL_ERROR
690
1046
  }
691
1047
  }
692
1048
 
693
- /** Fails the handshake with `alert` and answers it. */
1049
+ /**
1050
+ * Fails the handshake with `alert` and answers it, wiping the resumption
1051
+ * master secret `sign` derived ahead and what a ticket being read left: its
1052
+ * PSK, early secret and binder key, and the salt.
1053
+ */
694
1054
  fail(alert: i32): i32 {
1055
+ this.wipeResumption()
1056
+ const pool: TlsSecretPool = this.secrets()
1057
+ pool.zero(TLS_SECRET_PSK)
1058
+ pool.zero(TLS_SECRET_EARLY)
1059
+ pool.zero(TLS_SECRET_BINDER_KEY)
1060
+ secureZero(this.salt)
1061
+ secureZero(this.opened.psk)
695
1062
  this.state = TLS_STATE_FAILED
696
1063
  this.alert = alert
697
1064
  return alert
698
1065
  }
699
1066
 
700
- /** The array secret `index` lives in under the negotiated hash: exactly HashLen bytes (the CertificateVerify input, 98 more). */
701
- secretArray(index: i32): u8[] {
702
- const pool: u8[][] = this.hashLength === SHA384_SIZE ? this.secrets384 : this.secrets256
703
- return index >= 0 && index < toI32(pool.length) ? pool[index] : this.none
1067
+ /**
1068
+ * Wipes the resumption master secret, which `sign` derives ahead of the
1069
+ * client's Finished. `issue` calls it once the ticket is sealed, and the
1070
+ * carrier when a connection ends, closed or failed, without one (TLS-1).
1071
+ */
1072
+ wipeResumption(): void {
1073
+ this.secrets().zero(TLS_SECRET_RESUMPTION)
1074
+ }
1075
+
1076
+ /** The pool of the negotiated hash: SHA-384's for a SHA-384 suite, SHA-256's otherwise. */
1077
+ secrets(): TlsSecretPool {
1078
+ return this.hashLength === SHA384_SIZE ? this.secrets384 : this.secrets256
1079
+ }
1080
+
1081
+ /** Whether the handshake has derived secret `index` (`TLS_SECRET_*`) for this connection. */
1082
+ known(index: i32): boolean {
1083
+ return index >= 0 && index < 31 && (this.held & (1 << index)) !== 0
1084
+ }
1085
+
1086
+ /**
1087
+ * Whether the secret the server *reads* `level` under — the client's
1088
+ * traffic secret, `clientHandshake` or `clientApplication` of `secrets()` —
1089
+ * is known yet. The Initial level has none: it is cleartext in TLS, and
1090
+ * QUIC derives its own from the connection id.
1091
+ */
1092
+ readKnown(level: i32): boolean {
1093
+ if (level === TLS_LEVEL_HANDSHAKE) {
1094
+ return this.known(TLS_SECRET_CLIENT_HANDSHAKE)
1095
+ }
1096
+ return level === TLS_LEVEL_APPLICATION && this.known(TLS_SECRET_CLIENT_APPLICATION)
1097
+ }
1098
+
1099
+ /** Whether the secret the server *writes* `level` under — `serverHandshake` or `serverApplication` — is known yet. */
1100
+ writeKnown(level: i32): boolean {
1101
+ if (level === TLS_LEVEL_HANDSHAKE) {
1102
+ return this.known(TLS_SECRET_SERVER_HANDSHAKE)
1103
+ }
1104
+ return level === TLS_LEVEL_APPLICATION && this.known(TLS_SECRET_SERVER_APPLICATION)
1105
+ }
1106
+
1107
+ /** Zeroes secret `index` of `secrets()` once a carrier has taken what it needs from it; it stays known. */
1108
+ wipeSecret(index: i32): void {
1109
+ this.secrets().zero(index)
1110
+ }
1111
+
1112
+ /** Zeroes the ephemeral key, which the record layer does once the ServerHello is written: not while a hello that offers a ticket waits for `resume`, whose exchange still needs it. */
1113
+ wipeEphemeral(): void {
1114
+ exposeWith(this.ephemeral, TLS_FROM, tlsZeroHeld)
1115
+ }
1116
+
1117
+ /** Wipes every secret the server holds: the ephemeral key and both pools. `restart` calls it, and the carriers when a connection ends. */
1118
+ wipe(): void {
1119
+ wipe(this.ephemeral)
1120
+ this.secrets256.wipe()
1121
+ this.secrets384.wipe()
704
1122
  }
705
1123
 
706
1124
  /** The level the client's next bytes must arrive at, or -1 when the state expects none. */
@@ -949,9 +1367,15 @@ export class TlsServer {
949
1367
  if (this.retried && suite !== this.suite) {
950
1368
  return TLS_ALERT_ILLEGAL_PARAMETER
951
1369
  }
1370
+ // A ticket offered in `psk_dhe_ke` that this server reads: the
1371
+ // identities are read by `resume`, and a hello that resumes needs no
1372
+ // signature (row 14 of "Resumption" in `docs/security/tls.md`).
1373
+ const offersTicket: boolean = this.tickets && hello.hasPreSharedKey && hello.pskDheKe
952
1374
  // §9.2: a certificate-authenticated handshake needs all three, and
953
- // `supported_groups` and `key_share` come as a pair.
954
- if (!hello.hasSignatureAlgorithms || !hello.hasSupportedGroups || !hello.hasKeyShare) {
1375
+ // `supported_groups` and `key_share` come as a pair. A hello offering a
1376
+ // ticket has `signature_algorithms` checked by `answerHello`, once it is
1377
+ // known not to resume.
1378
+ if ((!offersTicket && !hello.hasSignatureAlgorithms) || !hello.hasSupportedGroups || !hello.hasKeyShare) {
955
1379
  return TLS_ALERT_MISSING_EXTENSION
956
1380
  }
957
1381
  if (this.config.quic && !hello.hasQuicTransportParameters) {
@@ -960,7 +1384,10 @@ export class TlsServer {
960
1384
  if (!tlsWindowHasU16(data, hello.groupsAt, hello.groupsLength, TLS_GROUP_X25519)) {
961
1385
  return TLS_ALERT_HANDSHAKE_FAILURE
962
1386
  }
963
- if (!tlsWindowHasU16(data, hello.schemesAt, hello.schemesLength, this.config.signatureScheme)) {
1387
+ if (
1388
+ !offersTicket &&
1389
+ !tlsWindowHasU16(data, hello.schemesAt, hello.schemesLength, this.config.signatureScheme)
1390
+ ) {
964
1391
  return TLS_ALERT_HANDSHAKE_FAILURE
965
1392
  }
966
1393
  if (!this.retried) {
@@ -968,7 +1395,16 @@ export class TlsServer {
968
1395
  this.hashLength = tlsSuiteHashLength(suite)
969
1396
  this.transcript.reset(this.hashLength)
970
1397
  }
971
- this.transcript.update(data, TLS_FROM, 4 + length)
1398
+ if (offersTicket) {
1399
+ // The binders cover the transcript up to the binders list (§4.2.11.2),
1400
+ // so its hash is taken on the way through.
1401
+ const prefix: i32 = hello.pskBindersPrefixAt
1402
+ this.transcript.update(data, TLS_FROM, prefix)
1403
+ this.transcript.hashInto(this.binderHash, TLS_FROM)
1404
+ this.transcript.update(data, prefix, 4 + length - prefix)
1405
+ } else {
1406
+ this.transcript.update(data, TLS_FROM, 4 + length)
1407
+ }
972
1408
  if (!hello.hasX25519Share) {
973
1409
  // §4.1.4: one HelloRetryRequest; a second ClientHello without the share
974
1410
  // the first one asked for is `illegal_parameter`.
@@ -1012,71 +1448,376 @@ export class TlsServer {
1012
1448
  return alpnAlert
1013
1449
  }
1014
1450
  this.takeServerName(hello)
1451
+ this.ticketWanted =
1452
+ this.tickets &&
1453
+ hello.hasPskModes &&
1454
+ hello.pskDheKe &&
1455
+ (!hello.hasServerName || hello.serverNameLength <= TLS_TICKET_SERVER_NAME_MAX)
1456
+ const name: u8[] = this.issuing.serverName
1457
+ const nameLength: i32 = this.ticketWanted && hello.hasServerName ? hello.serverNameLength : 0
1458
+ for (
1459
+ let k: i32 = 0;
1460
+ k < nameLength &&
1461
+ k < toI32(name.length) &&
1462
+ hello.serverNameAt + k >= 0 &&
1463
+ hello.serverNameAt + k < toI32(data.length);
1464
+ k++
1465
+ ) {
1466
+ name[k] = data[hello.serverNameAt + k]
1467
+ }
1468
+ this.issuing.serverNameLength = nameLength
1015
1469
  if (this.config.quic) {
1016
- const parameters: u8[] = new Array<u8>(hello.quicLength)
1470
+ // Refilled in place, so a slot's handshakes keep nothing for it (QUIC-3).
1471
+ const parameters: u8[] = this.clientParameters
1472
+ while (parameters.length > 0) {
1473
+ parameters.pop()
1474
+ }
1017
1475
  for (
1018
1476
  let k: i32 = 0;
1019
- k < toI32(parameters.length) && hello.quicAt + k >= 0 && hello.quicAt + k < toI32(data.length);
1477
+ k < hello.quicLength && hello.quicAt + k >= 0 && hello.quicAt + k < toI32(data.length);
1020
1478
  k++
1021
1479
  ) {
1022
- parameters[k] = data[hello.quicAt + k]
1480
+ parameters.push(data[hello.quicAt + k])
1023
1481
  }
1024
1482
  this.clientTransportParameters = parameters
1025
1483
  }
1484
+ if (offersTicket) {
1485
+ // The carrier lends the ticket keys and the strike register to `resume`.
1486
+ this.state = TLS_STATE_WAIT_TICKET
1487
+ return 0
1488
+ }
1489
+ return this.answerHello(TLS_NO_IDENTITY)
1490
+ }
1491
+
1492
+ /**
1493
+ * Answers the ClientHello once it is known whether it resumes: the
1494
+ * ServerHello naming identity `index` and the resumed flight, or, when it
1495
+ * does not resume, the checks a certificate needs and the full flight.
1496
+ * Answers 0 or the alert.
1497
+ */
1498
+ answerHello(index: i32): i32 {
1499
+ const hello: TlsClientHelloView = this.hello
1500
+ if (!this.resumed) {
1501
+ if (!hello.hasSignatureAlgorithms) {
1502
+ return TLS_ALERT_MISSING_EXTENSION
1503
+ }
1504
+ if (!tlsWindowHasU16(this.input, hello.schemesAt, hello.schemesLength, this.config.signatureScheme)) {
1505
+ return TLS_ALERT_HANDSHAKE_FAILURE
1506
+ }
1507
+ tlsHexInto(
1508
+ this.hashLength === SHA384_SIZE ? TLS_DERIVED_SALT_SHA384 : TLS_DERIVED_SALT_SHA256,
1509
+ this.salt,
1510
+ TLS_FROM
1511
+ )
1512
+ }
1026
1513
  const exchangeAlert: i32 = this.exchangeKeys()
1027
1514
  if (exchangeAlert !== 0) {
1028
1515
  return exchangeAlert
1029
1516
  }
1030
- this.writeServerFlight()
1517
+ if (this.resumed) {
1518
+ this.writeResumedFlight(index)
1519
+ } else {
1520
+ this.writeServerFlight()
1521
+ }
1031
1522
  return 0
1032
1523
  }
1033
1524
 
1525
+ /**
1526
+ * Turns resumption on: from the next ClientHello, a hello offering a
1527
+ * ticket in `psk_dhe_ke` waits in `TLS_STATE_WAIT_TICKET` for `resume`,
1528
+ * and a connection whose client lists `psk_dhe_ke` may be issued a ticket
1529
+ * by `issue`. Kept across `restart`. A QUIC server never resumes, so on one
1530
+ * this does nothing.
1531
+ */
1532
+ enableTickets(): void {
1533
+ this.tickets = !this.config.quic
1534
+ }
1535
+
1536
+ /**
1537
+ * Whether the ticket in `opened` may resume this hello: its suite's hash is
1538
+ * the negotiated one, its PSK is that long, and it names the server name
1539
+ * this hello names, or none when the hello names none (rows 4 and 5).
1540
+ */
1541
+ ticketFits(): boolean {
1542
+ const hello: TlsClientHelloView = this.hello
1543
+ const opened: TlsTicketState = this.opened
1544
+ const h: i32 = this.hashLength
1545
+ if (tlsSuiteHashLength(opened.suite) !== h || opened.pskLength !== h) {
1546
+ return false
1547
+ }
1548
+ if (!hello.hasServerName) {
1549
+ return opened.serverNameLength === 0
1550
+ }
1551
+ return (
1552
+ opened.serverNameLength === hello.serverNameLength &&
1553
+ timingSafeEqualAt(hello.data, hello.serverNameAt, opened.serverName, TLS_FROM, hello.serverNameLength)
1554
+ )
1555
+ }
1556
+
1557
+ /**
1558
+ * The first identity whose ticket opens under `current` or `previous`,
1559
+ * fits (`ticketFits`) and is not yet recorded in `strikes`, left selected
1560
+ * in `hello` with its ticket in `opened`; or -1, with every PSK an
1561
+ * identity opened to zeroed. A full register ends the search and is
1562
+ * counted with `take`, so that a full register fails closed into a full
1563
+ * handshake (row 7).
1564
+ */
1565
+ selectTicket(
1566
+ current: Secret<u8[]>,
1567
+ currentInfo: TlsTicketKeyInfo,
1568
+ previous: Secret<u8[]>,
1569
+ previousInfo: TlsTicketKeyInfo,
1570
+ strikes: TlsTicketStrikes,
1571
+ now: i64
1572
+ ): i32 {
1573
+ const hello: TlsClientHelloView = this.hello
1574
+ const opened: TlsTicketState = this.opened
1575
+ const data: u8[] = this.input
1576
+ let chosen: i32 = -1
1577
+ using _scope = arena()
1578
+ for (let k: i32 = 0; k < hello.pskIdentityCount; k++) {
1579
+ if (!tlsSelectPskIdentity(hello, k)) {
1580
+ break
1581
+ }
1582
+ const ticket: u8[] = new Array<u8>(hello.pskIdentityLength)
1583
+ for (
1584
+ let n: i32 = 0;
1585
+ n < toI32(ticket.length) &&
1586
+ hello.pskIdentityAt + n >= 0 &&
1587
+ hello.pskIdentityAt + n < toI32(data.length);
1588
+ n++
1589
+ ) {
1590
+ ticket[n] = data[hello.pskIdentityAt + n]
1591
+ }
1592
+ const opens: boolean =
1593
+ tlsTicketOpen(current, currentInfo, ticket, now, opened) ||
1594
+ tlsTicketOpen(previous, previousInfo, ticket, now, opened)
1595
+ if (opens && this.ticketFits()) {
1596
+ const probe: i32 = tlsServerStrikeProbe(strikes, opened.id, now)
1597
+ if (probe === TLS_SERVER_STRIKE_FREE) {
1598
+ chosen = k
1599
+ break
1600
+ }
1601
+ if (probe === TLS_SERVER_STRIKE_FULL) {
1602
+ strikes.take(opened.id, opened.expiresAt(), now)
1603
+ secureZero(opened.psk)
1604
+ break
1605
+ }
1606
+ }
1607
+ secureZero(opened.psk)
1608
+ }
1609
+ if (chosen >= 0) {
1610
+ tlsSelectPskIdentity(hello, chosen)
1611
+ }
1612
+ return chosen
1613
+ }
1614
+
1615
+ /**
1616
+ * The selected ticket's PSK, early secret and binder key, and whether the
1617
+ * hello's binder for it verifies (§4.2.11.2). The PSK and binder key are
1618
+ * zeroed before it returns; the early secret stays for `saltFromEarly`.
1619
+ */
1620
+ bindTicket(): boolean {
1621
+ const hello: TlsClientHelloView = this.hello
1622
+ const pool: TlsSecretPool = this.secrets()
1623
+ const h: i32 = this.hashLength
1624
+ exposeWith(pool.psk, this.opened.psk, tlsHeldCopyIn)
1625
+ secureZero(this.opened.psk)
1626
+ using _scope = arena()
1627
+ const psk: u8[] = expose(pool.psk, tlsHeldClone)
1628
+ const early: u8[] = new Array<u8>(h)
1629
+ tlsPskEarlySecretInto(this.kdf, h, psk, early)
1630
+ secureZero(psk)
1631
+ exposeWith(pool.early, early, tlsHeldCopyIn)
1632
+ const binderKey: u8[] = new Array<u8>(h)
1633
+ tlsBinderKeyInto(this.kdf, h, early, binderKey)
1634
+ secureZero(early)
1635
+ exposeWith(pool.binderKey, binderKey, tlsHeldCopyIn)
1636
+ const verified: boolean = tlsBinderVerify(
1637
+ this.kdf,
1638
+ h,
1639
+ binderKey,
1640
+ this.binderHash,
1641
+ TLS_FROM,
1642
+ this.input,
1643
+ hello.pskBinderAt,
1644
+ hello.pskBinderLength
1645
+ )
1646
+ secureZero(binderKey)
1647
+ pool.zero(TLS_SECRET_PSK)
1648
+ pool.zero(TLS_SECRET_BINDER_KEY)
1649
+ return verified
1650
+ }
1651
+
1652
+ /** The salt a resumed handshake secret is extracted under, `Derive-Secret(early, "derived", "")`, into `salt`. */
1653
+ saltFromEarly(): void {
1654
+ const pool: TlsSecretPool = this.secrets()
1655
+ using _scope = arena()
1656
+ const early: u8[] = expose(pool.early, tlsHeldClone)
1657
+ tlsHandshakeSaltInto(this.kdf, this.hashLength, early, this.salt)
1658
+ secureZero(early)
1659
+ }
1660
+
1661
+ /**
1662
+ * In `TLS_STATE_WAIT_TICKET`: reads the hello's tickets under the keys the
1663
+ * carrier lends, `current` and `previous` with what each `info` says of
1664
+ * them (a program with one key passes it as both), records the one it
1665
+ * resumes in `strikes`, and answers the hello, resumed or in full, as the
1666
+ * table of "Resumption" in `docs/security/tls.md` says. A selected ticket
1667
+ * whose binder fails is `decrypt_error`, with nothing recorded (row 9).
1668
+ * Answers 0, or the alert the server fails with.
1669
+ */
1670
+ resume(
1671
+ current: Secret<u8[]>,
1672
+ currentInfo: TlsTicketKeyInfo,
1673
+ previous: Secret<u8[]>,
1674
+ previousInfo: TlsTicketKeyInfo,
1675
+ strikes: TlsTicketStrikes,
1676
+ now: i64
1677
+ ): i32 {
1678
+ if (this.state === TLS_STATE_FAILED) {
1679
+ return this.alert
1680
+ }
1681
+ if (this.state !== TLS_STATE_WAIT_TICKET) {
1682
+ return this.fail(TLS_ALERT_INTERNAL_ERROR)
1683
+ }
1684
+ const index: i32 = this.selectTicket(current, currentInfo, previous, previousInfo, strikes, now)
1685
+ let bound: boolean = true
1686
+ let resumed: boolean = false
1687
+ if (index >= 0) {
1688
+ bound = this.bindTicket()
1689
+ resumed = bound && strikes.take(this.opened.id, this.opened.expiresAt(), now)
1690
+ }
1691
+ if (resumed) {
1692
+ this.saltFromEarly()
1693
+ this.issuing.chainStart = this.opened.chainStart
1694
+ }
1695
+ secureZero(this.opened.psk)
1696
+ this.secrets().zero(TLS_SECRET_EARLY)
1697
+ if (!bound) {
1698
+ return this.fail(TLS_ALERT_DECRYPT_ERROR)
1699
+ }
1700
+ this.resumed = resumed
1701
+ const alert: i32 = this.answerHello(resumed ? index : TLS_NO_IDENTITY)
1702
+ return alert !== 0 ? this.fail(alert) : 0
1703
+ }
1704
+
1705
+ /**
1706
+ * A resumed flight: the ServerHello selecting identity `index`, the
1707
+ * handshake traffic secrets, EncryptedExtensions and the server's
1708
+ * Finished, after which the server waits for the client's.
1709
+ */
1710
+ writeResumedFlight(index: i32): void {
1711
+ const hello: TlsClientHelloView = this.hello
1712
+ const none: u8[] = []
1713
+ this.reserveOutput(
1714
+ TLS_LEVEL_INITIAL,
1715
+ tlsWriteServerHelloPsk(
1716
+ none,
1717
+ this.outputInitialLength,
1718
+ this.serverRandom,
1719
+ hello.data,
1720
+ hello.sessionIdAt,
1721
+ hello.sessionIdLength,
1722
+ this.suite,
1723
+ this.serverPublic,
1724
+ index
1725
+ )
1726
+ )
1727
+ const extensionsEnd: i32 = tlsWriteEncryptedExtensions(
1728
+ none,
1729
+ this.outputHandshakeLength,
1730
+ this.config.extraExtensions,
1731
+ this.alpn,
1732
+ hello.hasServerName,
1733
+ this.config.quic ? this.config.quicTransportParameters : null
1734
+ )
1735
+ this.reserveOutput(TLS_LEVEL_HANDSHAKE, extensionsEnd + 4 + this.hashLength)
1736
+ // The secrets' pool is the slot's, chosen before the blocks open.
1737
+ const pool: TlsSecretPool = this.secrets()
1738
+ this.writeResumedHello(pool, index)
1739
+ this.writeServerFinished(pool)
1740
+ this.masterSecretStep(pool)
1741
+ }
1742
+
1743
+ /** The resumed ServerHello, the handshake traffic secrets and EncryptedExtensions, in the room `writeResumedFlight` reserved. */
1744
+ writeResumedHello(pool: TlsSecretPool, index: i32): void {
1745
+ const hello: TlsClientHelloView = this.hello
1746
+ using _scope = arena()
1747
+ const helloAt: i32 = this.outputInitialLength
1748
+ const helloEnd: i32 = tlsWriteServerHelloPsk(
1749
+ this.outputInitial,
1750
+ helloAt,
1751
+ this.serverRandom,
1752
+ hello.data,
1753
+ hello.sessionIdAt,
1754
+ hello.sessionIdLength,
1755
+ this.suite,
1756
+ this.serverPublic,
1757
+ index
1758
+ )
1759
+ this.sent(TLS_LEVEL_INITIAL, helloAt, helloEnd)
1760
+ this.transcript.hashInto(this.hashes, TLS_FROM)
1761
+ const handshake: u8[] = expose(pool.handshake, tlsHeldClone)
1762
+ this.deriveInto(handshake, "c hs traffic", pool.clientHandshake)
1763
+ this.deriveInto(handshake, "s hs traffic", pool.serverHandshake)
1764
+ secureZero(handshake)
1765
+ this.held = this.held | (1 << TLS_SECRET_CLIENT_HANDSHAKE) | (1 << TLS_SECRET_SERVER_HANDSHAKE)
1766
+ const extensionsAt: i32 = this.outputHandshakeLength
1767
+ const written: i32 = tlsWriteEncryptedExtensions(
1768
+ this.outputHandshake,
1769
+ extensionsAt,
1770
+ this.config.extraExtensions,
1771
+ this.alpn,
1772
+ hello.hasServerName,
1773
+ this.config.quic ? this.config.quicTransportParameters : null
1774
+ )
1775
+ this.sent(TLS_LEVEL_HANDSHAKE, extensionsAt, written)
1776
+ }
1777
+
1034
1778
  /**
1035
1779
  * Runs the x25519 exchange on the caller's ephemeral key and the client's
1036
1780
  * share (`tlsExchange`), outside any arena block since its scope is its
1037
1781
  * own, and unpacks the server's public key and the handshake secret from
1038
- * the words, zeroing them. The ephemeral key is held as the caller's plain
1039
- * bytes, because a `Secret` may not live in a field (NL2430); the record
1040
- * layer wipes them once the ServerHello is written (TLS-1).
1782
+ * the words. The words, the ephemeral key's and the handshake secret's
1783
+ * among them, are wiped with one `wipe` after the branch on the alert, so
1784
+ * that a refused exchange leaves them zero too and `opt` has one wipe to
1785
+ * keep rather than two to merge. The ephemeral key is read out of its
1786
+ * `Secret` field a word at a time; the record layer wipes it once the
1787
+ * ServerHello is written (TLS-1).
1041
1788
  */
1042
1789
  exchangeKeys(): i32 {
1043
1790
  const w: TlsExchangeWords = this.exchange
1044
- const key: u8[] = this.ephemeralPrivate
1045
1791
  const share: u8[] = this.input
1046
1792
  const at: i32 = this.hello.x25519ShareAt
1047
- w.key0 = tlsWordAt(key, 0)
1048
- w.key1 = tlsWordAt(key, 8)
1049
- w.key2 = tlsWordAt(key, 16)
1050
- w.key3 = tlsWordAt(key, 24)
1793
+ w.key0 = exposeWith(this.ephemeral, TLS_FROM, tlsWordAt)
1794
+ w.key1 = exposeWith(this.ephemeral, TLS_FROM + 8, tlsWordAt)
1795
+ w.key2 = exposeWith(this.ephemeral, TLS_FROM + 16, tlsWordAt)
1796
+ w.key3 = exposeWith(this.ephemeral, TLS_FROM + 24, tlsWordAt)
1051
1797
  w.share0 = tlsWordAt(share, at)
1052
1798
  w.share1 = tlsWordAt(share, at + 8)
1053
1799
  w.share2 = tlsWordAt(share, at + 16)
1054
1800
  w.share3 = tlsWordAt(share, at + 24)
1055
1801
  w.hashLength = this.hashLength
1802
+ w.salt0 = tlsWordAt(this.salt, 0)
1803
+ w.salt1 = tlsWordAt(this.salt, 8)
1804
+ w.salt2 = tlsWordAt(this.salt, 16)
1805
+ w.salt3 = tlsWordAt(this.salt, 24)
1806
+ w.salt4 = tlsWordAt(this.salt, 32)
1807
+ w.salt5 = tlsWordAt(this.salt, 40)
1808
+ secureZero(this.salt)
1056
1809
  const alert: i32 = tlsExchange(w)
1057
- if (alert !== 0) {
1058
- return alert
1059
- }
1060
- const pub: u8[] = this.serverPublic
1061
- tlsWordInto(pub, 0, w.public0)
1062
- tlsWordInto(pub, 8, w.public1)
1063
- tlsWordInto(pub, 16, w.public2)
1064
- tlsWordInto(pub, 24, w.public3)
1065
- this.handshakeSecret = this.secretArray(TLS_SECRET_HANDSHAKE)
1066
- const handshake: u8[] = this.handshakeSecret
1067
- tlsWordInto(handshake, 0, w.secret0)
1068
- tlsWordInto(handshake, 8, w.secret1)
1069
- tlsWordInto(handshake, 16, w.secret2)
1070
- tlsWordInto(handshake, 24, w.secret3)
1071
- tlsWordInto(handshake, 32, w.secret4)
1072
- tlsWordInto(handshake, 40, w.secret5)
1073
- w.secret0 = toU64(0)
1074
- w.secret1 = toU64(0)
1075
- w.secret2 = toU64(0)
1076
- w.secret3 = toU64(0)
1077
- w.secret4 = toU64(0)
1078
- w.secret5 = toU64(0)
1079
- return 0
1810
+ if (alert === 0) {
1811
+ const pub: u8[] = this.serverPublic
1812
+ tlsWordInto(pub, 0, w.public0)
1813
+ tlsWordInto(pub, 8, w.public1)
1814
+ tlsWordInto(pub, 16, w.public2)
1815
+ tlsWordInto(pub, 24, w.public3)
1816
+ exposeWith(this.secrets().handshake, w, tlsHandshakeFromWords)
1817
+ this.held = this.held | (1 << TLS_SECRET_HANDSHAKE)
1818
+ }
1819
+ wipe(w)
1820
+ return alert
1080
1821
  }
1081
1822
 
1082
1823
  /**
@@ -1116,10 +1857,8 @@ export class TlsServer {
1116
1857
  TLS_LEVEL_HANDSHAKE,
1117
1858
  tlsWriteCertificate(none, extensionsEnd, this.config.certificateChain)
1118
1859
  )
1119
- // The secrets' arrays are the slot's, chosen before the block opens.
1120
- this.clientHandshakeSecret = this.secretArray(TLS_SECRET_CLIENT_HANDSHAKE)
1121
- this.serverHandshakeSecret = this.secretArray(TLS_SECRET_SERVER_HANDSHAKE)
1122
- this.toBeSigned = this.secretArray(TLS_SECRET_TO_BE_SIGNED)
1860
+ // The secrets' pool is the slot's, chosen before the block opens.
1861
+ const pool: TlsSecretPool = this.secrets()
1123
1862
  using _scope = arena()
1124
1863
  const helloAt: i32 = this.outputInitialLength
1125
1864
  const helloEnd: i32 = tlsWriteServerHello(
@@ -1135,24 +1874,11 @@ export class TlsServer {
1135
1874
  this.sent(TLS_LEVEL_INITIAL, helloAt, helloEnd)
1136
1875
 
1137
1876
  this.transcript.hashInto(this.hashes, TLS_FROM)
1138
- tlsDeriveSecretInto(
1139
- this.kdf,
1140
- h,
1141
- this.handshakeSecret,
1142
- "c hs traffic",
1143
- this.hashes,
1144
- TLS_FROM,
1145
- this.clientHandshakeSecret
1146
- )
1147
- tlsDeriveSecretInto(
1148
- this.kdf,
1149
- h,
1150
- this.handshakeSecret,
1151
- "s hs traffic",
1152
- this.hashes,
1153
- TLS_FROM,
1154
- this.serverHandshakeSecret
1155
- )
1877
+ const handshake: u8[] = expose(pool.handshake, tlsHeldClone)
1878
+ this.deriveInto(handshake, "c hs traffic", pool.clientHandshake)
1879
+ this.deriveInto(handshake, "s hs traffic", pool.serverHandshake)
1880
+ secureZero(handshake)
1881
+ this.held = this.held | (1 << TLS_SECRET_CLIENT_HANDSHAKE) | (1 << TLS_SECRET_SERVER_HANDSHAKE)
1156
1882
 
1157
1883
  const extensionsAt: i32 = this.outputHandshakeLength
1158
1884
  const written: i32 = tlsWriteEncryptedExtensions(
@@ -1172,10 +1898,23 @@ export class TlsServer {
1172
1898
  this.sent(TLS_LEVEL_HANDSHAKE, written, certificateEnd)
1173
1899
 
1174
1900
  this.transcript.hashInto(this.hashes, TLS_FROM)
1175
- tlsWriteCertificateVerifyContent(this.toBeSigned, TLS_FROM, this.hashes, TLS_FROM, h)
1901
+ tlsWriteCertificateVerifyContent(pool.toBeSigned, TLS_FROM, this.hashes, TLS_FROM, h)
1902
+ this.held = this.held | (1 << TLS_SECRET_TO_BE_SIGNED)
1176
1903
  this.state = TLS_STATE_WAIT_SIGNATURE
1177
1904
  }
1178
1905
 
1906
+ /**
1907
+ * Derive-Secret(`prk`, `label`, the transcript hash in `hashes`) into the
1908
+ * held secret `dst` (RFC 8446 §7.1): derived into a plain array that dies
1909
+ * with the caller's arena block, copied in, and zeroed.
1910
+ */
1911
+ deriveInto(prk: u8[], label: string, dst: Secret<u8[]>): void {
1912
+ const out: u8[] = new Array<u8>(this.hashLength)
1913
+ tlsDeriveSecretInto(this.kdf, this.hashLength, prk, label, this.hashes, TLS_FROM, out)
1914
+ exposeWith(dst, out, tlsHeldCopyIn)
1915
+ secureZero(out)
1916
+ }
1917
+
1179
1918
  /**
1180
1919
  * What CertificateVerify signs (RFC 8446 §4.4.3), while the state is
1181
1920
  * `TLS_STATE_WAIT_SIGNATURE`; `null` at any other time.
@@ -1184,7 +1923,7 @@ export class TlsServer {
1184
1923
  if (this.state !== TLS_STATE_WAIT_SIGNATURE) {
1185
1924
  return null
1186
1925
  }
1187
- return this.toBeSigned
1926
+ return this.secrets().toBeSigned
1188
1927
  }
1189
1928
 
1190
1929
  /**
@@ -1205,11 +1944,17 @@ export class TlsServer {
1205
1944
  }
1206
1945
  const h: i32 = this.hashLength
1207
1946
  this.reserveOutput(TLS_LEVEL_HANDSHAKE, this.outputHandshakeLength + 8 + signatureLength + 4 + h)
1208
- // The secrets' arrays are the slot's, chosen before the block opens.
1209
- this.clientApplicationSecret = this.secretArray(TLS_SECRET_CLIENT_APPLICATION)
1210
- this.serverApplicationSecret = this.secretArray(TLS_SECRET_SERVER_APPLICATION)
1211
- this.exporterSecret = this.secretArray(TLS_SECRET_EXPORTER)
1212
- this.expectedClientFinished = this.secretArray(TLS_SECRET_CLIENT_FINISHED)
1947
+ // The secrets' pool is the slot's, chosen before the blocks open.
1948
+ const pool: TlsSecretPool = this.secrets()
1949
+ this.writeCertificateVerify(signature)
1950
+ this.writeServerFinished(pool)
1951
+ this.masterSecretStep(pool)
1952
+ this.held = this.held & ~(1 << TLS_SECRET_TO_BE_SIGNED)
1953
+ return 0
1954
+ }
1955
+
1956
+ /** CertificateVerify, carrying `signature`, at the Handshake level, in the room `sign` reserved. */
1957
+ writeCertificateVerify(signature: u8[]): void {
1213
1958
  using _scope = arena()
1214
1959
  const verifyAt: i32 = this.outputHandshakeLength
1215
1960
  const verifyEnd: i32 = tlsWriteCertificateVerify(
@@ -1219,80 +1964,167 @@ export class TlsServer {
1219
1964
  signature
1220
1965
  )
1221
1966
  this.sent(TLS_LEVEL_HANDSHAKE, verifyAt, verifyEnd)
1967
+ }
1968
+
1969
+ /**
1970
+ * The server's Finished at the Handshake level, in the room reserved for
1971
+ * it: its `verify_data`, under the server's handshake traffic secret, goes
1972
+ * straight into its place in the flight.
1973
+ */
1974
+ writeServerFinished(pool: TlsSecretPool): void {
1975
+ const h: i32 = this.hashLength
1976
+ using _scope = arena()
1977
+ const at: i32 = this.outputHandshakeLength
1222
1978
  this.transcript.hashInto(this.hashes, TLS_FROM)
1223
- // The Finished's verify_data goes straight into its place in the flight.
1979
+ const serverHandshake: u8[] = expose(pool.serverHandshake, tlsHeldClone)
1224
1980
  tlsFinishedVerifyDataInto(
1225
1981
  this.kdf,
1226
1982
  h,
1227
- this.serverHandshakeSecret,
1983
+ serverHandshake,
1228
1984
  this.hashes,
1229
1985
  TLS_FROM,
1230
1986
  this.outputHandshake,
1231
- verifyEnd + 4
1232
- )
1233
- const finishedEnd: i32 = tlsWriteFinished(
1234
- this.outputHandshake,
1235
- verifyEnd,
1236
- this.outputHandshake,
1237
- verifyEnd + 4,
1238
- h
1987
+ at + 4
1239
1988
  )
1240
- this.sent(TLS_LEVEL_HANDSHAKE, verifyEnd, finishedEnd)
1989
+ secureZero(serverHandshake)
1990
+ const finishedEnd: i32 = tlsWriteFinished(this.outputHandshake, at, this.outputHandshake, at + 4, h)
1991
+ this.sent(TLS_LEVEL_HANDSHAKE, at, finishedEnd)
1992
+ }
1241
1993
 
1994
+ /**
1995
+ * Once the server's Finished is written, full handshake or resumed: the
1996
+ * master secret, the application and exporter secrets, the client's
1997
+ * Finished the server expects, and the resumption master secret, after
1998
+ * which the server waits for that Finished.
1999
+ */
2000
+ masterSecretStep(pool: TlsSecretPool): void {
2001
+ const h: i32 = this.hashLength
2002
+ using _scope = arena()
1242
2003
  this.transcript.hashInto(this.hashes, TLS_FROM)
2004
+ const handshake: u8[] = expose(pool.handshake, tlsHeldClone)
1243
2005
  const master: u8[] = new Array<u8>(h)
1244
- tlsMasterSecretInto(this.kdf, h, this.handshakeSecret, master)
1245
- tlsDeriveSecretInto(
2006
+ tlsMasterSecretInto(this.kdf, h, handshake, master)
2007
+ secureZero(handshake)
2008
+ this.deriveInto(master, "c ap traffic", pool.clientApplication)
2009
+ this.deriveInto(master, "s ap traffic", pool.serverApplication)
2010
+ this.deriveInto(master, "exp master", pool.exporter)
2011
+ const clientHandshake: u8[] = expose(pool.clientHandshake, tlsHeldClone)
2012
+ const finished: u8[] = pool.clientFinished
2013
+ tlsFinishedVerifyDataInto(this.kdf, h, clientHandshake, this.hashes, TLS_FROM, finished, TLS_FROM)
2014
+ secureZero(clientHandshake)
2015
+ // The only client Finished `handleFinished` accepts is the one just
2016
+ // computed, so the transcript through it is known now (RFC 8446 §4.6.1),
2017
+ // and the resumption master secret is derived while the master secret is.
2018
+ const clientFinished: u8[] = new Array<u8>(4 + h)
2019
+ tlsWriteFinished(clientFinished, TLS_FROM, finished, TLS_FROM, h)
2020
+ this.transcript.update(clientFinished, TLS_FROM, toI32(clientFinished.length))
2021
+ this.transcript.hashInto(this.hashes, TLS_FROM)
2022
+ this.deriveInto(master, "res master", pool.resumption)
2023
+ secureZero(master)
2024
+ this.held =
2025
+ this.held |
2026
+ (1 << TLS_SECRET_CLIENT_APPLICATION) |
2027
+ (1 << TLS_SECRET_SERVER_APPLICATION) |
2028
+ (1 << TLS_SECRET_EXPORTER) |
2029
+ (1 << TLS_SECRET_CLIENT_FINISHED)
2030
+ this.state = TLS_STATE_WAIT_FINISHED
2031
+ }
2032
+
2033
+ /**
2034
+ * Once the handshake is done, at most once a connection: seals a ticket
2035
+ * for it under `key`, which `info` describes, and writes the
2036
+ * NewSessionTicket carrying it at the Application level, for the carrier
2037
+ * to send under the server's application keys. Answers whether one was
2038
+ * written: none is when tickets are off, the client did not list
2039
+ * `psk_dhe_ke`, or the key does not seal. On a connected connection's
2040
+ * first call the resumption master secret is wiped before it returns,
2041
+ * written or not, so a carrier that issues no ticket still calls this, or
2042
+ * `wipeResumption`. Before the handshake is done, or on a second call, it
2043
+ * answers false and touches nothing: a secret wiped then would leave no
2044
+ * ticket to issue, and one wiped already has nothing left to wipe.
2045
+ */
2046
+ issue(key: Secret<u8[]>, info: TlsTicketKeyInfo, now: i64): boolean {
2047
+ if (this.state !== TLS_STATE_CONNECTED || this.issued) {
2048
+ return false
2049
+ }
2050
+ this.issued = true
2051
+ const written: boolean = this.ticketWanted && this.writeTicket(key, info, now)
2052
+ this.wipeResumption()
2053
+ return written
2054
+ }
2055
+
2056
+ /** The resumption PSK for nonce `ticketNonce`, into `issuing.psk` (§4.6.1). */
2057
+ resumptionPsk(): void {
2058
+ const pool: TlsSecretPool = this.secrets()
2059
+ using _scope = arena()
2060
+ const resumption: u8[] = expose(pool.resumption, tlsHeldClone)
2061
+ tlsResumptionPskInto(
1246
2062
  this.kdf,
1247
- h,
1248
- master,
1249
- "c ap traffic",
1250
- this.hashes,
2063
+ this.hashLength,
2064
+ resumption,
2065
+ this.ticketNonce,
1251
2066
  TLS_FROM,
1252
- this.clientApplicationSecret
2067
+ 1,
2068
+ this.issuing.psk
1253
2069
  )
1254
- tlsDeriveSecretInto(
1255
- this.kdf,
1256
- h,
1257
- master,
1258
- "s ap traffic",
1259
- this.hashes,
1260
- TLS_FROM,
1261
- this.serverApplicationSecret
2070
+ secureZero(resumption)
2071
+ }
2072
+
2073
+ /** `issue`'s ticket: sealed into `ticketScratch`, its PSK zeroed, and the NewSessionTicket written. */
2074
+ writeTicket(key: Secret<u8[]>, info: TlsTicketKeyInfo, now: i64): boolean {
2075
+ const issuing: TlsTicketState = this.issuing
2076
+ issuing.suite = this.suite
2077
+ issuing.lifetime = TLS_TICKET_LIFETIME_SECONDS
2078
+ issuing.pskLength = this.hashLength
2079
+ if (!this.resumed) {
2080
+ issuing.chainStart = now
2081
+ }
2082
+ this.resumptionPsk()
2083
+ const length: i32 = tlsTicketSeal(key, info, issuing, now, this.ticketScratch)
2084
+ secureZero(issuing.psk)
2085
+ if (length === 0) {
2086
+ return false
2087
+ }
2088
+ const b: u8[] = this.ageAddBytes
2089
+ crypto.getRandomValues(b)
2090
+ const ageAdd: u32 = toU32(
2091
+ (toI64(b[0]) << toI64(24)) | (toI64(b[1]) << toI64(16)) | (toI64(b[2]) << toI64(8)) | toI64(b[3])
1262
2092
  )
1263
- tlsDeriveSecretInto(this.kdf, h, master, "exp master", this.hashes, TLS_FROM, this.exporterSecret)
1264
- tlsFinishedVerifyDataInto(
1265
- this.kdf,
1266
- h,
1267
- this.clientHandshakeSecret,
1268
- this.hashes,
2093
+ this.outputApplicationLength = tlsWriteNewSessionTicket(
2094
+ this.outputApplication,
2095
+ TLS_FROM,
2096
+ toU32(issuing.lifetime),
2097
+ ageAdd,
2098
+ this.ticketNonce,
2099
+ TLS_FROM,
2100
+ 1,
2101
+ this.ticketScratch,
1269
2102
  TLS_FROM,
1270
- this.expectedClientFinished,
1271
- TLS_FROM
2103
+ length,
2104
+ this.none
1272
2105
  )
1273
- secureZero(master)
1274
- this.toBeSigned = this.none
1275
- this.state = TLS_STATE_WAIT_FINISHED
1276
- return 0
2106
+ return true
1277
2107
  }
1278
2108
 
1279
2109
  /**
1280
2110
  * The client's Finished (§4.4.4), `input[0 .. 4 + length)`: its
1281
2111
  * `verify_data` is HashLen bytes and must equal the HMAC the server
1282
2112
  * computed, compared in constant time. A wrong length is `decode_error`, a
1283
- * wrong value `decrypt_error`.
2113
+ * wrong value `decrypt_error`. Once it verifies, the resumption master
2114
+ * secret `sign` derived is the connection's, and known. The transcript already holds this Finished: `sign` absorbed the
2115
+ * one it expected, and these bytes are equal to it.
1284
2116
  */
1285
2117
  handleFinished(length: i32): i32 {
1286
2118
  if (length !== this.hashLength) {
1287
2119
  return TLS_ALERT_DECODE_ERROR
1288
2120
  }
1289
- if (!timingSafeEqualAt(this.input, 4, this.expectedClientFinished, TLS_FROM, this.hashLength)) {
2121
+ const pool: TlsSecretPool = this.secrets()
2122
+ if (!timingSafeEqualAt(this.input, 4, pool.clientFinished, TLS_FROM, this.hashLength)) {
1290
2123
  return TLS_ALERT_DECRYPT_ERROR
1291
2124
  }
1292
- this.transcript.update(this.input, TLS_FROM, 4 + length)
1293
2125
  // The handshake secret has done its work: wiped, and no longer held.
1294
- secureZero(this.handshakeSecret)
1295
- this.handshakeSecret = this.none
2126
+ pool.zero(TLS_SECRET_HANDSHAKE)
2127
+ this.held = (this.held | (1 << TLS_SECRET_RESUMPTION)) & ~(1 << TLS_SECRET_HANDSHAKE)
1296
2128
  this.state = TLS_STATE_CONNECTED
1297
2129
  return 0
1298
2130
  }
@@ -1337,8 +2169,15 @@ export class TlsServer {
1337
2169
  this.outputHandshakeLength = 0
1338
2170
  return out
1339
2171
  }
2172
+ case TLS_LEVEL_APPLICATION: {
2173
+ const out: u8[] = new Array<u8>(this.outputApplicationLength)
2174
+ for (let k: i32 = 0; k < toI32(out.length) && k < toI32(this.outputApplication.length); k++) {
2175
+ out[k] = this.outputApplication[k]
2176
+ }
2177
+ this.outputApplicationLength = 0
2178
+ return out
2179
+ }
1340
2180
  default:
1341
- // Nothing is written at the Application level before NewSessionTicket, which is out of scope.
1342
2181
  return none
1343
2182
  }
1344
2183
  }
@@ -1349,20 +2188,8 @@ export class TlsServer {
1349
2188
  this.outputInitialLength = 0
1350
2189
  } else if (level === TLS_LEVEL_HANDSHAKE) {
1351
2190
  this.outputHandshakeLength = 0
2191
+ } else if (level === TLS_LEVEL_APPLICATION) {
2192
+ this.outputApplicationLength = 0
1352
2193
  }
1353
2194
  }
1354
-
1355
- /**
1356
- * The secret the server *reads* `level` under — the client's traffic secret
1357
- * — or `null` while it is not known yet. The Initial level has none: it is
1358
- * cleartext in TLS, and QUIC derives its own from the connection id.
1359
- */
1360
- readSecret(level: i32): u8[] | null {
1361
- return tlsSecretAt(level, this.clientHandshakeSecret, this.clientApplicationSecret)
1362
- }
1363
-
1364
- /** The secret the server *writes* `level` under — its own traffic secret — or `null` while it is not known yet. */
1365
- writeSecret(level: i32): u8[] | null {
1366
- return tlsSecretAt(level, this.serverHandshakeSecret, this.serverApplicationSecret)
1367
- }
1368
2195
  }