Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 31 additions & 30 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ non-Fedora host.
│ │ cp ISO → /out/ │ │ │
│ │ sha256sum → /out/SHA256SUMS │ │ │
│ └───────────────────────────────────────────────────┘ │ │
└──────────────────────────────────────────────────┘ │
│ └───────────────────────────────────────────────────────┘
│ │
│ ├─[signing]─ si-sign-outputs.sh --no-smoke │
│ │ └── gpg --detach-sign ISO + SHA256SUMS │
Expand Down Expand Up @@ -157,7 +157,7 @@ flake.nix
├── modules/builder.nix podman + make + git so the live image can
│ rebuild safe-image (self-hosting)
├── modules/sway.nix Sway WM, keybindings, fonts, dark mode
├── modules/netmode.nix si-netmode, nftables, polkit, systemd
├── modules/netmode.nix safe-netmode, nftables, polkit, systemd
├── modules/yubikey-gpg.nix GPG agent, YubiKey udev rules, scdaemon,
│ pcscd, safe-yubikey-fetch-pubkey command
└── modules/docs.nix safe-docs (glow TUI + ghostwriter), in-image docs
Expand All @@ -183,7 +183,7 @@ BIOS/UEFI
│ └── re-apply udev rules to USB devices
│ (ensures MODE="0666" on YubiKey in QEMU)
├── si-netmode-default-offline.service (oneshot)
├── safe-netmode-default-offline.service (oneshot)
│ runs before network-pre.target and NetworkManager
│ ├── nftables lockdown table active
│ ├── rfkill block all
Expand All @@ -208,35 +208,36 @@ which is before NetworkManager, DHCP, or any network service can start.
## Network mode state machine

```
┌─────────────────────────┐
BOOT (offline) │
└────────────┬────────────┘
si-netmode-default-offline.service
┌─────────────────────────
│ BOOT (offline)
└────────────┬────────────
safe-netmode-default-offline.service
┌──────────────────────────┐
┌──────▶│ OFFLINE |◀───────┐
│ │ - nftables lockdown │ │
│ │ - rfkill block all │ │
│ │ - interfaces down │ │
│ │ - routes flushed │ │
│ │ - NetworkManager off │ │
│ └────────────┬─────────────┘ │
│ │ si-netmode online │
│ │ (sudo / polkit) │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ ONLINE │ │
│ │ - nftables: outbound ok │ │
│ │ - rfkill unblock all │ │
│ │ - interfaces up │ │
│ │ - NetworkManager on │ │
│ └────────────┬─────────────┘ │
│ │ si-netmode offline │
└─────────────────────┘ (sudo / polkit) │
Sway keybindings: │
Mod+Shift+i → si-netmode online ───┤
Mod+Shift+o → si-netmode offline ──┘
┌───────▶│ OFFLINE │◀────────────┐
│ │ - nftables lockdown │ │
│ │ - rfkill block all │ │
│ │ - interfaces down │ │
│ │ - routes flushed │ │
│ │ - NetworkManager off │ │
│ └────────────┬─────────────┘ │
│ │ safe-netmode online │
│ │ (sudo / polkit) │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ ONLINE │ │
│ │ - nftables: outbound ok │ │
│ │ - rfkill unblock all │ │
│ │ - interfaces up │ │
│ │ - NetworkManager on │ │
│ └────────────┬─────────────┘ │
│ │ safe-netmode offline │
└─────────────────────┘ (sudo / polkit) │
Sway keybindings (wrappers add a desktop │
notification, then call safe-netmode): │
Mod+Shift+i → safe-network-on ────────┤
Mod+Shift+o → safe-network-off ────────┘
```

