@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
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,8 +84,9 @@
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
92
  import { P256SignScratch, P256_SIGNATURE_SIZE, p256SignSha256, p256SignSha256Into } from "nish/crypto/p256"
@@ -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
@@ -373,41 +413,64 @@ export class TlsP256Signer {
373
413
  const tlsIsZeroSecret = (shared: u8[]): boolean => timingSafeEqual(shared, new Array<u8>(X25519_SIZE))
374
414
 
375
415
  /**
376
- * `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
377
417
  * secret is extracted under: a constant of the hash alone (RFC 8448 §3 prints
378
418
  * the SHA-256 one; the SHA-384 one was computed with Python's hmac and
379
419
  * hashlib). It is written out rather than derived because the functions below
380
420
  * run on the ECDHE secret inside `expose`, and Expand-Label may panic with a
381
- * 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.
382
424
  */
383
425
  const TLS_DERIVED_SALT_SHA256: string = "6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba"
384
426
  const TLS_DERIVED_SALT_SHA384: string =
385
427
  "1591dac5cbbf0330a4a84de9c753330e92d01f0a88214b4464972fd668049e93e52f2b16fad922fdc0584478428f282b"
386
428
 
387
- /** The bytes of a lowercase hex constant of this module, in a fresh array. */
388
- const tlsHexBytes = (text: string): u8[] => {
389
- const out: u8[] = new Array<u8>(toI32(text.length) >> 1)
390
- tlsHexInto(text, out, TLS_FROM)
391
- return out
392
- }
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)
393
436
 
394
- /** The SHA-256 handshake secret: HKDF-Extract of the ECDHE secret under the derived salt (run inside `expose`). */
395
- const tlsHandshakeFromEcdhe256 = (ecdhe: u8[]): u8[] =>
396
- 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)
397
439
 
398
- /** The SHA-384 handshake secret: HKDF-Extract of the ECDHE secret under the derived salt (run inside `expose`). */
399
- const tlsHandshakeFromEcdhe384 = (ecdhe: u8[]): u8[] =>
400
- 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
+ }
401
445
 
402
- /** The secret of `level` from one side's pair, or `null` for any other level or one not derived yet (still empty). */
403
- const tlsSecretAt = (level: i32, handshake: u8[], application: u8[]): u8[] | null => {
404
- const chosen: u8[] = level === TLS_LEVEL_APPLICATION ? application : handshake
405
- if ((level !== TLS_LEVEL_HANDSHAKE && level !== TLS_LEVEL_APPLICATION) || toI32(chosen.length) === 0) {
406
- 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]
407
450
  }
408
- return chosen
451
+ return 0
409
452
  }
410
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]
463
+ }
464
+ return copy
465
+ }
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
+
411
474
  // ---- The exchange, in a scope of its own --------------------------------------
412
475
 
413
476
  /**
@@ -427,9 +490,9 @@ const tlsSecretAt = (level: i32, handshake: u8[], application: u8[]): u8[] | nul
427
490
  * `tests/link/net_tls_memory` would notice.
428
491
  *
429
492
  * The words hold secrets — the private key and the handshake secret — in
430
- * fields, where `secureZero` cannot reach, so each is zeroed by an ordinary
431
- * store as soon as it has been read, which stands because the words stay
432
- * 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`).
433
496
  */
434
497
  class TlsExchangeWords {
435
498
  key0: u64 = 0
@@ -450,6 +513,17 @@ class TlsExchangeWords {
450
513
  secret3: u64 = 0
451
514
  secret4: u64 = 0
452
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
453
527
  /** 32 or 48: which handshake secret to extract. */
454
528
  hashLength: i32 = 0
455
529
  }
@@ -474,10 +548,21 @@ const tlsWordInto = (b: u8[], at: i32, v: u64): void => {
474
548
  }
475
549
  }
