@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/tls/record.ts
CHANGED
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
*
|
|
12
12
|
* **Framing.** A record is a five-byte header — the content type, a legacy
|
|
13
13
|
* version and a 16-bit length — and that many bytes. `TlsRecordReader` holds
|
|
14
|
-
* the bytes of a stream that are not yet a whole record, in a buffer
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* the bytes of a stream that are not yet a whole record, in a buffer big
|
|
15
|
+
* enough for the largest record the RFC allows (2^14 + 256 bytes of body),
|
|
16
|
+
* borrowed from a `BufferPool` only while it holds any, and says as soon as
|
|
17
|
+
* a header is in whether the record is one this layer will read: a content
|
|
18
|
+
* type it does not know is `unexpected_message`, and a length past the limit
|
|
19
|
+
* is `record_overflow`, before any of the body is waited for. The legacy
|
|
20
|
+
* version is ignored, as §5.1 says it must be.
|
|
20
21
|
*
|
|
21
22
|
* **Protection.** `TlsRecordProtection` is one direction of a connection:
|
|
22
23
|
* cleartext until `install` gives it a suite and a traffic secret, then
|
|
@@ -44,24 +45,32 @@
|
|
|
44
45
|
* derived from it are held in the protection's fields, because a `Secret` may
|
|
45
46
|
* not be one (NL2430), in arrays the protection reuses for every key it is
|
|
46
47
|
* given, and are zeroed when it is installed again or cleared — the key and
|
|
47
|
-
* IV with `secureZero
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
48
|
+
* IV with `secureZero`, the schedule with `aesKeyWipe`, the volatile `wipe`
|
|
49
|
+
* of its round keys (the kernels' powers of H included) and of H. The key and
|
|
50
|
+
* IV are derived straight into place, in an HKDF scratch that is wiped after
|
|
51
|
+
* each derivation, and the schedule is expanded into place by `aesKeyInto`
|
|
52
|
+
* (TLS-2 in `docs/security/tls.md`). A caller that holds the traffic secret
|
|
53
|
+
* as a `Secret` installs it with `installHeld` and ratchets it in place with
|
|
54
|
+
* `advanceHeld`, so the secret itself is never plain outside `exposeWith`.
|
|
52
55
|
*
|
|
53
56
|
* The record layer is sans-IO: `nish/net/tls/record-server` runs it over a
|
|
54
57
|
* stream for `TlsServer`, and `nish/net/tls-tcp` puts that on a socket.
|
|
55
58
|
* Written from RFC 8446 §5, not ported from another implementation.
|
|
56
59
|
*/
|
|
57
|
-
import {
|
|
60
|
+
import { BufferPool } from "nish/net/buffer-pool"
|
|
61
|
+
import { AesKey, aesGcmOpen, aesGcmSeal, aesKeyInto, aesKeyWipe } from "nish/crypto/aes"
|
|
58
62
|
import { chacha20Poly1305Open, chacha20Poly1305Seal } from "nish/crypto/chacha20poly1305"
|
|
59
63
|
import {
|
|
60
64
|
TLS_ALERT_INTERNAL_ERROR,
|
|
61
65
|
TLS_ALERT_UNEXPECTED_MESSAGE,
|
|
62
66
|
TLS_LEGACY_VERSION,
|
|
63
67
|
} from "nish/net/tls/codec"
|
|
64
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
HkdfScratch,
|
|
70
|
+
hkdfExpandLabelInto,
|
|
71
|
+
hkdfExpandLabelSha256Held,
|
|
72
|
+
hkdfExpandLabelSha384Held,
|
|
73
|
+
} from "nish/crypto/hkdf"
|
|
65
74
|
import { SHA384_SIZE } from "nish/crypto/sha512"
|
|
66
75
|
import {
|
|
67
76
|
TLS_CHACHA20_POLY1305_SHA256,
|
|
@@ -69,8 +78,10 @@ import {
|
|
|
69
78
|
tlsExpandLabel,
|
|
70
79
|
tlsSuiteHashLength,
|
|
71
80
|
tlsSuiteKeyLength,
|
|
81
|
+
tlsTrafficKeysFromHeld,
|
|
72
82
|
tlsTrafficKeysInto,
|
|
73
83
|
} from "nish/net/tls/schedule"
|
|
84
|
+
import { Secret, expose } from "nish:secret"
|
|
74
85
|
|
|
75
86
|
// ---- The wire's constants ------------------------------------------------------
|
|
76
87
|
|
|
@@ -134,11 +145,12 @@ const TLS_RECORD_FROM: i32 = 0
|
|
|
134
145
|
/**
|
|
135
146
|
* The traffic secret a KeyUpdate moves one direction to (RFC 8446 §7.2):
|
|
136
147
|
* `HKDF-Expand-Label(secret, "traffic upd", "", Hash.length)` over the hash
|
|
137
|
-
* `hashLength` names
|
|
148
|
+
* `hashLength` names, held as a `Secret`. The old secret is the caller's to
|
|
149
|
+
* wipe.
|
|
138
150
|
*/
|
|
139
|
-
export const tlsNextTrafficSecret = (hashLength: i32,
|
|
151
|
+
export const tlsNextTrafficSecret = (hashLength: i32, trafficSecret: Secret<u8[]>): Secret<u8[]> => {
|
|
140
152
|
const none: u8[] = []
|
|
141
|
-
return tlsExpandLabel(hashLength,
|
|
153
|
+
return tlsExpandLabel(hashLength, trafficSecret, "traffic upd", none, hashLength)
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
/** Whether `[off, off + len)` is a window inside an array of `length` elements: every caller's range check. */
|
|
@@ -182,44 +194,65 @@ export const tlsRecordLength = (buf: u8[], off: i32, avail: i32): i32 => {
|
|
|
182
194
|
return TLS_RECORD_HEADER_SIZE + length
|
|
183
195
|
}
|
|
184
196
|
|
|
185
|
-
/**
|
|
186
|
-
* Zeroes an AES key schedule's round keys and H with ordinary stores, which
|
|
187
|
-
* stand because the schedule stays reachable for the next key (TLS-2).
|
|
188
|
-
*/
|
|
189
|
-
const tlsClearAesKey = (aes: AesKey): void => {
|
|
190
|
-
for (let k: i32 = 0; k < toI32(aes.roundKeys.length); k++) {
|
|
191
|
-
aes.roundKeys[k] = toU64(0)
|
|
192
|
-
}
|
|
193
|
-
aes.hHi = toU64(0)
|
|
194
|
-
aes.hLo = toU64(0)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
197
|
/**
|
|
198
198
|
* The bytes of a stream that are not yet a whole record. The buffer is
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
199
|
+
* `TLS_MAX_RECORD` bytes, borrowed from `pool` by the first `push` that brings
|
|
200
|
+
* a byte, kept between records, and given back, zeroed with `giveSecret`, by
|
|
201
|
+
* `reset`, as its connection ends: a reader that has not been pushed to since
|
|
202
|
+
* holds no buffer at all. `push` takes what fits, and the
|
|
203
|
+
* bytes of a record stay until `consume` lets them go. The caller reads
|
|
204
|
+
* `buffer[start .. start + next())` for the record, while `next()` is above 0.
|
|
205
|
+
*
|
|
206
|
+
* A reader starts with a pool of its own, one buffer deep; `borrowFrom` points
|
|
207
|
+
* it at a pool a server shares between its slots.
|
|
202
208
|
*/
|
|
203
209
|
export class TlsRecordReader {
|
|
210
|
+
// References, then 32-bit fields: the order the layout packs without padding.
|
|
211
|
+
/** The bytes held, or `empty` while the reader holds none. */
|
|
204
212
|
buffer: u8[]
|
|
213
|
+
/** The zero-length array `buffer` is while no buffer is borrowed. */
|
|
214
|
+
empty: u8[]
|
|
215
|
+
/** Where `buffer` comes from and goes back to. */
|
|
216
|
+
pool: BufferPool
|
|
205
217
|
/** The first byte not yet consumed. */
|
|
206
218
|
start: i32 = 0
|
|
207
219
|
/** One past the last byte pushed. */
|
|
208
220
|
end: i32 = 0
|
|
209
221
|
|
|
210
222
|
constructor() {
|
|
211
|
-
this.
|
|
223
|
+
this.empty = []
|
|
224
|
+
this.buffer = this.empty
|
|
225
|
+
this.pool = new BufferPool("TlsRecordReader.buffer", TLS_MAX_RECORD, 1)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Borrows from `pool` from now on, giving back first, zeroed, any buffer
|
|
230
|
+
* held from the old one. Every byte held is forgotten, as `reset` does.
|
|
231
|
+
*/
|
|
232
|
+
borrowFrom(pool: BufferPool): void {
|
|
233
|
+
this.reset()
|
|
234
|
+
this.pool = pool
|
|
212
235
|
}
|
|
213
236
|
|
|
214
|
-
/** Forgets every byte, for a connection that starts again in the same slot. */
|
|
237
|
+
/** Forgets every byte, for a connection that starts again in the same slot, and gives the buffer back. */
|
|
215
238
|
reset(): void {
|
|
216
239
|
this.start = 0
|
|
217
240
|
this.end = 0
|
|
241
|
+
this.giveBack()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Gives the buffer back, zeroed, if one is held. */
|
|
245
|
+
giveBack(): void {
|
|
246
|
+
if (this.buffer.length > 0) {
|
|
247
|
+
// Every byte of it may have held a record: zero all of it.
|
|
248
|
+
this.pool.giveSecret(this.buffer, TLS_MAX_RECORD)
|
|
249
|
+
this.buffer = this.empty
|
|
250
|
+
}
|
|
218
251
|
}
|
|
219
252
|
|
|
220
253
|
/** How many bytes `push` would take now. */
|
|
221
254
|
space(): i32 {
|
|
222
|
-
return
|
|
255
|
+
return TLS_MAX_RECORD - (this.end - this.start)
|
|
223
256
|
}
|
|
224
257
|
|
|
225
258
|
/**
|
|
@@ -231,16 +264,22 @@ export class TlsRecordReader {
|
|
|
231
264
|
if (!tlsRecordWindowFits(toI32(data.length), off, len)) {
|
|
232
265
|
return -TLS_ALERT_INTERNAL_ERROR
|
|
233
266
|
}
|
|
267
|
+
if (len === 0) {
|
|
268
|
+
return 0
|
|
269
|
+
}
|
|
270
|
+
if (this.buffer.length === 0) {
|
|
271
|
+
this.buffer = this.pool.take()
|
|
272
|
+
}
|
|
234
273
|
const held: i32 = this.end - this.start
|
|
235
274
|
// The held bytes move to the front only when the new ones would not fit after them.
|
|
236
|
-
if (this.start > 0 &&
|
|
275
|
+
if (this.start > 0 && TLS_MAX_RECORD - this.end < len) {
|
|
237
276
|
for (let k: i32 = 0; k < held; k++) {
|
|
238
277
|
this.buffer[k] = this.buffer[this.start + k]
|
|
239
278
|
}
|
|
240
279
|
this.start = 0
|
|
241
280
|
this.end = held
|
|
242
281
|
}
|
|
243
|
-
const room: i32 =
|
|
282
|
+
const room: i32 = TLS_MAX_RECORD - this.end
|
|
244
283
|
const take: i32 = len < room ? len : room
|
|
245
284
|
for (let k: i32 = 0; k < take; k++) {
|
|
246
285
|
this.buffer[this.end + k] = data[off + k]
|
|
@@ -260,7 +299,11 @@ export class TlsRecordReader {
|
|
|
260
299
|
return length > avail ? 0 : length
|
|
261
300
|
}
|
|
262
301
|
|
|
263
|
-
/**
|
|
302
|
+
/**
|
|
303
|
+
* Lets the first `n` held bytes go: the record `next` answered, once it is
|
|
304
|
+
* handled. When none are left the next record starts at the front; the
|
|
305
|
+
* buffer stays until `reset`.
|
|
306
|
+
*/
|
|
264
307
|
consume(n: i32): void {
|
|
265
308
|
const held: i32 = this.end - this.start
|
|
266
309
|
let take: i32 = n < held ? n : held
|
|
@@ -334,16 +377,16 @@ export class TlsRecordProtection {
|
|
|
334
377
|
|
|
335
378
|
/**
|
|
336
379
|
* Back to cleartext, with the sequence number at 0 and both keys, the IV
|
|
337
|
-
* and both AES key schedules
|
|
338
|
-
*
|
|
339
|
-
* the
|
|
380
|
+
* and both AES key schedules wiped: the keys and IV with `secureZero`, and
|
|
381
|
+
* each schedule with `aesKeyWipe`, its whole `roundKeys` and its H, which
|
|
382
|
+
* the next `install` fills again (TLS-2).
|
|
340
383
|
*/
|
|
341
384
|
clear(): void {
|
|
342
385
|
secureZero(this.key16)
|
|
343
386
|
secureZero(this.key32)
|
|
344
387
|
secureZero(this.iv)
|
|
345
|
-
|
|
346
|
-
|
|
388
|
+
aesKeyWipe(this.aes128)
|
|
389
|
+
aesKeyWipe(this.aes256)
|
|
347
390
|
this.suite = 0
|
|
348
391
|
this.sequence = 0
|
|
349
392
|
this.contentType = 0
|
|
@@ -443,6 +486,54 @@ export class TlsRecordProtection {
|
|
|
443
486
|
return this.install(suite, secret)
|
|
444
487
|
}
|
|
445
488
|
|
|
489
|
+
/**
|
|
490
|
+
* `install` from a traffic secret the caller holds as a `Secret`: the key
|
|
491
|
+
* and IV are derived from it straight into this direction's arrays, the
|
|
492
|
+
* plain output of each derivation wiped, and the AES key schedule expanded
|
|
493
|
+
* into place inside an arena block, as `derive`'s is. `false`, leaving the
|
|
494
|
+
* direction cleartext, where `install` answers it.
|
|
495
|
+
*/
|
|
496
|
+
installHeld(suite: i32, secret: Secret<u8[]>): boolean {
|
|
497
|
+
this.clear()
|
|
498
|
+
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
499
|
+
if (hashLength === 0 || expose(secret, (bytes: u8[]): i32 => toI32(bytes.length)) !== hashLength) {
|
|
500
|
+
return false
|
|
501
|
+
}
|
|
502
|
+
this.reserve(suite)
|
|
503
|
+
tlsTrafficKeysFromHeld(suite, secret, this.key, this.iv)
|
|
504
|
+
const aes: AesKey | null = this.aes
|
|
505
|
+
if (aes !== null && suite !== TLS_CHACHA20_POLY1305_SHA256) {
|
|
506
|
+
using _scope = arena()
|
|
507
|
+
aesKeyInto(this.key, aes)
|
|
508
|
+
}
|
|
509
|
+
this.suite = suite
|
|
510
|
+
return true
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* `advance` on a traffic secret the caller holds as a `Secret`: the next
|
|
515
|
+
* traffic secret (§7.2) is written over `secret`'s bytes in place, a
|
|
516
|
+
* ratchet that leaves no copy of either, and the keys are installed from
|
|
517
|
+
* it with `installHeld`. False, as `advance` answers, when the keys do not
|
|
518
|
+
* install; a secret that is not its hash's length is left as it was.
|
|
519
|
+
*/
|
|
520
|
+
advanceHeld(secret: Secret<u8[]>): boolean {
|
|
521
|
+
const suite: i32 = this.suite
|
|
522
|
+
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
523
|
+
if (hashLength === 0) {
|
|
524
|
+
return false
|
|
525
|
+
}
|
|
526
|
+
if (expose(secret, (bytes: u8[]): i32 => toI32(bytes.length)) === hashLength) {
|
|
527
|
+
const none: u8[] = []
|
|
528
|
+
if (hashLength === SHA384_SIZE) {
|
|
529
|
+
hkdfExpandLabelSha384Held(secret, secret, "traffic upd", none)
|
|
530
|
+
} else {
|
|
531
|
+
hkdfExpandLabelSha256Held(secret, secret, "traffic upd", none)
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return this.installHeld(suite, secret)
|
|
535
|
+
}
|
|
536
|
+
|
|
446
537
|
/** The per-record nonce (§5.3): the IV with the sequence number XORed into its last eight bytes, big-endian. */
|
|
447
538
|
nonceInto(nonce: u8[]): void {
|
|
448
539
|
for (let k: i32 = 0; k < TLS_IV_SIZE && k < toI32(nonce.length) && k < toI32(this.iv.length); k++) {
|