Mode transitions are atomic: the old nftables table is deleted before the new
Expand Down
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ SPICE=1 make test-gui # use spice-app + remote-viewer (clipboard on Fedora
out/
├── safe-live-nixos-sway-<label>-x86_64-linux.iso # live ISO
├── safe-live-nixos-sway-<label>-x86_64-linux.iso.asc # GPG signature (after sign)
├── SHA256SUMS # checksums
└── SHA256SUMS.asc # checksum signature (after sign)
├── SHA256SUMS # checksums
└── SHA256SUMS.asc # checksum signature (after sign)
```

The ISO is not committed to the repository. See [Reproducibility](#reproducibility).
Expand Down Expand Up @@ -150,7 +150,7 @@ Replace `/dev/sdX` with your USB device. For full write-back verification see
│ ├── base.nix # Base packages, user, autologin, serial console
│ ├── builder.nix # Podman + make + git so the image can rebuild itself
│ ├── sway.nix # Sway WM, keybindings, fonts, dark mode
│ ├── netmode.nix # si-netmode, nftables, polkit, systemd service
│ ├── netmode.nix # safe-netmode, nftables, polkit, systemd service
│ ├── yubikey-gpg.nix # GPG, YubiKey udev rules, scdaemon PC/SC config
│ └── docs.nix # safe-docs command, in-image documentation
├── image/
Expand Down Expand Up @@ -202,9 +202,9 @@ Toggle from Sway (Mod = Win/Super key):
Or from the terminal:

```bash
sudo si-netmode offline
sudo si-netmode online
si-netmode status
sudo safe-netmode offline
sudo safe-netmode online
safe-netmode status
```

## YubiKey & GPG guides
Expand Down Expand Up @@ -268,26 +268,30 @@ invoke `gpg --card-edit > fetch`, which tries two paths in order:
1. **Card URL** — set with `gpg --card-edit > admin > url`. Used if present.
2. **Keyserver lookup by fingerprint** — fallback when no URL is on the card.
Uses whichever keyserver is configured in `dirmngr.conf` (the live image
ships `keyserver hkp://keyserver.ubuntu.com:80`).
ships `keyserver hkps://keyserver.ubuntu.com:443`).

URL-first is the right order: a URL you control (your own server, a
GitHub raw URL, etc.) is more durable than a public keyserver that may
hold stale data, go down, or strip identity packets. Set one with
`gpg --card-edit > admin > url <https-url-to-pubkey>` once and the
keyserver becomes a fallback rather than the primary path.
keyserver becomes a fallback rather than the primary path. Note that
`ykman` cannot set this field — `gpg --card-edit > admin > url` is the
only supported way. See
[guides/02-yubikey-setup.md](image/docs/guides/02-yubikey-setup.md) for
the full step.

If your key was ever uploaded to the configured keyserver, fetch
typically succeeds even without a URL on the card. If both attempts fail:

```bash
gpg --import /path/to/your-pubkey.asc # from a file
gpg --keyserver hkps://keys.openpgp.org --recv-keys <fingerprint> # try another server
gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-keys <fingerprint> # try another server
```

### Verifying from another machine

```bash
gpg --keyserver hkps://keys.openpgp.org --recv-keys <fingerprint>
gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-keys <fingerprint>
gpg --verify out/safe-live-nixos-sway-*.iso.asc
gpg --verify out/SHA256SUMS.asc
```
Expand Down
2 changes: 1 addition & 1 deletion guides/building-safely.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Replace `/dev/sdX` with your target USB device — verify with `lsblk` first.
### Verify the write

```bash
ISO="out/safe-live-nixos-sway-$(ls out/*.iso | head -1 | grep -oP '\K[^/]+(?=\.iso)').iso"
ISO=$(ls out/*.iso | head -1)
ISO_SIZE=$(stat -c%s "$ISO")
sudo dd if=/dev/sdX bs=4M status=none \
| head -c "${ISO_SIZE}" \
Expand Down
2 changes: 1 addition & 1 deletion guides/debugging-build-failures.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ nix build .#nixosConfigurations.safe-live.config.system.build.isoImage \
--out-link /tmp/result --no-warn-dirty -L
```

`-L` (keep-going + verbose) prints each builder's output live, which is useful
`-L` (`--print-build-logs`) prints each builder's output live, which is useful
when you want to watch the failure in real time rather than retrieve it after
the fact.

Expand Down
12 changes: 6 additions & 6 deletions image/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ Mod = Win/Super key
## Network

```bash
sudo si-netmode offline # full lockdown
sudo si-netmode online # allow outbound
si-netmode status # links, routes, firewall state
safe-status # full system overview
sudo safe-netmode offline # full lockdown
sudo safe-netmode online # allow outbound
safe-netmode status # links, routes, firewall state
safe-status # full system overview
```