476
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
+
477
562
  /**
478
563
  * The exchange of RFC 8446 §7.4.2 on `w`: the server's public key and the
479
564
  * handshake secret from the ephemeral key and the client's share, both
480
- * 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,
481
566
  * `illegal_parameter` for a share whose shared secret is all zeros (§7.4.2,
482
567
  * RFC 7748 §6.1), or `internal_error` when the exchange answers nothing. The
483
568
  * copy of the key and the ECDHE secret are `Secret`s wiped on every path, and
@@ -487,15 +572,18 @@ const tlsWordInto = (b: u8[], at: i32, v: u64): void => {
487
572
  * returns.
488
573
  */
489
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)
490
582
  const raw: u8[] = new Array<u8>(X25519_SIZE)
491
583
  tlsWordInto(raw, 0, w.key0)
492
584
  tlsWordInto(raw, 8, w.key1)
493
585
  tlsWordInto(raw, 16, w.key2)
494
586
  tlsWordInto(raw, 24, w.key3)
495
- w.key0 = toU64(0)
496
- w.key1 = toU64(0)
497
- w.key2 = toU64(0)
498
- w.key3 = toU64(0)
499
587
  const share: u8[] = new Array<u8>(X25519_SIZE)
500
588
  tlsWordInto(share, 0, w.share0)
501
589
  tlsWordInto(share, 8, w.share1)
@@ -505,33 +593,40 @@ const tlsExchange = (w: TlsExchangeWords): i32 => {
505
593
  const serverPublic: u8[] | null = x25519Base(key)
506
594
  const shared: Secret<u8[]> | null = x25519(key, share)
507
595
  wipe(key)
596
+ let alert: i32 = 0
508
597
  if (shared === null) {
509
- return TLS_ALERT_INTERNAL_ERROR
510
- }
511
- // RFC 7748 §6.1 leaves the all-zero check to the protocol, and RFC 8446
512
- // §7.4.2 makes it: a low-order client share gives the zero secret, which
513
- // anybody can compute. The secret is secret, so the compare reads it all.
514
- 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
+ }
515
624
  wipe(shared)
516
- return serverPublic === null ? TLS_ALERT_INTERNAL_ERROR : TLS_ALERT_ILLEGAL_PARAMETER
517
- }
518
- const handshake: u8[] =
519
- w.hashLength === SHA384_SIZE
520
- ? expose(shared, tlsHandshakeFromEcdhe384)
521
- : expose(shared, tlsHandshakeFromEcdhe256)
522
- wipe(shared)
523
- w.public0 = tlsWordAt(serverPublic, 0)
524
- w.public1 = tlsWordAt(serverPublic, 8)
525
- w.public2 = tlsWordAt(serverPublic, 16)
526
- w.public3 = tlsWordAt(serverPublic, 24)
527
- w.secret0 = tlsWordAt(handshake, 0)
528
- w.secret1 = tlsWordAt(handshake, 8)
529
- w.secret2 = tlsWordAt(handshake, 16)
530
- w.secret3 = tlsWordAt(handshake, 24)
531
- w.secret4 = tlsWordAt(handshake, 32)
532
- w.secret5 = tlsWordAt(handshake, 40)
533
- secureZero(handshake)
534
- 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
535
630
  }
536
631
 
537
632
  // ---- Buffers -------------------------------------------------------------------
@@ -542,20 +637,126 @@ const TLS_INPUT_START: i32 = 2048
542
637
  const TLS_INITIAL_OUTPUT_SIZE: i32 = 256
543
638
  /** The longest DER ECDSA P-256 signature, what the flight is first sized for: two 33-byte integers and their headers. */
544
639
  const TLS_ECDSA_SIGNATURE_MAX: i32 = 72
545
- /** Which secret each of a server's secret arrays holds, by index into `secrets256` and `secrets384`. */
546
- const TLS_SECRET_HANDSHAKE: i32 = 0
547
- const TLS_SECRET_CLIENT_HANDSHAKE: i32 = 1
548
- const TLS_SECRET_SERVER_HANDSHAKE: i32 = 2
549
- const TLS_SECRET_CLIENT_APPLICATION: i32 = 3
550
- const TLS_SECRET_SERVER_APPLICATION: i32 = 4
551
- const TLS_SECRET_EXPORTER: i32 = 5
552
- 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
553
652
  /** The CertificateVerify input sits beside them: 98 bytes and the transcript hash. */
