@amritk/nish-x86_64-linux 0.17.0 → 0.18.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/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.h +20 -6
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +11 -0
- package/runtime/runtime.c +20 -10
- package/scripts/build.sh +13 -9
- package/std/README.md +9 -8
- package/std/crypto/p256.ts +473 -10
- package/std/json.ts +297 -41
- package/std/net/http3-server.ts +22 -16
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +489 -213
- package/std/net/quic-packet.ts +226 -2
- package/std/net/quic-stream.ts +10 -4
- package/std/net/quic.ts +484 -120
- package/std/net/tls-tcp.ts +8 -4
- package/std/net/tls.ts +114 -45
- package/std/text.ts +77 -0
package/std/net/quic-packet.ts
CHANGED
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
* does not yet, and `docs/security/quic.md` records that as QUIC-1. The
|
|
57
57
|
* Initial keys are not secret, because anyone who reads the client's first
|
|
58
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.
|
|
59
63
|
*
|
|
60
64
|
* **Constant time.** The AEADs and the header-protection masks are
|
|
61
65
|
* `nish/crypto/aes` and `nish/crypto/chacha20poly1305`, with their guarantees.
|
|
@@ -73,10 +77,17 @@
|
|
|
73
77
|
* private functions share the importing program's flat symbol namespace
|
|
74
78
|
* (`docs/wp26-stdlib.md` §3e).
|
|
75
79
|
*/
|
|
76
|
-
import { aesGcmOpen, aesGcmSeal, aesHeaderMask, aesKey, AesKey } from "nish/crypto/aes"
|
|
80
|
+
import { aesGcmOpen, aesGcmSeal, aesHeaderMask, aesKey, aesKeyInto, AesKey } from "nish/crypto/aes"
|
|
77
81
|
import { chacha20HeaderMask, chacha20Poly1305Open, chacha20Poly1305Seal } from "nish/crypto/chacha20poly1305"
|
|
78
82
|
import { timingSafeEqual } from "nish/crypto/ct"
|
|
79
|
-
import {
|
|
83
|
+
import {
|
|
84
|
+
HkdfScratch,
|
|
85
|
+
hkdfExpandLabelInto,
|
|
86
|
+
hkdfExpandLabelSha256,
|
|
87
|
+
hkdfExpandLabelSha384,
|
|
88
|
+
hkdfExtractInto,
|
|
89
|
+
hkdfExtractSha256,
|
|
90
|
+
} from "nish/crypto/hkdf"
|
|
80
91
|
|
|
81
92
|
/** QUIC version 1 (RFC 9000 §15), the only version this module protects. */
|
|
82
93
|
export const QUIC_VERSION_1: i64 = 1
|
|
@@ -1046,6 +1057,219 @@ export const quicKeysUpdate = (keys: QuicKeys, nextSecret: u8[]): QuicKeys | nul
|
|
|
1046
1057
|
return quicPacketDerive(keys.aead, nextSecret, keys.hp, keys.hpAes)
|
|
1047
1058
|
}
|
|
1048
1059
|
|
|
1060
|
+
// ---- Keys in a connection slot ----------------------------------------------
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* The storage one direction of one level's packet keys lives in for a
|
|
1064
|
+
* connection slot (QUIC-3): a key and a header-protection key of each
|
|
1065
|
+
* length, the IV, and an AES key schedule of each size for each, made once,
|
|
1066
|
+
* and the `QuicKeys` that `quicKeysInto` and `quicKeysUpdateInto` point at
|
|
1067
|
+
* the ones the negotiated AEAD uses. So installing a level's keys, or a key
|
|
1068
|
+
* 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.
|
|
1071
|
+
*/
|
|
1072
|
+
export class QuicKeysSlot {
|
|
1073
|
+
/** The keys, pointing into the arrays below; `aead` is 0 until something is derived. */
|
|
1074
|
+
keys: QuicKeys
|
|
1075
|
+
key16: u8[]
|
|
1076
|
+
key32: u8[]
|
|
1077
|
+
hp16: u8[]
|
|
1078
|
+
hp32: u8[]
|
|
1079
|
+
packet128: AesKey
|
|
1080
|
+
packet256: AesKey
|
|
1081
|
+
hp128: AesKey
|
|
1082
|
+
hp256: AesKey
|
|
1083
|
+
|
|
1084
|
+
constructor() {
|
|
1085
|
+
this.key16 = new Array<u8>(16)
|
|
1086
|
+
this.key32 = new Array<u8>(32)
|
|
1087
|
+
this.hp16 = new Array<u8>(16)
|
|
1088
|
+
this.hp32 = new Array<u8>(32)
|
|
1089
|
+
this.keys = new QuicKeys(0, this.key16, new Array<u8>(QUIC_IV_SIZE), this.hp16)
|
|
1090
|
+
this.packet128 = new AesKey(10, new Array<u64>(88))
|
|
1091
|
+
this.packet256 = new AesKey(14, new Array<u64>(120))
|
|
1092
|
+
this.hp128 = new AesKey(10, new Array<u64>(88))
|
|
1093
|
+
this.hp256 = new AesKey(14, new Array<u64>(120))
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* 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.
|
|
1100
|
+
*/
|
|
1101
|
+
wipe(): void {
|
|
1102
|
+
secureZero(this.key16)
|
|
1103
|
+
secureZero(this.key32)
|
|
1104
|
+
secureZero(this.hp16)
|
|
1105
|
+
secureZero(this.hp32)
|
|
1106
|
+
secureZero(this.keys.iv)
|
|
1107
|
+
quicPacketClearAes(this.packet128)
|
|
1108
|
+
quicPacketClearAes(this.packet256)
|
|
1109
|
+
quicPacketClearAes(this.hp128)
|
|
1110
|
+
quicPacketClearAes(this.hp256)
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
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
|
+
/** Points `slot.keys` at the arrays `aead` needs: the packet key and its schedule, and the header-protection ones. */
|
|
1125
|
+
const quicPacketPoint = (slot: QuicKeysSlot, aead: i32): void => {
|
|
1126
|
+
const keys: QuicKeys = slot.keys
|
|
1127
|
+
const wide: boolean = quicPacketKeyLength(aead) === 32
|
|
1128
|
+
keys.aead = aead
|
|
1129
|
+
keys.key = wide ? slot.key32 : slot.key16
|
|
1130
|
+
keys.hp = wide ? slot.hp32 : slot.hp16
|
|
1131
|
+
keys.packetAes = null
|
|
1132
|
+
keys.hpAes = null
|
|
1133
|
+
if (aead !== QUIC_AEAD_CHACHA20_POLY1305) {
|
|
1134
|
+
keys.packetAes = wide ? slot.packet256 : slot.packet128
|
|
1135
|
+
keys.hpAes = wide ? slot.hp256 : slot.hp128
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* `quicPacketDerive` in place: the packet key and IV from `secret` into
|
|
1141
|
+
* `keys`' own arrays, and for the AES suites the key's schedule expanded
|
|
1142
|
+
* into its own `AesKey`. HKDF runs in `kdf`, which wipes what it held, and
|
|
1143
|
+
* the expansion's temporaries go with the arena block.
|
|
1144
|
+
*/
|
|
1145
|
+
const quicPacketDeriveInto = (kdf: HkdfScratch, keys: QuicKeys, secret: u8[]): void => {
|
|
1146
|
+
const aead: i32 = keys.aead
|
|
1147
|
+
const hashLength: i32 = quicPacketSecretLength(aead)
|
|
1148
|
+
using _scope = arena()
|
|
1149
|
+
const none: u8[] = []
|
|
1150
|
+
hkdfExpandLabelInto(kdf, hashLength, secret, "quic key", none, 0, 0, keys.key, 0, quicPacketKeyLength(aead))
|
|
1151
|
+
hkdfExpandLabelInto(kdf, hashLength, secret, "quic iv", none, 0, 0, keys.iv, 0, QUIC_IV_SIZE)
|
|
1152
|
+
const packetAes: AesKey | null = keys.packetAes
|
|
1153
|
+
if (packetAes !== null) {
|
|
1154
|
+
aesKeyInto(keys.key, packetAes)
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* `quicKeys` into `slot`: the packet key, IV and header-protection key of
|
|
1160
|
+
* RFC 9001 §5.1 derived from `secret` straight into the slot's arrays, in
|
|
1161
|
+
* `kdf`, with the AES schedules expanded in place. Answers the slot's keys,
|
|
1162
|
+
* or `null`, deriving nothing, where `quicKeys` answers `null`. The keys
|
|
1163
|
+
* are the slot's: the next derivation into it overwrites them.
|
|
1164
|
+
*/
|
|
1165
|
+
export const quicKeysInto = (
|
|
1166
|
+
kdf: HkdfScratch,
|
|
1167
|
+
slot: QuicKeysSlot,
|
|
1168
|
+
aead: i32,
|
|
1169
|
+
secret: u8[]
|
|
1170
|
+
): QuicKeys | null => {
|
|
1171
|
+
if (!quicPacketSecretFits(aead, secret)) {
|
|
1172
|
+
return null
|
|
1173
|
+
}
|
|
1174
|
+
quicPacketPoint(slot, aead)
|
|
1175
|
+
const keys: QuicKeys = slot.keys
|
|
1176
|
+
quicPacketDeriveInto(kdf, keys, secret)
|
|
1177
|
+
{
|
|
1178
|
+
using _scope = arena()
|
|
1179
|
+
const none: u8[] = []
|
|
1180
|
+
hkdfExpandLabelInto(
|
|
1181
|
+
kdf,
|
|
1182
|
+
quicPacketSecretLength(aead),
|
|
1183
|
+
secret,
|
|
1184
|
+
"quic hp",
|
|
1185
|
+
none,
|
|
1186
|
+
0,
|
|
1187
|
+
0,
|
|
1188
|
+
keys.hp,
|
|
1189
|
+
0,
|
|
1190
|
+
quicPacketKeyLength(aead)
|
|
1191
|
+
)
|
|
1192
|
+
const hpAes: AesKey | null = keys.hpAes
|
|
1193
|
+
if (hpAes !== null) {
|
|
1194
|
+
aesKeyInto(keys.hp, hpAes)
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return keys
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* `quicKeysUpdate` into `slot`, which must not be the slot `keys` live in:
|
|
1202
|
+
* the next generation's packet key and IV from `nextSecret`, and `keys`'
|
|
1203
|
+
* header-protection key shared, as every generation shares it (RFC 9001
|
|
1204
|
+
* §6). Answers the slot's keys, or `null` where `quicKeysUpdate` does.
|
|
1205
|
+
*/
|
|
1206
|
+
export const quicKeysUpdateInto = (
|
|
1207
|
+
kdf: HkdfScratch,
|
|
1208
|
+
slot: QuicKeysSlot,
|
|
1209
|
+
keys: QuicKeys,
|
|
1210
|
+
nextSecret: u8[]
|
|
1211
|
+
): QuicKeys | null => {
|
|
1212
|
+
if (!quicPacketKeysUsable(keys) || !quicPacketSecretFits(keys.aead, nextSecret)) {
|
|
1213
|
+
return null
|
|
1214
|
+
}
|
|
1215
|
+
quicPacketPoint(slot, keys.aead)
|
|
1216
|
+
const next: QuicKeys = slot.keys
|
|
1217
|
+
next.hp = keys.hp
|
|
1218
|
+
next.hpAes = keys.hpAes
|
|
1219
|
+
quicPacketDeriveInto(kdf, next, nextSecret)
|
|
1220
|
+
return next
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* `quicKeyUpdateSecret` into `out`, which must be exactly the secret's
|
|
1225
|
+
* length and not `secret` itself. Answers whether it derived, which it does
|
|
1226
|
+
* where `quicKeyUpdateSecret` answers a secret.
|
|
1227
|
+
*/
|
|
1228
|
+
export const quicKeyUpdateSecretInto = (kdf: HkdfScratch, aead: i32, secret: u8[], out: u8[]): boolean => {
|
|
1229
|
+
const length: i32 = quicPacketSecretLength(aead)
|
|
1230
|
+
if (!quicPacketSecretFits(aead, secret) || toI32(out.length) !== length) {
|
|
1231
|
+
return false
|
|
1232
|
+
}
|
|
1233
|
+
using _scope = arena()
|
|
1234
|
+
const none: u8[] = []
|
|
1235
|
+
hkdfExpandLabelInto(kdf, length, secret, "quic ku", none, 0, 0, out, 0, length)
|
|
1236
|
+
return true
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* `quicInitialSecrets` of the Destination Connection ID `buf[at .. at +
|
|
1241
|
+
* length)`, read in place, into `client` and `server`, 32 bytes each.
|
|
1242
|
+
* Answers whether it derived: not for an ID over 20 bytes, a window outside
|
|
1243
|
+
* `buf`, or an output of another length.
|
|
1244
|
+
*/
|
|
1245
|
+
export const quicInitialSecretsInto = (
|
|
1246
|
+
kdf: HkdfScratch,
|
|
1247
|
+
buf: u8[],
|
|
1248
|
+
at: i32,
|
|
1249
|
+
length: i32,
|
|
1250
|
+
client: u8[],
|
|
1251
|
+
server: u8[]
|
|
1252
|
+
): boolean => {
|
|
1253
|
+
if (
|
|
1254
|
+
length < 0 ||
|
|
1255
|
+
length > QUIC_MAX_CID_LENGTH ||
|
|
1256
|
+
at < 0 ||
|
|
1257
|
+
at > toI32(buf.length) - length ||
|
|
1258
|
+
toI32(client.length) !== 32 ||
|
|
1259
|
+
toI32(server.length) !== 32
|
|
1260
|
+
) {
|
|
1261
|
+
return false
|
|
1262
|
+
}
|
|
1263
|
+
using _scope = arena()
|
|
1264
|
+
const none: u8[] = []
|
|
1265
|
+
const initial: u8[] = new Array<u8>(32)
|
|
1266
|
+
hkdfExtractInto(kdf, 32, quicPacketInitialSalt(), quicPacketSlice(buf, at, at + length), initial, 0)
|
|
1267
|
+
hkdfExpandLabelInto(kdf, 32, initial, "client in", none, 0, 0, client, 0, 32)
|
|
1268
|
+
hkdfExpandLabelInto(kdf, 32, initial, "server in", none, 0, 0, server, 0, 32)
|
|
1269
|
+
secureZero(initial)
|
|
1270
|
+
return true
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1049
1273
|
/** The bits of the first byte header protection covers: four in a long header, five in a short one (RFC 9001 §5.4.1). */
|
|
1050
1274
|
const quicPacketProtectedBits = (first: i32): i32 => ((first & 0x80) !== 0 ? 0x0f : 0x1f)
|
|
1051
1275
|
|
package/std/net/quic-stream.ts
CHANGED
|
@@ -1224,10 +1224,16 @@ export class QuicStreams {
|
|
|
1224
1224
|
if (k >= 0 && k < count) {
|
|
1225
1225
|
const stream: QuicStream = this.slots[k]
|
|
1226
1226
|
if (stream.id >= 0 && stream.wantsToSend()) {
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1227
|
+
let next: i32 = at
|
|
1228
|
+
if (stream.resendLow >= 0 || stream.resendFin) {
|
|
1229
|
+
next = this.putResend(stream, buf, at, end)
|
|
1230
|
+
}
|
|
1231
|
+
// A resend range acknowledged in full since it was queued is
|
|
1232
|
+
// cleared with nothing written: the new bytes go in its place
|
|
1233
|
+
// (H3-7), rather than a packet with none while they wait.
|
|
1234
|
+
if (next === at && stream.resendLow < 0 && !stream.resendFin) {
|
|
1235
|
+
next = this.putNew(stream, k, buf, at, end)
|
|
1236
|
+
}
|
|
1231
1237
|
if (next > at) {
|
|
1232
1238
|
return next
|
|
1233
1239
|
}
|