@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 +10 -1
- package/dist/kernel-worker.js +312 -14
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +209 -75
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +312 -14
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +209 -75
- package/dist/process-worker.js.map +3 -3
- package/dist/types/fs/opfs.d.ts +1 -0
- package/dist/types/fs/remote.d.ts +1 -0
- package/dist/types/kernel/net/tls-client.d.ts +12 -0
- package/dist/types/kernel/net/tls-engine.d.ts +7 -1
- package/dist/types/kernel/net/tls-server.d.ts +34 -0
- package/dist/types/kernel/net/tls-uplink.d.ts +11 -1
- package/dist/types/kernel/socket-syscalls.d.ts +11 -0
- package/dist/types/kernel/socket.d.ts +9 -0
- package/dist/types/process/js/js-net.d.ts +16 -0
- package/package.json +3 -3
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
|
|
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`;
|
package/dist/kernel-worker.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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 ?
|
|
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
|
|
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(
|
|
17535
|
-
left:
|
|
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
|
|
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 =
|
|
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 =
|
|
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) :
|
|
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(),
|