feat(nixpak): pass FIDO2 keys to Firefox, Chromium and Chrome (#515)

* feat(nixpak): pass FIDO2 keys to Firefox, Chromium and Chrome

Assisted-by: DeepSeek Harness (deepseek-flash)

* feat(nixpak): support FIDO2 hotplug via udev-maintained node directory

Replace launch-time hidraw discovery with host udev rules that hard-link
FIDO-tagged hidraw nodes into /dev/fido-hidraw and remove them on unplug.
The sandbox dev-binds that directory and gets static /dev/hidrawN symlinks
into it, so keys plugged in after launch work and no stale node survives a
reused hidraw minor.

Narrow the extra mounts from whole /sys and /run/udev to /sys/class/hidraw,
/run/udev/data and /run/udev/control; the policy suite now forbids the
broad mounts and checks the wrapper and bind follow the fido2 option.

Assisted-by: Claude Code (claude-opus-5-5)

* refactor(nixpak): rewrite FIDO2 bwrap wrapper in Python

The bash argument loop was hard to follow. Python inserts the /dev/hidrawN
symlinks before "--" with a single list splice, and the header now explains
the whole host-udev -> bind -> symlink chain step by step.

Assisted-by: Claude Code (claude-opus-5-5)

* docs(canokey): document phone-only management policy

PCs only use the CanoKey; CanoKey Console management stays in the phone
app and the Admin PIN is never typed on a PC. Record what the config
enforces (sandboxed browsers get no raw USB/CCID, no vendor WebUSB udev
rule) and what it does not (uaccess on the raw USB node, PC/SC access to
the Admin applet, CTAP management from browsers).

Assisted-by: Claude Code (claude-opus-5-5)

* docs(canokey): separate management from use, link CanoKey docs

Assisted-by: Claude Code (claude-opus-5-5)

* fix(nixpak): create /dev/fido-hidraw from the udev rule

NixOS applies /dev tmpfiles entries only at boot; switch-to-configuration
runs systemd-tmpfiles-resetup with --exclude-prefix=/dev. After a switch
without reboot the directory was missing and every `ln` failed with exit
code 1, so no browser saw the key. The add rule now runs `mkdir -p` first.

Also fix the documented trigger command: `udevadm trigger` defaults to
`change`, which the add-only rule ignores.

Assisted-by: Claude Code (claude-opus-5-5)

* refactor(nixpak): keep tmpfiles as the only creator of /dev/fido-hidraw

The /dev tmpfiles units run before systemd-udevd on every boot, so the
directory always exists before coldplug. The mkdir in the udev rule only
covered the first switch without reboot and duplicated the tmpfiles rule;
drop it and document the one-time `systemd-tmpfiles --prefix` command.

Assisted-by: Claude Code (claude-opus-5-5)

* docs(nixpak): record FIDO2 hardware validation in Firefox and Chrome

After a reboot on ai with the CanoKey present at boot, udev linked only
the FIDO interface and WebAuthn works in Firefox and Chrome. Chromium and
plugging the key in after launch remain pending.

Assisted-by: Claude Code (claude-opus-5-5)
This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-11-04 13:38:32 +08:00
1 parent 432de26764
commit 2cf3a58028
15 files changed
+171 -11

No files matched your search

+10 -2
View File
@@ -332,6 +332,13 @@ isolation. Source review and socket metadata—not reads of private credentials
Browser media and browserpass are enabled only for Firefox/Chromium/Chrome. Brave Origin and Edge Browser media and browserpass are enabled only for Firefox/Chromium/Chrome. Brave Origin and Edge
are backup browsers with no media grants. browserpass exposes password-store/GPG data and agent are backup browsers with no media grants. browserpass exposes password-store/GPG data and agent
operations. operations.
- Firefox/Chromium/Chrome receive FIDO2 security-key passthrough for WebAuthn/passkeys. Host udev
rules hard-link only FIDO-tagged hidraw nodes into `/dev/fido-hidraw` and remove them on unplug;
the sandbox binds that directory plus read-only `/sys/class/hidraw` and the udev database. The
CanoKey KBD and CCID interfaces, other HID devices and raw USB stay outside the sandbox; Brave
Origin and Edge receive no FIDO2 passthrough. A compromised browser can use the key's FIDO
interface; the key's PIN and touch policy remain the authentication boundary. Hotplug works for
host-network Firefox/Chromium; pasta-isolated Chrome needs the key present at launch.
- Nixpak supplements Chromium's renderer sandbox, which remains enabled. Zoom/WeChat retain their - Nixpak supplements Chromium's renderer sandbox, which remains enabled. Zoom/WeChat retain their
FHS runtimes inside the outer boundary; inner mounts cannot recover hidden host resources. FHS runtimes inside the outer boundary; inner mounts cannot recover hidden host resources.
- Desktop access has limits: GPU drivers remain exposed, PulseAudio permits recording, read-only - Desktop access has limits: GPU drivers remain exposed, PulseAudio permits recording, read-only
@@ -348,8 +355,9 @@ isolation. Source review and socket metadata—not reads of private credentials
- Policy/build passes for all nine wrappers. Interactive validation passed for Firefox, Chrome, - Policy/build passes for all nine wrappers. Interactive validation passed for Firefox, Chrome,
Chromium, Brave Origin, Edge, QQ, Telegram and Zoom; the Chromium smoke test also validates Chromium, Brave Origin, Edge, QQ, Telegram and Zoom; the Chromium smoke test also validates
renderer sandboxing, environment clearing, bind scope and profile persistence. WeChat's renderer sandboxing, environment clearing, bind scope and profile persistence. WeChat's
interactive Wayland check remains pending. See the interactive Wayland check remains pending. FIDO2 browser passthrough passed WebAuthn with the
[runtime validation](../hardening/tests/runtime-validation.md). CanoKey in Firefox and Chrome; Chromium and plugging the key in after launch still need
validation. See the [runtime validation](../hardening/tests/runtime-validation.md).
#### Agent-first diagnostics by host role #### Agent-first diagnostics by host role
+30 -7
View File
@@ -18,8 +18,12 @@ workflow.
browsers share an app-specific `/tmp` so external URLs reuse one browser instance. browsers share an app-specific `/tmp` so external URLs reuse one browser instance.
- Read-only `/nix/store`; GPU and display; fonts and themes; LAN and Internet. - Read-only `/nix/store`; GPU and display; fonts and themes; LAN and Internet.
- Portals for file chooser, URI opening, notifications, input methods and document exports. - Portals for file chooser, URI opening, notifications, input methods and document exports.
- No whole host home, system D-Bus, SSH agent, USB/serial or raw camera. Raw PipeWire is granted - No whole host home, system D-Bus, SSH agent, raw USB/serial or raw camera. For
only to MuseScore for its ALSA-via-PipeWire audio path; see [Limits](#limits). Firefox/Chromium/Chrome, the only device passthrough is the FIDO HID interface of a security key:
host udev rules hard-link FIDO-tagged hidraw nodes into `/dev/fido-hidraw`, which the sandbox
binds together with read-only `/sys/class/hidraw` and the udev database, so keys can be plugged in
after launch. Other HID interfaces, CCID and raw USB stay hidden. Raw PipeWire is granted only to
MuseScore for its ALSA-via-PipeWire audio path; see [Limits](#limits).
- Host loopback only for Firefox/Chromium; all other apps use pasta-isolated loopback. Chromium is - Host loopback only for Firefox/Chromium; all other apps use pasta-isolated loopback. Chromium is
an intentional development exception. Brave Origin and Edge are backup browsers and receive no an intentional development exception. Brave Origin and Edge are backup browsers and receive no
media grants. media grants.
@@ -31,9 +35,9 @@ explicitly; other files use the document portal. Every app also has private writ
| App | Network | Writable paths | Grants | | App | Network | Writable paths | Grants |
| ------------ | ------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | ------------ | ------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Firefox | Host loopback | `~/.mozilla`, personal files, Trash, Firefox artwork | Audio, browserpass, MPRIS, idle; camera/screen sharing intentionally unavailable | | Firefox | Host loopback | `~/.mozilla`, personal files, Trash, Firefox artwork | Audio, browserpass, MPRIS, idle, FIDO2 key; camera/screen sharing intentionally unavailable |
| Chromium | Host loopback | `~/.config/chromium`, personal files | Media, browserpass, MPRIS, idle, v12 Secret | | Chromium | Host loopback | `~/.config/chromium`, personal files | Media, browserpass, MPRIS, idle, FIDO2 key, v12 Secret |
| Chrome | Isolated | `~/.config/google-chrome`, Downloads | Media, browserpass, MPRIS, idle, v12 Secret | | Chrome | Isolated | `~/.config/google-chrome`, Downloads | Media, browserpass, MPRIS, idle, FIDO2 key, v12 Secret |
| Brave Origin | Isolated | `~/.config/BraveSoftware/Brave-Origin`, Downloads | v12 Secret | | Brave Origin | Isolated | `~/.config/BraveSoftware/Brave-Origin`, Downloads | v12 Secret |
| Edge | Isolated | `~/.config/microsoft-edge`, Downloads | v12 Secret | | Edge | Isolated | `~/.config/microsoft-edge`, Downloads | v12 Secret |
| QQ | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility | | QQ | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility |
@@ -73,7 +77,22 @@ All browsers get read-only NSS certificates, printing, native notifications and
- PulseAudio grants recording authority. Read-only GPG sockets still permit signing and decryption. - PulseAudio grants recording authority. Read-only GPG sockets still permit signing and decryption.
- pasta isolates loopback, not LAN access. Chromecast discovery is unverified. - pasta isolates loopback, not LAN access. Chromecast discovery is unverified.
- Brave Origin and Edge intentionally have no PulseAudio, Camera, ScreenCast or MPRIS grants. They - Brave Origin and Edge intentionally have no PulseAudio, Camera, ScreenCast or MPRIS grants. They
are backup browsers, not media browsers. are backup browsers, not media browsers. They also receive no FIDO2 passthrough.
- FIDO2 passthrough is limited to the hidraw interface whose report descriptor declares the FIDO
usage page. On the CanoKey that is the FIDO interface only: its KBD and CCID interfaces, other HID
devices and raw USB stay outside the sandbox. A compromised browser process can send raw CTAP to
the key; the key's PIN and touch policy remain the authentication boundary. The read-only udev
database exposes properties of all host devices, much of which is already readable via the sysfs
device binds.
- CanoKey management (CanoKey Console over WebUSB) is intentionally unavailable: Firefox has no
WebUSB and the sandbox exposes no raw USB. PCs only use the key; management happens in the phone
app. See [canokey.nix](../modules/nixos/desktop/canokey.nix) for the policy and its limits.
- FIDO2 hotplug relies on udev events, which only reach the host network namespace. Firefox and
Chromium therefore detect keys plugged in after launch; pasta-isolated Chrome needs the key
present before launch. `/dev/fido-hidraw` is created at boot. When first deploying with `switch`
instead of a reboot, run `sudo systemd-tmpfiles --create --prefix=/dev/fido-hidraw`, then replug
the key (or `sudo udevadm trigger --action=add -s hidraw`) and fully restart the browsers: a
browser started while the directory was missing has no bind for it.
- Firefox camera and remote screen sharing are intentionally disabled. Firefox does not support the - Firefox camera and remote screen sharing are intentionally disabled. Firefox does not support the
XDG Camera/ScreenCast portal path used here, so granting camera or screen-capture access would XDG Camera/ScreenCast portal path used here, so granting camera or screen-capture access would
require widening the sandbox with raw `/dev/video*` or direct PipeWire access. We choose not to require widening the sandbox with raw `/dev/video*` or direct PipeWire access. We choose not to
@@ -108,6 +127,8 @@ All browsers get read-only NSS certificates, printing, native notifications and
and user workflows still need confirmation. and user workflows still need confirmation.
- Chromium smoke test passed: renderer sandbox, environment clearing, bind scope, disposable-profile - Chromium smoke test passed: renderer sandbox, environment clearing, bind scope, disposable-profile
persistence and loopback policy. persistence and loopback policy.
- FIDO2 browser passthrough is enabled for Firefox/Chromium/Chrome only. WebAuthn with the CanoKey
passed in Firefox and Chrome; Chromium and plugging the key in after launch still need validation.
- WeChat uses the native nixpkgs-master package with nixpak as the only bwrap layer. Build/policy - WeChat uses the native nixpkgs-master package with nixpak as the only bwrap layer. Build/policy
pass; interactive Wayland validation is still pending. pass; interactive Wayland validation is still pending.
- Evidence and repeat commands: [runtime validation](tests/runtime-validation.md), - Evidence and repeat commands: [runtime validation](tests/runtime-validation.md),
@@ -121,7 +142,9 @@ All browsers get read-only NSS certificates, printing, native notifications and
[desktop-integration.nix](nixpaks/modules/desktop-integration.nix): portal and desktop service [desktop-integration.nix](nixpaks/modules/desktop-integration.nix): portal and desktop service
grants. grants.
- [browser-permissions.nix](nixpaks/modules/browser-permissions.nix), - [browser-permissions.nix](nixpaks/modules/browser-permissions.nix),
[chromium-browser.nix](nixpaks/modules/chromium-browser.nix): browser scopes and launcher. [fido-bwrap.nix](nixpaks/modules/fido-bwrap.nix),
[chromium-browser.nix](nixpaks/modules/chromium-browser.nix): browser scopes, FIDO2 passthrough
wrapper and launcher.
- [nixpaks/wechat.nix](nixpaks/wechat.nix): WeChat package and data setup. - [nixpaks/wechat.nix](nixpaks/wechat.nix): WeChat package and data setup.
- [kernel-hardening.nix](kernel-hardening.nix), [apparmor/](apparmor/): kernel sysctl and AppArmor - [kernel-hardening.nix](kernel-hardening.nix), [apparmor/](apparmor/): kernel sysctl and AppArmor
profiles. profiles.
+1
View File
@@ -13,6 +13,7 @@
desktopFile = "chromium-browser"; desktopFile = "chromium-browser";
browserpass = true; browserpass = true;
media = true; media = true;
fido2 = true;
hostLoopback = true; hostLoopback = true;
personalFiles = true; personalFiles = true;
secretPortal = true; secretPortal = true;
+15
View File
@@ -16,6 +16,21 @@ let
wrapper = _pkgs: path: (_pkgs.callPackage path callArgs); wrapper = _pkgs: path: (_pkgs.callPackage path callArgs);
in in
{ {
# FIDO2 hotplug for sandboxed browsers (see modules/fido-bwrap.nix). The
# directory is created at boot, before udevd starts, so the browser bind
# succeeds even with no key present. NixOS applies /dev tmpfiles entries only
# at boot; after a switch without reboot, run
# `sudo systemd-tmpfiles --create --prefix=/dev/fido-hidraw` once.
# Hard links share the device inode, so logind's uaccess ACLs stay
# authoritative, and removal on unplug leaves no stale node for a reused
# hidraw minor. 60-fido-id.rules sets ID_FIDO_TOKEN from the report
# descriptor, so the CanoKey's keyboard interface is never linked.
systemd.tmpfiles.rules = [ "d /dev/fido-hidraw 0755 root root -" ];
services.udev.extraRules = ''
ACTION=="add", SUBSYSTEM=="hidraw", ENV{ID_FIDO_TOKEN}=="1", RUN+="${pkgs.coreutils}/bin/ln -f /dev/%k /dev/fido-hidraw/%k"
ACTION=="remove", SUBSYSTEM=="hidraw", RUN+="${pkgs.coreutils}/bin/rm -f /dev/fido-hidraw/%k"
'';
# Add nixpaked Apps into nixpkgs, and reference them in home-manager or other nixos modules # Add nixpaked Apps into nixpkgs, and reference them in home-manager or other nixos modules
nixpkgs.overlays = [ nixpkgs.overlays = [
(_: super: { (_: super: {
+1
View File
@@ -37,6 +37,7 @@ let
media = true; media = true;
personalFiles = true; personalFiles = true;
browserpass = true; browserpass = true;
fido2 = true;
}; };
# Firefox keeps the host network: local development servers and the # Firefox keeps the host network: local development servers and the
+1
View File
@@ -13,5 +13,6 @@
desktopFile = "google-chrome"; desktopFile = "google-chrome";
browserpass = true; browserpass = true;
media = true; media = true;
fido2 = true;
secretPortal = true; secretPortal = true;
} }
@@ -1,14 +1,19 @@
# Shared browser capabilities. Network topology and application profiles are # Shared browser capabilities. Network topology and application profiles are
# selected by the app; personal files, browserpass, Secret portal and media are explicit opt-ins. # selected by the app; personal files, browserpass, Secret portal, media and
# FIDO2 security-key access are explicit opt-ins.
{ {
config, config,
lib, lib,
pkgs,
sloth, sloth,
... ...
}: }:
let let
cfg = config.browserPermissions; cfg = config.browserPermissions;
passwordStore = sloth.concat' sloth.homeDir "/.local/share/password-store"; passwordStore = sloth.concat' sloth.homeDir "/.local/share/password-store";
# Maintained by the host udev rules in ../default.nix.
fidoDir = "/dev/fido-hidraw";
fidoBwrap = import ./fido-bwrap.nix { inherit pkgs fidoDir; };
in in
{ {
imports = [ ./desktop-base.nix ]; imports = [ ./desktop-base.nix ];
@@ -17,6 +22,7 @@ in
personalFiles = lib.mkEnableOption "personal Documents, media directories and Trash"; personalFiles = lib.mkEnableOption "personal Documents, media directories and Trash";
browserpass = lib.mkEnableOption "browserpass access to the password store and GPG agent"; browserpass = lib.mkEnableOption "browserpass access to the password store and GPG agent";
secretPortal = lib.mkEnableOption "the app-scoped Secret portal for v12 cookie encryption"; secretPortal = lib.mkEnableOption "the app-scoped Secret portal for v12 cookie encryption";
fido2 = lib.mkEnableOption "FIDO2 security-key (CanoKey/YubiKey) WebAuthn and passkey access";
}; };
config = { config = {
portalGroups = { portalGroups = {
@@ -36,6 +42,7 @@ in
accessibility = true; accessibility = true;
}; };
bubblewrap = { bubblewrap = {
package = lib.mkIf cfg.fido2 fidoBwrap;
sockets.pulse = cfg.media; sockets.pulse = cfg.media;
bind.rw = [ bind.rw = [
sloth.xdgDownloadDir sloth.xdgDownloadDir
@@ -57,7 +64,17 @@ in
(sloth.concat' sloth.homeDir "/.gnupg") (sloth.concat' sloth.homeDir "/.gnupg")
passwordStore passwordStore
(sloth.concat' sloth.runtimeDir "/gnupg") (sloth.concat' sloth.runtimeDir "/gnupg")
]
# libudev enumeration and hotplug: hidraw class links (device directories
# come from desktop-base/GPU sysfs binds), the udev database without which
# devnode-backed devices count as uninitialized, and the control socket
# whose presence enables the udev monitor. Root owns the socket.
++ lib.optionals cfg.fido2 [
"/sys/class/hidraw"
"/run/udev/data"
"/run/udev/control"
]; ];
bind.dev = lib.optional cfg.fido2 fidoDir;
env = lib.optionalAttrs cfg.browserpass { PASSWORD_STORE_DIR = passwordStore; }; env = lib.optionalAttrs cfg.browserpass { PASSWORD_STORE_DIR = passwordStore; };
}; };
}; };
@@ -21,6 +21,7 @@
secretPortal ? false, secretPortal ? false,
hostLoopback ? false, hostLoopback ? false,
personalFiles ? false, personalFiles ? false,
fido2 ? false,
privateWindowFlag ? "--incognito", privateWindowFlag ? "--incognito",
}: }:
let let
@@ -64,6 +65,7 @@ let
personalFiles personalFiles
media media
secretPortal secretPortal
fido2
; ;
}; };
bubblewrap.bind.rw = [ bubblewrap.bind.rw = [
+51
View File
@@ -0,0 +1,51 @@
# Replacement `bwrap` for browsers with browserPermissions.fido2 (Firefox,
# Chromium, Chrome), so they can use FIDO2 security keys.
#
# The problem: browsers find keys by listing /sys/class/hidraw, then open the
# FIDO ones at /dev/hidrawN (e.g. /dev/hidraw6). The sandbox /dev is a private
# tmpfs, so those nodes are missing. The number N is only known once a key is
# plugged in, which may be after the browser starts.
#
# How keys reach the sandbox:
# 1. Host (../default.nix): when a key is plugged in, udev hard-links its FIDO
# node into /dev/fido-hidraw/ and deletes it on unplug. Non-FIDO interfaces,
# such as the CanoKey keyboard, are never linked.
# 2. browser-permissions.nix binds /dev/fido-hidraw into the sandbox. It is a
# shared live directory, so later plug/unplug shows up inside immediately.
# 3. This script creates /dev/hidraw0 ... /dev/hidraw63 in the sandbox as
# symlinks into /dev/fido-hidraw/. A link works only while a FIDO key with
# that number is plugged in; the others fail to open with ENOENT.
#
# nixpak has no option for creating symlinks, so this script adds the
# `--symlink` options and then runs the real bwrap.
{ pkgs, fidoDir }:
pkgs.writers.writePython3Bin "bwrap"
{
# Interpolated store paths exceed the default line length.
flakeIgnore = [ "E501" ];
}
''
import os
import sys
REAL_BWRAP = "${pkgs.bubblewrap}/bin/bwrap"
FIDO_DIR = "${fidoDir}"
# The kernel allows at most 64 hidraw devices (HIDRAW_MAX_DEVICES).
HIDRAW_COUNT = 64
args = sys.argv[1:]
# nixpak runs: bwrap <sandbox options> -- <browser command>
# Insert the symlinks just before "--": by then nixpak's "--dev /dev" has
# created the sandbox /dev they live in. nixpak's D-Bus proxy sandbox runs
# bwrap without "--" and stays unchanged.
if "--" in args:
symlinks = []
for n in range(HIDRAW_COUNT):
# e.g. --symlink /dev/fido-hidraw/hidraw6 /dev/hidraw6
symlinks += ["--symlink", f"{FIDO_DIR}/hidraw{n}", f"/dev/hidraw{n}"]
separator = args.index("--")
args[separator:separator] = symlinks
os.execv(REAL_BWRAP, [REAL_BWRAP, *args])
''
+12 -1
View File
@@ -42,7 +42,9 @@ let
|| lib.hasPrefix "/dev/video" path || lib.hasPrefix "/dev/video" path
|| lib.hasPrefix "/dev/bus/usb" path || lib.hasPrefix "/dev/bus/usb" path
|| path == "/run/dbus" || path == "/run/dbus"
|| path == "/run/dbus/system_bus_socket"; || path == "/run/dbus/system_bus_socket"
|| path == "/sys"
|| path == "/run/udev";
hasHostFontconfig = app: lib.elem [ "/etc/static/fonts" "/etc/fonts" ] app.bubblewrap.bind.ro; hasHostFontconfig = app: lib.elem [ "/etc/static/fonts" "/etc/fonts" ] app.bubblewrap.bind.ro;
@@ -95,6 +97,14 @@ let
!(app.browserPermissions.secretPortal or false) !(app.browserPermissions.secretPortal or false)
&& !(hasDbusFragment app "org.freedesktop.portal.Secret"); && !(hasDbusFragment app "org.freedesktop.portal.Secret");
# FIDO2 needs both the udev-maintained node directory and the bwrap wrapper
# that links /dev/hidrawN into it; neither may leak to other apps.
fido2Check =
app: expected:
(app.browserPermissions.fido2 or false) == expected
&& lib.elem "/dev/fido-hidraw" app.bubblewrap.bind.dev == expected
&& (lib.getName app.bubblewrap.package == "bwrap") == expected;
trayCheck = trayCheck =
app: expected: app: expected:
if expected then if expected then
@@ -130,6 +140,7 @@ let
"${name}/tray" = trayCheck app (spec.tray or false); "${name}/tray" = trayCheck app (spec.tray or false);
"${name}/browserpass" = "${name}/browserpass" =
(app.browserPermissions.browserpass or false) == (spec.browserpass or false); (app.browserPermissions.browserpass or false) == (spec.browserpass or false);
"${name}/fido2" = fido2Check app (spec.fido2 or false);
"${name}/personalFiles" = "${name}/personalFiles" =
(app.browserPermissions.personalFiles or false) == (spec.personalFiles or false); (app.browserPermissions.personalFiles or false) == (spec.personalFiles or false);
"${name}/secretPortal" = secretPortalCheck app (spec.secretPortal or false); "${name}/secretPortal" = secretPortalCheck app (spec.secretPortal or false);
+1
View File
@@ -7,4 +7,5 @@ mkAppPolicy "chromium" {
personalFiles = true; personalFiles = true;
browserpass = true; browserpass = true;
secretPortal = true; secretPortal = true;
fido2 = true;
} }
+1
View File
@@ -17,6 +17,7 @@ mkAppPolicy "firefox" {
personalFiles = true; personalFiles = true;
browserpass = true; browserpass = true;
secretPortal = false; secretPortal = false;
fido2 = true;
extraChecks = { extraChecks = {
capabilitiesMatchChromium = capabilitiesMatchChromium =
firefox.browserPermissions.media == chromium.browserPermissions.media firefox.browserPermissions.media == chromium.browserPermissions.media
+1
View File
@@ -7,4 +7,5 @@ mkAppPolicy "google-chrome" {
personalFiles = false; personalFiles = false;
browserpass = true; browserpass = true;
secretPortal = true; secretPortal = true;
fido2 = true;
} }
+14
View File
@@ -87,6 +87,20 @@ v10 storage; monitor `Local State.os_crypt.portal.prev_init_success`. Firefox us
instead. Browserpass separately exposes the password store and GPG-agent socket to instead. Browserpass separately exposes the password store and GPG-agent socket to
Firefox/Chromium/Chrome only. Firefox/Chromium/Chrome only.
## FIDO2 passthrough
Passed after a reboot on `ai` (CanoKey Canary, PR #515 head): with the key present at boot, the host
linked only its FIDO interface into `/dev/fido-hidraw`, and WebAuthn works in Firefox and Chrome.
Chromium, plugging the key in after browser launch, and the in-sandbox keyboard-interface check are
still pending. Full checklist:
1. On the host, `ls -l /dev/fido-hidraw/` lists only the FIDO interface (`ID_FIDO_TOKEN=1`); it
follows plug and unplug.
2. Firefox/Chromium: start without the key, plug it in, then register and sign in on
<https://webauthn.io>. Unplug and replug without restarting, then sign in again.
3. Chrome: the same flow with the key present at launch; record whether a later plug is detected.
4. In each sandbox, the CanoKey keyboard interface's `/dev/hidrawN` fails to open with `ENOENT`.
## Zoom ## Zoom
Zoom keeps its upstream FHS runtime inside the outer nixpak boundary. The nested sandbox propagates Zoom keeps its upstream FHS runtime inside the outer nixpak boundary. The nested sandbox propagates
+13
View File
@@ -12,6 +12,19 @@ in
enable = lib.mkEnableOption "CanoKey hardware security key support"; enable = lib.mkEnableOption "CanoKey hardware security key support";
}; };
# CanoKey docs: https://docs.canokeys.org/userguide/
#
# Principle: management and use happen on different devices. Linux PCs only
# use the key (WebAuthn/passkeys, OpenPGP, SSH). CanoKey Console management
# (Admin PIN, application/factory reset, device settings) happens only in the
# phone app (https://docs.canokeys.org/userguide/admin/), so the Admin PIN is
# never typed on a PC.
#
# Sandboxed browsers back this up: they see only the FIDO HID interface
# (hardening/nixpaks), not raw USB or CCID, so the WebUSB console cannot reach
# the key, and the vendor's WebUSB udev rule (MODE 0666 on the raw USB node)
# is deliberately not installed. Unsandboxed software is trusted; if it is
# malicious, the PC is compromised regardless.
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
# systemd tags FIDO/CCID devices uaccess (60-fido-id.rules); pcscd covers OpenPGP. # systemd tags FIDO/CCID devices uaccess (60-fido-id.rules); pcscd covers OpenPGP.
services.pcscd.enable = true; services.pcscd.enable = true;