This commit is contained in:
Laan Tungir
2026-07-21 13:15:53 -04:00
parent e38d575939
commit d3e3a3d35a
12 changed files with 1369 additions and 193 deletions

1
.gitignore vendored
View File

@@ -1 +0,0 @@
fips/

192
README.md
View File

@@ -1,192 +0,0 @@
# sys-fips Setup for Qubes OS
> Goal: run a **single FIPS node** inside one ProxyVM (`sys-fips`) so any AppVM routed through it can access the FIPS mesh.
## Architecture
```text
AppVMs → sys-fips (ProxyVM) → sys-firewall → sys-net → Internet
└─ fips0 (TUN) → FIPS mesh (encrypted)
```
This setup is intentionally single-node per VM.
If you want another independent FIPS identity/path, clone the VM (for example, `sys-fips2`).
---
## Files in this directory
| File | Purpose |
|---|---|
| `README.md` | This guide |
| `configs/fips.yaml` | Single-node config template |
| `scripts/00-build-fips.sh` | Run in build qube: build `fips`, `fipsctl`, `fipstop` into local `bin/` |
| `scripts/01-dom0-create-proxyvm.sh` | Run in dom0: creates `sys-fips` ProxyVM |
| `scripts/02-install-fips.sh` | Run in `sys-fips`: installs deps, binaries, and systemd units |
| `scripts/03-configure-identity.sh` | Run in `sys-fips`: configures `/etc/fips/fips.yaml`, identity mode |
| `scripts/03-generate-node-configs.sh` | Compatibility wrapper to `03-configure-identity.sh` |
| `scripts/04-start-fips.sh` | Run in `sys-fips`: starts single FIPS node |
| `scripts/04-start-fips-nodes.sh` | Compatibility wrapper to `04-start-fips.sh` |
| `scripts/05-stop-fips.sh` | Run in `sys-fips`: stops node + cleanup |
| `scripts/05-stop-fips-nodes.sh` | Compatibility wrapper to `05-stop-fips.sh` |
| `scripts/06-configure-dns.sh` | Run in `sys-fips`: dnsmasq forwards `.fips` to localhost:5354 and applies Qubes `/32` DNS compatibility patch |
| `scripts/07-route-appvms.sh` | Run in `sys-fips`: IPv6/ip6tables forwarding for routed AppVMs |
| `scripts/08-add-peer.sh` | Run in `sys-fips`: add static peer to `/etc/fips/fips.yaml` |
| `scripts/09-add-known-peers.sh` | Run in `sys-fips`: add two known bootstrap peers and restart FIPS |
| `scripts/test-connectivity.sh` | Unified test script |
| `scripts/test-local.sh` | Compatibility wrapper to `test-connectivity.sh --local` |
| `scripts/test-from-appvm.sh` | Compatibility wrapper to `test-connectivity.sh --appvm` |
| `scripts/test-full-suite.sh` | Compatibility wrapper to `test-connectivity.sh --full` |
---
## Quick start
### 1) Create `sys-fips` (run in dom0)
```bash
sudo bash 01-dom0-create-proxyvm.sh [template] [upstream_netvm]
```
Defaults:
- template: `debian-12`
- upstream netvm: `sys-firewall`
For VPN chaining, pass `sys-vpn` as upstream:
```bash
sudo bash 01-dom0-create-proxyvm.sh debian-12 sys-vpn
```
### 2) Build in this repo, then copy this entire folder
From this `fips_setup` repo in your build qube:
```bash
bash ./scripts/00-build-fips.sh
qvm-copy-to-vm sys-fips /home/user/anvil/fips_setup/
```
This keeps your update workflow simple: pull latest code, rebuild once in this repo, copy the folder, run install in `sys-fips`.
### 3) Install/configure/start (inside `sys-fips`)
```bash
cd ~/QubesIncoming/*/fips_setup/scripts
sudo bash 02-install-fips.sh
bash 03-configure-identity.sh
sudo bash 04-start-fips.sh
sudo bash 06-configure-dns.sh
sudo bash 07-route-appvms.sh
```
### 4) Add at least one external peer
```bash
sudo bash 08-add-peer.sh
sudo systemctl restart fips
```
Or add both known bootstrap peers automatically:
```bash
sudo bash 09-add-known-peers.sh
```
### 5) Test
```bash
sudo bash test-connectivity.sh --full
```
### 6) Point AppVMs at `sys-fips` (in dom0)
```bash
qvm-prefs my-appvm netvm sys-fips
```
---
## Clone workflow
```bash
# dom0
qvm-clone sys-fips sys-fips2
qvm-start sys-fips2
```
Inside `sys-fips2`:
```bash
sudo bash 05-stop-fips.sh
sudo rm -f /etc/fips/fips.key /etc/fips/fips.pub
bash 03-configure-identity.sh
sudo bash 04-start-fips.sh
```
Now `sys-fips` and `sys-fips2` are independent FIPS nodes.
---
## Notes
- One FIPS node per ProxyVM is the production target.
- The previous two-node local loopback approach was useful for local experiments but is not required for normal Qubes deployment.
- FIPS mesh encryption is end-to-end. A VPN upstream is optional and can be chained.
- `06-configure-dns.sh` removes dnsmasq `--local-service` from Debian's helper to avoid dropping AppVM DNS on Qubes `/32` links.
---
## Lessons learned from real `sys-fips` troubleshooting
These are the key fixes required to make AppVM → `sys-fips` → FIPS mesh work reliably in Qubes.
### 1) Intercept AppVM DNS in `sys-fips` with nftables
Qubes AppVM DNS normally targets the Qubes gateway addresses (for example `10.139.1.1` / `10.139.1.2`).
To make `.fips` resolution work from routed AppVMs, `sys-fips` must DNAT DNS to local dnsmasq.
Required nft hooks in `sys-fips`:
- `table ip qubes`, chain `dnat-dns`
- `table ip qubes`, chain `custom-input`
`dnat-dns` redirects UDP/TCP 53 traffic to local DNS (`10.139.1.1:53`) and `custom-input` allows DNS from AppVM interfaces (for example `vif+`).
### 2) IPv6 forwarding + route to FIPS space
`sys-fips` must forward IPv6 and route FIPS mesh space (`fd00::/8`) to `fips0`:
- enable IPv6 forwarding (`sysctl net.ipv6.conf.all.forwarding=1`)
- ensure route exists: `ip -6 route add fd00::/8 dev fips0`
Without this, AppVM traffic may resolve `.fips` names but fail to reach the resolved FIPS IPv6 destination.
### 3) AppVMs need a valid IPv6 source + route toward `sys-fips`
In testing, routed AppVM access required:
- a synthetic IPv6 on `eth0` (example: `fdaa::15/128`)
- route of FIPS space via Qubes link-local gateway:
- `ip -6 route add fd00::/8 via fe80::fcff:ffff:feff:ffff dev eth0`
This ensures traffic to resolved `.fips` AAAA records leaves the AppVM through `sys-fips`.
### 4) Persist network fixes in `/rw/config/rc.local`
For reboot safety:
- persist nftables DNS interception + IPv6 forwarding/route in `sys-fips` `/rw/config/rc.local`
- persist AppVM synthetic IPv6 + route in each AppVM `/rw/config/rc.local` that needs `.fips`
Do not rely on one-off manual commands for production.
### 5) Chromium-family browser caveat
Observed behavior:
- Firefox followed system resolver path and worked once networking was fixed.
- Chromium-family browsers (`agent-browser`, Brave) could still show `ERR_NAME_NOT_RESOLVED` even when `dig`, `getent`, `ping`, and `curl` worked.
Current operational workaround is to pin the `.fips` hostname in `/etc/hosts` for Chromium-family use-cases.
See `plans/chromium-fips-workaround.md` for tested commands and policy notes.

