@amritk/nish-x86_64-linux 0.18.0 → 0.21.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 (104) hide show
  1. package/INSTALL.md +58 -28
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2759 -0
  4. package/package.json +3 -2
  5. package/runtime/browser.d.ts +14 -0
  6. package/runtime/browser.mjs +88 -0
  7. package/runtime/core/arrays.d.ts +18 -0
  8. package/runtime/core/arrays.mjs +125 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +70 -0
  11. package/runtime/core/host.mjs +311 -0
  12. package/runtime/core/numbers.d.ts +53 -0
  13. package/runtime/core/numbers.mjs +316 -0
  14. package/runtime/core/result.d.ts +41 -0
  15. package/runtime/core/result.mjs +127 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +99 -5
  22. package/runtime/nish.h +289 -19
  23. package/runtime/nish.mjs +39 -3
  24. package/runtime/node.mjs +383 -0
  25. package/runtime/runtime-aes.c +490 -0
  26. package/runtime/runtime-bytes.c +401 -0
  27. package/runtime/runtime-cross.c +366 -0
  28. package/runtime/runtime-host.c +8 -7
  29. package/runtime/runtime-net.c +93 -34
  30. package/runtime/runtime-os.c +214 -16
  31. package/runtime/runtime-parallel.c +590 -107
  32. package/runtime/runtime-simd.c +235 -11
  33. package/runtime/runtime-term.c +504 -0
  34. package/runtime/runtime-wasm.c +5 -14
  35. package/runtime/runtime.c +257 -17
  36. package/runtime/shim.mjs +6 -915
  37. package/runtime/sqlite/LICENSE.md +85 -0
  38. package/runtime/sqlite/README.md +45 -0
  39. package/runtime/sqlite/nish-sqlite.c +359 -0
  40. package/runtime/sqlite/sqlite3.c +271131 -0
  41. package/runtime/sqlite/sqlite3.h +14447 -0
  42. package/scripts/build.sh +437 -28
  43. package/std/LICENSE-fast-float +23 -0
  44. package/std/LICENSE-fdlibm +8 -0
  45. package/std/README.md +108 -58
  46. package/std/base64.ts +116 -0
  47. package/std/bytes.ts +419 -0
  48. package/std/cli.ts +328 -0
  49. package/std/collections.ts +6 -5
  50. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  51. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  52. package/std/crypto/aes.ts +238 -76
  53. package/std/crypto/base64url.ts +125 -0
  54. package/std/crypto/bcrypt.ts +760 -0
  55. package/std/crypto/chacha20poly1305.ts +43 -9
  56. package/std/crypto/ed25519.ts +840 -0
  57. package/std/crypto/hkdf.ts +304 -12
  58. package/std/crypto/hmac.ts +382 -54
  59. package/std/crypto/p256.ts +932 -61
  60. package/std/crypto/x25519.ts +55 -37
  61. package/std/crypto/x509-random.ts +71 -0
  62. package/std/crypto/x509.ts +20 -131
  63. package/std/json-tokenizer.ts +4237 -0
  64. package/std/json-typed.ts +1014 -0
  65. package/std/json-value.ts +1182 -0
  66. package/std/json.ts +1202 -34
  67. package/std/math.ts +608 -0
  68. package/std/net/buffer-pool.ts +151 -0
  69. package/std/net/hpack.ts +416 -112
  70. package/std/net/http-fields.ts +93 -0
  71. package/std/net/http1-server.ts +501 -62
  72. package/std/net/http1.ts +31 -6
  73. package/std/net/http2-tls.ts +112 -24
  74. package/std/net/http2.ts +333 -75
  75. package/std/net/http3-server.ts +261 -43
  76. package/std/net/http3.ts +187 -28
  77. package/std/net/postgres.ts +1875 -0
  78. package/std/net/prefork.ts +412 -0
  79. package/std/net/quic-listener.ts +108 -11
  80. package/std/net/quic-packet.ts +243 -82
  81. package/std/net/quic-recovery.ts +361 -30
  82. package/std/net/quic-stream.ts +424 -24
  83. package/std/net/quic.ts +519 -167
  84. package/std/net/router.ts +676 -0
  85. package/std/net/tls/codec.ts +426 -27
  86. package/std/net/tls/record-server.ts +478 -132
  87. package/std/net/tls/record.ts +133 -42
  88. package/std/net/tls/schedule.ts +464 -55
  89. package/std/net/tls/ticket.ts +610 -0
  90. package/std/net/tls-tcp.ts +196 -53
  91. package/std/net/tls.ts +1018 -260
  92. package/std/net/url.ts +571 -0
  93. package/std/random.ts +94 -0
  94. package/std/regex.ts +4274 -0
  95. package/std/sqlite.ts +391 -0
  96. package/std/term-io.ts +978 -0
  97. package/std/term-pixels.ts +874 -0
  98. package/std/term-probe.ts +459 -0
  99. package/std/term-rate.ts +993 -0
  100. package/std/term.ts +2453 -0
  101. package/std/text.ts +90 -7
  102. package/std/threads.ts +438 -14
  103. package/std/yaml-lines.ts +141 -0
  104. package/std/yaml.ts +4675 -0