554
- const TLS_SECRET_TO_BE_SIGNED: i32 = 7
555
- 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
556
661
  /** What CertificateVerify signs besides the hash (RFC 8446 §4.4.3): 64 spaces, the 33-byte context and a zero. */
557
662
  const TLS_CERTIFICATE_VERIFY_PREFIX: i32 = 98
558
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
+
559
760
  /**
560
761
  * The most the server's Handshake-level flight under `config` takes with an
561
762
  * ECDSA P-256 signature: EncryptedExtensions with every extension it may
@@ -582,6 +783,73 @@ const tlsFlightCapacity = (config: TlsServerConfig): i32 => {
582
783
  return extensions + certificate + 8 + TLS_ECDSA_SIGNATURE_MAX + 4 + SHA384_SIZE
583
784
  }
584
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
+
585
853
  /**
586
854
  * One TLS 1.3 server handshake. Make one per connection, or one per slot and
587
855
  * `restart` it for each connection the slot takes; feed it what the client
@@ -591,13 +859,15 @@ const tlsFlightCapacity = (config: TlsServerConfig): i32 => {
591
859
  *
592
860
  * The fields are readable: `state` and `alert`, the negotiated `suite`,
593
861
  * `serverName`, `alpn` and the client's `clientTransportParameters`, and the
594
- * 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.
595
865
  *
596
866
  * **What it allocates.** Everything it keeps is allocated by the constructor
597
867
  * and reused by `restart`: the input and output buffers, the transcript's
598
- * hashers, the HKDF scratch, and an array of exactly HashLen bytes for every
599
- * secret under each hash, which the secret fields point at as each is
600
- * 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
601
871
  * `sign` do their work inside `using a = arena()` blocks, so the compiler has
602
872
  * proved that what they allocate on the way dies before they return. Four
603
873
  * things grow or are made outside those blocks: the input buffer, doubled up
@@ -611,7 +881,8 @@ const tlsFlightCapacity = (config: TlsServerConfig): i32 => {
611
881
  export class TlsServer {
612
882
  config: TlsServerConfig
613
883
  serverRandom: u8[]
614
- 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[]>
615
886
  /** The client's `host_name`, or empty. */
616
887
  serverName: string = ""
617
888
  /** The ALPN protocol chosen, one of the configuration's, or empty. */