2
configs/laantungir/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Never commit the private key
fips.key

View File

@@ -0,0 +1,368 @@
# FIPS server setup — laantungir.net
This directory (`configs/laantungir/`) contains the complete configuration
needed to recreate the FIPS mesh node running on `laantungir.net` (VPS at
`15.235.3.231`).
It lives inside the `fips_setup` repo alongside the fips source tree
(`fips/`), build scripts (`scripts/`), and other configs (`configs/`).
This subdir is specifically for the **production laantungir.net server**;
the other `configs/*.yaml` files are for local Qubes/test nodes.
It exists so that if the server dies, gets rebuilt, or migrates to a new
host, you can restore the node with the **same identity** (same npub,
same NodeAddr, same position in the spanning tree) and the **same working
advert publishing** in a few minutes.
## Files
| File | Installs to | Purpose |
|------|-------------|---------|
| [`fips.yaml`](fips.yaml) | `/etc/fips/fips.yaml` | Daemon config (transports, Nostr, peers, TUN, DNS) |
| [`fips.service`](fips.service) | `/etc/systemd/system/fips.service` | systemd unit |
| [`fips.key`](fips.key) | `/etc/fips/fips.key` | **Private key (nsec) — SECRET, gitignored** |
| [`fips.pub`](fips.pub) | `/etc/fips/fips.pub` | Public key (npub) |
| [`hosts`](hosts) | `/etc/fips/hosts` | Mesh host aliases (`<name>.fips` → npub) |
| [`torrc.fips.conf`](torrc.fips.conf) | lines for `/etc/tor/torrc` | Tor hidden service for the FIPS Tor transport |
| [`.gitignore`](.gitignore) | — | Prevents `fips.key` from being committed (in this subdir) |
## Advertised endpoints
The node advertises **three** transports in its Nostr kind 37195 advert,
giving peers a choice based on their network situation:
| Transport | Advertised address | Best for | Latency |
|---|---|---|---|
| `udp` | `15.235.3.231:2121` | Direct peers with no NAT | ~50ms |
| `tcp` | `15.235.3.231:8443` | Direct peers who prefer TCP | ~50ms |
| `tor` | `t62hfd2dvvbmqc5pd6umw2e6fl52g4soshzrfufuvzagkjxjdly5soid.onion:9443` | NAT'd/censored peers | ~200-500ms |
A peer's `fipsd` tries endpoints in order and picks whichever works.
If UDP fails (NAT), it falls back to TCP; if TCP fails (firewall), it
falls back to Tor. This is the integrated transport layer — not just
"here's a Tor address," but "here are three ways to reach me, use
whichever your network allows."
## Node identity
- **npub:** `npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud`
- **NodeAddr:** `e3da8293e6ea58807bd02b4749824243` (first 16 bytes of SHA-256 of the pubkey)
- **Mesh IPv6:** derived from the NodeAddr in `fd00::/8`
- **Tor onion:** `t62hfd2dvvbmqc5pd6umw2e6fl52g4soshzrfufuvzagkjxjdly5soid.onion`
The private key in `fips.key` is the **only** thing that ties this node
to its identity. Lose it and you become a new node with a new NodeAddr,
new tree position, and no peers. Back it up somewhere safe outside this
repo (the `.gitignore` keeps it out of git, which is correct for the
repo but means you need a separate secure backup).
## Server details
- **Host:** `laantungir.net` (SSH: `ssh ubuntu@laantungir.net`)
- **Public IP:** `15.235.3.231` on `eth0`
- **OS:** Ubuntu (VPS)
- **No NAT** — the public IP is directly on the interface
- **fips binary:** `/usr/local/bin/fips` (v0.4.0, rev d5ee526f0e-dirty)
- **fipsctl:** `/usr/local/bin/fipsctl`
- **Control socket:** `/run/fips/control.sock` (group `fips` — use `sudo fipsctl` if not in the group)
## How to recreate from scratch
### 1. Install the fips binary
Copy `/usr/local/bin/fips` and `/usr/local/bin/fipsctl` from a build or
another server. They're a single static-ish Rust binary each. Version
used here: `fips 0.4.0 (rev d5ee526f0e-dirty)`.
### 2. Create the fips user/group and directories
```bash
sudo useradd --system --no-create-home --shell /usr/sbin/nologin fips 2>/dev/null || true
sudo mkdir -p /etc/fips /var/lib/fips
```
### 3. Install the identity
```bash
sudo cp fips.key /etc/fips/fips.key
sudo cp fips.pub /etc/fips/fips.pub
sudo chmod 600 /etc/fips/fips.key
sudo chmod 644 /etc/fips/fips.pub
sudo chown root:root /etc/fips/fips.key /etc/fips/fips.pub
```
### 4. Install the config
```bash
sudo cp fips.yaml /etc/fips/fips.yaml
sudo cp hosts /etc/fips/hosts
```
**If the server has a different public IP**, edit `fips.yaml` and change
every `15.235.3.231` to the new IP (there are two: UDP `bind_addr` and
TCP `bind_addr`). Do not change them to `0.0.0.0` — see
"Why bind to the public IP" below.
### 5. Install the systemd unit
```bash
sudo cp fips.service /etc/systemd/system/fips.service
sudo systemctl daemon-reload
```
### 6. Set up Tor (for the Tor transport)
```bash
sudo apt install tor
sudo mkdir -p /var/lib/tor/fips_mesh
sudo chown debian-tor:debian-tor /var/lib/tor/fips_mesh
# Append the lines from torrc.fips.conf to /etc/tor/torrc
sudo systemctl enable --now tor
# Wait a few seconds, then verify the onion address:
sudo cat /var/lib/tor/fips_mesh/hostname
# Should print: t62hfd2dvvbmqc5pd6umw2e6fl52g4soshzrfufuvzagkjxjdly5soid.onion
```
If the onion address is **different**, you have a new hidden service
identity. That's fine for connectivity, but the advertised onion will
differ from the one in this README. Update this README if you want it
to stay accurate.
### 7. Open firewall ports (if applicable)
The daemon needs inbound on:
- **UDP 2121** (mesh peer links)
- **TCP 8443** (mesh peer links, TCP transport)
- **TCP 9443** on `127.0.0.1` only (Tor → fipsd, no external exposure)
If you use UFW:
```bash
sudo ufw allow 2121/udp
sudo ufw allow 8443/tcp
```
### 8. Start the daemon
```bash
sudo systemctl enable --now fips
sudo systemctl status fips
sudo journalctl -u fips -f
```
### 9. Verify the advert is published
Wait ~30 seconds for the initial advert publish, then check from your
laptop (or any machine with `nak`):
```bash
nak req -k 37195 -a c0c3f6b0952e527146aeff70ee4a69e34959b7d62eba069a8e44f58b0962f08e \
--stream wss://nos.lol wss://offchain.pub
```
You should see a kind 37195 event with `endpoints` containing
`15.235.3.231:2121` (udp) and `15.235.3.231:8443` (tcp). If you see
nothing, see "Troubleshooting" below.
### 10. Verify peers are connecting
```bash
sudo fipsctl show status # check peer_count, estimated_mesh_size
sudo fipsctl show peers # list authenticated peers
sudo fipsctl show tree # spanning tree position
```
Within a few minutes `peer_count` should climb past 6 and keep rising.
`estimated_mesh_size` should be in the hundreds.
## Why bind to the public IP (not 0.0.0.0)
This is the single most important gotcha in this config, and the reason
this directory exists.
**The problem:** `fips.yaml` has a top-level `nostr.advertise: true`
flag that makes you *think* your node is publishing its advert. But
there's a **second, per-transport flag** `advertise_on_nostr` that
**defaults to `false`** ([`fips/src/config/transport.rs:109`](../../fips/src/config/transport.rs:109)).
With it false, `build_overlay_advert()` skips every transport, the
endpoints list is empty, and `publish_advert()` returns `Ok(())`
silently — no error, no log line, no advert. Your node consumes the
mesh's adverts fine but never contributes its own, so other nodes can't
discover you via Nostr.
**The fix has three parts, all required for UDP/TCP:**
1. Set `advertise_on_nostr: true` on each transport you want advertised.
This applies to **all three transports** (UDP, TCP, Tor) — each
defaults to `false` independently.
2. Bind to a **specific IP** (the public IP), not `0.0.0.0`. Why:
`build_overlay_advert()` uses `handle.local_addr()` and skips
endpoints where `addr.ip().is_unspecified()`
([`fips/src/node/lifecycle.rs:1447`](../../fips/src/node/lifecycle.rs:1447)).
Binding to `0.0.0.0` makes `local_addr()` return `0.0.0.0`, which is
unspecified, so the endpoint gets skipped even with
`advertise_on_nostr: true`. (Tor is exempt — it uses
`handle.onion_address()` instead of `local_addr()`, so the bind IP
doesn't matter for Tor advertising.)
3. For UDP on a public-IP server, also set `public: true` so the advert
says `addr: "15.235.3.231:2121"` (directly reachable) instead of
`addr: "nat"` (requires STUN hole-punching). A public-IP server
doesn't need hole-punching.
**For Tor specifically**, the requirements are different:
- `advertise_on_nostr: true` (same as UDP/TCP).
- `advertised_port` must match the **virtual port** (first number) in
torrc's `HiddenServicePort <virtual> 127.0.0.1:<local>` directive.
Our torrc has `HiddenServicePort 9443 127.0.0.1:9443`, so
`advertised_port: 9443`. If you change torrc to the common
`HiddenServicePort 443 127.0.0.1:9443` convention, set
`advertised_port: 443` to match.
- The onion address itself comes from `handle.onion_address()`, which
is read from the `hostname_file` at startup — no bind-IP issue.
**Symptom if you get this wrong:** `nak req -k 37195 -a <your-hex-pubkey>`
returns zero events from all relays, and `sudo journalctl -u fips | grep
-i advert` shows no `advert: published` lines and no error lines either.
Peer count stays low (only your 2 configured peers + whoever happened to
find you through the mesh).
## How to check if the advert is live
From any machine with `nak` installed:
```bash
# Get your hex pubkey from the npub
HEX=$(nak decode npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud)
# Query the relays
nak req -k 37195 -a "$HEX" --stream wss://nos.lol wss://offchain.pub wss://relay.damus.io
```
You should see one event with `endpoints` in the content. The advert
expires after ~1 hour and is republished every ~30 minutes
([`../../fips_playground/plans/fips-explained.md`](../../fips_playground/plans/fips-explained.md:104)),
so if the daemon is down for >1 hour the advert disappears.
## Troubleshooting
### `fipsctl` says "Permission denied"
```bash
sudo fipsctl show status # works immediately
# or add yourself to the fips group for passwordless access:
sudo usermod -aG fips $USER
# log out and back in
```
### No advert on relays (`nak` returns nothing)
1. Check `nostr.advertise: true` and `nostr.enabled: true` in `fips.yaml`.
2. Check `advertise_on_nostr: true` on at least one transport.
3. Check the transport `bind_addr` is a **specific IP**, not `0.0.0.0`.
4. Check `sudo journalctl -u fips | grep -i "advert\|publish"` for errors.
5. Check the relays are reachable: `nak relay wss://nos.lol`.
6. Bump log level to debug:
```bash
sudo systemctl edit fips # add: Environment=RUST_LOG=fips::discovery::nostr=debug
sudo systemctl restart fips
sudo journalctl -u fips -f | grep -i advert
```
### Peer count not growing
- If the advert is live but peers aren't connecting, check firewall
rules (UDP 2121, TCP 8443 must be open inbound).
- Check `sudo fipsctl show routing` → `retries` for peers in backoff.
- Check `sudo journalctl -u fips | grep -i "handshake\|connect"` for
handshake failures.
### `fips.v0l.io` alias rejected ("invalid character '.'")
The alias validator in `fips` doesn't allow dots. Use dashes:
`fips-v0l-io` instead of `fips.v0l.io`. The `addresses[].addr` field
can still use dots (it's a hostname, not an alias). This config already
uses `fips-v0l-io` as the alias.
### `Address family not supported by protocol (os error 97)` on handshake
Seen intermittently on some links. Usually an IPv6/IPv4 mismatch on a
specific peer's transport. Not blocking; the daemon retries. If it
persists for a specific peer, check that the peer's advertised address
matches a transport you have enabled.
## Useful commands (cheat sheet)
```bash
# Status overview
sudo fipsctl show status
# Peers (direct links)
sudo fipsctl show peers
# Spanning tree position + peer tree positions
sudo fipsctl show tree
# Bloom filter state (per-peer reachability counts)
sudo fipsctl show bloom
# Coordinate cache (named nodes with fresh paths)
sudo fipsctl show cache
# Routing state (pending lookups, retries, counters)
sudo fipsctl show routing
# All known node identities
sudo fipsctl show identity-cache
# Link quality (MMP metrics)
sudo fipsctl show mmp
# Active links
sudo fipsctl show links
# End-to-end sessions
sudo fipsctl show sessions
# Historical peer count sparkline
sudo fipsctl stats history peer_count --window 1h --plot
# Live logs
sudo journalctl -u fips -f
# Check advert on Nostr
nak req -k 37195 -a c0c3f6b0952e527146aeff70ee4a69e34959b7d62eba069a8e44f58b0962f08e --stream wss://nos.lol wss://offchain.pub
```
## Config reference (what each block does)
See [`../../fips_playground/plans/fips-explained.md`](../../fips_playground/plans/fips-explained.md)
for the full freshman-level explanation of how FIPS works. Quick map of
how this config maps to the four layers described there:
| Config block | Layer | What it does |
|---|---|---|
| `node.discovery.nostr` | Discovery | Nostr rendezvous: publish/subscribe kind 37195 adverts |
| `transports.*` | Links | UDP/TCP/Tor listeners; `advertise_on_nostr` + `public` (UDP) + `advertised_port` (Tor) control advert content |
| `peers` | Links | Static peers to auto-connect on startup (the 2 you typed in) |
| `tun` | Application | `fips0` TUN device for `.fips` IPv6 routing |
| `dns` | Application | Local DNS resolver mapping `<npub>.fips` → mesh IPv6 |
The spanning tree, bloom filters, and coordinate cache (layers 34 in
the explained doc) are automatic — no config needed.
## Backups on the server
The server keeps timestamped backups of `fips.yaml` in `/etc/fips/`:
```
fips.yaml.bak.20260720_141730 # original (before advert fix)
fips.yaml.bak.20260720153329 # pre-fix snapshot
```
These are created manually with `sudo cp /etc/fips/fips.yaml /etc/fips/fips.yaml.bak.$(date +%Y%m%d%H%M%S)`.
## Date of this snapshot
2026-07-20. Config verified working: advert live on relays with all
three transports (UDP, TCP, Tor), peer_count climbing (6 → 13+ within
3 minutes of the initial fix), mesh_size ~1231. Tor endpoint added
in a follow-up edit the same day.

View File

@@ -0,0 +1 @@
npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud

View File

@@ -0,0 +1,28 @@
# systemd unit for the FIPS daemon
# Installed at: /etc/systemd/system/fips.service
# Enable with: sudo systemctl enable --now fips
[Unit]
Description=FIPS Mesh Network Daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/fips --config /etc/fips/fips.yaml
Restart=on-failure
RestartSec=5
# Control socket directory (/run/fips/).
# Group-accessible so 'fips' group members can use fipsctl/fipstop.
RuntimeDirectory=fips
RuntimeDirectoryMode=0750
# Security hardening (daemon runs as root for TUN and raw sockets)
ProtectHome=yes
PrivateTmp=yes
ProtectKernelModules=yes
ProtectKernelTunables=no
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,60 @@
# FIPS daemon configuration for laantungir.net
# Server: VPS at 15.235.3.231 (public IP, no NAT)
# Node identity: npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud
# NodeAddr: e3da8293e6ea58807bd02b4749824243
# Installed at: /etc/fips/fips.yaml
#
# IMPORTANT: This config binds transports to the public IP 15.235.3.231
# (not 0.0.0.0) so that build_overlay_advert() produces a non-unspecified
# local_addr() and the endpoints actually get advertised on Nostr.
# See README.md "Why bind to the public IP" for the full rationale.
node:
identity:
persistent: true
discovery:
nostr:
enabled: true
advertise: true
policy: open
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "15.235.3.231:2121"
advertise_on_nostr: true # REQUIRED: defaults to false; without this no advert is published
public: true # Advertise as host:port (not "nat") — server has a public IP
tcp:
bind_addr: "15.235.3.231:8443"
advertise_on_nostr: true # REQUIRED: defaults to false
tor:
mode: "directory"
socks5_addr: "127.0.0.1:9050"
directory_service:
hostname_file: "/var/lib/tor/fips_mesh/hostname"
bind_addr: "127.0.0.1:9443"
advertise_on_nostr: true # REQUIRED: defaults to false; without this Tor endpoint isn't advertised
advertised_port: 9443 # Must match the virtual port in torrc's HiddenServicePort directive
peers:
- npub: "npub1qmc3cvfz0yu2hx96nq3gp55zdan2qclealn7xshgr448d3nh6lks7zel98"
alias: "fips-test-node"
addresses:
- transport: udp
addr: "217.77.8.91:2121"
connect_policy: auto_connect
- npub: "npub1zv58cn7v83mxvttl70w5fwjwuclfmntv9cnmv5wmz2nzz88u5urqvdx96n"
alias: "fips-v0l-io" # NOTE: dots are rejected by the alias validator; use dashes
addresses:
- transport: udp
addr: "fips.v0l.io:2121"
connect_policy: auto_connect

8
configs/laantungir/hosts Normal file
View File

@@ -0,0 +1,8 @@
# /etc/fips/hosts — FIPS mesh host aliases
# Maps friendly names to npubs so you can do `curl http://<name>.fips/...`
# Format: <alias> <npub>
# NOTE: aliases must not contain dots (the validator rejects them).
laantungir npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud
relay npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud
blossom npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud

View File

@@ -0,0 +1,16 @@
# Tor hidden service for the FIPS mesh
# Add these lines to /etc/tor/torrc (or a file in /etc/tor/torrc.d/)
# then restart tor: sudo systemctl restart tor
#
# This exposes the FIPS daemon's Tor transport (bound to 127.0.0.1:9443)
# as a hidden service so that NAT'd peers can reach you over Tor.
#
# The onion address is written to /var/lib/tor/fips_mesh/hostname
# and read by fipsd at startup to populate the Tor endpoint in the
# Nostr advert. Current onion:
# t62hfd2dvvbmqc5pd6umw2e6fl52g4soshzrfufuvzagkjxjdly5soid.onion
# Keep only FIPS mesh in this managed block.
HiddenServiceDir /var/lib/tor/fips_mesh/
HiddenServicePort 9443 127.0.0.1:9443
# === LAANTUNGIR TOR SERVICES END ===

View File

@@ -0,0 +1,355 @@
# FIPS Browser Extension — Possibilities Discussion
## Context
FIPS uses Nostr keypairs (secp256k1) as node addresses. Browsers access FIPS sites via `.fips` hostnames (e.g., `npub1abc...xyz.fips`) that resolve to `fd00::/8` IPv6 addresses through a local FIPS DNS resolver on port 5354. Traffic is encrypted with Noise IK (hop-by-hop) + Noise XK (end-to-end), but browsers see plain HTTP and show "Not Secure" warnings.
nos2x is an existing browser extension that provides Nostr signing via a `window.nostr` API. Since FIPS uses the same secp256k1 keypairs, a nos2x-based extension could serve double duty: Nostr signing + FIPS features, sharing one keypair.
This document explores **everything** a FIPS browser extension could do, grouped by problem domain.
---
## Problem 1: DNS Resolution for `.fips` Domains
### The Problem
Chromium-family browsers use their own DNS resolution path that bypasses the system resolver. Even when `dig`, `getent`, `ping`, and `curl` all resolve `.fips` correctly, Chromium shows `ERR_NAME_NOT_RESOLVED`. Firefox works because it follows the system resolver path. Current workaround is pinning `.fips` hostnames in `/etc/hosts`, which is manual and fragile.
### What the Extension Could Do
**Option A: `webRequest` DNS interception**
Use the `webRequest` API to intercept requests to `*.fips` domains before they hit the browser's DNS resolver. The extension resolves the hostname itself (either by querying the local FIPS DNS on `127.0.0.1:5354`, or by computing the IPv6 directly from the npub) and redirects the request to the resolved IPv6 address.
- Pros: Works without modifying browser DNS settings; portable
- Cons: `webRequest` is being restricted in Manifest V3; may need `declarativeNetRequest` instead; URL bar still shows `.fips` which is good, but the connection goes to the IPv6
**Option B: `proxy` API with custom PAC script**
Use the `proxy` API to provide a Proxy Auto-Config (PAC) script that matches `*.fips` domains and routes them through a custom proxy handler. The extension resolves the `.fips` name and connects directly to the IPv6 address.
- Pros: Clean separation; PAC scripts are well-supported
- Cons: More complex; need to handle the proxy connection logic
**Option C: Direct npub → IPv6 computation in the extension**
Since the npub → IPv6 derivation is pure computation (npub → public key → SHA-256 → node_addr → `fd00::/8` IPv6), the extension could compute the IPv6 address directly without any DNS query. The extension parses the `npub1...fips` hostname, decodes the bech32 npub, hashes it, and derives the IPv6 address.
- Pros: Zero network dependency for resolution; works even if FIPS DNS isn't running; instant
- Cons: Only works for direct npub addresses (`npub1xxx.fips`), not friendly hostnames (`mysite.fips`); friendly hostnames still need a lookup against `/etc/fips/hosts` or the FIPS DNS resolver
**Option D: Hybrid — compute for npub addresses, query for friendly hostnames**
Combine Option C with a DNS query fallback. For `npub1*.fips`, compute the IPv6 directly. For other `*.fips` names, query the local FIPS DNS resolver on `127.0.0.1:5354` (or a configurable remote address).
- Pros: Best of both worlds; npub addresses always resolve; friendly names work when FIPS DNS is available
- Cons: Slightly more complex implementation
---
## Problem 2: Security Indicators — "Not Secure" Warning
### The Problem
FIPS traffic is encrypted with double-layer Noise protocol encryption (IK hop-by-hop + XK end-to-end), but the browser sees plain HTTP to an IPv6 address. The URL bar shows "Not Secure," which is misleading — the traffic is actually more encrypted than standard TLS.
### What the Extension Could Do
**Option A: Toolbar badge/icon**
Show a custom toolbar icon that changes state based on whether the current page is a `.fips` site. When on a `.fips` site, show a green "FIPS" badge or lock icon. Clicking it shows a popup explaining the FIPS encryption (Noise IK + Noise XK, end-to-end encrypted, mesh-routed).
- Pros: Simple, reliable, no browser security model conflicts
- Cons: Doesn't change the URL bar "Not Secure" text; users have to notice the badge
**Option B: Page-level banner/indicator**
Inject a content script that adds a small banner or corner badge on `.fips` pages showing "FIPS Encrypted" with details on hover. Similar to how the Tor Browser shows onion circuit information.
- Pros: Visible on the page itself; can show rich detail
- Cons: Can be spoofed by page content (though extension can verify the URL is `.fips` first); visually intrusive
**Option C: Local TLS proxy (MITM approach)**
Run a local proxy within the extension (or a companion native app) that terminates TLS for `.fips` domains. The extension generates self-signed certificates for `.fips` hostnames on the fly and installs them as trusted. The browser sees HTTPS with a valid cert; the proxy handles the actual FIPS connection underneath.
- Pros: Browser shows the green lock; proper HTTPS UX
- Cons: Complex; requires native messaging component; certificate trust management is fragile across browsers; security implications of MITM; may conflict with browser security policies
**Option D: Custom protocol handler (`fips://`)**
Register a custom protocol handler for `fips://` URLs. Instead of `http://npub1abc.fips`, users would use `fips://npub1abc`. The extension handles the protocol, resolves the address, and displays the content. The browser wouldn't show HTTP security warnings because it's not HTTP.
- Pros: Clean separation from HTTP; no "Not Secure" issue; distinct UX
- Cons: Links and bookmarks need to use `fips://`; not all browser APIs work with custom protocols; may have rendering limitations; breaks compatibility with existing `.fips` HTTP URLs
**Option E: Badge + popup with circuit details (Tor Browser style)**
Combine Option A with a detailed popup that shows the FIPS "circuit" — which peers your traffic is routing through, the encryption layers, the destination npub, and the mesh path. Modeled after Tor Browser's circuit display.
- Pros: Educational; builds trust; shows real security information
- Cons: Requires access to FIPS daemon routing info; more complex
---
## Problem 3: Nostr Signing (nos2x Feature Parity)
### The Problem
Nostr users need a signing extension to interact with Nostr applications (signing events, encrypting/decrypting messages). nos2x provides this via `window.nostr` API. Since FIPS uses the same secp256k1 keypairs, the extension should provide this functionality too.
### What the Extension Could Do
**Full nos2x parity:**
- Inject `window.nostr` object into web pages
- Implement the NIP-07 API: `getPublicKey()`, `signEvent()`, `getRelays()`, `nip04.encrypt()`, `nip04.decrypt()`, `nip44.encrypt()`, `nip44.decrypt()`
- Prompt user to approve/reject signing requests (popup with event details)
- Maintain a whitelist of sites allowed to request signatures
**FIPS-specific signing:**
- Sign FIPS authentication challenges (prove you own the npub that a FIPS service expects)
- Sign FIPS peer introduction messages
- Use the same keypair for both Nostr and FIPS identity — your `window.nostr` public key IS your FIPS node address
**Multi-identity support:**
- Manage multiple keypairs (different Nostr/FIPS identities)
- Switch between identities per-site
- Each identity has its own npub, .fips address, and IPv6
---
## Problem 4: FIPS Identity Management
### The Problem
Users need to generate, import, and manage their FIPS keypair. Currently this is done via command-line scripts (`03-configure-identity.sh`). The extension could provide a GUI for this.
### What the Extension Could Do
- **Generate new keypair** — Create a new secp256k1 keypair; show the npub, .fips address, and IPv6
- **Import existing nsec** — Import a Nostr private key to use as FIPS identity
- **Export keypair** — Backup your private key (with encryption/password protection)
- **Display identity info** — Show your npub, .fips address, IPv6 address, and QR codes for sharing
- **Multiple identities** — Create and switch between multiple FIPS/Nostr identities
- **Identity linking** — Show that your Nostr identity and FIPS identity are the same key
---
## Problem 5: FIPS Node Status and Monitoring
### The Problem
Users need to know if their FIPS node is running, connected to peers, and reachable. Currently this requires `fipstop` TUI or `fipsctl` CLI. The extension could show this in the browser.
### What the Extension Could Do
- **Connection status** — Show if the local FIPS node is running and reachable
- **Peer list** — Show connected peers, their npubs, aliases, and transport types
- **Mesh status** — Show network size, your tree depth, routing coordinates
- **Your address** — Show your npub, .fips hostname, and IPv6 address
- **Health alerts** — Notify if FIPS node goes down, peer count drops to zero, etc.
- **Traffic stats** — Show bytes sent/received over FIPS mesh
Implementation: The extension would communicate with the local FIPS daemon via its control socket/API (same API that `fipsctl` uses). This could be via HTTP, WebSocket, or a native messaging port.
---
## Problem 6: FIPS Hosts Management (Friendly Hostnames)
### The Problem
Friendly `.fips` hostnames (like `mysite.fips`) require entries in `/etc/fips/hosts` on the visitor's node. This is a manual command-line operation. The extension could provide a UI for managing these mappings.
### What the Extension Could Do
- **View host mappings** — List all friendly hostname → npub mappings
- **Add/edit/remove mappings** — GUI for managing `/etc/fips/hosts` entries
- **Auto-add from Nostr** — If a Nostr profile lists a FIPS address, auto-suggest adding it to hosts
- **Bookmark .fips sites** — Save and organize .fips sites you visit
- **Share host mappings** — Export/import host mapping lists
- **Sync via Nostr** — Publish your host mappings as a Nostr event so followers can import them
Implementation: The extension would need to either (a) write to `/etc/fips/hosts` via native messaging, or (b) maintain its own host map and use it for DNS resolution (combining with Problem 1's hybrid approach).
---
## Problem 7: FIPS Address Detection and Linkification
### The Problem
When someone shares a FIPS npub address in a web page, chat, or Nostr note, it's just text. Users have to manually copy it and construct a `.fips` URL. The extension could make npubs clickable.
### What the Extension Could Do
- **Detect npubs on pages** — Scan page content for `npub1...` patterns and `.fips` URLs
- **Linkify npubs** — Convert `npub1abc...` text into clickable links to `http://npub1abc.fips`
- **Context menu** — Right-click an npub to open it as a `.fips` site, copy the `.fips` URL, or add to hosts
- **QR code generation** — Generate QR codes for `.fips` addresses for easy sharing
- **Omnibox support** — Type an npub directly in the address bar and have it resolve to the `.fips` site
---
## Problem 8: FIPS Content Discovery
### The Problem
There's no directory of FIPS-hosted services. Users have to know the npub of the node they want to visit. The extension could help users discover FIPS content.
### What the Extension Could Do
- **FIPS directory** — Maintain a curated or crowd-sourced list of known FIPS services (websites, relays, git servers, etc.)
- **Nostr integration** — Use Nostr events to discover FIPS services (e.g., a NIP that publishes "I host a FIPS service at npub X")
- **Bookmarks** — Pre-populate with known FIPS services from the project
- **Search** — Search across known FIPS services by name, type, or npub
---
## Problem 9: Gateway/Proxy for Non-FIPS Users
### The Problem
Only people running a FIPS node can access `.fips` sites. This limits the audience. A gateway could let non-FIPS users access FIPS content through a public FIPS node.
### What the Extension Could Do
- **Gateway routing** — Route `.fips` requests through a public FIPS gateway node when no local FIPS node is detected
- **Gateway selection** — Let users choose a trusted gateway (or run their own)
- **Trust indicators** — Show whether you're accessing FIPS directly (full encryption) or through a gateway (gateway sees traffic)
- **Fallback mode** — Automatically fall back to gateway mode when local FIPS node is down
Caveat: Gateway access breaks the end-to-end encryption promise — the gateway operator can see traffic. This should be clearly communicated. This is similar to how Tor has entry nodes vs. running your own relay.
---
## Problem 10: Mixed Content and Resource Loading
### The Problem
A `.fips` web page might load resources (images, scripts, CSS) from the regular internet, creating a mixed content situation. Some resources are FIPS-encrypted, others are not.
### What the Extension Could Do
- **Mixed content indicator** — Show what percentage of page resources are FIPS vs. clearnet
- **Resource list** — Show which resources loaded over FIPS vs. regular internet
- **Upgrade to FIPS** — If a resource is available over FIPS (same npub), automatically upgrade the request
- **Block non-FIPS resources** — Option to block all non-FIPS resources on `.fips` pages for maximum privacy
---
## Problem 11: FIPS Peer Management
### The Problem
Adding FIPS peers currently requires command-line scripts (`08-add-peer.sh`). The extension could provide a GUI for peer management.
### What the Extension Could Do
- **View peer list** — Show configured peers with their npub, alias, transport, and connection status
- **Add/remove peers** — GUI for adding new peers (npub + transport address)
- **Peer discovery** — Discover peers via Nostr (e.g., people publishing their FIPS node address in Nostr profiles)
- **Bootstrap peers** — One-click add of known bootstrap peers
- **Peer health** — Show latency, packet loss, and uptime per peer
Implementation: Via native messaging to the FIPS daemon, or via the FIPS control API.
---
## Problem 12: Qubes OS Integration
### The Problem
In Qubes OS, the browser runs in an AppVM while FIPS runs in `sys-fips` ProxyVM. The extension in the AppVM browser needs to communicate with the FIPS node in a different VM.
### What the Extension Could Do
- **Cross-VM status** — Show FIPS node status from `sys-fips` in the AppVM browser
- **Qubes-aware DNS** — Handle the Qubes DNS forwarding path (AppVM → sys-fips dnsmasq → FIPS DNS)
- **VM health** — Show if `sys-fips` is running and properly chained
- **Auto-configure** — Detect Qubes environment and configure DNS resolution accordingly
---
## Problem 13: Nostr-FIPS Crossover Features
### The Problem
Since FIPS and Nostr share the same keypair, there are natural crossover features that neither system alone provides.
### What the Extension Could Do
- **Nostr profile → FIPS address** — When viewing a Nostr profile, show their FIPS `.fips` address and offer to visit it
- **FIPS site → Nostr identity** — When visiting a `.fips` site, look up the npub on Nostr and show their Nostr profile, posts, and relay list
- **Verified identity** — Cross-verify: the FIPS node you're connected to proves it owns the npub, and the Nostr profile proves the same npub — therefore the FIPS site is authentically operated by that Nostr identity
- **Nostr relay over FIPS** — Connect to Nostr relays hosted on FIPS nodes (e.g., `ws://npub1abc.fips`) with the extension handling both the FIPS connection and Nostr signing
- **FIPS service announcements via Nostr** — Publish and consume Nostr events that announce FIPS services (kind: FIPS service descriptor, content: service type, npub, port)
---
## Architecture Considerations
### Manifest V2 vs V3
- `webRequest` blocking is restricted in Manifest V3 (Chrome). Firefox still supports it with some changes.
- `declarativeNetRequest` is the MV3 replacement but is less flexible.
- For DNS interception, the `proxy` API may be more future-proof.
- Firefox supports both MV2 and MV3; Chrome is MV3-only.
### Native Messaging
Some features (writing to `/etc/fips/hosts`, communicating with FIPS daemon, managing system-level config) require native messaging — a companion native app that the extension communicates with via stdio.
- Pros: Full system access; can talk to FIPS daemon directly
- Cons: Requires installing a native component; not portable to all platforms; more complex setup
### Shared Keypair Architecture
The keypair is the foundation. The extension should:
1. Store the private key securely (browser extension storage, encrypted)
2. Derive everything from it: npub, .fips address, IPv6 address
3. Use it for both Nostr signing and FIPS identity
4. Optionally sync with a hardware signer (NIP-46, Nostr Connect)
### Communication with FIPS Daemon
Options for talking to the FIPS daemon from the extension:
- **HTTP API** — If FIPS exposes a control API on localhost
- **WebSocket** — If FIPS exposes a WebSocket control interface
- **Native messaging** — Companion app that talks to FIPS daemon
- **File-based** — Read `/etc/fips/fips.yaml`, `/etc/fips/hosts`, `/etc/fips/fips.pub` directly (via native messaging)
---
## Summary: Feature Priority Map
| Feature | Problem Solved | Complexity | Impact |
|---|---|---|---|
| DNS resolution for `.fips` | Chromium can't resolve `.fips` | Medium | Critical — nothing works without this |
| Security indicator badge | "Not Secure" warning is misleading | Low | High — user trust |
| Nostr signing (nos2x parity) | Nostr app interaction | Medium | High — core feature for Nostr users |
| Identity management GUI | Key management is CLI-only | Medium | Medium — convenience |
| Node status monitoring | No visibility into FIPS state | Medium | Medium — operational |
| Hosts management GUI | Friendly hostnames are CLI-only | Low | Medium — convenience |
| npub linkification | npubs aren't clickable | Low | Medium — UX improvement |
| Content discovery | No way to find FIPS sites | High | Medium — ecosystem growth |
| Gateway for non-FIPS users | Only FIPS users can access | Very High | High — adoption, but breaks E2E |
| Mixed content handling | Partial encryption visibility | Medium | Low — edge case |
| Peer management GUI | Peer management is CLI-only | Medium | Low — operational |
| Qubes integration | Cross-VM communication | High | Low — Qubes-specific |
| Nostr-FIPS crossover | Identity verification, discovery | High | High — unique value proposition |
---
## Open Questions for Discussion
1. Which of these features feel most essential vs. nice-to-have?
2. Is the gateway/proxy approach (Problem 9) worth exploring, or should we focus on users who already run FIPS?
3. How much of this should be in the extension vs. a companion native app?
4. Should we target Firefox first (better webRequest support, system DNS) or Chrome/Chromium first (bigger user base, worse DNS behavior)?
5. Is the Nostr-FIPS crossover (Problem 13) a compelling enough unique feature to prioritize?
6. Should the extension try to replace the FIPS DNS resolver entirely (computing npub → IPv6 in the extension), or work alongside it?

View File

@@ -0,0 +1,271 @@
# FIPS Extension: Accessing .fips Content Without a Local Daemon
## The Question
> If a user came across a website with FIPS links embedded in it, could the browser extension resolve those links and make them usable without the user having the FIPS daemon installed and running?
## Short Answer
**Yes, but with important tradeoffs.** The extension can always resolve `.fips` names (the math is pure computation). But actually *connecting* to the destination requires either a local FIPS node or a gateway. There are three approaches, ranging from simple-but-less-private to ambitious-but-fully-encrypted.
---
## Why Name Resolution Works Without a Daemon
The npub → IPv6 derivation is **pure computation** — no network, no daemon, no DNS:
```
npub1qmc3... (bech32 string)
↓ decode
32-byte public key
↓ SHA-256, take first 16 bytes
node_addr (16 bytes)
↓ prepend 0xfd
fd00::/8 IPv6 address
```
The extension can do this entirely in JavaScript. Bech32 decoding and SHA-256 are both trivial in JS (Web Crypto API has `crypto.subtle.digest('SHA-256', ...)`). So for any `npub1xxx.fips` URL, the extension can compute the target IPv6 address instantly, with zero network dependency.
**Friendly hostnames** (`mysite.fips` instead of `npub1xxx.fips`) are different — those require a lookup against `/etc/fips/hosts` or the FIPS DNS resolver. Without a daemon, the extension would need its own host map (stored in extension storage) or a gateway that can do the lookup.
---
## The Connectivity Problem
Here's the catch: the resolved IPv6 address (e.g., `fdab:1234:...`) lives in the `fd00::/8` ULA range. This address only exists **inside the FIPS mesh**. Without a FIPS node running:
- There's no `fips0` TUN interface
- There's no route for `fd00::/8` traffic
- The OS has nowhere to send packets to that IPv6 address
- The connection fails with "No route to host"
So resolution works, but connectivity doesn't — unless we provide an alternative path.
---
## Three Approaches to Daemon-Free Access
### Approach 1: Gateway Proxy (Simple, Less Private)
The extension routes `.fips` requests through a public FIPS gateway — a server that runs FIPS and acts as an HTTP proxy for non-FIPS users.
**How it works:**
```
User browser (no FIPS)
↓ extension intercepts request to npub1abc.fips
↓ extension resolves npub → IPv6 (pure computation)
↓ extension sends request to gateway: "fetch http://[fdab:...]/ for me"
FIPS Gateway (runs FIPS daemon)
↓ receives proxy request
↓ connects to fdab:... through its local FIPS mesh
↓ fetches the content
↓ returns it to the extension
User sees the FIPS-hosted page
```
**Implementation in the extension:**
- Use the `proxy` API to route `*.fips` requests through a configured gateway URL
- The gateway is an HTTP CONNECT proxy or a reverse proxy that accepts `.fips` hostnames
- Extension resolves the npub locally and passes the resolved address to the gateway (or just passes the hostname and lets the gateway resolve it)
**Tradeoffs:**
- ✅ Simple to implement — just a proxy configuration
- ✅ Works for all `.fips` content (HTTP, WebSocket, etc.)
- ✅ Gateway can be self-hosted (your own FIPS node on a VPS) or public
-**Gateway operator can see all traffic** — breaks end-to-end encryption
- ❌ Trust required in the gateway operator
- ❌ Gateway becomes a centralization point (though users can choose their own)
**This is analogous to Tor2Web gateways** — they let non-Tor users access `.onion` sites, but the gateway can see the traffic. It's a tradeoff between accessibility and privacy.
**Mitigations:**
- Let users run their own gateway (their FIPS node on a VPS or home server)
- Support multiple gateways and let users choose
- Clearly indicate in the UI when you're on "gateway mode" vs "direct FIPS mode"
- Sites can use end-to-end app-level encryption (e.g., the site itself serves over HTTPS with a self-signed cert whose fingerprint is published via Nostr) on top of the FIPS transport
---
### Approach 2: Lightweight Browser FIPS Client (Ambitious, Fully Encrypted)
The extension implements enough of the FIPS protocol in JavaScript to participate in the mesh directly, without a full daemon. It connects to a FIPS bootstrap node via WebSocket and establishes an end-to-end encrypted session to the destination.
**How it works:**
```
User browser (no FIPS daemon)
↓ extension intercepts request to npub1abc.fips
↓ extension resolves npub → IPv6 (pure computation)
↓ extension connects to a FIPS bootstrap node via WebSocket
↓ extension runs FSP (FIPS Session Protocol) — Noise XK handshake
↓ extension establishes end-to-end encrypted session to npub1abc
↓ extension sends HTTP request through the encrypted session
FIPS Bootstrap Node (just a regular FIPS peer)
↓ receives the session packet
↓ routes it through the mesh toward npub1abc
↓ (bootstrap node can't read the payload — it's end-to-end encrypted)
Destination node (npub1abc)
↓ receives session, decrypts payload
↓ returns HTTP response through the encrypted session
User sees the FIPS-hosted page — fully end-to-end encrypted
```
**What the extension would need to implement in JS:**
- Bech32 decoding (for npub parsing) — trivial
- SHA-256 (for node_addr derivation) — Web Crypto API
- secp256k1 (for keypair generation and signing) — existing JS libraries (e.g., @noble/secp256k1, nostr-tools)
- Noise XK handshake (for end-to-end session establishment) — JS Noise implementation or custom
- FSP session protocol (framing, port multiplexing) — port from Rust
- WebSocket transport (to connect to a FIPS bootstrap node) — native browser API
- HTTP-over-FSP (carry HTTP requests/responses over the FSP session) — custom layer
**Tradeoffs:**
-**Full end-to-end encryption** — the bootstrap node can't read your traffic
- ✅ No trust required in the bootstrap node (it's just a router)
- ✅ True to FIPS's privacy model
- ✅ The extension IS a FIPS node (lightweight, browser-based)
- ❌ Significant implementation effort — Noise protocol, FSP, mesh routing in JS
- ❌ Browser limitations: WebSocket is the only viable transport (no raw UDP/TCP)
- ❌ Can't run a full mesh node in a browser (no TUN, no listening sockets, no UDP)
- ❌ Performance — JS crypto is slower than Rust, though WebCrypto helps
- ❌ The bootstrap node needs to support WebSocket inbound (FIPS does support TCP, and WebSocket is essentially TCP-over-HTTP)
**This is the most exciting option.** It means the extension is a **first-class FIPS client** — not just a proxy consumer. The user gets real end-to-end encryption without installing anything. The browser extension becomes the FIPS node, using a bootstrap peer the same way a full FIPS node would.
**Key insight:** The extension doesn't need to implement the full mesh protocol (FMP with spanning tree, bloom filters, routing). It only needs:
1. **FSP** (session protocol) — to establish end-to-end encrypted sessions
2. **A single transport** — WebSocket to a bootstrap node
3. **The bootstrap node handles mesh routing** — it receives the session packet and forwards it through the mesh using FMP
The extension is a "FIPS client" not a "FIPS router." It doesn't relay traffic for others; it just creates sessions to destinations through a bootstrap peer. This is a much smaller scope than a full FIPS node.
---
### Approach 3: Hybrid — Gateway Now, Browser Client Later
Start with Approach 1 (gateway proxy) for initial release, then evolve to Approach 2 (browser FIPS client) as the project matures.
**Phase 1:** Extension uses gateway proxy mode. Users can access `.fips` content immediately. The extension clearly shows "gateway mode" with a warning about reduced privacy.
**Phase 2:** Extension implements FSP over WebSocket. Users who want full encryption can switch to "direct mode." Gateway mode remains as a fallback.
**Phase 3:** Extension can optionally become a full FIPS node (with WebRTC for UDP-like transport, service worker for background operation, etc.) — but this is speculative and far future.
---
## The User Experience
Here's how it would look to a user who discovers a FIPS link:
### Scenario: Alice publishes a blog post with a FIPS link
1. **Bob visits Alice's regular blog** (clearnet, `https://alice.com/blog`)
2. The blog post contains a link: `<a href="http://npub1qmc3...98.fips/">My FIPS-hosted mirror</a>`
3. **Bob has the extension installed** but no FIPS daemon
4. Bob clicks the link
5. The extension:
- Detects the `.fips` URL
- Resolves `npub1qmc3...98``fdab:1234:...` (pure computation, instant)
- Checks for local FIPS node → not found
- Falls back to gateway mode (or browser client mode)
- Fetches the content through the gateway/mesh
- Displays the page in the browser
6. **Bob sees Alice's FIPS-hosted mirror** — he didn't need to install FIPS
### What the extension shows Bob:
- **Toolbar badge:** "FIPS" with a color indicating mode:
- 🟢 Green: Direct FIPS (local daemon, full encryption)
- 🟡 Yellow: Browser client mode (end-to-end encrypted via WebSocket bootstrap)
- 🟠 Orange: Gateway mode (gateway can see traffic)
- **Popup on click:** Shows the destination npub, the resolved IPv6, the connection mode, and the encryption status
- **Page indicator:** Small banner on first visit explaining "This site is hosted on the FIPS mesh"
### Linkification on regular pages:
The extension can also **scan pages for npub addresses** and linkify them. If a page contains the text `npub1qmc3...98`, the extension converts it to a clickable link to `http://npub1qmc3...98.fips/`. This means FIPS addresses work anywhere — blog posts, Nostr notes, chat messages, forums.
---
## What Makes This Compelling
This is the feature that could drive FIPS adoption. Right now, FIPS has a chicken-and-egg problem:
- Content creators won't host on FIPS if no one can access it
- Users won't install FIPS if there's no content to access
The extension breaks this cycle:
1. **Content creators** host on FIPS (they run a FIPS node — it's their server)
2. **Regular users** install the browser extension (one click, no daemon)
3. The extension makes `.fips` links work transparently
4. Users who want full privacy can upgrade to running a FIPS node later
5. The extension's Nostr signing features (nos2x parity) give users another reason to install it
**The extension becomes the FIPS on-ramp.** You install it for Nostr signing (like nos2x), and as a side effect, `.fips` links just work. You don't even need to know what FIPS is — the extension handles it.
---
## Technical Feasibility Assessment
### Approach 1 (Gateway) — Feasible Now
| Component | Status |
|---|---|
| npub → IPv6 in JS | Trivial — bech32 + SHA-256 |
| `proxy` API for routing | Standard browser extension API |
| Gateway server | Needs to be built — an HTTP proxy that connects through FIPS |
| Friendly hostname support | Extension can maintain its own host map, or gateway can resolve |
The gateway server is the main thing that needs to be built. It's essentially:
- An HTTP proxy that accepts requests for `*.fips` hostnames
- Resolves the hostname (npub computation or FIPS DNS)
- Connects to the destination through its local FIPS mesh
- Returns the response
This could be a simple Rust service built on top of the FIPS library, or even a configuration of an existing proxy (like a custom nginx module or a small Go/Rust HTTP proxy).
### Approach 2 (Browser Client) — Feasible But Significant Work
| Component | Status |
|---|---|
| npub → IPv6 in JS | Trivial |
| secp256k1 in JS | Existing libraries (@noble/secp256k1, nostr-tools) |
| Noise XK in JS | Needs implementation or port — no widely-used JS Noise library |
| FSP framing in JS | Needs port from Rust |
| WebSocket transport | Native browser API |
| HTTP-over-FSP | Needs design and implementation |
| Bootstrap node WebSocket support | FIPS supports TCP; WebSocket gateway needed (or FIPS adds WS transport) |
The Noise XK implementation is the hardest part. There are Noise libraries in various languages but a production-quality JS implementation that matches FIPS's exact handshake would need work. The FSP framing is straightforward — it's a binary protocol with length-prefixed messages.
The bootstrap node would need to accept WebSocket connections. FIPS currently supports TCP, and a WebSocket is essentially TCP with an HTTP upgrade handshake. Either:
- FIPS adds a WebSocket transport (small addition to the transport layer)
- A small bridge service converts WebSocket → TCP for the FIPS bootstrap node
### Approach 3 (Hybrid) — Most Pragmatic
Start with gateway, ship something that works, then build the browser client for users who want full encryption. This gives the project:
- Immediate value (gateway mode works day one)
- A path to full privacy (browser client mode)
- A reason for users to upgrade from gateway to direct mode
---
## Open Questions
1. **Gateway trust model:** Should the extension ship with default gateways, or require users to configure their own? Shipping defaults is more convenient but creates trust assumptions.
2. **Browser client scope:** If we go with Approach 2, should the extension implement the full FSP protocol, or just enough for HTTP GET/POST? WebSocket support in FIPS would be needed either way.
3. **Friendly hostnames without a daemon:** Should the extension maintain its own host map (synced via Nostr?) for friendly names, or should we focus on npub addresses for daemon-free users?
4. **Nostr-based discovery:** Could we use Nostr events to publish FIPS service directories? E.g., a NIP where kind:XXXX events announce "I host service Y at npub Z on port P." The extension could index these and provide a FIPS service browser.
5. **Gateway as a FIPS feature:** Should the gateway proxy be a first-class FIPS feature (built into `fipsctl` or as a separate `fips-gateway` binary), or a separate project?

View File

@@ -0,0 +1,260 @@
# 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`](plans/fips-overview.md:38)):
```
┌─────────────────────────────────────────────────┐
│ 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`](plans/fips-overview.md:117):
> 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.