@amritk/nish-x86_64-linux 0.17.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +231 -20
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +16 -3
- package/runtime/runtime.c +89 -23
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +225 -20
- package/std/README.md +86 -55
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +1376 -42
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1412 -70
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +231 -45
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +576 -219
- package/std/net/quic-packet.ts +443 -58
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +434 -28
- package/std/net/quic.ts +922 -206
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +204 -57
- package/std/net/tls.ts +1132 -305
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/text.ts +77 -0
- package/std/threads.ts +438 -14
package/std/net/quic-packet.ts
CHANGED
|
@@ -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
|
-
*
|
|
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,14 +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
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
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).
|
|
59
79
|
*
|
|
60
80
|
* **Constant time.** The AEADs and the header-protection masks are
|
|
61
81
|
* `nish/crypto/aes` and `nish/crypto/chacha20poly1305`, with their guarantees.
|
|
@@ -73,10 +93,31 @@
|
|
|
73
93
|
* private functions share the importing program's flat symbol namespace
|
|
74
94
|
* (`docs/wp26-stdlib.md` §3e).
|
|
75
95
|
*/
|
|
76
|
-
import {
|
|
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"
|
|
77
106
|
import { chacha20HeaderMask, chacha20Poly1305Open, chacha20Poly1305Seal } from "nish/crypto/chacha20poly1305"
|
|
78
107
|
import { timingSafeEqual } from "nish/crypto/ct"
|
|
79
|
-
import {
|
|
108
|
+
import {
|
|
109
|
+
HkdfScratch,
|
|
110
|
+
hkdfExpandLabelHeldInto,
|
|
111
|
+
hkdfExpandLabelInto,
|
|
112
|
+
hkdfExpandLabelSha256,
|
|
113
|
+
hkdfExpandLabelSha256Held,
|
|
114
|
+
hkdfExpandLabelSha256Secret,
|
|
115
|
+
hkdfExpandLabelSha384,
|
|
116
|
+
hkdfExpandLabelSha384Held,
|
|
117
|
+
hkdfExpandLabelSha384Secret,
|
|
118
|
+
hkdfExtractInto,
|
|
119
|
+
hkdfExtractSha256,
|
|
120
|
+
} from "nish/crypto/hkdf"
|
|
80
121
|
|
|
81
122
|
/** QUIC version 1 (RFC 9000 §15), the only version this module protects. */
|
|
82
123
|
export const QUIC_VERSION_1: i64 = 1
|
|
@@ -964,6 +1005,28 @@ const quicPacketSecretLength = (aead: i32): i32 => (aead === QUIC_AEAD_AES_256_G
|
|
|
964
1005
|
const quicPacketSecretFits = (aead: i32, secret: u8[]): boolean =>
|
|
965
1006
|
quicPacketKeyLength(aead) !== 0 && toI32(secret.length) === quicPacketSecretLength(aead)
|
|
966
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
|
+
|
|
967
1030
|
/**
|
|
968
1031
|
* Whether `keys` has the shape `quicKeys` gives: a known AEAD, key and
|
|
969
1032
|
* header-protection key of its length, a 12-byte IV, and for the AES suites
|
|
@@ -985,65 +1048,362 @@ const quicPacketKeysUsable = (keys: QuicKeys): boolean => {
|
|
|
985
1048
|
}
|
|
986
1049
|
|
|
987
1050
|
/**
|
|
988
|
-
*
|
|
989
|
-
*
|
|
990
|
-
*
|
|
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`.
|
|
991
1066
|
*/
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
quicPacketExpandLabel(aead, secret, "quic key", quicPacketKeyLength(aead)),
|
|
996
|
-
quicPacketExpandLabel(aead, secret, "quic iv", QUIC_IV_SIZE),
|
|
997
|
-
hp
|
|
998
|
-
)
|
|
999
|
-
keys.hpAes = hpAes
|
|
1000
|
-
if (aead !== QUIC_AEAD_CHACHA20_POLY1305) {
|
|
1001
|
-
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
|
|
1002
1070
|
}
|
|
1003
|
-
|
|
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
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
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.
|
|
1087
|
+
*/
|
|
1088
|
+
export const quicKeyUpdateSecret = (aead: i32, trafficSecret: Secret<u8[]>): Secret<u8[]> | null => {
|
|
1089
|
+
if (!quicPacketHeldFits(aead, trafficSecret)) {
|
|
1090
|
+
return null
|
|
1091
|
+
}
|
|
1092
|
+
return quicPacketExpandHeld(aead, trafficSecret, "quic ku", quicPacketSecretLength(aead))
|
|
1004
1093
|
}
|
|
1005
1094
|
|
|
1006
1095
|
/**
|
|
1007
|
-
* The
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
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.
|
|
1012
1106
|
*/
|
|
1013
|
-
export const
|
|
1014
|
-
|
|
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)) {
|
|
1015
1113
|
return null
|
|
1016
1114
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
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
|
|
1019
1130
|
}
|
|
1020
1131
|
|
|
1021
1132
|
/**
|
|
1022
|
-
*
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
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.
|
|
1139
|
+
*/
|
|
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)
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// ---- Keys in a connection slot ----------------------------------------------
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* The storage one direction of one level's packet keys lives in for a
|
|
1158
|
+
* connection slot (QUIC-3): a key and a header-protection key of each
|
|
1159
|
+
* length, the IV, and an AES key schedule of each size for each, made once,
|
|
1160
|
+
* and the `QuicKeys` that `quicKeysInto` and `quicKeysUpdateInto` point at
|
|
1161
|
+
* the ones the negotiated AEAD uses. So installing a level's keys, or a key
|
|
1162
|
+
* update's, writes into memory the slot already holds and keeps nothing in
|
|
1163
|
+
* the arena. The slot is its owner's to wipe (QUIC-1): `wipe` zeroes all of
|
|
1164
|
+
* it.
|
|
1165
|
+
*/
|
|
1166
|
+
export class QuicKeysSlot {
|
|
1167
|
+
/** The keys, pointing into the arrays below; `aead` is 0 until something is derived. */
|
|
1168
|
+
keys: QuicKeys
|
|
1169
|
+
key16: u8[]
|
|
1170
|
+
key32: u8[]
|
|
1171
|
+
hp16: u8[]
|
|
1172
|
+
hp32: u8[]
|
|
1173
|
+
packet128: AesKey
|
|
1174
|
+
packet256: AesKey
|
|
1175
|
+
hp128: AesKey
|
|
1176
|
+
hp256: AesKey
|
|
1177
|
+
|
|
1178
|
+
constructor() {
|
|
1179
|
+
this.key16 = new Array<u8>(16)
|
|
1180
|
+
this.key32 = new Array<u8>(32)
|
|
1181
|
+
this.hp16 = new Array<u8>(16)
|
|
1182
|
+
this.hp32 = new Array<u8>(32)
|
|
1183
|
+
this.keys = new QuicKeys(0, this.key16, new Array<u8>(QUIC_IV_SIZE), this.hp16)
|
|
1184
|
+
this.packet128 = new AesKey(10, new Array<u64>(88))
|
|
1185
|
+
this.packet256 = new AesKey(14, new Array<u64>(120))
|
|
1186
|
+
this.hp128 = new AesKey(10, new Array<u64>(88))
|
|
1187
|
+
this.hp256 = new AesKey(14, new Array<u64>(120))
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Zeroes every key the slot holds: the keys and the IV with `secureZero`,
|
|
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.
|
|
1194
|
+
*/
|
|
1195
|
+
wipe(): void {
|
|
1196
|
+
secureZero(this.key16)
|
|
1197
|
+
secureZero(this.key32)
|
|
1198
|
+
secureZero(this.hp16)
|
|
1199
|
+
secureZero(this.hp32)
|
|
1200
|
+
secureZero(this.keys.iv)
|
|
1201
|
+
aesKeyWipe(this.packet128)
|
|
1202
|
+
aesKeyWipe(this.packet256)
|
|
1203
|
+
aesKeyWipe(this.hp128)
|
|
1204
|
+
aesKeyWipe(this.hp256)
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/** Points `slot.keys` at the arrays `aead` needs: the packet key and its schedule, and the header-protection ones. */
|
|
1209
|
+
const quicPacketPoint = (slot: QuicKeysSlot, aead: i32): void => {
|
|
1210
|
+
const keys: QuicKeys = slot.keys
|
|
1211
|
+
const wide: boolean = quicPacketKeyLength(aead) === 32
|
|
1212
|
+
keys.aead = aead
|
|
1213
|
+
keys.key = wide ? slot.key32 : slot.key16
|
|
1214
|
+
keys.hp = wide ? slot.hp32 : slot.hp16
|
|
1215
|
+
keys.packetAes = null
|
|
1216
|
+
keys.hpAes = null
|
|
1217
|
+
if (aead !== QUIC_AEAD_CHACHA20_POLY1305) {
|
|
1218
|
+
keys.packetAes = wide ? slot.packet256 : slot.packet128
|
|
1219
|
+
keys.hpAes = wide ? slot.hp256 : slot.hp128
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* `quicPacketDerive` in place: the packet key and IV from `secret` into
|
|
1225
|
+
* `keys`' own arrays, and for the AES suites the key's schedule expanded
|
|
1226
|
+
* into its own `AesKey`. HKDF runs in `kdf`, which wipes what it held, and
|
|
1227
|
+
* the expansion's temporaries go with the arena block.
|
|
1228
|
+
*/
|
|
1229
|
+
const quicPacketDeriveInto = (kdf: HkdfScratch, keys: QuicKeys, secret: u8[]): void => {
|
|
1230
|
+
const aead: i32 = keys.aead
|
|
1231
|
+
const hashLength: i32 = quicPacketSecretLength(aead)
|
|
1232
|
+
using _scope = arena()
|
|
1233
|
+
const none: u8[] = []
|
|
1234
|
+
hkdfExpandLabelInto(kdf, hashLength, secret, "quic key", none, 0, 0, keys.key, 0, quicPacketKeyLength(aead))
|
|
1235
|
+
hkdfExpandLabelInto(kdf, hashLength, secret, "quic iv", none, 0, 0, keys.iv, 0, QUIC_IV_SIZE)
|
|
1236
|
+
const packetAes: AesKey | null = keys.packetAes
|
|
1237
|
+
if (packetAes !== null) {
|
|
1238
|
+
aesKeyInto(keys.key, packetAes)
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* `quicKeys` into `slot`: the packet key, IV and header-protection key of
|
|
1244
|
+
* RFC 9001 §5.1 derived from `secret` straight into the slot's arrays, in
|
|
1245
|
+
* `kdf`, with the AES schedules expanded in place. Answers the slot's keys,
|
|
1246
|
+
* or `null`, deriving nothing, where `quicKeys` answers `null`. The keys
|
|
1247
|
+
* are the slot's: the next derivation into it overwrites them.
|
|
1027
1248
|
*/
|
|
1028
|
-
export const
|
|
1249
|
+
export const quicKeysInto = (
|
|
1250
|
+
kdf: HkdfScratch,
|
|
1251
|
+
slot: QuicKeysSlot,
|
|
1252
|
+
aead: i32,
|
|
1253
|
+
secret: u8[]
|
|
1254
|
+
): QuicKeys | null => {
|
|
1029
1255
|
if (!quicPacketSecretFits(aead, secret)) {
|
|
1030
1256
|
return null
|
|
1031
1257
|
}
|
|
1032
|
-
|
|
1258
|
+
quicPacketPoint(slot, aead)
|
|
1259
|
+
const keys: QuicKeys = slot.keys
|
|
1260
|
+
quicPacketDeriveInto(kdf, keys, secret)
|
|
1261
|
+
{
|
|
1262
|
+
using _scope = arena()
|
|
1263
|
+
const none: u8[] = []
|
|
1264
|
+
hkdfExpandLabelInto(
|
|
1265
|
+
kdf,
|
|
1266
|
+
quicPacketSecretLength(aead),
|
|
1267
|
+
secret,
|
|
1268
|
+
"quic hp",
|
|
1269
|
+
none,
|
|
1270
|
+
0,
|
|
1271
|
+
0,
|
|
1272
|
+
keys.hp,
|
|
1273
|
+
0,
|
|
1274
|
+
quicPacketKeyLength(aead)
|
|
1275
|
+
)
|
|
1276
|
+
const hpAes: AesKey | null = keys.hpAes
|
|
1277
|
+
if (hpAes !== null) {
|
|
1278
|
+
aesKeyInto(keys.hp, hpAes)
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return keys
|
|
1033
1282
|
}
|
|
1034
1283
|
|
|
1035
1284
|
/**
|
|
1036
|
-
*
|
|
1037
|
-
* and IV
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
* keys `quicKeys` did not make or a secret of the wrong length.
|
|
1285
|
+
* `quicKeysUpdate` into `slot`, which must not be the slot `keys` live in:
|
|
1286
|
+
* the next generation's packet key and IV from `nextSecret`, and `keys`'
|
|
1287
|
+
* header-protection key shared, as every generation shares it (RFC 9001
|
|
1288
|
+
* §6). Answers the slot's keys, or `null` where `quicKeysUpdate` does.
|
|
1041
1289
|
*/
|
|
1042
|
-
export const
|
|
1290
|
+
export const quicKeysUpdateInto = (
|
|
1291
|
+
kdf: HkdfScratch,
|
|
1292
|
+
slot: QuicKeysSlot,
|
|
1293
|
+
keys: QuicKeys,
|
|
1294
|
+
nextSecret: u8[]
|
|
1295
|
+
): QuicKeys | null => {
|
|
1043
1296
|
if (!quicPacketKeysUsable(keys) || !quicPacketSecretFits(keys.aead, nextSecret)) {
|
|
1044
1297
|
return null
|
|
1045
1298
|
}
|
|
1046
|
-
|
|
1299
|
+
quicPacketPoint(slot, keys.aead)
|
|
1300
|
+
const next: QuicKeys = slot.keys
|
|
1301
|
+
next.hp = keys.hp
|
|
1302
|
+
next.hpAes = keys.hpAes
|
|
1303
|
+
quicPacketDeriveInto(kdf, next, nextSecret)
|
|
1304
|
+
return next
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/**
|
|
1308
|
+
* `quicKeyUpdateSecret` into `out`, which must be exactly the secret's
|
|
1309
|
+
* length and not `secret` itself. Answers whether it derived, which it does
|
|
1310
|
+
* where `quicKeyUpdateSecret` answers a secret.
|
|
1311
|
+
*/
|
|
1312
|
+
export const quicKeyUpdateSecretInto = (kdf: HkdfScratch, aead: i32, secret: u8[], out: u8[]): boolean => {
|
|
1313
|
+
const length: i32 = quicPacketSecretLength(aead)
|
|
1314
|
+
if (!quicPacketSecretFits(aead, secret) || toI32(out.length) !== length) {
|
|
1315
|
+
return false
|
|
1316
|
+
}
|
|
1317
|
+
using _scope = arena()
|
|
1318
|
+
const none: u8[] = []
|
|
1319
|
+
hkdfExpandLabelInto(kdf, length, secret, "quic ku", none, 0, 0, out, 0, length)
|
|
1320
|
+
return true
|
|
1321
|
+
}
|
|
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
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* `quicInitialSecrets` of the Destination Connection ID `buf[at .. at +
|
|
1377
|
+
* length)`, read in place, into `client` and `server`, 32 bytes each.
|
|
1378
|
+
* Answers whether it derived: not for an ID over 20 bytes, a window outside
|
|
1379
|
+
* `buf`, or an output of another length.
|
|
1380
|
+
*/
|
|
1381
|
+
export const quicInitialSecretsInto = (
|
|
1382
|
+
kdf: HkdfScratch,
|
|
1383
|
+
buf: u8[],
|
|
1384
|
+
at: i32,
|
|
1385
|
+
length: i32,
|
|
1386
|
+
client: u8[],
|
|
1387
|
+
server: u8[]
|
|
1388
|
+
): boolean => {
|
|
1389
|
+
if (
|
|
1390
|
+
length < 0 ||
|
|
1391
|
+
length > QUIC_MAX_CID_LENGTH ||
|
|
1392
|
+
at < 0 ||
|
|
1393
|
+
at > toI32(buf.length) - length ||
|
|
1394
|
+
toI32(client.length) !== 32 ||
|
|
1395
|
+
toI32(server.length) !== 32
|
|
1396
|
+
) {
|
|
1397
|
+
return false
|
|
1398
|
+
}
|
|
1399
|
+
using _scope = arena()
|
|
1400
|
+
const none: u8[] = []
|
|
1401
|
+
const initial: u8[] = new Array<u8>(32)
|
|
1402
|
+
hkdfExtractInto(kdf, 32, quicPacketInitialSalt(), quicPacketSlice(buf, at, at + length), initial, 0)
|
|
1403
|
+
hkdfExpandLabelInto(kdf, 32, initial, "client in", none, 0, 0, client, 0, 32)
|
|
1404
|
+
hkdfExpandLabelInto(kdf, 32, initial, "server in", none, 0, 0, server, 0, 32)
|
|
1405
|
+
secureZero(initial)
|
|
1406
|
+
return true
|
|
1047
1407
|
}
|
|
1048
1408
|
|
|
1049
1409
|
/** The bits of the first byte header protection covers: four in a long header, five in a short one (RFC 9001 §5.4.1). */
|
|
@@ -1152,12 +1512,17 @@ export const quicSealInPlace = (
|
|
|
1152
1512
|
const payloadAt: i32 = start + headerLength
|
|
1153
1513
|
{
|
|
1154
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)
|
|
1155
1519
|
const sealed: u8[] | null = quicPacketSeal(
|
|
1156
1520
|
keys,
|
|
1157
|
-
|
|
1521
|
+
nonce,
|
|
1158
1522
|
quicPacketSlice(buf, start, payloadAt),
|
|
1159
1523
|
quicPacketSlice(buf, payloadAt, payloadAt + payloadLength)
|
|
1160
1524
|
)
|
|
1525
|
+
wipe(nonce)
|
|
1161
1526
|
if (sealed === null || payloadAt + toI32(sealed.length) > toI32(buf.length)) {
|
|
1162
1527
|
return -1
|
|
1163
1528
|
}
|
|
@@ -1167,15 +1532,22 @@ export const quicSealInPlace = (
|
|
|
1167
1532
|
keys,
|
|
1168
1533
|
quicPacketSlice(buf, sampleAt, sampleAt + QUIC_SAMPLE_SIZE)
|
|
1169
1534
|
)
|
|
1170
|
-
if (mask === null
|
|
1535
|
+
if (mask === null) {
|
|
1171
1536
|
return -1
|
|
1172
1537
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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
|
+
}
|
|
1177
1545
|
}
|
|
1178
1546
|
}
|
|
1547
|
+
wipe(mask)
|
|
1548
|
+
if (!masked) {
|
|
1549
|
+
return -1
|
|
1550
|
+
}
|
|
1179
1551
|
}
|
|
1180
1552
|
return end
|
|
1181
1553
|
}
|
|
@@ -1263,13 +1635,16 @@ export const quicUnprotectHeader = (
|
|
|
1263
1635
|
keys,
|
|
1264
1636
|
quicPacketSlice(datagram, sampleAt, sampleAt + QUIC_SAMPLE_SIZE)
|
|
1265
1637
|
)
|
|
1266
|
-
if (mask === null
|
|
1638
|
+
if (mask === null) {
|
|
1267
1639
|
packet.error = QUIC_ERR_KEYS
|
|
1268
1640
|
return []
|
|
1269
1641
|
}
|
|
1642
|
+
const usable: boolean = toI32(mask.length) >= 5
|
|
1270
1643
|
const protectedFirst: i32 = toI32(datagram[header.start])
|
|
1271
1644
|
const long: boolean = (protectedFirst & 0x80) !== 0
|
|
1272
|
-
const first: i32 =
|
|
1645
|
+
const first: i32 = usable
|
|
1646
|
+
? protectedFirst ^ (toI32(mask[0]) & quicPacketProtectedBits(protectedFirst))
|
|
1647
|
+
: protectedFirst
|
|
1273
1648
|
const pnLength: i32 = (first & 3) + 1
|
|
1274
1649
|
// The sample check above put 20 bytes after `pnOffset` inside the packet,
|
|
1275
1650
|
// so the packet number's at most 4 are there too.
|
|
@@ -1284,6 +1659,13 @@ export const quicUnprotectHeader = (
|
|
|
1284
1659
|
truncated = (truncated << 8) | toI64(b)
|
|
1285
1660
|
}
|
|
1286
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
|
+
}
|
|
1287
1669
|
packet.firstByte = first
|
|
1288
1670
|
packet.pnLength = pnLength
|
|
1289
1671
|
packet.packetNumber = quicPacketNumberDecode(largestPn, truncated, pnLength)
|
|
@@ -1338,12 +1720,15 @@ export const quicDecryptPayload = (
|
|
|
1338
1720
|
return null
|
|
1339
1721
|
}
|
|
1340
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)
|
|
1341
1725
|
const plain: u8[] | null = quicPacketOpen(
|
|
1342
1726
|
keys,
|
|
1343
|
-
|
|
1727
|
+
nonce,
|
|
1344
1728
|
clear,
|
|
1345
1729
|
quicPacketSlice(datagram, payloadStart, header.end)
|
|
1346
1730
|
)
|
|
1731
|
+
wipe(nonce)
|
|
1347
1732
|
if (plain === null) {
|
|
1348
1733
|
packet.error = QUIC_ERR_DECRYPT
|
|
1349
1734
|
return null
|