@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.
@@ -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, tlsSignEcdsaP256 } from "nish/net/tls"
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. The key is borrowed: the caller made the
319
- * `Secret`, and wipes it. A malformed key fails the connection with
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 = tlsSignEcdsaP256(key, input)
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 minimal DER INTEGER for an unsigned big-endian magnitude: leading zeros dropped, one added when the top bit is set. */
242
- const tlsDerInteger = (magnitude: u8[]): u8[] => {
243
- const body: u8[] = []
244
- let leading: boolean = true
245
- for (const b of magnitude) {
246
- if (leading && toI32(b) === 0) {
247
- continue
248
- }
249
- if (leading && toI32(b) >= 0x80) {
250
- body.push(toU8(0))
251
- }
252
- leading = false
253
- body.push(b)
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
- // Zero is one zero octet, not an empty integer (X.690 §8.3.1).
256
- if (toI32(body.length) === 0) {
257
- body.push(toU8(0))
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
- const out: u8[] = [toU8(0x02), toU8(toI32(body.length))]
260
- for (const b of body) {
261
- out.push(b)
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 out
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 r: u8[] = []
276
- const s: u8[] = []
277
- for (let k: i32 = 0; k < toI32(rs.length); k++) {
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, a copy of the client's
547
- * transport parameters (TLS-3).
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
- const parameters: u8[] = new Array<u8>(hello.quicLength)
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 < toI32(parameters.length) && hello.quicAt + k >= 0 && hello.quicAt + k < toI32(data.length);
1088
+ k < hello.quicLength && hello.quicAt + k >= 0 && hello.quicAt + k < toI32(data.length);
1020
1089
  k++
1021
1090
  ) {
1022
- parameters[k] = data[hello.quicAt + k]
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
+ }