@@ -629,22 +900,11 @@ export class TlsServer {
629
900
  outputInitial: u8[]
630
901
  /** What the server has written at the Handshake level and not handed on: `outputHandshake[0 .. outputHandshakeLength)`. */
631
902
  outputHandshake: u8[]
632
- handshakeSecret: u8[]
633
- clientHandshakeSecret: u8[]
634
- serverHandshakeSecret: u8[]
635
- clientApplicationSecret: u8[]
636
- serverApplicationSecret: u8[]
637
- /** `exporter_master_secret` (RFC 8446 §7.5), for a caller's own exporters. */
638
- exporterSecret: u8[]
639
- /** The CertificateVerify input while `state` is `TLS_STATE_WAIT_SIGNATURE`. */
640
- toBeSigned: u8[]
641
- /** The client Finished's `verify_data`, known once the server's Finished is written. */
642
- expectedClientFinished: u8[]
643
- /** 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. */
644
904
  none: u8[]
645
- /** The arrays the secret fields point into: `TLS_SECRET_COUNT` of each hash's lengths. */
646
- secrets256: u8[][]
647
- secrets384: u8[][]
905
+ /** The secrets under each hash (`TlsSecretPool`); `secrets()` answers the negotiated hash's. */
906
+ secrets256: TlsSecretPool
907
+ secrets384: TlsSecretPool
648
908
  /** The ClientHello last read, as windows into `input`. */
649
909
  hello: TlsClientHelloView
650
910
  /** Where every HKDF and HMAC of the schedule runs. */
@@ -655,6 +915,33 @@ export class TlsServer {
655
915
  serverPublic: u8[]
656
916
  /** A transcript hash on its way into the schedule: HashLen bytes of it. */
657
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
658
945
  state: i32 = 0
659
946
  /** The alert the server failed with, or 0. */
660
947
  alert: i32 = 0
@@ -664,10 +951,23 @@ export class TlsServer {
664
951
  inputLength: i32 = 0
665
952
  outputInitialLength: i32 = 0
666
953
  outputHandshakeLength: i32 = 0
954
+ outputApplicationLength: i32 = 0
667
955
  /** Whether a HelloRetryRequest was sent, so a second miss is fatal. */
668
956
  retried: boolean = false
669
957
  /** Whether `config` passed `tlsConfigFits`, decided once by the constructor. */
670
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
671
971
 
672
972
  /**
673
973
  * A server for one connection under `config`, with the 32-byte server
@@ -678,7 +978,7 @@ export class TlsServer {
678
978
  constructor(config: TlsServerConfig, serverRandom: u8[], ephemeralPrivate: u8[]) {
679
979
  this.config = config
680
980
  this.serverRandom = serverRandom
681
- this.ephemeralPrivate = ephemeralPrivate
981
+ this.ephemeral = secret(new Array<u8>(X25519_SIZE))
682
982
  this.none = []
683
983
  this.clientTransportParameters = this.none
684
984
  this.clientParameters = []
@@ -687,41 +987,41 @@ export class TlsServer {
687
987
  this.input = new Array<u8>(TLS_INPUT_START)
688
988
  this.outputInitial = new Array<u8>(TLS_INITIAL_OUTPUT_SIZE)
689
989
  this.outputHandshake = new Array<u8>(this.configFits ? tlsFlightCapacity(config) : TLS_FROM)
690
- this.secrets256 = []
691
- this.secrets384 = []
692
- for (let k: i32 = 0; k < TLS_SECRET_COUNT; k++) {
693
- const extra: i32 = k === TLS_SECRET_TO_BE_SIGNED ? TLS_CERTIFICATE_VERIFY_PREFIX : 0
694
- this.secrets256.push(new Array<u8>(SHA256_SIZE + extra))
695
- this.secrets384.push(new Array<u8>(SHA384_SIZE + extra))
696
- }
990
+ this.secrets256 = new TlsSecretPool(SHA256_SIZE)
991
+ this.secrets384 = new TlsSecretPool(SHA384_SIZE)
697
992
  this.hello = new TlsClientHelloView()
698
993
  this.kdf = new HkdfScratch()
699
994
  this.exchange = new TlsExchangeWords()
700
995
  this.serverPublic = new Array<u8>(X25519_SIZE)
701
996
  this.hashes = new Array<u8>(SHA384_SIZE)
702
- this.handshakeSecret = this.none
703
- this.clientHandshakeSecret = this.none
704
- this.serverHandshakeSecret = this.none
705
- this.clientApplicationSecret = this.none
706
- this.serverApplicationSecret = this.none
707
- this.exporterSecret = this.none
708
- this.toBeSigned = this.none
709
- 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)
710
1005
  this.restart(serverRandom, ephemeralPrivate)
711
1006
  }
712
1007
 
713
1008
  /**
714
1009
  * Starts the next connection in this server's buffers, with its own
715
1010
  * 32-byte random and x25519 private key, as a fresh `TlsServer` would:
716
- * every secret the last connection left in them is wiped with `secureZero`
717
- * and every secret field points at nothing again. The two arrays are kept,
718
- * 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
719
1015
  * length, or a configuration `tlsConfigFits` refused, leaves the server
720
1016
  * failed with `internal_error`.
721
1017
  */
722
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)
723
1024
  this.serverRandom = serverRandom
724
- this.ephemeralPrivate = ephemeralPrivate
725
1025
  this.state = TLS_STATE_WAIT_CLIENT_HELLO
726
1026
  this.alert = 0
727
1027
  this.suite = 0
@@ -733,39 +1033,92 @@ export class TlsServer {
733
1033
  this.inputLength = 0
734
1034
  this.outputInitialLength = 0
735
1035
  this.outputHandshakeLength = 0
736
- for (let k: i32 = 0; k < toI32(this.secrets256.length) && k < toI32(this.secrets384.length); k++) {
737
- secureZero(this.secrets256[k])
738
- secureZero(this.secrets384[k])
739
- }
740
- this.handshakeSecret = this.none
741
- this.clientHandshakeSecret = this.none
742
- this.serverHandshakeSecret = this.none
743
- this.clientApplicationSecret = this.none
744
- this.serverApplicationSecret = this.none
745
- this.exporterSecret = this.none
746
- this.toBeSigned = this.none
747
- this.expectedClientFinished = this.none
748
- if (
749
- toI32(serverRandom.length) !== TLS_RANDOM_SIZE ||
750
- toI32(ephemeralPrivate.length) !== X25519_SIZE ||
751
- !this.configFits
752
- ) {
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) {
753
1044
  this.state = TLS_STATE_FAILED
754
1045
  this.alert = TLS_ALERT_INTERNAL_ERROR
755
1046
  }
756
1047
  }
757
1048
 
758
- /** 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
+ */
759
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)
760
1062
  this.state = TLS_STATE_FAILED
761
1063
  this.alert = alert
762
1064
  return alert
763
1065
  }
