Files
fips_setup/plans/fips-extension-running-fipsd.md
Laan Tungir d3e3a3d35a .
2026-07-21 13:15:53 -04:00

15 KiB

Can the Extension Run the Existing FIPS Rust Code?

The Question

fipsd is a compiled Rust program. Could the extension run this program directly, saving us from rewriting FIPS in JavaScript?

Short Answer

A browser extension cannot run a compiled native binary directly — browser extensions run in a sandboxed JavaScript/WebAssembly environment and cannot execute native programs. But there are three paths to reuse the existing Rust code with varying degrees of "no install required."


Why the Extension Can't Just Run fipsd

Browser extensions are sandboxed. They cannot:

  • Execute native binaries (exec, spawn, etc.)
  • Create TUN interfaces
  • Open raw UDP/TCP sockets
  • Listen on ports
  • Access the filesystem (except a small extension-specific storage area)

The fipsd binary expects all of these — it creates a TUN interface, binds UDP/TCP ports, runs a DNS resolver, etc. None of that is possible inside a browser extension sandbox.


Path 1: Native Messaging (Requires Install — Defeats the Goal)

Browser extensions can communicate with native applications via the Native Messaging API. The extension sends messages to a native messaging host (a script or binary installed on the user's system), which can run fipsd or talk to it.

How it works:

Browser extension
    ↓ chrome.runtime.sendNativeMessage('com.fips.client', {...})
    ↓
Native messaging host (installed separately)
    ↓ communicates with fipsd via its control API
    ↓
fipsd (running as a system process)
    ↓ connects to FIPS mesh
    ↓
Content returned through the chain

The problem: The user must install both the native messaging host AND fipsd. This is exactly the "install FIPS" step we're trying to eliminate. It's useful for the "existing FIPS user" case (Problem 1-5 in the possibilities doc) but doesn't solve the "no daemon" access question.

When this is useful: For users who already run FIPS, native messaging lets the extension talk to their local fipsd for status, peer management, hosts file editing, etc. This is the right approach for the "FIPS power user" features. But it doesn't help with daemon-free access.


Path 2: Compile FIPS to WebAssembly (Reuse Protocol Code, Replace Transport)

This is the most promising path. Rust compiles to WebAssembly (Wasm). The browser can run Wasm. The question is: which parts of FIPS can run in Wasm, and which need adaptation?

FIPS's Architecture Is Already Separated

FIPS has clean layer separation (from plans/fips-overview.md):

┌─────────────────────────────────────────────────┐
│  Applications (SSH, curl, custom)                │
│  ↕ IPv6 TUN adapter (fd00::/8) + .fips DNS      │  ← Can't run in browser
├─────────────────────────────────────────────────┤
│  FSP — FIPS Session Protocol                     │
│  End-to-end Noise XK encryption between npubs    │  ← Pure computation — CAN run in Wasm
│  Port-based service multiplexing                 │
├─────────────────────────────────────────────────┤
│  FMP — FIPS Mesh Protocol                        │
│  Peer auth (Noise IK), spanning tree,            │  ← Pure computation — CAN run in Wasm
│  bloom filters, hop-by-hop forwarding            │
├─────────────────────────────────────────────────┤
│  Transport Layer                                 │
│  UDP | TCP | Ethernet | Tor | BLE                │  ← Can't run in browser — REPLACE with WebSocket
└─────────────────────────────────────────────────┘

What Can Run in Wasm

FIPS Component Wasm Compatible? Notes
secp256k1 crypto Yes Pure computation. secp256k1 crate has Wasm support, or use @noble/secp256k1
Noise IK / XK handshake Yes Pure computation — key derivation, encryption, decryption
FSP (session protocol) Yes Framing, port multiplexing, session management — all pure computation
FMP (mesh protocol) Yes Spanning tree, bloom filters, routing logic — all pure computation
npub → IPv6 derivation Yes Bech32 + SHA-256 — trivial in Wasm
Transport layer (UDP/TCP/BLE) No Raw sockets not available in browser
TUN interface No Can't create network interfaces
DNS resolver No Can't listen on ports
tokio async runtime No tokio doesn't work in Wasm; need browser async (Promises)

What Needs to Change

The key insight: FIPS's transport layer is already a trait/interface with a minimal API (send bytes, receive bytes, report MTU). The protocol layers are transport-agnostic. So we need to:

  1. Compile the protocol logic (FSP + FMP + crypto) to Wasm — This is the valuable code we don't want to rewrite
  2. Implement a WebSocket transport adapter — A new transport implementation that uses WebSocket instead of UDP/TCP. It implements the same trait interface.
  3. Replace tokio with browser async — The async runtime needs to change from tokio to browser-native async (Promises, setTimeout, WebSocket events). This is the most invasive change.
  4. Skip TUN and DNS — The extension doesn't need these. It intercepts HTTP requests at the browser API level instead of at the network layer.

How Much Code Reuse Is Realistic?

This depends on how FIPS is structured. If FIPS is structured as:

  • A library crate (fips-core or similar) containing the protocol logic
  • A binary crate (fipsd) containing the daemon (TUN, DNS, systemd, CLI)

Then compiling the library crate to Wasm is feasible. The library would need:

  • #![no_std] or conditional compilation for Wasm-incompatible parts
  • A WebSocket transport implementation (new code, but small — the transport interface is minimal)
  • Async runtime abstraction (replace tokio with a trait that can use either tokio or browser async)

If FIPS is a monolithic binary with protocol logic and system code intertwined, then more refactoring is needed to separate the Wasm-compatible parts.

The transport interface is the key. From plans/fips-overview.md:

FIPS treats transports as dumb pipes. Each transport implements: send bytes, receive bytes, report MTU. The mesh layer handles everything above that.

This means a WebSocket transport is just another implementation of that interface. The protocol layers don't know or care that the transport is WebSocket instead of UDP.

The Async Runtime Challenge

The biggest obstacle is the async runtime. FIPS likely uses tokio throughout for async I/O. tokio doesn't compile to Wasm because it relies on OS-level I/O (epoll, kqueue, IOCP).

Options:

  • wasm-bindgen-futures — Bridge between Rust futures and JavaScript Promises. The Wasm module can expose async functions that return Promises.
  • Callback-based — The Wasm module exposes synchronous functions and calls back into JavaScript when data arrives on the WebSocket.
  • wasm-bindgen + js-sys — Use JavaScript APIs directly from Rust via wasm-bindgen. WebSocket operations become calls to js-sys::WebSocket.

This is the most invasive change but it's mechanical — replacing tokio::net::UdpSocket with js_sys::WebSocket calls, replacing tokio::spawn with callback registration, etc.

What the Architecture Looks Like

┌──────────────────────────────────────────────────────┐
│  Browser Extension (JavaScript)                      │
│                                                      │
│  ┌─────────────┐    ┌─────────────────────────────┐  │
│  │ Content     │    │ FIPS Wasm Module            │  │
│  │ Script      │───▶│ (compiled from Rust)        │  │
│  │ (intercepts │    │                             │  │
│  │  .fips HTTP │    │ ┌───────────────────────┐   │  │
│  │  requests)  │    │ │ FSP + Noise XK        │   │  │
│  └─────────────┘    │ │ (end-to-end encrypt)  │   │  │
│                     │ └───────────────────────┘   │  │
│  ┌─────────────┐    │ ┌───────────────────────┐   │  │
│  │ WebSocket   │◀──▶│ │ WebSocket Transport   │   │  │
│  │ (to boot-   │    │ │ (new, implements the  │   │  │
│  │  strap node)│    │ │  transport trait)     │   │  │
│  └─────────────┘    │ └───────────────────────┘   │  │
│                     │ ┌───────────────────────┐   │  │
│                     │ │ npub → IPv6           │   │  │
│                     │ │ (bech32 + SHA-256)    │   │  │
│                     │ └───────────────────────┘   │  │
│                     └─────────────────────────────┘  │
└──────────────────────────────────────────────────────┘
         │
         │ WebSocket connection
         ▼
┌──────────────────────────────────────────────────────┐
│  FIPS Bootstrap Node                                 │
│  (regular FIPS node with WebSocket transport support)│
│  Receives session packets, routes through mesh       │
└──────────────────────────────────────────────────────┘

What the Bootstrap Node Needs

The bootstrap node needs to accept WebSocket connections from browser clients. Options:

Option A: FIPS adds a WebSocket transport

  • Add a WebSocket implementation to FIPS's transport layer
  • The bootstrap node listens for WebSocket connections and treats them as FIPS peers
  • This is the cleanest approach — WebSocket is just another transport

Option B: Small bridge service

  • A tiny service (Rust, Go, or even Node.js) that accepts WebSocket connections and bridges them to FIPS's TCP transport
  • The bridge does WebSocket ↔ TCP conversion
  • FIPS itself doesn't need to change
  • The bridge runs alongside fipsd on the bootstrap node

Option A is better long-term (WebSocket is a natural transport for browser clients), but Option B works without modifying FIPS.


Path 3: Compile FIPS to Wasm + Run in a Service Worker (Full Browser Node)

A service worker can run persistently in the background. If the FIPS Wasm module runs in a service worker, it can:

  • Maintain persistent WebSocket connections to bootstrap nodes
  • Handle .fips requests intercepted by the extension
  • Cache responses
  • Run even when no tab is open

This is essentially running a FIPS node inside the browser — a lightweight node that doesn't participate in mesh routing (no relaying for others) but can create end-to-end encrypted sessions to any FIPS destination.

The service worker + Wasm approach means:

  • No native install required
  • Full end-to-end encryption (Noise XK)
  • The extension IS a FIPS client
  • The protocol code is reused from the Rust implementation (compiled to Wasm)
  • Only the transport layer is new (WebSocket instead of UDP/TCP)

Comparison of All Paths

Path Requires Install? Reuses Rust Code? E2E Encryption? Effort
Native Messaging Yes (fipsd + host) Yes (runs fipsd) Yes (via fipsd) Low
Wasm (protocol only) No Yes (protocol compiled to Wasm) Yes Medium-High
Wasm + Service Worker No Yes (protocol compiled to Wasm) Yes Medium-High
Rewrite in JS No No Yes High
Gateway proxy No N/A (gateway runs fipsd) No (gateway sees traffic) Low-Medium

The Pragmatic Path

Here's what I'd recommend, combining the approaches:

Phase 1: Gateway Proxy + Native Messaging

  • Gateway proxy for daemon-free access (simple, works immediately)
  • Native messaging for users who already run fipsd (full features, status, peer management)
  • Ship something useful quickly

Phase 2: FIPS Wasm Module

  • Compile FIPS protocol logic (FSP, Noise, crypto) to Wasm
  • Implement WebSocket transport adapter
  • Add WebSocket transport to FIPS (or bridge service)
  • Extension uses Wasm module for end-to-end encrypted access without a daemon
  • This is the "best of both worlds" — reuses Rust code, no install, full encryption

Phase 3: Service Worker Integration

  • Run the Wasm module in a service worker for persistent background operation
  • Full browser-based FIPS client

What We Need to Know About the FIPS Codebase

To assess how feasible the Wasm approach is, we'd need to look at the FIPS Rust source code and answer:

  1. Is FIPS structured as a library + binary, or a monolithic binary? If there's a fips-core library crate, compiling it to Wasm is much easier.

  2. How tightly coupled is the async runtime? If tokio is used everywhere, the Wasm port needs an async runtime abstraction layer. If async is contained to the transport layer, it's easier.

  3. Does the transport layer use a trait/interface? If so, adding a WebSocket implementation is clean. If transports are hardcoded, refactoring is needed.

  4. What crates does FIPS depend on? Some crates don't compile to Wasm (anything using std::net, tokio, OS-specific APIs). We'd need to check the dependency tree.

  5. How large is the protocol logic (FSP + FMP + crypto)? This determines the Wasm binary size and whether it's practical to load in an extension.


Open Questions

  1. Is the FIPS source code available to examine? The plans reference https://github.com/jmcorgan/fips.git — is this the right repo, and can we look at its structure?

  2. Does FIPS already have a WebSocket transport, or would we need to add one? The overview mentions UDP, TCP, Ethernet, Tor, BLE — no WebSocket.

  3. How is the FIPS control API structured? If we go with native messaging for existing users, we need to know how fipsctl talks to fipsd.

  4. Could the Wasm approach work for FMP (mesh routing) too, or should we limit it to FSP (sessions only)? A browser client probably doesn't need to participate in mesh routing — it just needs to create sessions through a bootstrap node. This would mean only FSP + crypto needs to compile to Wasm, not FMP.