@ai-ecoverse/slicc-kernel 1.148.0 → 1.149.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
  - `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.
455
455
  - **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`.
456
456
  - **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.
457
- - **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 (`localhost`, `*.localhost`, `127/8`, `0.0.0.0`, `::1`, as `servername` or as `host`) is `ENOTSUP` at connect, as `ctx.fetch` refuses `https:` and `wss:` there. Connect in plain text to a kernel server. `host.slicc.internal` goes to the transport under its policy, as for `ctx.fetch`.
457
+ - **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`.
458
458
  - **Non-HTTP protocols aren't carried:**
459
459
  - the well-known TLS ports of other protocols (465 SMTPS, 993 IMAPS, 995 POP3S, 636 LDAPS, 853, 8883 MQTT, 6697 IRC, 5671 AMQP and the like) are refused at `connect` with `EPROTONOSUPPORT`;
460
460
  - non-HTTP first bytes on any other port get a `400` and a close;
@@ -504,6 +504,15 @@ The module's default export (or its `main` export) is called with a context and
504
504
  - **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.
505
505
  - With `rejectUnauthorized: false` it's `EINVAL`.
506
506
  - **STARTTLS:** `net.connect({ socket: conn, host, tls })` upgrades an open plain connection to an uplink destination in place, as Node's `tls.connect({ socket })` does. It resolves with a connection whose streams now carry the decrypted bytes and whose `tls` is the session. Don't hold a pending read on the plain connection while it upgrades. Pass `host` (or `tls.servername`): the plain connection only knows the address it reached, so without a name the certificate is checked against that IP, and a certificate for the host name fails.
507
+ - **Loopback:** `net.connect({ host, port, tls })` to `localhost` or a `127.x` address makes the handshake in the kernel too, over the loopback connection, with the same options as A2 (`ca`, `cert`/`key`, `sni`, `checkHostname`). Nothing on loopback is ever substituted (`substitutes: false`).
508
+ - **A TLS server** ([#1401](https://github.com/ai-ecoverse/slicc-kernel/issues/1401)), with the program's own certificate and key: `net.listen({ port, host, tls: { cert, key, passphrase, alpn, ca, requestCert, rejectUnauthorized } })` (and `sync.net.listen`) gives a listener whose connections are already secured, and `net.secure(conn, { server: true, cert, key, … })` secures one accepted connection. `net.secure(conn, tls)` without `server: true` is the client upgrade, as `connect({ socket })`.
509
+ - `cert` and `key` take the same forms as a client certificate, and are refused the same way, at `listen` (or `secure`) before anything is accepted: a key for another certificate is `ERR_OSSL_X509_KEY_VALUES_MISMATCH`, a wrong passphrase `ERR_OSSL_BAD_DECRYPT`.
510
+ - `alpn` is the server's list in order of preference; the first one the client also offers wins, and a client that offers ALPN but none of these fails the handshake.
511
+ - `requestCert` needs `ca` (the roots a client certificate must reach; else `EINVAL`). With it the handshake fails without a client certificate that verifies, unless `rejectUnauthorized: false`, which only asks for one and reports the verdict.
512
+ - A connection's `tls` is `{ path: 'kernel', protocol, alpnProtocol, servername (the SNI the client sent, or ''), authorized (whether a client certificate verified), authorizationError?, peerCertificates (the client's chain) }`.
513
+ - On a `listen({ tls })` listener, the kernel handshakes accepted connections concurrently, from the first `accept` on, and `accept` (and `for await`) yields each as its handshake finishes, so a client that connects and stays silent doesn't hold up the next. It holds at most `backlog` (up to 64) connections in a handshake or handshaken but not yet taken; beyond that it stops accepting, leaving the rest in the kernel's backlog, until one finishes or is taken. A connection whose handshake fails is closed and never yielded. `secure()` rejects instead.
514
+ - `handshakeTimeout` (milliseconds, 120000 by default, as Node's) ends a handshake that hasn't finished: the connection is closed with `ERR_TLS_HANDSHAKE_TIMEOUT`. A handshake, server or loopback client, waits without holding the program's other calls, and an aborted `accept({ signal })` or `connect({ signal })` ends it. A signal delivered to the program doesn't interrupt a handshake, as with `SA_RESTART`.
515
+ - **The key:** the kernel hands it to the engine once per listener (once per `secure()` call) and wipes its own copies; the engine keeps its copy until the listener is closed, and not in errors, logs, `ps`, `/proc` or the page. A forked child that inherits the listener shares that one copy, and it's freed when the last process closes it. **The program's own `key` string or `Buffer` stays in its realm**: JavaScript strings can't be wiped, so the kernel can't erase what the program holds.
507
516
  - **Sealed values in an A2 session** ([#1090](https://github.com/ai-ecoverse/slicc-kernel/issues/1090)) are substituted only at a credential position, only in a session the kernel verified itself (by its bundled roots, or by a vault anchor), and only as the vault allows. `substitutes` means the session **may** substitute: a placeholder at a recognized position will be offered to the vault, and whether a value comes back is the vault's and the anchor check's decision. It's true when all of these hold:
508
517
  - `verified` is true, or the engine can verify a chain on its own (wasm-tls-engine 4.2.0-5's `tls_chain_verify`) and the server name isn't an IP literal, so a vault anchor may verify the session (below);
509
518
  - ALPN didn't agree `h2`;
@@ -1071,6 +1071,9 @@ var RemoteWatch = class {
1071
1071
  await new Promise((resolve2) => setTimeout(resolve2, ANNOUNCE_POLL_MS));
1072
1072
  }
1073
1073
  }
1074
+ legacy() {
1075
+ return [...this.peers].filter((peer) => !this.ackers.has(peer));
1076
+ }
1074
1077
  crowded() {
1075
1078
  return !this.settled || this.peers.size > 0;
1076
1079
  }
@@ -1078,7 +1081,7 @@ var RemoteWatch = class {
1078
1081
  return `${this.name}:${tab}`;
1079
1082
  }
1080
1083
  receive(data) {
1081
- const sender = data.hello ?? data.here;
1084
+ const sender = data.hello ?? data.here ?? (data.paths !== void 0 ? data.tab : void 0);
1082
1085
  if (sender !== void 0 && data.renames) this.ackers.add(sender);
1083
1086
  if (data.hello !== void 0) this.greeted(data.hello);
1084
1087
  if (data.renaming !== void 0 && data.tab !== void 0) this.heard(data);
@@ -1185,7 +1188,7 @@ var RemoteWatch = class {
1185
1188
  announce(paths) {
1186
1189
  this.mine = [...paths];
1187
1190
  const seq2 = ++this.seq;
1188
- this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
1191
+ this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2, renames: true });
1189
1192
  return this.acks(seq2, this.peers);
1190
1193
  }
1191
1194
  renamingTree(rename, cap = RENAME_ACK_MAX_MS) {
@@ -2812,6 +2815,17 @@ var OpfsFs = class {
2812
2815
  }
2813
2816
  async admitted(locks, lock, from, to, place, hooks) {
2814
2817
  const remote = this.remote;
2818
+ const older = remote?.legacy() ?? [];
2819
+ if (older.length > 0) {
2820
+ this.ackBusy++;
2821
+ if (!this.warnedLegacy) {
2822
+ this.warnedLegacy = true;
2823
+ console.warn(
2824
+ `slicc-kernel: renaming ${from} to ${to} gave up (EBUSY): a tab running an older slicc-kernel shares this root and can't be waited for; reload it to rename directories (#1309)`
2825
+ );
2826
+ }
2827
+ throw fsError("EBUSY", from);
2828
+ }
2815
2829
  if (remote && !this.shared() && !await remote.joinedAll(RENAME_ACK_MAX_MS)) {
2816
2830
  this.ackBusy++;
2817
2831
  console.warn(
@@ -2862,6 +2876,7 @@ var OpfsFs = class {
2862
2876
  await run2;
2863
2877
  }
2864
2878
  renameAcks = 0;
2879
+ warnedLegacy = false;
2865
2880
  ackBusy = 0;
2866
2881
  ackMs = [];
2867
2882
  ownLocks = /* @__PURE__ */ new Set();
@@ -5939,6 +5954,9 @@ function link(a, b) {
5939
5954
  a.attach({ rx: ba, tx: ab });
5940
5955
  b.attach({ rx: ab, tx: ba });
5941
5956
  }
5957
+ var SERVE_CHUNK = 64 * 1024;
5958
+ var NEVER_ABORTED = new AbortController().signal;
5959
+ var EMPTY = new Uint8Array(0);
5942
5960
  var KernelSocket = class _KernelSocket {
5943
5961
  net;
5944
5962
  domain;
@@ -6136,6 +6154,54 @@ var KernelSocket = class _KernelSocket {
6136
6154
  if (this.state !== "open") throw new KernelError("EISCONN");
6137
6155
  return this.dial(addr, route2, secure2);
6138
6156
  }
6157
+ serving;
6158
+ served = false;
6159
+ get loopbackLinked() {
6160
+ return this.state === "connected" && !this.switch;
6161
+ }
6162
+ async secureLink(make, signal = NEVER_ABORTED) {
6163
+ const raw2 = this.connection();
6164
+ if (this.switch || this.served || this.writeShut || this.readShut)
6165
+ throw new KernelError("EINVAL");
6166
+ signal.throwIfAborted();
6167
+ this.served = true;
6168
+ let handshaking = true;
6169
+ const view2 = {
6170
+ localAddr: "",
6171
+ remoteAddr: "",
6172
+ read: async () => {
6173
+ const bytes = await raw2.rx.read(SERVE_CHUNK, handshaking ? signal : void 0).catch(() => EMPTY);
6174
+ return bytes.length > 0 ? bytes : null;
6175
+ },
6176
+ write: (bytes) => raw2.tx.write(bytes),
6177
+ closeWrite: () => raw2.tx.closeWrite(),
6178
+ close: () => {
6179
+ raw2.rx.closeRead();
6180
+ raw2.tx.closeWrite();
6181
+ }
6182
+ };
6183
+ let made2;
6184
+ const abandon = () => view2.close();
6185
+ signal.addEventListener("abort", abandon, { once: true });
6186
+ try {
6187
+ made2 = await make(view2);
6188
+ } catch (err) {
6189
+ view2.close();
6190
+ this.readShut = true;
6191
+ this.writeShut = true;
6192
+ this.changes.wake();
6193
+ throw signal.aborted ? signal.reason : err;
6194
+ } finally {
6195
+ handshaking = false;
6196
+ signal.removeEventListener("abort", abandon);
6197
+ }
6198
+ this.link = uplinkLink(made2.conn, (err) => {
6199
+ this.reset = err instanceof KernelError && err.code === "EPROTO" ? err : new KernelError("ECONNRESET");
6200
+ this.changes.wake();
6201
+ });
6202
+ this.changes.wake();
6203
+ return made2.info;
6204
+ }
6139
6205
  upgradeSecure(make) {
6140
6206
  if (this.state !== "connected") throw new KernelError("ENOTCONN");
6141
6207
  if (!this.switch) throw new KernelError("EOPNOTSUPP");
@@ -6267,6 +6333,8 @@ var KernelSocket = class _KernelSocket {
6267
6333
  }
6268
6334
  if (this.boundAt) this.net.release(this.boundAt, this, this.named);
6269
6335
  this.boundAt = void 0;
6336
+ this.serving?.release();
6337
+ this.serving = void 0;
6270
6338
  this.changes.wake();
6271
6339
  }
6272
6340
  static pair(net, domain, cred) {
@@ -7840,7 +7908,7 @@ function satisfies(version, range2) {
7840
7908
  }
7841
7909
 
7842
7910
  // src/version.ts
7843
- var KERNEL_VERSION = true ? "1.148.0" : "0.0.0-dev";
7911
+ var KERNEL_VERSION = true ? "1.149.0" : "0.0.0-dev";
7844
7912
  var OS_RELEASE = "6.0.0-slicc";
7845
7913
  function procVersion() {
7846
7914
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -10339,7 +10407,7 @@ async function followLinks(fs, path) {
10339
10407
  return current;
10340
10408
  }
10341
10409
  var NOBODY = { ruid: -1, euid: -1, suid: -1, rgid: -1, egid: -1, sgid: -1, groups: [] };
10342
- var EMPTY = new Uint8Array(0);
10410
+ var EMPTY2 = new Uint8Array(0);
10343
10411
  function quiet(promise) {
10344
10412
  return promise.catch(() => void 0);
10345
10413
  }
@@ -10422,13 +10490,13 @@ function withCommandDirs(fs, names2, userDir = async () => void 0, owners = fals
10422
10490
  return target?.path;
10423
10491
  }
10424
10492
  const absent2 = (err) => {
10425
- if (missing3(err)) return EMPTY;
10493
+ if (missing3(err)) return EMPTY2;
10426
10494
  throw err;
10427
10495
  };
10428
10496
  async function content(path, err) {
10429
10497
  if (!missing3(err) || !(await linked2(path))?.isFile) throw err;
10430
10498
  const target = await redirected2(path);
10431
- return target === void 0 ? EMPTY : fs.readFileBuffer(target).catch(absent2);
10499
+ return target === void 0 ? EMPTY2 : fs.readFileBuffer(target).catch(absent2);
10432
10500
  }
10433
10501
  const ranged2 = rangedOps(fs);
10434
10502
  async function stat(path, real) {
@@ -16702,6 +16770,8 @@ function realmCa(owner, store) {
16702
16770
  var TLS_CLIENT_INSECURE_SKIP_VERIFY = 1;
16703
16771
  var TLS_CLIENT_NO_SNI = 2;
16704
16772
  var TLS_CLIENT_VERIFY_CHAIN_ONLY = 4;
16773
+ var TLS_SERVER_REQUEST_CERT = 1;
16774
+ var TLS_SERVER_REQUIRE_CERT = 2;
16705
16775
  var SCRATCH = 64 * 1024;
16706
16776
  var EOF = -1;
16707
16777
  var TlsError = class extends Error {
@@ -16750,6 +16820,35 @@ var TlsEngine = class {
16750
16820
  this.m._free(name2);
16751
16821
  }
16752
16822
  }
16823
+ get serverSessions() {
16824
+ return typeof this.m._tls_server_new === "function";
16825
+ }
16826
+ importLeaf(chain2, key, pass2) {
16827
+ const leafImport = this.m._tls_leaf_import;
16828
+ const keyLen = key[0] === 45 ? key.length + 1 : key.length;
16829
+ const ptr = this.withBytes(
16830
+ chain2,
16831
+ (c) => this.withSecret(
16832
+ key,
16833
+ (k) => this.withSecret(
16834
+ pass2,
16835
+ (p) => leafImport(c, chain2.length, k, keyLen, pass2.length > 0 ? p : 0, pass2.length)
16836
+ )
16837
+ )
16838
+ );
16839
+ if (!ptr) throw this.error(this.m._tls_last_error(), "server certificate");
16840
+ return new TlsLeaf(this, this.m, ptr);
16841
+ }
16842
+ serverSession(leaf, alpn, clientCa, flags2) {
16843
+ const open2 = this.m._tls_server_new;
16844
+ const wire = alpnWire(alpn);
16845
+ const ptr = this.withBytes(
16846
+ wire,
16847
+ (p) => open2(leaf.ptr, wire.length > 0 ? p : 0, wire.length, clientCa?.ptr ?? 0, flags2)
16848
+ );
16849
+ if (!ptr) throw this.error(this.m._tls_last_error(), "tls server");
16850
+ return new TlsSession(this, this.m, ptr);
16851
+ }
16753
16852
  withBytes(bytes, fn) {
16754
16853
  const ptr = this.put(bytes);
16755
16854
  try {
@@ -16954,6 +17053,12 @@ var TlsSession = class {
16954
17053
  get version() {
16955
17054
  return this.c._tls_version(this.ptr);
16956
17055
  }
17056
+ get sni() {
17057
+ const out = this.engine.intoScratch(
17058
+ (p, max) => this.m._tls_sni(this.ptr, p, max)
17059
+ );
17060
+ return out instanceof Uint8Array ? new TextDecoder().decode(out) : "";
17061
+ }
16957
17062
  get alpn() {
16958
17063
  const out = this.engine.intoScratch((p, max) => this.c._tls_alpn(this.ptr, p, max));
16959
17064
  return out instanceof Uint8Array ? new TextDecoder().decode(out) : void 0;
@@ -17456,7 +17561,7 @@ var Upload = class {
17456
17561
  }
17457
17562
  }
17458
17563
  };
17459
- var EMPTY2 = new Uint8Array(0);
17564
+ var EMPTY3 = new Uint8Array(0);
17460
17565
  function message2(e) {
17461
17566
  return e instanceof Error ? e.message : String(e);
17462
17567
  }
@@ -17531,8 +17636,8 @@ var HttpHandles = class {
17531
17636
  const entry = {
17532
17637
  abort,
17533
17638
  head: Promise.resolve({ ok: true, kind: "void" }),
17534
- body: once(EMPTY2),
17535
- left: EMPTY2,
17639
+ body: once(EMPTY3),
17640
+ left: EMPTY3,
17536
17641
  done: false,
17537
17642
  reading: Promise.resolve(),
17538
17643
  cancel: async () => void 0,
@@ -18770,6 +18875,46 @@ async function tlsClient(conn, o, taken = new Uint8Array(0)) {
18770
18875
  throw err;
18771
18876
  }
18772
18877
  }
18878
+ async function serverHandshake(conn, session, inbox) {
18879
+ for (; ; ) {
18880
+ let done5;
18881
+ try {
18882
+ done5 = session.handshake();
18883
+ } catch (err) {
18884
+ await send(conn, session).catch(() => void 0);
18885
+ const text = err instanceof TlsError ? err.message : String(err);
18886
+ throw new KernelError("EPROTO", `ERR_SSL_HANDSHAKE_FAILURE: ${text}${alertOf(session)}`);
18887
+ }
18888
+ await send(conn, session);
18889
+ if (done5) return;
18890
+ if (!await inbox.more()) {
18891
+ throw new KernelError(
18892
+ "ECONNRESET",
18893
+ "the client closed the connection during the TLS handshake"
18894
+ );
18895
+ }
18896
+ }
18897
+ }
18898
+ async function tlsServer(conn, session, clientCerts) {
18899
+ const inbox = new Ciphertext(conn, session);
18900
+ try {
18901
+ await serverHandshake(conn, session, inbox);
18902
+ const chain2 = session.peerChain();
18903
+ const code = clientCerts ? verifyCode(session.verifyResult) : void 0;
18904
+ const info = {
18905
+ protocol: session.version === 772 ? "TLSv1.3" : "TLSv1.2",
18906
+ alpnProtocol: session.alpn ?? false,
18907
+ servername: session.sni,
18908
+ authorized: clientCerts && chain2.length > 0 && code === void 0,
18909
+ ...clientCerts && chain2.length > 0 && code ? { authorizationError: code } : {},
18910
+ peerCertificates: chain2
18911
+ };
18912
+ return { conn: plaintext(conn, session, inbox), info };
18913
+ } catch (err) {
18914
+ session.free();
18915
+ throw err;
18916
+ }
18917
+ }
18773
18918
 
18774
18919
  // src/kernel/net/tls-chain.ts
18775
18920
  var CN_MISMATCH = 4;
@@ -19222,7 +19367,7 @@ var RANK = {
19222
19367
  };
19223
19368
  var CR2 = 13;
19224
19369
  var LF2 = 10;
19225
- var EMPTY3 = new Uint8Array(0);
19370
+ var EMPTY4 = new Uint8Array(0);
19226
19371
  function register(automaton, sources, ids, bytes, source) {
19227
19372
  const key = Array.from(bytes).join(",");
19228
19373
  let id2 = ids.get(key);
@@ -19402,12 +19547,12 @@ function compile(secrets, options = {}) {
19402
19547
  return { text: decoder4.decode(bytes), hits };
19403
19548
  },
19404
19549
  stream() {
19405
- let pending = EMPTY3;
19550
+ let pending = EMPTY4;
19406
19551
  const hits = /* @__PURE__ */ new Map();
19407
19552
  const run2 = (final) => {
19408
19553
  if (c.secrets.length === 0 || pending.length === 0) {
19409
19554
  const out = pending;
19410
- pending = EMPTY3;
19555
+ pending = EMPTY4;
19411
19556
  return out;
19412
19557
  }
19413
19558
  const result = pass(c, pending, final);
@@ -19421,7 +19566,7 @@ function compile(secrets, options = {}) {
19421
19566
  return run2(false);
19422
19567
  },
19423
19568
  idle() {
19424
- return pending.length < FRAGMENT ? run2(true) : EMPTY3;
19569
+ return pending.length < FRAGMENT ? run2(true) : EMPTY4;
19425
19570
  },
19426
19571
  end() {
19427
19572
  return run2(true);
@@ -21583,6 +21728,7 @@ function sealedConn(conn, s) {
21583
21728
  }
21584
21729
 
21585
21730
  // src/kernel/net/tls-uplink.ts
21731
+ var handshakeAbort = (proc) => proc.cancelled ?? proc.blocking();
21586
21732
  var isIP2 = (text) => ipv4Bytes(text) !== void 0 || text.includes(":") && ipv6Groups(text) !== void 0;
21587
21733
  var LABEL2 = /^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$/;
21588
21734
  function serverName(name2) {
@@ -21703,6 +21849,9 @@ function takeCert(req) {
21703
21849
  const cert = req.clientCert;
21704
21850
  if (!cert) return void 0;
21705
21851
  delete req.clientCert;
21852
+ return certBytes(cert);
21853
+ }
21854
+ function certBytes(cert) {
21706
21855
  if (!Array.isArray(cert.chain) || cert.chain.length === 0 || typeof cert.key !== "string")
21707
21856
  throw new KernelError("EINVAL", "ERR_INVALID_ARG_VALUE: cert and key go together");
21708
21857
  let ders;
@@ -21878,8 +22027,32 @@ async function connectSecure(proc, socket, host, port, req) {
21878
22027
  }
21879
22028
  return sessionOf(info, servername);
21880
22029
  }
22030
+ async function loopbackSecure(proc, socket, servername, req) {
22031
+ const prepared = await prepare2(supportOf(proc.net), req, servername);
22032
+ let ran = false;
22033
+ try {
22034
+ const info = await socket.secureLink(async (raw2) => {
22035
+ ran = true;
22036
+ try {
22037
+ return await tlsClient(raw2, prepared.options);
22038
+ } finally {
22039
+ prepared.own?.release();
22040
+ }
22041
+ }, handshakeAbort(proc));
22042
+ return sessionOf({ ...info, substitutes: false }, servername);
22043
+ } finally {
22044
+ if (!ran) {
22045
+ prepared.options.session.free();
22046
+ prepared.own?.release();
22047
+ }
22048
+ }
22049
+ }
21881
22050
  async function upgradeSecure(proc, socket, req) {
21882
22051
  const peer = socket.peer;
22052
+ if (socket.loopbackLinked) {
22053
+ const host2 = req.servername || (peer?.family === "inet" ? peer.host : "localhost");
22054
+ return loopbackSecure(proc, socket, serverName(host2), req);
22055
+ }
21883
22056
  const host = req.servername || (peer?.family === "inet" ? peer.host : "");
21884
22057
  const servername = serverName(host);
21885
22058
  const port = peer?.family === "inet" ? peer.port : 0;
@@ -21894,6 +22067,120 @@ async function upgradeSecure(proc, socket, req) {
21894
22067
  }
21895
22068
  }
21896
22069
 
22070
+ // src/kernel/net/tls-server.ts
22071
+ var HANDSHAKE_TIMEOUT_MS = 12e4;
22072
+ function leafOf(engine, cert) {
22073
+ try {
22074
+ return engine.importLeaf(cert.chain, cert.key, cert.pass);
22075
+ } catch (err) {
22076
+ throw keyRefused(err, cert.pass.length > 0 && encryptedKey(cert.key));
22077
+ } finally {
22078
+ wipe(cert);
22079
+ }
22080
+ }
22081
+ function storeOf(engine, ca) {
22082
+ const store = engine.store();
22083
+ try {
22084
+ for (const der2 of ca) store.add(base64Der(der2));
22085
+ return store;
22086
+ } catch (err) {
22087
+ store.release();
22088
+ throw new KernelError("EINVAL", `ERR_INVALID_ARG_VALUE: ca: ${err.message}`);
22089
+ }
22090
+ }
22091
+ function flagsOf(req, store) {
22092
+ if (!req.requestCert) return 0;
22093
+ if (!store) throw new KernelError("EINVAL", "ERR_INVALID_ARG_VALUE: requestCert needs a ca");
22094
+ return req.rejectUnauthorized === false ? TLS_SERVER_REQUEST_CERT : TLS_SERVER_REQUIRE_CERT;
22095
+ }
22096
+ function timeoutOf(req) {
22097
+ const ms = req.handshakeTimeout ?? HANDSHAKE_TIMEOUT_MS;
22098
+ if (!Number.isInteger(ms) || ms <= 0)
22099
+ throw new KernelError(
22100
+ "EINVAL",
22101
+ "ERR_OUT_OF_RANGE: handshakeTimeout must be a positive integer"
22102
+ );
22103
+ return ms;
22104
+ }
22105
+ function handshakeSignal(proc, ms) {
22106
+ const timer = new AbortController();
22107
+ const id2 = setTimeout(
22108
+ () => timer.abort(new KernelError("EPROTO", "ERR_TLS_HANDSHAKE_TIMEOUT: TLS handshake timeout")),
22109
+ ms
22110
+ );
22111
+ return {
22112
+ signal: AbortSignal.any([handshakeAbort(proc), timer.signal]),
22113
+ done: () => clearTimeout(id2)
22114
+ };
22115
+ }
22116
+ async function serverOf(proc, req) {
22117
+ const own = req.cert;
22118
+ delete req.cert;
22119
+ if (!own) throw new KernelError("EINVAL", "ERR_INVALID_ARG_VALUE: a server needs cert and key");
22120
+ const cert = certBytes(own);
22121
+ let engine;
22122
+ try {
22123
+ engine = await supportOf(proc.net).engine();
22124
+ if (!engine.serverSessions)
22125
+ throw new KernelError("ENOTSUP", "the installed tls engine has no server sessions");
22126
+ } catch (err) {
22127
+ wipe(cert);
22128
+ throw err;
22129
+ }
22130
+ const leaf = leafOf(engine, cert);
22131
+ let store;
22132
+ try {
22133
+ store = req.ca && req.ca.length > 0 ? storeOf(engine, req.ca) : void 0;
22134
+ const flags2 = flagsOf(req, store);
22135
+ return {
22136
+ engine,
22137
+ leaf,
22138
+ store,
22139
+ alpn: req.alpn ?? [],
22140
+ flags: flags2,
22141
+ timeoutMs: timeoutOf(req),
22142
+ release() {
22143
+ leaf.release();
22144
+ store?.release();
22145
+ }
22146
+ };
22147
+ } catch (err) {
22148
+ leaf.release();
22149
+ store?.release();
22150
+ throw err;
22151
+ }
22152
+ }
22153
+ async function listenSecure(proc, socket, req) {
22154
+ if (!socket.listening) throw new KernelError("EINVAL", "not a listening socket");
22155
+ const server = await serverOf(proc, req);
22156
+ socket.serving?.release();
22157
+ socket.serving = server;
22158
+ }
22159
+ async function serveSecure(proc, socket, req) {
22160
+ const shared = req.listener?.serving;
22161
+ if (req.listener && !shared) throw new KernelError("EINVAL", "the listener has no tls");
22162
+ const server = shared ?? await serverOf(proc, req);
22163
+ try {
22164
+ const { engine, leaf, alpn, store, flags: flags2 } = server;
22165
+ const limit = handshakeSignal(proc, server.timeoutMs);
22166
+ const info = await socket.secureLink(
22167
+ (raw2) => tlsServer(raw2, engine.serverSession(leaf, alpn, store, flags2), flags2 !== 0),
22168
+ limit.signal
22169
+ ).finally(limit.done);
22170
+ return {
22171
+ path: "kernel",
22172
+ protocol: info.protocol,
22173
+ alpnProtocol: info.alpnProtocol,
22174
+ servername: info.servername,
22175
+ authorized: info.authorized,
22176
+ ...info.authorizationError ? { authorizationError: info.authorizationError } : {},
22177
+ peerCertificates: info.peerCertificates.map(encode)
22178
+ };
22179
+ } finally {
22180
+ if (!shared) server.release();
22181
+ }
22182
+ }
22183
+
21897
22184
  // src/kernel/unix-dgram.ts
21898
22185
  var SOL_SOCKET3 = 1;
21899
22186
  var SO_TYPE3 = 3;
@@ -22056,7 +22343,9 @@ var SOCKET_OPS = [
22056
22343
  "sock-sendto",
22057
22344
  "sock-recvfrom",
22058
22345
  "sock-tls",
22059
- "sock-tls-upgrade"
22346
+ "sock-tls-upgrade",
22347
+ "sock-tls-serve",
22348
+ "sock-tls-listen"
22060
22349
  ];
22061
22350
  function socketAt(fds, fd) {
22062
22351
  const file = anySocketAt(fds, fd);
@@ -22132,6 +22421,14 @@ async function socketSyscall(req, proc) {
22132
22421
  return ok3(await secureConnect(req, proc));
22133
22422
  case "sock-tls-upgrade":
22134
22423
  return ok3(await upgradeSecure(proc, socketAt(fds, req.fd), req));
22424
+ case "sock-tls-serve": {
22425
+ const { from, ...rest2 } = req;
22426
+ const listener = from === void 0 ? {} : { listener: socketAt(fds, from) };
22427
+ return ok3(await serveSecure(proc, socketAt(fds, req.fd), { ...rest2, ...listener }));
22428
+ }
22429
+ case "sock-tls-listen":
22430
+ await listenSecure(proc, socketAt(fds, req.fd), req);
22431
+ return ok3({});
22135
22432
  default:
22136
22433
  return datagram(req, proc);
22137
22434
  }
@@ -24563,6 +24860,7 @@ var WasmProcess = class {
24563
24860
  fds: this.fds,
24564
24861
  net: this.net,
24565
24862
  blocking: () => this.blockingSignal(cancelled),
24863
+ cancelled,
24566
24864
  resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking),
24567
24865
  cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid }),
24568
24866
  stamp: () => this.connectorStamp(),