764
1066
 
765
- /** The array secret `index` lives in under the negotiated hash: exactly HashLen bytes (the CertificateVerify input, 98 more). */
766
- secretArray(index: i32): u8[] {
767
- const pool: u8[][] = this.hashLength === SHA384_SIZE ? this.secrets384 : this.secrets256
768
- 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()
769
1122
  }
770
1123
 
771
1124
  /** The level the client's next bytes must arrive at, or -1 when the state expects none. */
@@ -1014,9 +1367,15 @@ export class TlsServer {
1014
1367
  if (this.retried && suite !== this.suite) {
1015
1368
  return TLS_ALERT_ILLEGAL_PARAMETER
1016
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
1017
1374
  // §9.2: a certificate-authenticated handshake needs all three, and
1018
- // `supported_groups` and `key_share` come as a pair.
1019
- 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) {
1020
1379
  return TLS_ALERT_MISSING_EXTENSION
1021
1380
  }
1022
1381
  if (this.config.quic && !hello.hasQuicTransportParameters) {
@@ -1025,7 +1384,10 @@ export class TlsServer {
1025
1384
  if (!tlsWindowHasU16(data, hello.groupsAt, hello.groupsLength, TLS_GROUP_X25519)) {
1026
1385
  return TLS_ALERT_HANDSHAKE_FAILURE
1027
1386
  }
1028
- 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
+ ) {
1029
1391
  return TLS_ALERT_HANDSHAKE_FAILURE
1030
1392
  }
1031
1393
  if (!this.retried) {
@@ -1033,7 +1395,16 @@ export class TlsServer {
1033
1395
  this.hashLength = tlsSuiteHashLength(suite)
1034
1396
  this.transcript.reset(this.hashLength)
1035
1397
  }
1036
- 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
+ }
1037
1408
  if (!hello.hasX25519Share) {
1038
1409
  // §4.1.4: one HelloRetryRequest; a second ClientHello without the share
1039
1410
  // the first one asked for is `illegal_parameter`.
@@ -1077,6 +1448,24 @@ export class TlsServer {
1077
1448
  return alpnAlert
1078
1449
  }
1079
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
1080
1469
  if (this.config.quic) {
1081
1470
  // Refilled in place, so a slot's handshakes keep nothing for it (QUIC-3).
1082
1471
  const parameters: u8[] = this.clientParameters
@@ -1092,60 +1481,343 @@ export class TlsServer {
1092
1481
  }
1093
1482
  this.clientTransportParameters = parameters
1094
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
+ }
1095
1513
  const exchangeAlert: i32 = this.exchangeKeys()
1096
1514
  if (exchangeAlert !== 0) {
1097
1515
  return exchangeAlert
1098
1516
  }
1099
- this.writeServerFlight()
1517
+ if (this.resumed) {
1518
+ this.writeResumedFlight(index)
1519
+ } else {
1520
+ this.writeServerFlight()
1521
+ }
1100
1522
  return 0
1101
1523
  }
1102
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
+
1103
1778
  /**
1104
1779
  * Runs the x25519 exchange on the caller's ephemeral key and the client's
1105
1780
  * share (`tlsExchange`), outside any arena block since its scope is its
1106
1781
  * own, and unpacks the server's public key and the handshake secret from
1107
- * the words, zeroing them. The ephemeral key is held as the caller's plain
1108
- * bytes, because a `Secret` may not live in a field (NL2430); the record
1109
- * 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).
1110
1788
  */
1111
1789
  exchangeKeys(): i32 {
1112
1790
  const w: TlsExchangeWords = this.exchange
1113
- const key: u8[] = this.ephemeralPrivate
1114
1791
  const share: u8[] = this.input
1115
1792
  const at: i32 = this.hello.x25519ShareAt
1116
- w.key0 = tlsWordAt(key, 0)
1117
- w.key1 = tlsWordAt(key, 8)
1118
- w.key2 = tlsWordAt(key, 16)
1119
- 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)
1120
1797
  w.share0 = tlsWordAt(share, at)
1121
1798
  w.share1 = tlsWordAt(share, at + 8)
1122
1799
  w.share2 = tlsWordAt(share, at + 16)
1123
1800
  w.share3 = tlsWordAt(share, at + 24)
1124
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)
1125
1809
  const alert: i32 = tlsExchange(w)
1126
- if (alert !== 0) {
1127
- return alert
1128
- }
1129
- const pub: u8[] = this.serverPublic
1130
- tlsWordInto(pub, 0, w.public0)
1131
- tlsWordInto(pub, 8, w.public1)
1132
- tlsWordInto(pub, 16, w.public2)
1133
- tlsWordInto(pub, 24, w.public3)
1134
- this.handshakeSecret = this.secretArray(TLS_SECRET_HANDSHAKE)
1135
- const handshake: u8[] = this.handshakeSecret
1136
- tlsWordInto(handshake, 0, w.secret0)
1137
- tlsWordInto(handshake, 8, w.secret1)
1138
- tlsWordInto(handshake, 16, w.secret2)
1139
- tlsWordInto(handshake, 24, w.secret3)
1140
- tlsWordInto(handshake, 32, w.secret4)
1141
- tlsWordInto(handshake, 40, w.secret5)
1142
- w.secret0 = toU64(0)
1143
- w.secret1 = toU64(0)
1144
- w.secret2 = toU64(0)
1145
- w.secret3 = toU64(0)
1146
- w.secret4 = toU64(0)
1147
- w.secret5 = toU64(0)
1148
- 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
1149
1821
  }