@@ -5,14 +5,23 @@
5
5
  * Initial secrets, AEAD packet protection, header protection and the Retry
6
6
  * integrity tag.
7
7
  *
8
- * import { quicInitialSecrets, quicKeys, quicParseHeader, quicOpenPacket,
8
+ * import { quicInitialSecrets, quicKeys, quicParseHeader, quicOpenPacket, QuicKeysSlot,
9
9
  * QUIC_AEAD_AES_128_GCM, QUIC_PACKET_OK } from "nish/net/quic-packet";
10
+ * import { Secret, secret, wipe } from "nish:secret";
10
11
  *
11
12
  * const secrets = quicInitialSecrets(clientDcid); // RFC 9001 §5.2
12
- * const keys = quicKeys(QUIC_AEAD_AES_128_GCM, secrets.client);
13
+ * if (secrets === null) { … a DCID over 20 bytes … }
14
+ * const client: u8[] = new Array<u8>(secrets.client.length); // public, so a copy
15
+ * client.set(secrets.client);
16
+ * const held: Secret<u8[]> = secret(client);
17
+ * const slot = new QuicKeysSlot(); // ours, so ours to wipe
18
+ * const keys = quicKeys(slot, QUIC_AEAD_AES_128_GCM, held);
19
+ * wipe(held);
20
+ * if (keys === null) { … }
13
21
  * const header = quicParseHeader(datagram, 0, 8); // 8: our short-header DCID length
14
22
  * const packet = quicOpenPacket(keys, datagram, header, largestReceived);
15
23
  * if (packet.error === QUIC_PACKET_OK) { … packet.payload holds the frames … }
24
+ * slot.wipe(); // once the level is done
16
25
  *
17
26
  * The module is sans-IO: bytes in, bytes out, no socket and no clock. It
18
27
  * frames and protects packets and stops there, so it knows nothing of frames,
@@ -48,18 +57,25 @@
48
57
  * carries its own Length, and `QuicHeader.end` is where the next packet in the
49
58
  * datagram starts. A short header has none, so it runs to the datagram's end.
50
59
  *
51
- * **Secrets are not wiped.** `QuicKeys` holds a packet key, an IV and a
52
- * header-protection key, and `quicKeys`, `quicKeyUpdateSecret` and the seal
53
- * and open functions hold traffic secrets and keys in arena memory until that
54
- * memory is reused. `nish/crypto`'s signers and X25519 now hold their keys as
55
- * a `Secret` from `nish:secret` and wipe them (ECC-2, X509-7); this module
56
- * does not yet, and `docs/security/quic.md` records that as QUIC-1. The
57
- * Initial keys are not secret, because anyone who reads the client's first
58
- * DCID can derive them (RFC 9001 §5.2); the Handshake and 1-RTT keys are.
59
- * A connection slot derives into a `QuicKeysSlot` instead (`quicKeysInto`,
60
- * `quicKeysUpdateInto`, `quicKeyUpdateSecretInto`, `quicInitialSecretsInto`):
61
- * its keys live in arrays the slot owns, which `QuicKeysSlot.wipe` zeroes,
62
- * and the derivation's own temporaries go with its arena block.
60
+ * **Secrets.** `QuicKeys` holds a packet key, an IV and a header-protection
61
+ * key, pointing into a `QuicKeysSlot`'s arrays. `quicKeys` and
62
+ * `quicKeysUpdate` take a traffic secret as a `Secret` from `nish:secret`
63
+ * and a slot the caller owns, and derive into the slot; a slot the caller
64
+ * owns is the caller's to wipe, and `QuicKeysSlot.wipe` zeroes it with
65
+ * stores no optimiser removes, AES schedules included (QUIC-1).
66
+ * `quicKeyUpdateSecret` answers the next secret as a `Secret`, and every
67
+ * derivation wipes its HKDF intermediates before it returns. `quicKeysWipe`
68
+ * zeroes the arrays one `QuicKeys` points at. The Initial keys are not
69
+ * secret, because anyone who reads the client's first DCID can derive them
70
+ * (RFC 9001 §5.2), so `quicInitialSecrets` answers plain arrays, and
71
+ * `quicKeysInto`, `quicKeysUpdateInto`, `quicKeyUpdateSecretInto` and
72
+ * `quicInitialSecretsInto` derive from plain secrets over a caller's
73
+ * `HkdfScratch`; the Handshake and 1-RTT keys are secret. Each derivation's
74
+ * own temporaries go with its arena block. A slot whose traffic secret is
75
+ * held as a `Secret` may also derive with `quicKeysHeldInto`, and ratchet
76
+ * the secret in place with `quicKeyUpdateSecretHeldInto`. Sealing and
77
+ * opening a packet wipe the nonce, which gives the IV away, and the
78
+ * header-protection mask once each is used (QUIC-2).
63
79
  *
64
80
  * **Constant time.** The AEADs and the header-protection masks are
65
81
  * `nish/crypto/aes` and `nish/crypto/chacha20poly1305`, with their guarantees.
@@ -77,14 +93,28 @@
77
93
  * private functions share the importing program's flat symbol namespace
78
94
  * (`docs/wp26-stdlib.md` §3e).
79
95
  */
80
- import { aesGcmOpen, aesGcmSeal, aesHeaderMask, aesKey, aesKeyInto, AesKey } from "nish/crypto/aes"
96
+ import { Secret, expose, wipe } from "nish:secret"
97
+ import {
98
+ aesGcmOpen,
99
+ aesGcmSeal,
100
+ aesHeaderMask,
101
+ aesKey,
102
+ aesKeyInto,
103
+ aesKeyWipe,
104
+ AesKey,
105
+ } from "nish/crypto/aes"
81
106
  import { chacha20HeaderMask, chacha20Poly1305Open, chacha20Poly1305Seal } from "nish/crypto/chacha20poly1305"
82
107
  import { timingSafeEqual } from "nish/crypto/ct"
83
108
  import {
84
109
  HkdfScratch,
110
+ hkdfExpandLabelHeldInto,
85
111
  hkdfExpandLabelInto,
86
112
  hkdfExpandLabelSha256,
113
+ hkdfExpandLabelSha256Held,
114
+ hkdfExpandLabelSha256Secret,
87
115
  hkdfExpandLabelSha384,
116
+ hkdfExpandLabelSha384Held,
117
+ hkdfExpandLabelSha384Secret,
88
118
  hkdfExtractInto,
89
119
  hkdfExtractSha256,
90
120
  } from "nish/crypto/hkdf"
@@ -975,6 +1005,28 @@ const quicPacketSecretLength = (aead: i32): i32 => (aead === QUIC_AEAD_AES_256_G
975
1005
  const quicPacketSecretFits = (aead: i32, secret: u8[]): boolean =>
976
1006
  quicPacketKeyLength(aead) !== 0 && toI32(secret.length) === quicPacketSecretLength(aead)
977
1007
 
1008
+ /** `quicPacketSecretFits` for a held secret, whose length is public, read through `expose`. */
1009
+ const quicPacketHeldFits = (aead: i32, trafficSecret: Secret<u8[]>): boolean =>
1010
+ quicPacketKeyLength(aead) !== 0 &&
1011
+ expose(trafficSecret, quicPacketBytesLength) === quicPacketSecretLength(aead)
1012
+
1013
+ /** The length of `bytes`, for `expose` to read off a held secret. */
1014
+ const quicPacketBytesLength = (bytes: u8[]): i32 => toI32(bytes.length)
1015
+
1016
+ /** A plain copy of `bytes`, for `expose` to hand a derived key to the `QuicKeys` that holds it. */
1017
+ const quicPacketDup = (bytes: u8[]): u8[] => quicPacketSlice(bytes, 0, toI32(bytes.length))
1018
+
1019
+ /**
1020
+ * `quicPacketExpandLabel` of a held secret, answered held: the
1021
+ * `hkdfExpandLabelSha*Secret` form, which wipes its own intermediates.
1022
+ */
1023
+ const quicPacketExpandHeld = (aead: i32, prk: Secret<u8[]>, label: string, length: i32): Secret<u8[]> => {
1024
+ const none: u8[] = []
1025
+ return aead === QUIC_AEAD_AES_256_GCM
1026
+ ? hkdfExpandLabelSha384Secret(prk, label, none, length)
1027
+ : hkdfExpandLabelSha256Secret(prk, label, none, length)
1028
+ }
1029
+
978
1030
  /**
979
1031
  * Whether `keys` has the shape `quicKeys` gives: a known AEAD, key and
980
1032
  * header-protection key of its length, a 12-byte IV, and for the AES suites
@@ -996,65 +1048,107 @@ const quicPacketKeysUsable = (keys: QuicKeys): boolean => {
996
1048
  }
997
1049
 
998
1050
  /**
999
- * Keys for `aead` with the packet key and IV derived from `secret` (RFC 9001
1000
- * §5.1) and the header-protection key given, which a key update keeps
1001
- * (§6). The caller has checked `secret` with `quicPacketSecretFits`.
1051
+ * The packet-protection keys of RFC 9001 §5.1 from a traffic secret, held as
1052
+ * a `Secret` the caller still owns and wipes, derived into `slot`, which the
1053
+ * caller owns too: the AEAD key (`quic key`), the IV (`quic iv`) and the
1054
+ * header-protection key (`quic hp`), each HKDF-Expand-Label with an empty
1055
+ * context under the hash `aead` pairs with, written into the slot's arrays
1056
+ * and the AES schedules expanded there. The secret is read through a plain
1057
+ * copy inside an arena block, which is zeroed, and the derivation's
1058
+ * temporaries go with the block, so the call keeps in the arena only the
1059
+ * `HkdfScratch` it makes for the derivation, which wipes itself before the
1060
+ * derivation returns.
1061
+ * Answers the slot's keys, which the next derivation into it overwrites and
1062
+ * `QuicKeysSlot.wipe` zeroes, or `null`, deriving nothing, for an `aead`
1063
+ * this module does not know or a secret that is not that hash's length. The
1064
+ * Initial secrets are public (RFC 9001 §5.2), so a caller deriving Initial
1065
+ * keys wraps a copy of one in `secret`, or uses `quicKeysInto`.
1002
1066
  */
1003
- const quicPacketDerive = (aead: i32, secret: u8[], hp: u8[], hpAes: AesKey | null): QuicKeys => {
1004
- const keys: QuicKeys = new QuicKeys(
1005
- aead,
1006
- quicPacketExpandLabel(aead, secret, "quic key", quicPacketKeyLength(aead)),
1007
- quicPacketExpandLabel(aead, secret, "quic iv", QUIC_IV_SIZE),
1008
- hp
1009
- )
1010
- keys.hpAes = hpAes
1011
- if (aead !== QUIC_AEAD_CHACHA20_POLY1305) {
1012
- keys.packetAes = aesKey(keys.key)
1067
+ export const quicKeys = (slot: QuicKeysSlot, aead: i32, trafficSecret: Secret<u8[]>): QuicKeys | null => {
1068
+ if (!quicPacketHeldFits(aead, trafficSecret)) {
1069
+ return null
1013
1070
  }
1014
- return keys
1071
+ const scratch: HkdfScratch = new HkdfScratch()
1072
+ {
1073
+ using _scope = arena()
1074
+ const plain: u8[] = expose(trafficSecret, quicPacketDup)
1075
+ quicKeysInto(scratch, slot, aead, plain)
1076
+ secureZero(plain)
1077
+ }
1078
+ return slot.keys
1015
1079
  }
1016
1080
 
1017
1081
  /**
1018
- * The packet-protection keys of RFC 9001 §5.1 from a traffic `secret`: the
1019
- * AEAD key (`quic key`), the IV (`quic iv`) and the header-protection key
1020
- * (`quic hp`), each HKDF-Expand-Label with an empty context under the hash
1021
- * `aead` pairs with. Answers `null` for an `aead` this module does not know,
1022
- * or a secret that is not that hash's length.
1082
+ * The next generation's traffic secret for a key update (RFC 9001 §6.1):
1083
+ * HKDF-Expand-Label(`trafficSecret`, `quic ku`, "", Hash.length), taken and
1084
+ * answered as a `Secret` the caller wipes. The header-protection key does
1085
+ * not change with it (§6), so the new keys are `quicKeysUpdate` of the old
1086
+ * keys and this. Answers `null` where `quicKeys` would.
1023
1087
  */
1024
- export const quicKeys = (aead: i32, secret: u8[]): QuicKeys | null => {
1025
- if (!quicPacketSecretFits(aead, secret)) {
1088
+ export const quicKeyUpdateSecret = (aead: i32, trafficSecret: Secret<u8[]>): Secret<u8[]> | null => {
1089
+ if (!quicPacketHeldFits(aead, trafficSecret)) {
1026
1090
  return null
1027
1091
  }
1028
- const hp: u8[] = quicPacketExpandLabel(aead, secret, "quic hp", quicPacketKeyLength(aead))
1029
- return quicPacketDerive(aead, secret, hp, aead === QUIC_AEAD_CHACHA20_POLY1305 ? null : aesKey(hp))
1092
+ return quicPacketExpandHeld(aead, trafficSecret, "quic ku", quicPacketSecretLength(aead))
1030
1093
  }
1031
1094
 
1032
1095
  /**
1033
- * The next generation's traffic secret for a key update (RFC 9001 §6.1):
1034
- * HKDF-Expand-Label(`secret`, `quic ku`, "", Hash.length). The
1035
- * header-protection key does not change with it (§6), so the new keys are
1036
- * `quicKeysUpdate` of the old keys and this. Answers `null` where `quicKeys`
1037
- * would.
1096
+ * The next generation's keys in a key update (RFC 9001 §6), derived into
1097
+ * `slot`, which the caller owns and which must not be the slot `keys` live
1098
+ * in: the packet key and IV from `nextSecret`, which `quicKeyUpdateSecret`
1099
+ * made from the current secret and the caller still owns, read through a
1100
+ * plain copy inside an arena block that is zeroed, and `keys`'
1101
+ * header-protection key kept, since header protection never changes with
1102
+ * the phase. The new generation holds its own copy of that key and its own
1103
+ * schedule, in the slot, so wiping one generation leaves the other usable.
1104
+ * Answers the slot's keys, or `null`, deriving nothing, for keys `quicKeys`
1105
+ * did not make, keys that live in `slot`, or a secret of the wrong length.
1038
1106
  */
1039
- export const quicKeyUpdateSecret = (aead: i32, secret: u8[]): u8[] | null => {
1040
- if (!quicPacketSecretFits(aead, secret)) {
1107
+ export const quicKeysUpdate = (
1108
+ slot: QuicKeysSlot,
1109
+ keys: QuicKeys,
1110
+ nextSecret: Secret<u8[]>
1111
+ ): QuicKeys | null => {
1112
+ if (keys === slot.keys || !quicPacketKeysUsable(keys) || !quicPacketHeldFits(keys.aead, nextSecret)) {
1041
1113
  return null
1042
1114
  }
1043
- return quicPacketExpandLabel(aead, secret, "quic ku", quicPacketSecretLength(aead))
1115
+ quicPacketPoint(slot, keys.aead)
1116
+ const next: QuicKeys = slot.keys
1117
+ next.hp.set(keys.hp)
1118
+ const scratch: HkdfScratch = new HkdfScratch()
1119
+ {
1120
+ using _scope = arena()
1121
+ const hpAes: AesKey | null = next.hpAes
1122
+ if (hpAes !== null) {
1123
+ aesKeyInto(next.hp, hpAes)
1124
+ }
1125
+ const plain: u8[] = expose(nextSecret, quicPacketDup)
1126
+ quicPacketDeriveInto(scratch, next, plain)
1127
+ secureZero(plain)
1128
+ }
1129
+ return next
1044
1130
  }
1045
1131
 
1046
1132
  /**
1047
- * The next generation's keys in a key update (RFC 9001 §6): the packet key
1048
- * and IV derived from `nextSecret`, which `quicKeyUpdateSecret` made from the
1049
- * current secret, and `keys`' header-protection key kept, expanded as it was,
1050
- * since header protection never changes with the phase. Answers `null` for
1051
- * keys `quicKeys` did not make or a secret of the wrong length.
1133
+ * Zeroes every array `keys` holds: the packet key, the IV and the
1134
+ * header-protection key with `secureZero`, and each AES schedule with
1135
+ * `aesKeyWipe`, whose volatile stores reach the whole of `roundKeys` (the
1136
+ * kernels' powers of H included) and H itself. It zeroes the arrays `keys`
1137
+ * points at now; a slot's arrays of the other lengths are
1138
+ * `QuicKeysSlot.wipe`'s, which zeroes them all.
1052
1139
  */
1053
- export const quicKeysUpdate = (keys: QuicKeys, nextSecret: u8[]): QuicKeys | null => {
1054
- if (!quicPacketKeysUsable(keys) || !quicPacketSecretFits(keys.aead, nextSecret)) {
1055
- return null
1140
+ export const quicKeysWipe = (keys: QuicKeys): void => {
1141
+ secureZero(keys.key)
1142
+ secureZero(keys.iv)
1143
+ secureZero(keys.hp)
1144
+ const packetAes: AesKey | null = keys.packetAes
1145
+ if (packetAes !== null) {
1146
+ aesKeyWipe(packetAes)
1147
+ }
1148
+ const hpAes: AesKey | null = keys.hpAes
1149
+ if (hpAes !== null) {
1150
+ aesKeyWipe(hpAes)
1056
1151
  }
1057
- return quicPacketDerive(keys.aead, nextSecret, keys.hp, keys.hpAes)
1058
1152
  }
1059
1153
 
1060
1154
  // ---- Keys in a connection slot ----------------------------------------------
@@ -1066,8 +1160,8 @@ export const quicKeysUpdate = (keys: QuicKeys, nextSecret: u8[]): QuicKeys | nul
1066
1160
  * and the `QuicKeys` that `quicKeysInto` and `quicKeysUpdateInto` point at
1067
1161
  * the ones the negotiated AEAD uses. So installing a level's keys, or a key
1068
1162
  * update's, writes into memory the slot already holds and keeps nothing in
1069
- * the arena, where `quicKeys` makes a fresh `QuicKeys` and four arrays a
1070
- * level. `wipe` zeroes all of it.
1163
+ * the arena. The slot is its owner's to wipe (QUIC-1): `wipe` zeroes all of
1164
+ * it.
1071
1165
  */
1072
1166
  export class QuicKeysSlot {
1073
1167
  /** The keys, pointing into the arrays below; `aead` is 0 until something is derived. */
@@ -1095,8 +1189,8 @@ export class QuicKeysSlot {
1095
1189
 
1096
1190
  /**
1097
1191
  * Zeroes every key the slot holds: the keys and the IV with `secureZero`,
1098
- * the AES schedules' `u64` words with ordinary stores, which stand because
1099
- * the schedules stay reachable for the next derivation to fill.
1192
+ * and each AES schedule with `aesKeyWipe`, whose volatile stores reach the
1193
+ * whole of `roundKeys` (the kernels' powers of H included) and H itself.
1100
1194
  */
1101
1195
  wipe(): void {
1102
1196
  secureZero(this.key16)
@@ -1104,23 +1198,13 @@ export class QuicKeysSlot {
1104
1198
  secureZero(this.hp16)
1105
1199
  secureZero(this.hp32)
1106
1200
  secureZero(this.keys.iv)
1107
- quicPacketClearAes(this.packet128)
1108
- quicPacketClearAes(this.packet256)
1109
- quicPacketClearAes(this.hp128)
1110
- quicPacketClearAes(this.hp256)
1201
+ aesKeyWipe(this.packet128)
1202
+ aesKeyWipe(this.packet256)
1203
+ aesKeyWipe(this.hp128)
1204
+ aesKeyWipe(this.hp256)
1111
1205
  }
1112
1206
  }
1113
1207
 
1114
- /** Zeroes an AES key schedule and its GCM hash key with ordinary stores (see `QuicKeysSlot.wipe`). */
1115
- const quicPacketClearAes = (key: AesKey): void => {
1116
- const words: u64[] = key.roundKeys
1117
- for (let k: i32 = 0; k < toI32(words.length); k += 1) {
1118
- words[k] = 0
1119
- }
1120
- key.hHi = 0
1121
- key.hLo = 0
1122
- }
1123
-
1124
1208
  /** Points `slot.keys` at the arrays `aead` needs: the packet key and its schedule, and the header-protection ones. */
1125
1209
  const quicPacketPoint = (slot: QuicKeysSlot, aead: i32): void => {
1126
1210
  const keys: QuicKeys = slot.keys
@@ -1236,6 +1320,58 @@ export const quicKeyUpdateSecretInto = (kdf: HkdfScratch, aead: i32, secret: u8[
1236
1320
  return true
1237
1321
  }
1238
1322
 
1323
+ /**
1324
+ * `quicKeysInto` from a traffic secret held as a `Secret`: the packet key, IV
1325
+ * and header-protection key derived from it straight into the slot's arrays,
1326
+ * the plain output of each derivation wiped, and the AES schedules expanded
1327
+ * in place inside an arena block. Answers the slot's keys, or `null`,
1328
+ * deriving nothing, where `quicKeys` answers `null`.
1329
+ */
1330
+ export const quicKeysHeldInto = (slot: QuicKeysSlot, aead: i32, secret: Secret<u8[]>): QuicKeys | null => {
1331
+ if (!quicPacketHeldFits(aead, secret)) {
1332
+ return null
1333
+ }
1334
+ quicPacketPoint(slot, aead)
1335
+ const keys: QuicKeys = slot.keys
1336
+ const hashLength: i32 = quicPacketSecretLength(aead)
1337
+ const keyLength: i32 = quicPacketKeyLength(aead)
1338
+ const none: u8[] = []
1339
+ hkdfExpandLabelHeldInto(hashLength, secret, "quic key", none, keys.key, 0, keyLength)
1340
+ hkdfExpandLabelHeldInto(hashLength, secret, "quic iv", none, keys.iv, 0, QUIC_IV_SIZE)
1341
+ hkdfExpandLabelHeldInto(hashLength, secret, "quic hp", none, keys.hp, 0, keyLength)
1342
+ using _scope = arena()
1343
+ const packetAes: AesKey | null = keys.packetAes
1344
+ if (packetAes !== null) {
1345
+ aesKeyInto(keys.key, packetAes)
1346
+ }
1347
+ const hpAes: AesKey | null = keys.hpAes
1348
+ if (hpAes !== null) {
1349
+ aesKeyInto(keys.hp, hpAes)
1350
+ }
1351
+ return keys
1352
+ }
1353
+
1354
+ /**
1355
+ * `quicKeyUpdateSecret` from one held secret into another (RFC 9001 §6.1):
1356
+ * HKDF-Expand-Label(`src`, `quic ku`, "", Hash.length) written over `dst`'s
1357
+ * bytes in place, under SHA-384 for a 48-byte secret and SHA-256 for a
1358
+ * 32-byte one. `dst` may be `src`, which ratchets it. Answers whether it
1359
+ * derived: not unless both hold 32 bytes or both 48.
1360
+ */
1361
+ export const quicKeyUpdateSecretHeldInto = (dst: Secret<u8[]>, src: Secret<u8[]>): boolean => {
1362
+ const length: i32 = expose(src, quicPacketBytesLength)
1363
+ if ((length !== 32 && length !== 48) || expose(dst, quicPacketBytesLength) !== length) {
1364
+ return false
1365
+ }
1366
+ const none: u8[] = []
1367
+ if (length === 48) {
1368
+ hkdfExpandLabelSha384Held(dst, src, "quic ku", none)
1369
+ } else {
1370
+ hkdfExpandLabelSha256Held(dst, src, "quic ku", none)
1371
+ }
1372
+ return true
1373
+ }
1374
+
1239
1375
  /**
1240
1376
  * `quicInitialSecrets` of the Destination Connection ID `buf[at .. at +
1241
1377
  * length)`, read in place, into `client` and `server`, 32 bytes each.
@@ -1376,12 +1512,17 @@ export const quicSealInPlace = (
1376
1512
  const payloadAt: i32 = start + headerLength
1377
1513
  {
1378
1514
  using _scope = arena()
1515
+ // The nonce is the IV with a public number XORed in, so it gives the IV
1516
+ // away, and the mask is the header-protection key's keystream: both are
1517
+ // wiped once used, before the arena block takes them back.
1518
+ const nonce: u8[] = quicPacketNonce(keys, packetNumber)
1379
1519
  const sealed: u8[] | null = quicPacketSeal(
1380
1520
  keys,
1381
- quicPacketNonce(keys, packetNumber),
1521
+ nonce,
1382
1522
  quicPacketSlice(buf, start, payloadAt),
1383
1523
  quicPacketSlice(buf, payloadAt, payloadAt + payloadLength)
1384
1524
  )
1525
+ wipe(nonce)
1385
1526
  if (sealed === null || payloadAt + toI32(sealed.length) > toI32(buf.length)) {
1386
1527
  return -1
1387
1528
  }
@@ -1391,15 +1532,22 @@ export const quicSealInPlace = (
1391
1532
  keys,
1392
1533
  quicPacketSlice(buf, sampleAt, sampleAt + QUIC_SAMPLE_SIZE)
1393
1534
  )
1394
- if (mask === null || toI32(mask.length) < 5) {
1535
+ if (mask === null) {
1395
1536
  return -1
1396
1537
  }
1397
- buf[start] = toU8(first ^ (toI32(mask[0]) & quicPacketProtectedBits(first)))
1398
- for (let k: i32 = 0; k < pnLength && k + 1 < toI32(mask.length); k += 1) {
1399
- if (pnOffset + k >= 0 && pnOffset + k < toI32(buf.length)) {
1400
- buf[pnOffset + k] = buf[pnOffset + k] ^ mask[k + 1]
1538
+ const masked: boolean = toI32(mask.length) >= 5
1539
+ if (masked) {
1540
+ buf[start] = toU8(first ^ (toI32(mask[0]) & quicPacketProtectedBits(first)))
1541
+ for (let k: i32 = 0; k < pnLength && k + 1 < toI32(mask.length); k += 1) {
1542
+ if (pnOffset + k >= 0 && pnOffset + k < toI32(buf.length)) {
1543
+ buf[pnOffset + k] = buf[pnOffset + k] ^ mask[k + 1]
1544
+ }
1401
1545
  }
1402
1546
  }
1547
+ wipe(mask)
1548
+ if (!masked) {
1549
+ return -1
1550
+ }
1403
1551
  }
1404
1552
  return end
1405
1553
  }
@@ -1487,13 +1635,16 @@ export const quicUnprotectHeader = (
1487
1635
  keys,
1488
1636
  quicPacketSlice(datagram, sampleAt, sampleAt + QUIC_SAMPLE_SIZE)
1489
1637
  )
1490
- if (mask === null || toI32(mask.length) < 5) {
1638
+ if (mask === null) {
1491
1639
  packet.error = QUIC_ERR_KEYS
1492
1640
  return []
1493
1641
  }
1642
+ const usable: boolean = toI32(mask.length) >= 5
1494
1643
  const protectedFirst: i32 = toI32(datagram[header.start])
1495
1644
  const long: boolean = (protectedFirst & 0x80) !== 0
1496
- const first: i32 = protectedFirst ^ (toI32(mask[0]) & quicPacketProtectedBits(protectedFirst))
1645
+ const first: i32 = usable
1646
+ ? protectedFirst ^ (toI32(mask[0]) & quicPacketProtectedBits(protectedFirst))
1647
+ : protectedFirst
1497
1648
  const pnLength: i32 = (first & 3) + 1
1498
1649
  // The sample check above put 20 bytes after `pnOffset` inside the packet,
1499
1650
  // so the packet number's at most 4 are there too.
@@ -1508,6 +1659,13 @@ export const quicUnprotectHeader = (
1508
1659
  truncated = (truncated << 8) | toI64(b)
1509
1660
  }
1510
1661
  }
1662
+ // The mask is the header-protection key's keystream: wiped once used, on
1663
+ // the one path every mask takes, so that `-O2` counts this wipe alone.
1664
+ wipe(mask)
1665
+ if (!usable) {
1666
+ packet.error = QUIC_ERR_KEYS
1667
+ return []
1668
+ }
1511
1669
  packet.firstByte = first
1512
1670
  packet.pnLength = pnLength
1513
1671
  packet.packetNumber = quicPacketNumberDecode(largestPn, truncated, pnLength)
@@ -1562,12 +1720,15 @@ export const quicDecryptPayload = (
1562
1720
  return null
1563
1721
  }
1564
1722
  const payloadStart: i32 = header.start + toI32(clear.length)
1723
+ // The nonce gives the IV away (see `quicSealInPlace`): wiped once used.
1724
+ const nonce: u8[] = quicPacketNonce(keys, packet.packetNumber)
1565
1725
  const plain: u8[] | null = quicPacketOpen(
1566
1726
  keys,
1567
- quicPacketNonce(keys, packet.packetNumber),
1727
+ nonce,
1568
1728
  clear,
1569
1729
  quicPacketSlice(datagram, payloadStart, header.end)
1570
1730
  )
1731
+ wipe(nonce)
1571
1732
  if (plain === null) {
1572
1733
  packet.error = QUIC_ERR_DECRYPT
1573
1734
  return null