From a69ef122dc6943b61b23331a412cd80839e22176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ryan4yin=20=7C=20=E4=BA=8C=E8=8A=B1?= Date: Thu, 5 Nov 2026 20:22:09 -0600 Subject: [PATCH] feat(canokey): announce touch requests with a toast and a sound (#518) Assisted-by: DeepSeek Harness (deepseek-flash) --- docs/security-keys.md | 36 +++++++++++++++++-- .../gui/base/noctalia/config/config.toml | 28 +++++++++++++++ home/linux/gui/base/noctalia/default.nix | 3 ++ modules/nixos/desktop/canokey.nix | 11 ++++++ 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/docs/security-keys.md b/docs/security-keys.md index 2b4820a0..11b69e34 100644 --- a/docs/security-keys.md +++ b/docs/security-keys.md @@ -84,6 +84,36 @@ released firmware, but each NFC tap is a new card session, so their PINs are sti | WebAuthn minimum PIN length | 6–8 | the PIN locks after a few failures | | Long-press reset | off, unless the app documents it as PIN-gated | keeps reset a phone-side management action | +### 1.3 Touch feedback + +The LED is easy to miss, so touch requests also raise a desktop notification: a transient toast (and +a notification sound, this sender only) that closes itself once the touch is done. +`programs.yubikey-touch-detector` ([canokey.nix](../modules/nixos/desktop/canokey.nix)) reads the +FIDO interface's hidraw reports for touch-waiting signals, and proxies `gpg-agent` for +smartcard-backed OpenPGP operations; the sound comes from Noctalia's notification filter +([config.toml](../home/linux/gui/base/noctalia/config/config.toml)). + +| Flow | Notified | Why | +| -------------------------------------- | -------- | ----------------------------------------------------- | +| sudo (pam-u2f) | yes | CTAPHID keepalive / U2F `0x6985` from the FIDO hidraw | +| ssh/scp/rsync/git over ssh (FIDO2 key) | yes | same | +| WebAuthn in browsers | yes | same; browsers read the same hidraw interface | +| pass / GPG decrypt and sign | yes | gpg-agent proxy; heuristic, see below | +| PIV / OATH | no | the detector has no source for those applets | +| shoukei LUKS unlock | no | initrd, before any desktop user service | +| PIN prompts | no | a PIN is not a touch; pinentry shows that prompt | + +A `switch` only enables the service for the next login: a running session does not start newly +deployed user services, so after the first deploy start it once with +`systemctl --user start yubikey-touch-detector`. + +Check it: `systemctl --user status yubikey-touch-detector`, then run something that asks for a touch +(`sudo true`, or `ssh` to a host) and watch for the toast. Logs: +`journalctl --user -u yubikey-touch-detector -f`. The GPG detector only starts once +`private-keys-v1.d` holds on-card stubs, and it treats an operation pending for 400 ms outside +pinentry as a touch request. It proxies only the standard `gpg-agent` and `$SSH_AUTH_SOCK` sockets, +so clients that use `S.gpg-agent.extra` or `S.gpg-agent.browser` get no notification. + ## 2. Enroll a key Only after section 1.1. Do these in order; each step can be rolled back on its own. Only the OpenPGP @@ -215,9 +245,9 @@ After any reset, rerun sections 1 and 2 for the affected applications. ## Enrollment status -| Key | Firmware | sudo | SSH | shoukei LUKS | OpenPGP | Browsers | -| ------------------------ | -------- | ----------------- | ---------------------------------------------------------- | ----------------------------- | -------- | -------------------------------- | -| CanoKey Canary (primary) | 3.0.3 | works on idols-ai | key created; public key not yet in `mainSshAuthorizedKeys` | enrolled; unlock not verified | deferred | WebAuthn works in Firefox/Chrome | +| Key | Firmware | sudo | SSH | shoukei LUKS | OpenPGP | Browsers | +| ------------------------ | -------- | ----------------- | ---------------------------------------------------------- | ----------------------------- | ----------- | -------------------------------- | +| CanoKey Canary (primary) | 3.0.3 | works on idols-ai | key created; public key not yet in `mainSshAuthorizedKeys` | enrolled; unlock not verified | on the card | WebAuthn works in Firefox/Chrome | shoukei's first boots with the FIDO2 slot asked for the PIN, then fell back to the passphrase: USB only came up after switch-root, because on linux-asahi 7.2 its dwc3/PHY depend on the Thunderbolt diff --git a/home/linux/gui/base/noctalia/config/config.toml b/home/linux/gui/base/noctalia/config/config.toml index 805ecdfb..c4b15cc5 100644 --- a/home/linux/gui/base/noctalia/config/config.toml +++ b/home/linux/gui/base/noctalia/config/config.toml @@ -63,6 +63,34 @@ media = false # corner so transient popups no longer sit under the top bar. [notification] position = "bottom_right" +# Only the security key's touch prompt is audible: its notification matches the +# first filter, every other notification (e.g. per-track media notices) gets the +# catch-all silent filter. First matching enabled filter wins. +filter_order = [ "security-key", "silent" ] + +[notification.filter.security-key] +enabled = true +match = "yubikey-touch-detector" +play_sound = true +# The pending operation blocks until the key is touched, so also surface it +# while Do Not Disturb is on. +bypass_dnd = true + +[notification.filter.silent] +enabled = true +match_content = ".*" +play_sound = false + +# Shell sounds stay off except the notification sound allowed by the filter +# above, so volume, charger and screenshot actions stay quiet. This plays the +# freedesktop sound theme's `message-new-instant` event. +[audio] +enable_sounds = true +enable_volume_sounds = false +enable_notification_sounds = true +enable_power_sounds = false +enable_screenshot_sounds = false +sound_theme = "freedesktop" [theme] source = "builtin" diff --git a/home/linux/gui/base/noctalia/default.nix b/home/linux/gui/base/noctalia/default.nix index c3eabcc4..e2300d8e 100644 --- a/home/linux/gui/base/noctalia/default.nix +++ b/home/linux/gui/base/noctalia/default.nix @@ -25,6 +25,9 @@ # app2unit is still used to launch desktop entries as systemd user units. home.packages = [ pkgs.app2unit # Launch Desktop Entries (or arbitrary commands) as Systemd user units + # `message-new-instant` and the other shell sound events; the sound setting in + # config.toml only plays the security-key notification. + pkgs.sound-theme-freedesktop ] ++ (lib.optionals pkgs.stdenv.hostPlatform.isx86_64 [ pkgs.gpu-screen-recorder # recoding screen diff --git a/modules/nixos/desktop/canokey.nix b/modules/nixos/desktop/canokey.nix index b956730f..7f3ebc20 100644 --- a/modules/nixos/desktop/canokey.nix +++ b/modules/nixos/desktop/canokey.nix @@ -34,6 +34,17 @@ in }; security.pam.services.sudo.u2f.enable = myvars.canokeyU2fKeys != [ ]; + # A blink is easy to miss, so touch requests also raise a desktop + # notification. The package's service starts with graphical-session.target + # and sends through libnotify by default; Noctalia plays a sound for this + # sender only (home/linux/gui/base/noctalia/config/config.toml). Coverage + # and limits: docs/security-keys.md. + programs.yubikey-touch-detector.enable = true; + systemd.user.services.yubikey-touch-detector.environment = { + YUBIKEY_TOUCH_DETECTOR_NOTIFY_TITLE = "Security key: touch needed"; + YUBIKEY_TOUCH_DETECTOR_NOTIFY_BODY = "Touch the security key to continue"; + }; + environment.systemPackages = with pkgs; [ ccid pcsc-tools