@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.
- 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 +215 -18
- 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 +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- 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 +932 -61
- 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 +1120 -34
- 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 +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- 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 +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -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,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
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
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 {
|
|
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
|
-
*
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
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
|
|
1004
|
-
|
|
1005
|
-
|
|
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
|
-
|
|
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
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
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
|
|
1025
|
-
if (!
|
|
1088
|
+
export const quicKeyUpdateSecret = (aead: i32, trafficSecret: Secret<u8[]>): Secret<u8[]> | null => {
|
|
1089
|
+
if (!quicPacketHeldFits(aead, trafficSecret)) {
|
|
1026
1090
|
return null
|
|
1027
1091
|
}
|
|
1028
|
-
|
|
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
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
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
|
|
1040
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
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
|
|
1054
|
-
|
|
1055
|
-
|
|
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
|
|
1070
|
-
*
|
|
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
|
-
*
|
|
1099
|
-
* the
|
|
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
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
-
|
|
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
|
|
1535
|
+
if (mask === null) {
|
|
1395
1536
|
return -1
|
|
1396
1537
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|