Boots offline. Online mode allows outbound traffic and starts NetworkManager.
Expand Down Expand Up @@ -67,7 +67,7 @@ trust dependency on the host's OS or container daemon.
```bash
# 1. Boot the verified live ISO.
# 2. Bring up the network and clone the source.
sudo si-netmode online
sudo safe-netmode online
git clone <repo-url> && cd safe-image
# 3. Build — same Makefile targets as on a normal host.
make build-no-sign # or `make build` to sign with your YubiKey
Expand All @@ -87,7 +87,7 @@ ykman openpgp info # OpenPGP applet status
gpg --card-status # GPG card info

# Fresh boot — pull your public key from the URL stored on the card.
# Needs network: run `sudo si-netmode online` first.
# Needs network: run `sudo safe-netmode online` first.
safe-yubikey-fetch-pubkey
```

Expand Down
18 changes: 16 additions & 2 deletions image/docs/gpg-yubikey.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,27 @@ ykman openpgp info
gpg --card-status
```

## KDF (PIN hashing)

KDF (Key Derived Function) makes `gpg` hash the PIN on the host before
sending it to the card, so the PIN is never transmitted over PC/SC or stored
on the card in clear text. It is enabled per card with
`gpg --card-edit > admin > kdf-setup` and shows up in `gpg --card-status` as
`KDF setting ......: on`.

It **must be turned on while the card is empty** — once subkeys are loaded
(`keytocard`) the setting is locked and changing it returns
`Conditions of use not satisfied`, recoverable only by a full
`ykman openpgp reset`. Provisioning enables it as the first step; see
[guides/02-yubikey-setup.md](guides/02-yubikey-setup.md#1-enable-kdf-then-change-the-yubikey-pins).

## Bootstrap the keyring on a fresh boot

The live image's home is tmpfs, so the GPG keyring starts empty every
boot. Re-import the public key:

```bash
sudo si-netmode online # the fetch needs network
sudo safe-netmode online # the fetch needs network
safe-yubikey-fetch-pubkey # one-shot wrapper around gpg card fetch
```

Expand All @@ -42,7 +56,7 @@ attempts in order:
the card. Used if set.
2. **Keyserver lookup by fingerprint** — used when no URL is set. Pulls
from whichever keyserver is configured in `/etc/gnupg/dirmngr.conf`.
This image ships `hkp://keyserver.ubuntu.com:80`.
This image ships `hkps://keyserver.ubuntu.com:443`.

So even with no URL on the card, the fetch succeeds if your key was
uploaded to that keyserver. The same helper runs automatically inside
Expand Down
34 changes: 31 additions & 3 deletions image/docs/guides/01-generate-keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,47 @@ encryption, and authentication.

## Before you begin

> **STOP -- Verify you are offline.**
### Set the correct system time (briefly online, then offline)

GPG records creation and expiry timestamps **inside** the key material, so the
system clock must be correct *before* you generate anything. The live image is
amnesic and may boot with a wrong clock. Sync it over NTP in a short online
window, then return offline to do all key work:

```bash
sudo safe-netmode online # brief online window for NTP only
sudo timedatectl set-ntp true # enable NTP synchronization
timedatectl status # wait for: System clock synchronized: yes
date -u # sanity-check the date/time (UTC)
sudo safe-netmode offline # disable the network again
```

> **STOP -- Verify you are offline before continuing.**
>
> ```bash
> ip link | grep 'state UP'
> ```
>
> No interfaces should be UP. If any are, run `sudo si-netmode offline`.
> No interfaces should be UP. If any are, run `sudo safe-netmode offline`.

## 1. Create the master key (certify only)

The master key only certifies subkeys -- it does not sign, encrypt, or
authenticate. It will be backed up offline and never stored on the
YubiKey.

First, generate a strong random passphrase for the master (certify) key. This
emits 36 bytes of randomness, base64-armored (~48 characters) -- far stronger
than anything you would invent:

```bash
gpg --gen-random --armor 1 36
```

Record the output in your offline backup (write it down / store it on the
encrypted backup USB). It is the only thing protecting your certify key, and
you will paste it when GPG prompts for a passphrase below.

```bash
gpg --expert --full-gen-key
```
Expand All @@ -44,7 +71,8 @@ When prompted:
3. Select **Curve 25519** (option 1)
4. Set expiry to **0 (does not expire)** -- subkeys will expire instead
5. Enter your real name and email
6. Set a strong passphrase (you will only need it when certifying)
6. Paste the random passphrase you generated above (you will only need it
when certifying)

Note your key ID:

Expand Down
Loading