From 0e465785d2b551dfef0ae0be264fe16ece92c624 Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Sat, 15 Mar 2025 23:29:42 -0700 Subject: [PATCH 1/4] TLS --- Makefile | 4 +++ index.html | 1 + src/browser/fetch_network.js | 61 ++++++++++++++++++++++++++++++++-- src/browser/starter.js | 4 ++- src/virtio_console.js | 6 ++-- tests/devices/fetch_network.js | 13 ++++++++ 6 files changed, 82 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 1b2de7e809..537b0fa7cb 100644 --- a/Makefile +++ b/Makefile @@ -380,6 +380,10 @@ build/capstone-x86.min.js: mkdir -p build wget -nv -P build https://github.com/AlexAltea/capstone.js/releases/download/v3.0.5-rc1/capstone-x86.min.js +build/mitm.mjs: + mkdir -p build + wget -nv -P build https://github.com/basicer/mitm.js/releases/download/latest/mitm.mjs + build/libwabt.cjs: mkdir -p build wget -nv -P build https://github.com/WebAssembly/wabt/archive/1.0.6.zip diff --git a/index.html b/index.html index a99fbd28b0..85e195e8b7 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,7 @@ +
diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 5d0195a97d..b0bd77f6ce 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -36,6 +36,14 @@ export function FetchNetworkAdapter(bus, config) this.eth_encoder_buf = create_eth_encoder_buf(); this.fetch = (...args) => fetch(...args); + if(globalThis["mitmjs"]) { + globalThis["mitmjs"]().then(x => { + this.tls = new x["MITM"](); + this.tls["generateECCPrivateKey"](); + }); + } + + // Ex: 'https://corsproxy.io/?' this.cors_proxy = config.cors_proxy; @@ -64,6 +72,16 @@ FetchNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) this.tcp_conn[tuple] = conn; return true; } + if(packet.tcp.dport === 443 && this.tls) { + let conn = new TCPConnection(); + conn.state = TCP_STATE_SYN_RECEIVED; + conn.net = this; + conn.on("data", on_data_tls.bind(conn, {net: this})); + conn.tuple = tuple; + conn.accept(packet); + this.tcp_conn[tuple] = conn; + return true; + } return false; }; @@ -104,6 +122,9 @@ async function on_data_http(data) // fix "Mixed Content" errors target.protocol = "https:"; } + else if(this.tls) { + target.protocol = "https:"; + } let req_headers = new Headers(); for(let i = 1; i < headers.length; ++i) { @@ -144,7 +165,7 @@ async function on_data_http(data) const fetch_url = this.net.cors_proxy ? this.net.cors_proxy + encodeURIComponent(target.href) : target.href; const encoder = new TextEncoder(); let response_started = false; - this.net.fetch(fetch_url, opts).then((resp) => { + let handler = (resp) => { let resp_headers = new Headers(resp.headers); resp_headers.delete("content-encoding"); resp_headers.delete("keep-alive"); @@ -177,7 +198,13 @@ async function on_data_http(data) this.close(); }); } - }) + }; + + if(this.net.tls && /^https?:[/][/]mitm[.]it[/](ca|cert)[/.]pem/.test(target.href)) { + return handler(new Response(this.net.tls["getCACertificate"]())); + } + + this.net.fetch(fetch_url, opts).then(handler) .catch((e) => { console.warn("Fetch Failed: " + fetch_url + "\n" + e); if(!response_started) { @@ -188,6 +215,36 @@ async function on_data_http(data) } } + +async function on_data_tls(ctx, data) +{ + let packet = this; + if(!ctx.tls) { + ctx.tls = packet.net.tls["ssl"](); + ctx.write = d => { + let r = ctx.tls["write"](d); + }; + ctx.writev = v => { + for(const data of v) { + let r = ctx.tls["write"](data); + } + }; + ctx.close = () => { + ctx.tls.close(); + setTimeout(()=> packet.close(), 100); + }; + + ctx.tls["setWriteCallback"](d => { + let r = packet.write(d); + }); + + ctx.tls["setDataCallback"](d => { + on_data_http.call(ctx, d); + }); + } + ctx.tls.send(data); +} + FetchNetworkAdapter.prototype.fetch = async function(url, options) { if(this.cors_proxy) url = this.cors_proxy + encodeURIComponent(url); diff --git a/src/browser/starter.js b/src/browser/starter.js index 8a9e6ad715..a4f57817e7 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -242,9 +242,11 @@ V86.prototype.continue_init = async function(emulator, options) if(relay_url) { // TODO: remove bus, use direct calls instead - if(relay_url === "fetch") + if(relay_url.startsWith("fetch")) { this.network_adapter = new FetchNetworkAdapter(this.bus, options.net_device); + let parts = relay_url.split(";"); + if(parts[1]) this.network_adapter.cors_proxy = parts[1]; } else if(relay_url === "inbrowser") { diff --git a/src/virtio_console.js b/src/virtio_console.js index a8c87c5eec..9762ed44e0 100644 --- a/src/virtio_console.js +++ b/src/virtio_console.js @@ -107,11 +107,9 @@ export function VirtioConsole(cpu, bus) { dbg_assert(false, "VirtioConsole Notified for wrong queue: " + queue_id + " (expected queue_id of 2)"); - return; + } - const queue = this.virtio.queues[queue_id]; - // Full buffer looks like an empty buffer so prevent it from filling - while(queue.count_requests() > queue.size - 2) queue.pop_request(); + }, (queue_id) => { diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 8f7d94ce09..3fdeac08b1 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -249,6 +249,19 @@ if(isMainThread) assert(/400 Bad Request/.test(capture), "got error 400"); }, }, + { + name: "Fetch MITM cert", + start: () => + { + emulator.serial0_send("curl http://mitm.it/cert.pem\n"); + emulator.serial0_send("echo -e done\\\\tmitm cert\n"); + }, + end_trigger: "done\tmitm cert", + end: (capture) => + { + assert(/BEGIN CERTIFICATE/.test(capture), "BEGIN CERTIFICATE"); + }, + }, ]; From a3075a45ea8415e513a510c99f25bd38309b404a Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Mon, 17 Mar 2025 17:25:37 -0700 Subject: [PATCH 2/4] Extra TCP --- src/browser/fake_network.js | 33 ++++++++++++++++++------- src/browser/fetch_network.js | 48 ++++++++++++------------------------ src/browser/wisp_network.js | 13 +++------- 3 files changed, 44 insertions(+), 50 deletions(-) diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index e358f921ac..0a8f6f36d8 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -246,10 +246,28 @@ function handle_fake_tcp(packet, adapter) if(packet.tcp.syn) { if(adapter.tcp_conn[tuple]) { dbg_log("SYN to already opened port", LOG_FETCH); + delete adapter.tcp_conn[tuple]; } - if(adapter.on_tcp_connection(packet, tuple)) { - return; + + let conn = new TCPConnection(); + conn.state = TCP_STATE_SYN_RECEIVED; + conn.net = adapter; + conn.tuple = tuple; + conn.last = packet; + + conn.hsrc = packet.eth.dest; + conn.psrc = packet.ipv4.dest; + conn.sport = packet.tcp.dport; + conn.hdest = packet.eth.src; + conn.dport = packet.tcp.sport; + conn.pdest = packet.ipv4.src; + + adapter.bus.pair.send('tcp-connection', conn); + + if(adapter.on_tcp_connection) { + adapter.on_tcp_connection(conn, packet); } + if (adapter.tcp_conn[tuple]) return; } if(!adapter.tcp_conn[tuple]) { @@ -1099,16 +1117,12 @@ TCPConnection.prototype.connect = function() { }; -TCPConnection.prototype.accept = function(packet) { +TCPConnection.prototype.accept = function(packet=undefined) { + packet = packet || this.last; + this.net.tcp_conn[this.tuple] = this; this.seq = 1338; this.ack = packet.tcp.seq + 1; this.start_seq = packet.tcp.seq; - this.hsrc = this.net.router_mac; - this.psrc = packet.ipv4.dest; - this.sport = packet.tcp.dport; - this.hdest = packet.eth.src; - this.dport = packet.tcp.sport; - this.pdest = packet.ipv4.src; this.winsize = packet.tcp.winsize; let reply = this.ipv4_reply(); @@ -1127,6 +1141,7 @@ TCPConnection.prototype.accept = function(packet) { }; TCPConnection.prototype.process = function(packet) { + this.last = packet; if(this.state === TCP_STATE_CLOSED) { // dbg_log(`TCP[${this.tuple}]: WARNING: connection already closed, packet dropped`, LOG_FETCH); const reply = this.packet_reply(packet, {rst: true}); diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index b0bd77f6ce..2d5edc56ba 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -43,7 +43,6 @@ export function FetchNetworkAdapter(bus, config) }); } - // Ex: 'https://corsproxy.io/?' this.cors_proxy = config.cors_proxy; @@ -54,37 +53,22 @@ export function FetchNetworkAdapter(bus, config) { this.send(data); }, this); + this.bus.register("tcp-connection", (conn) => { + if(conn.sport === 80) { + conn.on("data", on_data_http); + conn.accept(); + } + if(conn.sport === 443 && this.tls) { + conn.on("data", on_data_tls.bind(conn, {net: this})); + conn.accept(); + } + }, this); } FetchNetworkAdapter.prototype.destroy = function() { }; -FetchNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) -{ - if(packet.tcp.dport === 80) { - let conn = new TCPConnection(); - conn.state = TCP_STATE_SYN_RECEIVED; - conn.net = this; - conn.on("data", on_data_http); - conn.tuple = tuple; - conn.accept(packet); - this.tcp_conn[tuple] = conn; - return true; - } - if(packet.tcp.dport === 443 && this.tls) { - let conn = new TCPConnection(); - conn.state = TCP_STATE_SYN_RECEIVED; - conn.net = this; - conn.on("data", on_data_tls.bind(conn, {net: this})); - conn.tuple = tuple; - conn.accept(packet); - this.tcp_conn[tuple] = conn; - return true; - } - return false; -}; - FetchNetworkAdapter.prototype.connect = function(port) { return fake_tcp_connect(port, this); @@ -222,27 +206,27 @@ async function on_data_tls(ctx, data) if(!ctx.tls) { ctx.tls = packet.net.tls["ssl"](); ctx.write = d => { - let r = ctx.tls["write"](d); + let r = ctx.tls["dataIn"](d); }; ctx.writev = v => { for(const data of v) { - let r = ctx.tls["write"](data); + let r = ctx.tls["dataIn"](data); } }; ctx.close = () => { - ctx.tls.close(); + ctx.tls["close"](); setTimeout(()=> packet.close(), 100); }; - ctx.tls["setWriteCallback"](d => { + ctx.tls["setPacketOutCallback"](d => { let r = packet.write(d); }); - ctx.tls["setDataCallback"](d => { + ctx.tls["setDataOutCallback"](d => { on_data_http.call(ctx, d); }); } - ctx.tls.send(data); + ctx.tls["packetIn"](data); } FetchNetworkAdapter.prototype.fetch = async function(url, options) diff --git a/src/browser/wisp_network.js b/src/browser/wisp_network.js index e0de942ffa..7dd61a581d 100644 --- a/src/browser/wisp_network.js +++ b/src/browser/wisp_network.js @@ -185,17 +185,12 @@ WispNetworkAdapter.prototype.destroy = function() }; /** + * @param {TCPConnection} conn * @param {Uint8Array} packet - * @param {String} tuple */ -WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) +WispNetworkAdapter.prototype.on_tcp_connection = function(conn, packet) { - let conn = new TCPConnection(); - conn.state = TCP_STATE_SYN_RECEIVED; - conn.net = this; - conn.tuple = tuple; conn.stream_id = this.last_stream++; - this.tcp_conn[tuple] = conn; conn.on("data", data => { if(data.length !== 0) { @@ -222,7 +217,7 @@ WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) type: "CONNECT", stream_id: conn.stream_id, hostname: packet.ipv4.dest.join("."), - port: packet.tcp.dport, + port: conn.sport, data_callback: (data) => { conn.write(data); }, @@ -231,7 +226,7 @@ WispNetworkAdapter.prototype.on_tcp_connection = function(packet, tuple) } }); - conn.accept(packet); + conn.accept(); return true; }; From 0b0fd5ad2d8aca5d89aef37037c0faf849367548 Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Sun, 13 Apr 2025 18:52:26 -0700 Subject: [PATCH 3/4] TCP migrate to es6 --- src/browser/fake_network.js | 6 ++++-- src/browser/fetch_network.js | 15 ++++++++------- tests/devices/fetch_network.js | 4 ++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index 0a8f6f36d8..349748f135 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -243,7 +243,7 @@ function handle_fake_tcp(packet, adapter) { const tuple = `${packet.ipv4.src.join(".")}:${packet.tcp.sport}:${packet.ipv4.dest.join(".")}:${packet.tcp.dport}`; - if(packet.tcp.syn) { + if(packet.tcp.syn && !packet.tcp.ack) { if(adapter.tcp_conn[tuple]) { dbg_log("SYN to already opened port", LOG_FETCH); delete adapter.tcp_conn[tuple]; @@ -1101,7 +1101,9 @@ TCPConnection.prototype.connect = function() { this.ack = 1; this.start_seq = 0; this.winsize = 64240; - this.state = TCP_STATE_SYN_SENT; + if(this.state !== TCP_STATE_SYN_PROBE) { + this.state = TCP_STATE_SYN_SENT; + } let reply = this.ipv4_reply(); reply.ipv4.id = 2345; diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 2d5edc56ba..3379c309cb 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -1,5 +1,4 @@ import { LOG_FETCH } from "../const.js"; -import { h } from "../lib.js"; import { dbg_log } from "../log.js"; import { @@ -36,12 +35,14 @@ export function FetchNetworkAdapter(bus, config) this.eth_encoder_buf = create_eth_encoder_buf(); this.fetch = (...args) => fetch(...args); - if(globalThis["mitmjs"]) { - globalThis["mitmjs"]().then(x => { - this.tls = new x["MITM"](); - this.tls["generateECCPrivateKey"](); - }); - } + import("../../build/mitm.mjs").then(factory => factory.default()).then(obj => { + this.tls = new obj["MITM"](); + this.tls["generateECCPrivateKey"](); + console.log("TLS online"); + }).catch(e => { + console.log(e); + dbg_log("No TLS library detected."); + }); // Ex: 'https://corsproxy.io/?' this.cors_proxy = config.cors_proxy; diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index 3fdeac08b1..18cd15beda 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -241,9 +241,9 @@ if(isMainThread) start: () => { emulator.serial0_send("wget --header='testheader' -T 10 -O - test.domain\n"); - emulator.serial0_send("echo -e done\\\\theader without colon\n"); + emulator.serial0_send("echo -e done\\\\theader without separator\n"); }, - end_trigger: "done\theader without colon", + end_trigger: "done\theader without separator", end: (capture) => { assert(/400 Bad Request/.test(capture), "got error 400"); From 4bf61d6bb810ab8429b68cac97652f45df635f5e Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Thu, 3 Jul 2025 16:05:07 -0700 Subject: [PATCH 4/4] Fixes --- Makefile | 2 +- src/browser/fake_network.js | 4 ++-- src/browser/fetch_network.js | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 537b0fa7cb..c561c1ed1e 100644 --- a/Makefile +++ b/Makefile @@ -341,7 +341,7 @@ expect-tests: build/v86-debug.wasm build/libwabt.cjs make -C tests/expect/tests ./tests/expect/run.js -devices-test: build/v86-debug.wasm +devices-test: build/v86-debug.wasm build/mitm.mjs ./tests/devices/virtio_9p.js ./tests/devices/virtio_console.js ./tests/devices/fetch_network.js diff --git a/src/browser/fake_network.js b/src/browser/fake_network.js index 349748f135..57c243dc95 100644 --- a/src/browser/fake_network.js +++ b/src/browser/fake_network.js @@ -262,12 +262,12 @@ function handle_fake_tcp(packet, adapter) conn.dport = packet.tcp.sport; conn.pdest = packet.ipv4.src; - adapter.bus.pair.send('tcp-connection', conn); + adapter.bus.pair.send("tcp-connection", conn); if(adapter.on_tcp_connection) { adapter.on_tcp_connection(conn, packet); } - if (adapter.tcp_conn[tuple]) return; + if(adapter.tcp_conn[tuple]) return; } if(!adapter.tcp_conn[tuple]) { diff --git a/src/browser/fetch_network.js b/src/browser/fetch_network.js index 3379c309cb..fe80840c68 100644 --- a/src/browser/fetch_network.js +++ b/src/browser/fetch_network.js @@ -35,10 +35,9 @@ export function FetchNetworkAdapter(bus, config) this.eth_encoder_buf = create_eth_encoder_buf(); this.fetch = (...args) => fetch(...args); - import("../../build/mitm.mjs").then(factory => factory.default()).then(obj => { + eval(`import("../../build/mitm.mjs")`).then(factory => factory.default()).then(obj => { this.tls = new obj["MITM"](); this.tls["generateECCPrivateKey"](); - console.log("TLS online"); }).catch(e => { console.log(e); dbg_log("No TLS library detected.");