1150
1822
 
1151
1823
  /**
@@ -1185,10 +1857,8 @@ export class TlsServer {
1185
1857
  TLS_LEVEL_HANDSHAKE,
1186
1858
  tlsWriteCertificate(none, extensionsEnd, this.config.certificateChain)
1187
1859
  )
1188
- // The secrets' arrays are the slot's, chosen before the block opens.
1189
- this.clientHandshakeSecret = this.secretArray(TLS_SECRET_CLIENT_HANDSHAKE)
1190
- this.serverHandshakeSecret = this.secretArray(TLS_SECRET_SERVER_HANDSHAKE)
1191
- 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()
1192
1862
  using _scope = arena()
1193
1863
  const helloAt: i32 = this.outputInitialLength
1194
1864
  const helloEnd: i32 = tlsWriteServerHello(
@@ -1204,24 +1874,11 @@ export class TlsServer {
1204
1874
  this.sent(TLS_LEVEL_INITIAL, helloAt, helloEnd)
1205
1875
 
1206
1876
  this.transcript.hashInto(this.hashes, TLS_FROM)
1207
- tlsDeriveSecretInto(
1208
- this.kdf,
1209
- h,
1210
- this.handshakeSecret,
1211
- "c hs traffic",
1212
- this.hashes,
1213
- TLS_FROM,
1214
- this.clientHandshakeSecret
1215
- )
1216
- tlsDeriveSecretInto(
1217
- this.kdf,
1218
- h,
1219
- this.handshakeSecret,
1220
- "s hs traffic",
1221
- this.hashes,
1222
- TLS_FROM,
1223
- this.serverHandshakeSecret
1224
- )
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)
1225
1882
 
1226
1883
  const extensionsAt: i32 = this.outputHandshakeLength
1227
1884
  const written: i32 = tlsWriteEncryptedExtensions(
@@ -1241,10 +1898,23 @@ export class TlsServer {
1241
1898
  this.sent(TLS_LEVEL_HANDSHAKE, written, certificateEnd)
1242
1899
 
1243
1900
  this.transcript.hashInto(this.hashes, TLS_FROM)
1244
- 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)
1245
1903
  this.state = TLS_STATE_WAIT_SIGNATURE
1246
1904
  }
1247
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
+
1248
1918
  /**
1249
1919
  * What CertificateVerify signs (RFC 8446 §4.4.3), while the state is
1250
1920
  * `TLS_STATE_WAIT_SIGNATURE`; `null` at any other time.
@@ -1253,7 +1923,7 @@ export class TlsServer {
1253
1923
  if (this.state !== TLS_STATE_WAIT_SIGNATURE) {
1254
1924
  return null
1255
1925
  }
1256
- return this.toBeSigned
1926
+ return this.secrets().toBeSigned
1257
1927
  }
1258
1928
 
1259
1929
  /**
@@ -1274,11 +1944,17 @@ export class TlsServer {
1274
1944
  }
1275
1945
  const h: i32 = this.hashLength
1276
1946
  this.reserveOutput(TLS_LEVEL_HANDSHAKE, this.outputHandshakeLength + 8 + signatureLength + 4 + h)
1277
- // The secrets' arrays are the slot's, chosen before the block opens.
1278
- this.clientApplicationSecret = this.secretArray(TLS_SECRET_CLIENT_APPLICATION)
1279
- this.serverApplicationSecret = this.secretArray(TLS_SECRET_SERVER_APPLICATION)
1280
- this.exporterSecret = this.secretArray(TLS_SECRET_EXPORTER)
1281
- 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 {
1282
1958
  using _scope = arena()
1283
1959
  const verifyAt: i32 = this.outputHandshakeLength
1284
1960
  const verifyEnd: i32 = tlsWriteCertificateVerify(
@@ -1288,80 +1964,167 @@ export class TlsServer {
1288
1964
  signature
1289
1965
  )
1290
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
1291
1978
  this.transcript.hashInto(this.hashes, TLS_FROM)
1292
- // The Finished's verify_data goes straight into its place in the flight.
1979
+ const serverHandshake: u8[] = expose(pool.serverHandshake, tlsHeldClone)
1293
1980
  tlsFinishedVerifyDataInto(
1294
1981
  this.kdf,
1295
1982
  h,
1296
- this.serverHandshakeSecret,
1983
+ serverHandshake,
1297
1984
  this.hashes,
1298
1985
  TLS_FROM,
1299
1986
  this.outputHandshake,
1300
- verifyEnd + 4
1301
- )
1302
- const finishedEnd: i32 = tlsWriteFinished(
1303
- this.outputHandshake,
1304
- verifyEnd,
1305
- this.outputHandshake,
1306
- verifyEnd + 4,
1307
- h
1987
+ at + 4
1308
1988
  )
1309
- 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
+ }
1310
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()
1311
2003
  this.transcript.hashInto(this.hashes, TLS_FROM)
2004
+ const handshake: u8[] = expose(pool.handshake, tlsHeldClone)
1312
2005
  const master: u8[] = new Array<u8>(h)
1313
- tlsMasterSecretInto(this.kdf, h, this.handshakeSecret, master)
1314
- 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(
1315
2062
  this.kdf,
1316
- h,
1317
- master,
1318
- "c ap traffic",
1319
- this.hashes,
2063
+ this.hashLength,
2064
+ resumption,
2065
+ this.ticketNonce,
1320
2066
  TLS_FROM,
1321
- this.clientApplicationSecret
2067
+ 1,
2068
+ this.issuing.psk
1322
2069
  )
1323
- tlsDeriveSecretInto(
1324
- this.kdf,
1325
- h,
1326
- master,
1327
- "s ap traffic",
1328
- this.hashes,
1329
- TLS_FROM,
1330
- 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])
1331
2092
  )
1332
- tlsDeriveSecretInto(this.kdf, h, master, "exp master", this.hashes, TLS_FROM, this.exporterSecret)
1333
- tlsFinishedVerifyDataInto(
1334
- this.kdf,
1335
- h,
1336
- this.clientHandshakeSecret,
1337
- this.hashes,
2093
+ this.outputApplicationLength = tlsWriteNewSessionTicket(
2094
+ this.outputApplication,
2095
+ TLS_FROM,
2096
+ toU32(issuing.lifetime),
2097
+ ageAdd,
2098
+ this.ticketNonce,
1338
2099
  TLS_FROM,
1339
- this.expectedClientFinished,
1340
- TLS_FROM
2100
+ 1,
2101
+ this.ticketScratch,
2102
+ TLS_FROM,
2103
+ length,
2104
+ this.none
1341
2105
  )
1342
- secureZero(master)
1343
- this.toBeSigned = this.none
1344
- this.state = TLS_STATE_WAIT_FINISHED
1345
- return 0
2106
+ return true
1346
2107
  }
1347
2108
 
1348
2109
  /**
1349
2110
  * The client's Finished (§4.4.4), `input[0 .. 4 + length)`: its
1350
2111
  * `verify_data` is HashLen bytes and must equal the HMAC the server
1351
2112
  * computed, compared in constant time. A wrong length is `decode_error`, a
1352
- * 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.
1353
2116
  */
