@ai-ecoverse/slicc-kernel 1.153.0 → 1.154.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/README.md CHANGED
@@ -454,7 +454,7 @@ The module's default export (or its `main` export) is called with a context and
454
454
  - `sudo(args)`, what the [`sudo`](#sudo) command calls: `args` are its arguments after `sudo`, and it resolves with `{ pid }`, the command the kernel started as the program's child, to `wait` for; `{ run }`, an argv to run as the caller (with `users: 'off'`); `{ list }`, the text of `sudo -l`; or `{ error, errno }`, the message to print;
455
455
  - `crontab({ action, user?, text? })`, what the [`crontab`](#cron) command calls: `action` is `'list'`, `'remove'` or `'install'` (with the table as `text`), on the caller's own table or, for an effective uid of 0, on `user`'s. It resolves with `{ text }` (the table for `list`, `''` otherwise) or `{ error }`, the message to print. A malformed call is `EINVAL`;
456
456
  - `net`: `listen({ port, host, backlog })` and `connect({ host, port, signal })` (`host` defaults to `127.0.0.1`) on the kernel's loopback, the same sockets WASIX programs use, so a JS server is reached by other processes, by the page as `<port>.kernel.localhost` (`loopbackFetch`, `dial`) and by attached clients. A listener has its `port` (as bound, so port `0` picks a free one), `accept({ signal })`, `close()` and `for await` over its connections. A connection has `local` and `remote` addresses, `readable` and `writable` web streams (closing the writable shuts down the sending side, so the peer reads the end), and `close()`. The loopback is IPv4 (`127.0.0.1` by default, or `0.0.0.0`), so an IPv6 host such as `::1` fails with `EADDRNOTAVAIL` for both `listen` and `connect`, and binding and connecting fail as they would for a WASIX program (`EADDRINUSE`, `ECONNREFUSED`, `EADDRNOTAVAIL`). `sync.net.listen` takes the same options and gives the same listener, bound and listening when it returns, so its `port` is known at once, as Node's `listen()` needs ([#1398](https://github.com/ai-ecoverse/slicc-kernel/issues/1398)); it needs the sync lane. A blocked accept, read or write waits without holding anything in the kernel, so aborting it or closing the socket leaves nothing behind. A write to a connection the peer closed fails with `EPIPE` and no `SIGPIPE`;
457
- - `net.connect({ host, port, tls: { servername, alpn, rejectUnauthorized, ca, path, cert, key, passphrase, sni, checkHostname } })` takes one of two paths. By default it's **A1, TLS through the transport** ([#914](https://github.com/ai-ecoverse/slicc-kernel/issues/914)), described first below. For a destination the uplink routes it can also be **A2, a TLS handshake in the kernel** ([#922](https://github.com/ai-ecoverse/slicc-kernel/issues/922)), described after it. `conn.tls.path` (`'transport'` or `'kernel'`) and `conn.tls.substitutes` say which one was taken.
457
+ - `net.connect({ host, port, tls: { servername, alpn, rejectUnauthorized, ca, path, cert, key, passphrase, sni, checkHostname, keylog } })` takes one of two paths. By default it's **A1, TLS through the transport** ([#914](https://github.com/ai-ecoverse/slicc-kernel/issues/914)), described first below. For a destination the uplink routes it can also be **A2, a TLS handshake in the kernel** ([#922](https://github.com/ai-ecoverse/slicc-kernel/issues/922)), described after it. `conn.tls.path` (`'transport'` or `'kernel'`) and `conn.tls.substitutes` say which one was taken.
458
458
  - **What it does:** there's no handshake in the kernel. The connection carries plain HTTP/1.1 (and WebSocket upgrades) to the realm proxy's HTTP path for the origin `https://<servername or host>[:port]`, exactly as a `CONNECT` through `127.0.0.1:3128` does after the kernel terminates its TLS. The transport (the page's `fetch` or WebSocket, or the uplink's) makes the real TLS connection and verifies it. So `https.request` over a socket, keep-alive agents, `agent-base` and `wss://` clients work with the transport's semantics. That includes sealed placeholders in headers, which reach the transport exactly as from `ctx.fetch`.
459
459
  - **The `tls` property:** the connection's `tls` is `{ path: 'transport', substitutes: true, protocol: 'transport', alpnProtocol: 'http/1.1', authorized: true, servername }`. `authorized` means the transport verified the server. There's no peer certificate chain and no TLS version of the kernel's own, so don't read it as a real handshake's result.
460
460
  - **Options:** the transport always verifies, so `rejectUnauthorized: false` is accepted and changes nothing. Outside the uplink, a custom `ca` is refused (`ENOTSUP`), and so is an `alpn` list without `http/1.1` (`EPROTONOSUPPORT`). The kernel's own loopback never goes to the transport. A `host` of `localhost` or `127.x` makes the handshake in the kernel instead (Loopback, below), so with nothing listening it's `ECONNREFUSED`. The rest of it (`*.localhost`, `0.0.0.0`, `::1`, and any loopback `servername` for another host) is `ENOTSUP` at connect, as `ctx.fetch` refuses `https:` and `wss:` there. `host.slicc.internal` goes to the transport under its policy, as for `ctx.fetch`.
@@ -502,6 +502,7 @@ The module's default export (or its `main` export) is called with a context and
502
502
  - `pfx` is `ENOTSUP`: pass `cert` and `key`.
503
503
  - A server that requires a certificate and gets none refuses with an alert. On TLS 1.2 that fails the connect (`ERR_SSL_HANDSHAKE_FAILURE`). On TLS 1.3 the client's side of the handshake is already done, so the next read or write fails with `ERR_SSL_FATAL_ALERT`, as Node's fails after `secureConnect`.
504
504
  - **A fatal alert from the server**, at the handshake or after it, is in the error: its `detail` ends in ` (alert <n>)`, and a JS program's error has `alert: n`, the alert's description number (116 certificate_required, 40 handshake_failure).
505
+ - **`keylog: true`** ([#1504](https://github.com/ai-ecoverse/slicc-kernel/issues/1504), tls-engine 4.2.0-10) gives the session's NSS key log lines as `conn.tls.keylog`, a frozen list of strings: `CLIENT_RANDOM …` on TLS 1.2, the four `…_TRAFFIC_SECRET…` lines on TLS 1.3, the lines Node's `keylog` event and `SSLKEYLOGFILE` carry. **They are the session's secrets: anyone holding them and a capture of the traffic can read it.** So a keylogged session is never sealed (`substitutes: false`; a placeholder goes out as written), and `keylog` on the transport path (A1, where the page makes the TLS) is `ENOTSUP`. Without `keylog` the engine collects nothing. The kernel wipes its copies once the lines are in the reply (the engine zeroes its buffer as it hands them out); the lines reach the program as JS strings, which can't be wiped, so they live as long as the program keeps them, as with its own `key`. A TLS server takes `keylog` too (`listen({ tls })`, `secure(conn, { server: true })`), for every connection it serves.
505
506
  - **`sni: false`** sends no server name. The leaf is still checked against `servername` exactly as with SNI.
506
507
  - **`checkHostname: false`** checks the chain (trust, validity, usage) but **not that the leaf names `servername`**. The connection reports `hostnameChecked: false`, and `hostnameMatches` says what the skipped check found.
507
508
  - **The caller must then check the server's identity itself**, as slicc-jsh does with Node's `checkServerIdentity` and `peerCertificates[0]`; pass it only when the program supplied one.
@@ -8049,7 +8049,7 @@ function satisfies(version, range2) {
8049
8049
  }
8050
8050
 
8051
8051
  // src/version.ts
8052
- var KERNEL_VERSION = true ? "1.153.0" : "0.0.0-dev";
8052
+ var KERNEL_VERSION = true ? "1.154.0" : "0.0.0-dev";
8053
8053
  var OS_RELEASE = "6.0.0-slicc";
8054
8054
  function procVersion() {
8055
8055
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -17364,6 +17364,7 @@ var TLS_SERVER_REQUEST_CERT = 1;
17364
17364
  var TLS_SERVER_REQUIRE_CERT = 2;
17365
17365
  var TLS_SERVER_SELECT = 4;
17366
17366
  var TLS_HANDSHAKE_SELECT = 2;
17367
+ var TLS_KEYLOG = 8;
17367
17368
  var SCRATCH = 64 * 1024;
17368
17369
  var EOF = -1;
17369
17370
  var TlsError = class extends Error {
@@ -17431,6 +17432,9 @@ var TlsEngine = class {
17431
17432
  if (!ptr) throw this.error(this.m._tls_last_error(), "server certificate");
17432
17433
  return new TlsLeaf(this, this.m, ptr);
17433
17434
  }
17435
+ get keylogs() {
17436
+ return typeof this.m._tls_keylog === "function";
17437
+ }
17434
17438
  get serverSelect() {
17435
17439
  return typeof this.m._tls_server_select === "function";
17436
17440
  }
@@ -17461,9 +17465,11 @@ var TlsEngine = class {
17461
17465
  this.m._free(ptr);
17462
17466
  }
17463
17467
  }
17464
- intoScratch(fn) {
17468
+ intoScratch(fn, secret = false) {
17465
17469
  const n = fn(this.scratch, SCRATCH);
17466
- return n > 0 ? this.taken(n) : n;
17470
+ const out = n > 0 ? this.taken(n) : n;
17471
+ if (secret && n > 0) this.m.HEAPU8.fill(0, this.scratch, this.scratch + n);
17472
+ return out;
17467
17473
  }
17468
17474
  get sessionControls() {
17469
17475
  return typeof this.m._tls_client_cert === "function";
@@ -17674,6 +17680,14 @@ var TlsSession = class {
17674
17680
  get version() {
17675
17681
  return this.c._tls_version(this.ptr);
17676
17682
  }
17683
+ keylog() {
17684
+ const drain2 = this.m._tls_keylog;
17685
+ const out = this.engine.intoScratch((p, max) => drain2(this.ptr, p, max), true);
17686
+ if (!(out instanceof Uint8Array)) return [];
17687
+ const text = new TextDecoder().decode(out);
17688
+ out.fill(0);
17689
+ return text.split("\n").filter((line) => line.length > 0);
17690
+ }
17677
17691
  get sni() {
17678
17692
  const out = this.engine.intoScratch(
17679
17693
  (p, max) => this.m._tls_sni(this.ptr, p, max)
@@ -19310,7 +19324,7 @@ function isIP(name2) {
19310
19324
  return ipv4Bytes(name2) !== void 0 || name2.includes(":") && ipv6Groups(name2) !== void 0;
19311
19325
  }
19312
19326
  function clientFlags(o) {
19313
- return (o.insecure ? TLS_CLIENT_INSECURE_SKIP_VERIFY : 0) | (o.sni === false ? TLS_CLIENT_NO_SNI : 0) | (o.checkHostname === false ? TLS_CLIENT_VERIFY_CHAIN_ONLY : 0);
19327
+ return (o.insecure ? TLS_CLIENT_INSECURE_SKIP_VERIFY : 0) | (o.sni === false ? TLS_CLIENT_NO_SNI : 0) | (o.checkHostname === false ? TLS_CLIENT_VERIFY_CHAIN_ONLY : 0) | (o.keylog ? TLS_KEYLOG : 0);
19314
19328
  }
19315
19329
  function openSession(o) {
19316
19330
  return o.engine.clientSession(o.store, o.servername, o.alpn, clientFlags(o));
@@ -19425,7 +19439,8 @@ async function infoOf(o, session) {
19425
19439
  peerCertificates: chain2,
19426
19440
  ...controlsOf(o, session, protocol, chain2, shape),
19427
19441
  verified: verdictOf(o, session),
19428
- handshakeAt: Date.now()
19442
+ handshakeAt: Date.now(),
19443
+ ...o.keylog ? { keylog: session.keylog() } : {}
19429
19444
  };
19430
19445
  }
19431
19446
  var FATAL_ALERT = -30592;
@@ -19545,7 +19560,7 @@ async function serverHandshake(conn, session, inbox, hooks) {
19545
19560
  }
19546
19561
  }
19547
19562
  }
19548
- async function tlsServer(conn, session, clientCerts, select) {
19563
+ async function tlsServer(conn, session, clientCerts, select, keylog = false) {
19549
19564
  let first;
19550
19565
  const watched = {
19551
19566
  ...conn,
@@ -19568,7 +19583,8 @@ async function tlsServer(conn, session, clientCerts, select) {
19568
19583
  servername: session.sni,
19569
19584
  authorized: clientCerts && chain2.length > 0 && code === void 0,
19570
19585
  ...clientCerts && chain2.length > 0 && code ? { authorizationError: code } : {},
19571
- peerCertificates: chain2
19586
+ peerCertificates: chain2,
19587
+ ...keylog ? { keylog: session.keylog() } : {}
19572
19588
  };
19573
19589
  return { conn: plaintext(conn, session, inbox), info };
19574
19590
  } catch (err) {
@@ -22461,7 +22477,8 @@ function sessionOf(info, servername) {
22461
22477
  } : {},
22462
22478
  servername,
22463
22479
  peerCertificates: info.peerCertificates.map(encode),
22464
- ...controls(info)
22480
+ ...controls(info),
22481
+ ...info.keylog ? { keylog: info.keylog } : {}
22465
22482
  };
22466
22483
  }
22467
22484
  function controls(info) {
@@ -22603,11 +22620,14 @@ async function prepareWith(support, req, servername, cert) {
22603
22620
  insecure: req.insecure === true,
22604
22621
  ...req.sni === false ? { sni: false } : {},
22605
22622
  ...req.checkHostname === false ? { checkHostname: false } : {},
22623
+ ...req.keylog === true ? { keylog: true } : {},
22606
22624
  clientCertificate: cert !== void 0,
22607
22625
  bundled: own === void 0
22608
22626
  };
22609
22627
  let session;
22610
22628
  try {
22629
+ if (req.keylog === true && !engine.keylogs)
22630
+ throw new KernelError("ENOTSUP", "the installed tls engine has no key log");
22611
22631
  const controls2 = req.sni === false || req.checkHostname === false || cert !== void 0;
22612
22632
  if (controls2 && !engine.sessionControls) {
22613
22633
  throw new KernelError("ENOTSUP", "the installed tls engine has no client certificates");
@@ -22655,7 +22675,7 @@ function secure(prepared, seal) {
22655
22675
  try {
22656
22676
  const made2 = await tlsClient(raw2, prepared.options);
22657
22677
  const engine = prepared.options.engine;
22658
- const session = sealing({ ...seal, engine }, raw2, made2.info, prepared.options.servername);
22678
+ const session = prepared.options.keylog ? void 0 : sealing({ ...seal, engine }, raw2, made2.info, prepared.options.servername);
22659
22679
  if (!session) return made2;
22660
22680
  made2.info.substitutes = substitutes(session);
22661
22681
  if (made2.info.substitutes && session.verified)
@@ -22768,25 +22788,31 @@ function certFlags(req) {
22768
22788
  if (!req.requestCert) return 0;
22769
22789
  return req.rejectUnauthorized === false ? TLS_SERVER_REQUEST_CERT : TLS_SERVER_REQUIRE_CERT;
22770
22790
  }
22771
- function flagsOf(req) {
22772
- if (!req.select) return certFlags(req);
22791
+ var CERT_FLAGS = TLS_SERVER_REQUEST_CERT | TLS_SERVER_REQUIRE_CERT;
22792
+ function selectFlag(req) {
22793
+ if (!req.select) return 0;
22773
22794
  if (req.alpn && req.alpn.length > 0)
22774
22795
  throw new KernelError("EINVAL", "ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS: alpn with select");
22775
- return certFlags(req) | TLS_SERVER_SELECT;
22796
+ return TLS_SERVER_SELECT;
22797
+ }
22798
+ function flagsOf(req) {
22799
+ return certFlags(req) | selectFlag(req) | (req.keylog ? TLS_KEYLOG : 0);
22776
22800
  }
22777
- async function engineOf(proc, select) {
22801
+ async function engineOf(proc, { select, keylog }) {
22778
22802
  const engine = await supportOf(proc.net).engine();
22779
22803
  if (!engine.serverSessions)
22780
22804
  throw new KernelError("ENOTSUP", "the installed tls engine has no server sessions");
22805
+ if (keylog && !engine.keylogs)
22806
+ throw new KernelError("ENOTSUP", "the installed tls engine has no key log");
22781
22807
  if (select && !engine.serverSelect)
22782
22808
  throw new KernelError("ENOTSUP", "the installed tls engine has no server selection");
22783
22809
  return engine;
22784
22810
  }
22785
- async function importedLeaf(proc, own, select) {
22811
+ async function importedLeaf(proc, own, needs = {}) {
22786
22812
  const cert = certBytes(own);
22787
22813
  let engine;
22788
22814
  try {
22789
- engine = await engineOf(proc, select);
22815
+ engine = await engineOf(proc, needs);
22790
22816
  } catch (err) {
22791
22817
  wipe(cert);
22792
22818
  throw err;
@@ -22816,12 +22842,12 @@ async function serverOf(proc, req) {
22816
22842
  delete req.cert;
22817
22843
  if (!own && !req.select)
22818
22844
  throw new KernelError("EINVAL", "ERR_INVALID_ARG_VALUE: a server needs cert and key");
22819
- const { engine, leaf } = own ? await importedLeaf(proc, own, req.select) : { engine: await engineOf(proc, true), leaf: void 0 };
22845
+ const { engine, leaf } = own ? await importedLeaf(proc, own, req) : { engine: await engineOf(proc, { ...req, select: true }), leaf: void 0 };
22820
22846
  let mine;
22821
22847
  try {
22822
22848
  mine = req.ca && req.ca.length > 0 ? storeOf(engine, req.ca) : void 0;
22823
22849
  const flags2 = flagsOf(req);
22824
- const store = mine ?? (flags2 & ~TLS_SERVER_SELECT ? await defaultStore(engine, supportOf(proc.net).roots) : void 0);
22850
+ const store = mine ?? (flags2 & CERT_FLAGS ? await defaultStore(engine, supportOf(proc.net).roots) : void 0);
22825
22851
  return {
22826
22852
  engine,
22827
22853
  leaf,
@@ -22855,7 +22881,8 @@ function servedOf(info) {
22855
22881
  authorized: info.authorized,
22856
22882
  ...info.authorizationError ? { authorizationError: info.authorizationError } : {},
22857
22883
  peerCertificates: info.peerCertificates.map(encode),
22858
- cipher: info.cipher
22884
+ cipher: info.cipher,
22885
+ ...info.keylog ? { keylog: info.keylog } : {}
22859
22886
  };
22860
22887
  }
22861
22888
  var selections = /* @__PURE__ */ new WeakMap();
@@ -22878,13 +22905,14 @@ async function serveSecure(proc, socket, req) {
22878
22905
  const { engine, leaf, alpn, store, flags: flags2 } = server;
22879
22906
  const limit = handshakeSignal(proc, server.timeoutMs);
22880
22907
  const { pausedAt, select } = pausing(limit.signal);
22881
- const certs = (flags2 & ~TLS_SERVER_SELECT) !== 0;
22908
+ const certs = (flags2 & CERT_FLAGS) !== 0;
22882
22909
  const run2 = socket.secureLink(
22883
22910
  (raw2) => tlsServer(
22884
22911
  raw2,
22885
22912
  engine.serverSession(leaf, alpn, store, flags2),
22886
22913
  certs,
22887
- flags2 & TLS_SERVER_SELECT ? select : void 0
22914
+ flags2 & TLS_SERVER_SELECT ? select : void 0,
22915
+ (flags2 & TLS_KEYLOG) !== 0
22888
22916
  ),
22889
22917
  limit.signal,
22890
22918
  (reason) => !shared && reason === timedOut
@@ -23354,7 +23382,7 @@ var NOT_HTTP_PORTS = /* @__PURE__ */ new Set([
23354
23382
  ]);
23355
23383
  var HOST_NAME = /^[A-Za-z0-9.-]+$|^\[?[0-9A-Fa-f:.]+\]?$/;
23356
23384
  function sessionControls(req) {
23357
- return req.sni === false || req.checkHostname === false || req.clientCert !== void 0;
23385
+ return req.sni === false || req.checkHostname === false || req.clientCert !== void 0 || req.keylog === true;
23358
23386
  }
23359
23387
  function wantsKernel(req) {
23360
23388
  if (req.path === "kernel") return true;