@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/tls-tcp.ts
CHANGED
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
*/
|
|
62
62
|
import { Secret } from "nish:secret"
|
|
63
63
|
import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
|
|
64
|
-
import { TlsServer, TlsServerConfig
|
|
64
|
+
import { TlsP256Signer, TlsServer, TlsServerConfig } from "nish/net/tls"
|
|
65
65
|
import { TLS_ALERT_INTERNAL_ERROR } from "nish/net/tls/codec"
|
|
66
66
|
import { TLS_MAX_RECORD } from "nish/net/tls/record"
|
|
67
67
|
import {
|
|
@@ -103,6 +103,8 @@ export class TlsTcpServer {
|
|
|
103
103
|
scratch: u8[]
|
|
104
104
|
/** The last accepted peer's address, in `nish:net`'s 18-byte form. */
|
|
105
105
|
peer: u8[]
|
|
106
|
+
/** Signs every slot's CertificateVerify in `signP256`, one for the pool, keeping nothing per signature. */
|
|
107
|
+
signer: TlsP256Signer
|
|
106
108
|
/**
|
|
107
109
|
* Each slot's own copy of its connection's ephemeral key, so that wiping it
|
|
108
110
|
* can never reach an array the program has refilled for the next one.
|
|
@@ -141,6 +143,7 @@ export class TlsTcpServer {
|
|
|
141
143
|
}
|
|
142
144
|
this.scratch = new Array<u8>(TLS_MAX_RECORD)
|
|
143
145
|
this.peer = new Array<u8>(TLS_TCP_ADDRESS_SIZE)
|
|
146
|
+
this.signer = new TlsP256Signer()
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
/** How many slots the pool has. */
|
|
@@ -315,8 +318,9 @@ export class TlsTcpServer {
|
|
|
315
318
|
|
|
316
319
|
/**
|
|
317
320
|
* Signs `slot`'s CertificateVerify with the P-256 private key `key` and
|
|
318
|
-
* hands it over, as `sign` does
|
|
319
|
-
*
|
|
321
|
+
* hands it over, as `sign` does, through the pool's `TlsP256Signer`, so a
|
|
322
|
+
* handshake's signature keeps nothing. The key is borrowed: the caller made
|
|
323
|
+
* the `Secret`, and wipes it. A malformed key fails the connection with
|
|
320
324
|
* `internal_error`.
|
|
321
325
|
*/
|
|
322
326
|
signP256(slot: i32, key: Secret<u8[]>): i32 {
|
|
@@ -324,7 +328,7 @@ export class TlsTcpServer {
|
|
|
324
328
|
if (input === null) {
|
|
325
329
|
return this.interest(slot)
|
|
326
330
|
}
|
|
327
|
-
const signature: u8[] | null =
|
|
331
|
+
const signature: u8[] | null = this.signer.sign(key, input)
|
|
328
332
|
if (signature === null) {
|
|
329
333
|
this.connections[slot].fail(TLS_ALERT_INTERNAL_ERROR)
|
|
330
334
|
return this.flush(slot)
|
package/std/net/tls.ts
CHANGED
|
@@ -71,7 +71,7 @@ import { Secret, expose, secret, wipe } from "nish:secret"
|
|
|
71
71
|
import { HkdfScratch, hkdfExtractSha256, hkdfExtractSha384 } from "nish/crypto/hkdf"
|
|
72
72
|
import { SHA256_SIZE } from "nish/crypto/sha256"
|
|
73
73
|
import { SHA384_SIZE } from "nish/crypto/sha512"
|
|
74
|
-
import { p256SignSha256 } from "nish/crypto/p256"
|
|
74
|
+
import { P256SignScratch, P256_SIGNATURE_SIZE, p256SignSha256, p256SignSha256Into } from "nish/crypto/p256"
|
|
75
75
|
import { timingSafeEqual, timingSafeEqualAt } from "nish/crypto/ct"
|
|
76
76
|
import { X25519_SIZE, x25519, x25519Base } from "nish/crypto/x25519"
|
|
77
77
|
import {
|
|
@@ -238,29 +238,57 @@ const tlsBytesSpell = (data: u8[], at: i32, length: i32, text: string): boolean
|
|
|
238
238
|
return true
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
/** The
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
241
|
+
/** The shortest and longest DER `ECDSA-Sig-Value` of a P-256 signature: a SEQUENCE of two INTEGERs of 1 to 33 bytes. */
|
|
242
|
+
const TLS_P256_DER_MIN: i32 = 8
|
|
243
|
+
const TLS_P256_DER_MAX: i32 = 72
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The body length of the minimal DER INTEGER for the unsigned big-endian
|
|
247
|
+
* magnitude `rs[at .. at + 32)`: leading zeros dropped, one added when the top
|
|
248
|
+
* bit is set, and zero one zero octet rather than an empty integer (X.690
|
|
249
|
+
* §8.3.1).
|
|
250
|
+
*/
|
|
251
|
+
const tlsDerIntegerLength = (rs: u8[], at: i32): i32 => {
|
|
252
|
+
let first: i32 = 0
|
|
253
|
+
while (first < 31 && at + first >= 0 && at + first < toI32(rs.length) && toI32(rs[at + first]) === 0) {
|
|
254
|
+
first++
|
|
254
255
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
const top: boolean = at + first >= 0 && at + first < toI32(rs.length) && toI32(rs[at + first]) >= 0x80
|
|
257
|
+
return 32 - first + (top ? 1 : 0)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** That INTEGER, of body length `length`, written at `out[to ..)`; answers where it ends. */
|
|
261
|
+
const tlsDerIntegerInto = (out: u8[], to: i32, rs: u8[], at: i32, length: i32): i32 => {
|
|
262
|
+
const end: i32 = to + 2 + length
|
|
263
|
+
if (to < 0 || end > toI32(out.length)) {
|
|
264
|
+
return end
|
|
258
265
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
266
|
+
out[to] = toU8(0x02)
|
|
267
|
+
out[to + 1] = toU8(length)
|
|
268
|
+
for (let k: i32 = 0; k < length; k++) {
|
|
269
|
+
// The body's last `length` bytes are the magnitude's, a zero first when it is 33.
|
|
270
|
+
const from: i32 = at + 32 - length + k
|
|
271
|
+
const j: i32 = to + 2 + k
|
|
272
|
+
if (j >= 0 && j < toI32(out.length)) {
|
|
273
|
+
out[j] = from >= at && from >= 0 && from < toI32(rs.length) ? rs[from] : toU8(0)
|
|
274
|
+
}
|
|
262
275
|
}
|
|
263
|
-
return
|
|
276
|
+
return end
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** The length of `rs`'s DER `ECDSA-Sig-Value`: the SEQUENCE's two bytes and two INTEGERs. */
|
|
280
|
+
const tlsEcdsaDerLength = (rs: u8[]): i32 =>
|
|
281
|
+
6 + tlsDerIntegerLength(rs, TLS_FROM) + tlsDerIntegerLength(rs, 32)
|
|
282
|
+
|
|
283
|
+
/** `rs`'s DER `ECDSA-Sig-Value` into all of `der`, which is `tlsEcdsaDerLength(rs)` bytes. At most 72, so the length is one short-form byte. */
|
|
284
|
+
const tlsEcdsaDerInto = (der: u8[], rs: u8[]): void => {
|
|
285
|
+
if (toI32(der.length) < 2) {
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
der[0] = toU8(0x30)
|
|
289
|
+
der[1] = toU8(toI32(der.length) - 2)
|
|
290
|
+
const r: i32 = tlsDerIntegerLength(rs, TLS_FROM)
|
|
291
|
+
tlsDerIntegerInto(der, tlsDerIntegerInto(der, 2, rs, TLS_FROM, r), rs, 32, tlsDerIntegerLength(rs, 32))
|
|
264
292
|
}
|
|
265
293
|
|
|
266
294
|
/**
|
|
@@ -272,25 +300,9 @@ export const tlsEcdsaDerSignature = (rs: u8[]): u8[] | null => {
|
|
|
272
300
|
if (toI32(rs.length) !== 64) {
|
|
273
301
|
return null
|
|
274
302
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (k < 32) {
|
|
279
|
-
r.push(rs[k])
|
|
280
|
-
} else {
|
|
281
|
-
s.push(rs[k])
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
const body: u8[] = tlsDerInteger(r)
|
|
285
|
-
for (const b of tlsDerInteger(s)) {
|
|
286
|
-
body.push(b)
|
|
287
|
-
}
|
|
288
|
-
// At most 2 * (2 + 33) = 70 bytes, so the length is one short-form byte.
|
|
289
|
-
const out: u8[] = [toU8(0x30), toU8(toI32(body.length))]
|
|
290
|
-
for (const b of body) {
|
|
291
|
-
out.push(b)
|
|
292
|
-
}
|
|
293
|
-
return out
|
|
303
|
+
const der: u8[] = new Array<u8>(tlsEcdsaDerLength(rs))
|
|
304
|
+
tlsEcdsaDerInto(der, rs)
|
|
305
|
+
return der
|
|
294
306
|
}
|
|
295
307
|
|
|
296
308
|
/**
|
|
@@ -308,6 +320,55 @@ export const tlsSignEcdsaP256 = (priv: Secret<u8[]>, content: u8[]): u8[] | null
|
|
|
308
320
|
return tlsEcdsaDerSignature(rs)
|
|
309
321
|
}
|
|
310
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Signs CertificateVerify inputs for `ecdsa_secp256r1_sha256` with nothing
|
|
325
|
+
* kept per signature: `tlsSignEcdsaP256`'s answer, made in a
|
|
326
|
+
* `P256SignScratch` and handed back in one of this signer's own arrays, one
|
|
327
|
+
* per DER length (8 to 72 bytes), since `TlsServer.sign` takes the
|
|
328
|
+
* signature's length from its array and stores what it computes, so a
|
|
329
|
+
* signature it is handed cannot be a temporary. A carrier keeps one signer,
|
|
330
|
+
* not one per slot: signing is synchronous, and the array it answers is read
|
|
331
|
+
* by `sign` before the next signature replaces it.
|
|
332
|
+
*/
|
|
333
|
+
export class TlsP256Signer {
|
|
334
|
+
/** The P-256 signer's scratch. */
|
|
335
|
+
scratch: P256SignScratch
|
|
336
|
+
/** `r || s` as `p256SignSha256Into` writes it. */
|
|
337
|
+
rs: u8[]
|
|
338
|
+
/** The DER signatures, index `length - TLS_P256_DER_MIN`. */
|
|
339
|
+
ders: u8[][]
|
|
340
|
+
|
|
341
|
+
constructor() {
|
|
342
|
+
this.scratch = new P256SignScratch()
|
|
343
|
+
this.rs = new Array<u8>(P256_SIGNATURE_SIZE)
|
|
344
|
+
this.ders = []
|
|
345
|
+
for (let length: i32 = TLS_P256_DER_MIN; length <= TLS_P256_DER_MAX; length++) {
|
|
346
|
+
this.ders.push(new Array<u8>(length))
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* `tlsSignEcdsaP256(priv, content)`, byte for byte, in the array of this
|
|
352
|
+
* signer's that has its length; `null` for a malformed key. It keeps
|
|
353
|
+
* nothing: the P-256 signature releases what it allocated, and the DER is
|
|
354
|
+
* written in place. The key is borrowed.
|
|
355
|
+
*/
|
|
356
|
+
sign(priv: Secret<u8[]>, content: u8[]): u8[] | null {
|
|
357
|
+
if (
|
|
358
|
+
!p256SignSha256Into(this.scratch, priv, content, TLS_FROM, toI32(content.length), this.rs, TLS_FROM)
|
|
359
|
+
) {
|
|
360
|
+
return null
|
|
361
|
+
}
|
|
362
|
+
const at: i32 = tlsEcdsaDerLength(this.rs) - TLS_P256_DER_MIN
|
|
363
|
+
if (at < 0 || at >= toI32(this.ders.length)) {
|
|
364
|
+
return null
|
|
365
|
+
}
|
|
366
|
+
const der: u8[] = this.ders[at]
|
|
367
|
+
tlsEcdsaDerInto(der, this.rs)
|
|
368
|
+
return der
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
311
372
|
/** Whether an x25519 shared secret is all zeros, reading every byte (run inside `expose`). */
|
|
312
373
|
const tlsIsZeroSecret = (shared: u8[]): boolean => timingSafeEqual(shared, new Array<u8>(X25519_SIZE))
|
|
313
374
|
|
|
@@ -543,8 +604,9 @@ const tlsFlightCapacity = (config: TlsServerConfig): i32 => {
|
|
|
543
604
|
* to the largest message when a ClientHello does not fit (once per slot);
|
|
544
605
|
* the Handshake-level output, when a signature is longer than an ECDSA one;
|
|
545
606
|
* the `serverName` string, made when the client's host name differs from the
|
|
546
|
-
* last one this server held; and, over QUIC only,
|
|
547
|
-
* transport parameters
|
|
607
|
+
* last one this server held; and, over QUIC only, the array the client's
|
|
608
|
+
* transport parameters are copied into, which grows only when a client's
|
|
609
|
+
* are longer than any before (TLS-3).
|
|
548
610
|
*/
|
|
549
611
|
export class TlsServer {
|
|
550
612
|
config: TlsServerConfig
|
|
@@ -558,6 +620,8 @@ export class TlsServer {
|
|
|
558
620
|
lastServerName: string = ""
|
|
559
621
|
/** The client's `quic_transport_parameters`, opaque; empty unless the carrier is QUIC. */
|
|
560
622
|
clientTransportParameters: u8[]
|
|
623
|
+
/** The array `clientTransportParameters` points at once read, kept across `restart` and refilled in the room it has. */
|
|
624
|
+
clientParameters: u8[]
|
|
561
625
|
transcript: TlsTranscript
|
|
562
626
|
/** The bytes received at the current level and not yet a whole message: `input[0 .. inputLength)`. */
|
|
563
627
|
input: u8[]
|
|
@@ -617,6 +681,7 @@ export class TlsServer {
|
|
|
617
681
|
this.ephemeralPrivate = ephemeralPrivate
|
|
618
682
|
this.none = []
|
|
619
683
|
this.clientTransportParameters = this.none
|
|
684
|
+
this.clientParameters = []
|
|
620
685
|
this.transcript = new TlsTranscript(0)
|
|
621
686
|
this.configFits = tlsConfigFits(config)
|
|
622
687
|
this.input = new Array<u8>(TLS_INPUT_START)
|
|
@@ -1013,13 +1078,17 @@ export class TlsServer {
|
|
|
1013
1078
|
}
|
|
1014
1079
|
this.takeServerName(hello)
|
|
1015
1080
|
if (this.config.quic) {
|
|
1016
|
-
|
|
1081
|
+
// Refilled in place, so a slot's handshakes keep nothing for it (QUIC-3).
|
|
1082
|
+
const parameters: u8[] = this.clientParameters
|
|
1083
|
+
while (parameters.length > 0) {
|
|
1084
|
+
parameters.pop()
|
|
1085
|
+
}
|
|
1017
1086
|
for (
|
|
1018
1087
|
let k: i32 = 0;
|
|
1019
|
-
k <
|
|
1088
|
+
k < hello.quicLength && hello.quicAt + k >= 0 && hello.quicAt + k < toI32(data.length);
|
|
1020
1089
|
k++
|
|
1021
1090
|
) {
|
|
1022
|
-
parameters
|
|
1091
|
+
parameters.push(data[hello.quicAt + k])
|
|
1023
1092
|
}
|
|
1024
1093
|
this.clientTransportParameters = parameters
|
|
1025
1094
|
}
|
package/std/text.ts
CHANGED
|
@@ -191,3 +191,80 @@ export const firstDifference = (left: string[], right: string[]): i32 => {
|
|
|
191
191
|
}
|
|
192
192
|
return leftLength === rightLength ? -1 : i
|
|
193
193
|
}
|
|
194
|
+
|
|
195
|
+
/** The most bytes `indexOfAny` takes in its set: what one 16-byte vector of the runtime kernel compares. */
|
|
196
|
+
const INDEX_OF_ANY_MAX_SET: i32 = 16
|
|
197
|
+
|
|
198
|
+
/** The first byte that is not ASCII; `indexOfAny`'s set must stay below it. */
|
|
199
|
+
const NON_ASCII: i32 = 128
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The first index at or after `from` whose byte is one of `bytes`, or -1.
|
|
203
|
+
*
|
|
204
|
+
* `bytes` is a set of 1 to 16 ASCII bytes, and anything else panics: an empty
|
|
205
|
+
* set matches nothing, more than 16 is more than one vector of the runtime
|
|
206
|
+
* kernel compares, and an ASCII set is what keeps the answer the same under Node,
|
|
207
|
+
* where `charCodeAt` reads a UTF-16 unit rather than a byte. An ASCII byte
|
|
208
|
+
* never occurs inside a multi-byte UTF-8 sequence, so a non-ASCII `text`
|
|
209
|
+
* passes through and only its ASCII bytes can match — as in `splitLines`, the
|
|
210
|
+
* index is a byte offset natively. `from` is clamped to `[0, text.length]`,
|
|
211
|
+
* as `substring` clamps, so a negative `from` searches from the start and one
|
|
212
|
+
* past the end answers -1.
|
|
213
|
+
*
|
|
214
|
+
* Natively the search is the runtime's `nish_str_index_of_any`, which reads
|
|
215
|
+
* 16 or 32 bytes at a time (docs/LANGUAGE.md, "Byte search"). It is
|
|
216
|
+
* **variable-time**: how long it takes says where the first match is, so it is
|
|
217
|
+
* never for secret data.
|
|
218
|
+
*/
|
|
219
|
+
export const indexOfAny = (text: string, bytes: string, from: i32): i32 => {
|
|
220
|
+
const count: i32 = toI32(bytes.length)
|
|
221
|
+
if (count < 1 || count > INDEX_OF_ANY_MAX_SET) {
|
|
222
|
+
panic("indexOfAny: the set must hold 1 to 16 bytes")
|
|
223
|
+
}
|
|
224
|
+
let j: i32 = 0
|
|
225
|
+
while (j < count) {
|
|
226
|
+
if (toI32(bytes.charCodeAt(j)) >= NON_ASCII) {
|
|
227
|
+
panic("indexOfAny: every byte of the set must be ASCII")
|
|
228
|
+
}
|
|
229
|
+
j += 1
|
|
230
|
+
}
|
|
231
|
+
const length: i32 = toI32(text.length)
|
|
232
|
+
let start: i32 = from
|
|
233
|
+
if (start < 0) {
|
|
234
|
+
start = 0
|
|
235
|
+
}
|
|
236
|
+
if (start > length) {
|
|
237
|
+
start = length
|
|
238
|
+
}
|
|
239
|
+
return indexOfAnyFrom(text, bytes, start)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* `indexOfAny`'s walk, once its arguments are checked: the first index at or
|
|
244
|
+
* after `from` whose byte is in `bytes`, or -1.
|
|
245
|
+
*
|
|
246
|
+
* This loop is what runs under Node. Natively the compiler recognises the one
|
|
247
|
+
* call `indexOfAny` makes to it and calls the runtime kernel instead
|
|
248
|
+
* (`src/kernels.ts`), so the loop is the kernel's meaning written in the
|
|
249
|
+
* language rather than its implementation. The name is long for the reason
|
|
250
|
+
* `isTextBlankByte`'s is.
|
|
251
|
+
*/
|
|
252
|
+
const indexOfAnyFrom = (text: string, bytes: string, from: i32): i32 => {
|
|
253
|
+
const length: i32 = toI32(text.length)
|
|
254
|
+
const count: i32 = toI32(bytes.length)
|
|
255
|
+
let i: i32 = from
|
|
256
|
+
// `from` is never negative, because `indexOfAny` clamped it; the test is
|
|
257
|
+
// written because it is what proves `i` within `text`, as in `splitLines`.
|
|
258
|
+
while (i >= 0 && i < length) {
|
|
259
|
+
const code: i32 = toI32(text.charCodeAt(i))
|
|
260
|
+
let j: i32 = 0
|
|
261
|
+
while (j < count) {
|
|
262
|
+
if (toI32(bytes.charCodeAt(j)) === code) {
|
|
263
|
+
return i
|
|
264
|
+
}
|
|
265
|
+
j += 1
|
|
266
|
+
}
|
|
267
|
+
i += 1
|
|
268
|
+
}
|
|
269
|
+
return -1
|
|
270
|
+
}
|