1354
2117
  handleFinished(length: i32): i32 {
1355
2118
  if (length !== this.hashLength) {
1356
2119
  return TLS_ALERT_DECODE_ERROR
1357
2120
  }
1358
- 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)) {
1359
2123
  return TLS_ALERT_DECRYPT_ERROR
1360
2124
  }
1361
- this.transcript.update(this.input, TLS_FROM, 4 + length)
1362
2125
  // The handshake secret has done its work: wiped, and no longer held.
1363
- secureZero(this.handshakeSecret)
1364
- this.handshakeSecret = this.none
2126
+ pool.zero(TLS_SECRET_HANDSHAKE)
2127
+ this.held = (this.held | (1 << TLS_SECRET_RESUMPTION)) & ~(1 << TLS_SECRET_HANDSHAKE)
1365
2128
  this.state = TLS_STATE_CONNECTED
1366
2129
  return 0
1367
2130
  }
@@ -1406,8 +2169,15 @@ export class TlsServer {
1406
2169
  this.outputHandshakeLength = 0
1407
2170
  return out
1408
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
+ }
1409
2180
  default:
1410
- // Nothing is written at the Application level before NewSessionTicket, which is out of scope.
1411
2181
  return none
1412
2182
  }
1413
2183
  }
@@ -1418,20 +2188,8 @@ export class TlsServer {
1418
2188
  this.outputInitialLength = 0
1419
2189
  } else if (level === TLS_LEVEL_HANDSHAKE) {
1420
2190
  this.outputHandshakeLength = 0
2191
+ } else if (level === TLS_LEVEL_APPLICATION) {
2192
+ this.outputApplicationLength = 0
1421
2193
  }
1422
2194
  }
1423
-
1424
- /**
1425
- * The secret the server *reads* `level` under — the client's traffic secret
1426
- * — or `null` while it is not known yet. The Initial level has none: it is
1427
- * cleartext in TLS, and QUIC derives its own from the connection id.
1428
- */
1429
- readSecret(level: i32): u8[] | null {
1430
- return tlsSecretAt(level, this.clientHandshakeSecret, this.clientApplicationSecret)
1431
- }
1432
-
1433
- /** The secret the server *writes* `level` under — its own traffic secret — or `null` while it is not known yet. */
1434
- writeSecret(level: i32): u8[] | null {
1435
- return tlsSecretAt(level, this.serverHandshakeSecret, this.serverApplicationSecret)
1436
- }
1437
2195
  }