init: go-libfido2 integration via plugin#316
init: go-libfido2 integration via plugin#316pilotstew wants to merge 7 commits intoanatol:masterfrom
Conversation
|
Note on CGO_ENABLED requirement The switch from fido2-assert subprocess to go-libfido2 introduces a CGO dependency. go-libfido2 wraps the native libfido2 C library via #cgo directives, so the init binary now requires CGO_ENABLED=1 to build (which is the default — this only affects anyone explicitly building with CGO_ENABLED=0). Previously, shelling out to fido2-assert meant the init binary had no C dependencies and could be built fully statically. The trade-off is intentional: direct library integration eliminates the subprocess timing races and path-resolution failures that made FIDO2 unlock unreliable in practice. Happy to discuss if this is a concern. |
|
Note this PR contains multiple independent changes. Let's talk about the first one - the go-libfido. Using API directly is a preferred option over command line tooling for me - it provides type safety and efficiency. The issue here is that this way we link against libfido.so shared library and now booster must always be shipped with this dependency. Given that only tiny fraction requires FIDO functionality, imposing an extra dependency/size of the initramfs file to everyone is suboptimal. Ideally this *.so should be included into the initramfs and loaded dynamically only when user specifies that one wants to use FIDO. Essentially something like loadable library but for Go. Golang has https://pkg.go.dev/plugin and I would love to understand if it is applicable here. |
|
Pushed the simple |
|
The @pilotstew how did you verify these changes? Did you run tests? Boot with the modified booster? |
You're right, that was poor form. I'll create separate PR's in the future.
This is a far better approach. Completely agree and I will make it happen. Initially it looks like As for testing/review. I wrote some new unit/qemu tests but more are probably needed. In fact I was rereading #26 last night and realized I haven't fully implemented header handling nor testing. Particularly header on a different device is a bit tricky and will need to be reworked. My goal here was comprehensive support for crypttab and I want to be sure to include this before merge. Here's an overview of the testing setup. You can see a few holes still exist.
|
|
Flagging two design changes made after the original submission: 1. crypttab: switched from The original implementation used a separate 2. Detached LUKS headers: extended to cover block device and filesystem forms The original submission only supported headers bundled into the initramfs at build time. The implementation now covers all three forms:
Both changes are covered by unit tests and QEMU integration tests. Take your time with the review. Let me know if you think of any additional testing we made need. This is currently running on my machine but my setup is fairly simple using limine, yubikey pin+touch with passphrase fallback. All defined by /etc/crypttab rather than kernel cmdline using a single luks btrfs partition. |
7791197 to
701f992
Compare
|
Thank you for your work @pilotstew It would be great to have
as separate PR. Those changes look quite straight-forward and might be reviewed/merged faster than the rest of the changes. |
|
I can probably get to that tomorrow. If/when these get merged would it be possible to update aur/booster-git with these Plymouth/Crypttab changes. I'd like to see some solid live testing/feedback for a while before it goes into the main package. |
Sounds good. There are some good changes added to booster and good testing is needed (cc @dkwo from Void Linux). I just bumped |
701f992 to
1c42f49
Compare
|
Apologies for the delay. Pushed a clean rewrite — here's what changed from the original submission and why. fido2-assert → go-libfido2 via plugin The original already used go-libfido2 but imported it directly, linking libfido2.so into the init binary for all users. This rewrite builds enable_fido2 config flag New addition. The generator doesn't parse kernel cmdline parameters — it only reads Cleaner commit history The original had addendum commits and FIDO2 changes interleaved with crypttab work. This rewrite is FIDO2-only in 5 ordered commits — crypttab and detached header work is in #318 and #319. Hardware FIDO2 tests can't be automated as far as I know since they require a physical device — local testing was done with a Yubikey (PIN + touch, passphrase fallback). |
22798f4 to
34829ee
Compare
The init binary is built with CGO_ENABLED=0 for static linking, which rules out direct use of go-libfido2 (which wraps libfido2.so via CGO). Solve this by building a separate fido2plugin.so (with CGO enabled) that exports three symbols: Fido2Assertion, IsFido2PinInvalid, and IsFido2PinAuthBlocked. The main init binary loads this plugin at runtime via plugin.Open if enable_fido2 is set in the generator config. Two build-tag stubs are provided: - fido2_cgo.go (cgo): loads the plugin lazily on first use via sync.Once - fido2_nocgo.go (no cgo): returns errors unconditionally; satisfies the compiler when building without CGO This keeps the main init binary fully static while still supporting libfido2-based FIDO2 unlock when the plugin is present.
…ng and multi-device handling
Replace fido2-assert subprocess calls with direct calls through the
fido2plugin.so API.
Key changes:
- recoverFido2Password: use fido2Assertion() from the plugin instead of
exec("fido2-assert"); pass mappingName through for user-facing messages
("Waiting for FIDO2 security key for <name>...")
- PIN routing: prompt via Plymouth when enabled, console otherwise;
retry up to 3 times on invalid PIN; fall back to keyboard passphrase
when PIN auth is blocked by the key
- fido2Mu mutex: serialize FIDO2 operations across goroutines; concurrent
LUKS devices (e.g. RAID1) would otherwise interleave PIN prompts and
touch requests on the same key
- hidraw exhaustion: when no FIDO2 device is found, emit a "insert
security key or wait for passphrase prompt" message rather than failing
silently
- recoverTokenPassword: now returns bool (unlocked/not) and takes
mappingName; lets luksOpen skip the keyboard goroutine when a token
already succeeded
- luksOpen: launch all token goroutines concurrently; start the keyboard
passphrase goroutine only after tokenTimeout elapses or all tokens
finish, matching dracut/systemd-cryptsetup behavior; tokenTimeout field
added to luksMapping (0 = wait forever)
… rd.luks.options Add token-timeout=<duration> to rd.luks.options, controlling how long booster waits for hardware tokens (FIDO2, TPM2) before also starting the keyboard passphrase prompt. Accepts a decimal number followed by a unit (s, m, h), or a bare integer treated as seconds. Default (0) is to wait for all tokens to complete before prompting. Also accept fido2-device= and tpm2-device= without error for compatibility with systemd-cryptsetup conventions. Booster auto-detects enrolled tokens from the LUKS header so these flags have no additional effect. Document token-timeout= in the manpage.
Add enable_fido2 boolean to booster.yaml config. When true, the generator reads fido2plugin.so from alongside the init binary and embeds it in the initramfs at /usr/lib/booster/fido2plugin.so. The init binary loads this plugin at runtime to perform FIDO2 assertions without requiring CGO in the main binary. The explicit flag is needed because the generator cannot always detect FIDO2 usage at build time. When LUKS is configured via /etc/crypttab the generator can scan for fido2-device= entries and include the plugin automatically. When LUKS is configured via kernel cmdline (rd.luks.uuid=, rd.luks.name=) the generator has no visibility into what tokens are enrolled in the LUKS header — the user must set enable_fido2: true explicitly in that case. If enable_fido2 is not set the plugin is not included and FIDO2 token slots are silently skipped at boot. Document enable_fido2 in the manpage.
- systemd_fido2.sh: remove the luksKillSlot 0 call that was only needed to work around fido2-assert credential slot behavior - systemd_test.go: replace extraFiles: "fido2-assert" with enableFido2: true on all three FIDO2 test cases (basic, pin, uv) - util.go: add enableFido2 Opts field that sets enable_fido2 in the generated booster config; build fido2plugin.so alongside the init binary in the test setup (best-effort, skipped silently if libfido2 is not installed)
…recated) Users upgrading from the fido2-assert subprocess approach would silently lose FIDO2 unlock if they didn't add enable_fido2: true before regenerating. Detect fido2-assert in extra_files, emit a deprecation warning, and automatically enable the plugin. The binary itself remains in the initramfs as a harmless unused file.
34829ee to
eec2a26
Compare
Fix upstream Plymouth API changes: plymouthAskPassword now returns ([]byte, error) instead of string, and plymouthMessage is void. Also clear the "Waiting for FIDO2 security key" message when a device is detected (after acquiring the mutex) and before falling back to keyboard entry when no matching device is available.
93103c1 to
e95ff48
Compare
Replaces the
fido2-assertsubprocess with native go-libfido2 integration via the Gopluginpackage, keeping the main init binary CGO-free.Why plugin model
The main init binary is built with
CGO_ENABLED=0for static linking. go-libfido2 requires CGO.fido2plugin.sois built separately with CGO enabled and loaded at runtime viaplugin.Open— only whenenable_fido2: trueis set inbooster.yaml. Users without FIDO2 hardware pay no cost.Commits
init: load go-libfido2 via plugin—fido2plugin.soexportsFido2Assertion,IsFido2PinInvalid,IsFido2PinAuthBlocked; build-tag stubs (fido2_cgo.go/fido2_nocgo.go) satisfy the compiler forCGO_ENABLED=0buildsinit: replace fido2-assert with go-libfido2— PIN routing via Plymouth or console, 3 retries, keyboard fallback when PIN is blocked;fido2Mumutex serializes across concurrent LUKS goroutinesinit: token-timeout= and fido2-device=/tpm2-device= in rd.luks.options— controls how long to wait for tokens before starting keyboard prompt; acceptfido2-device=/tpm2-device=without error for systemd-cryptsetup compatibilitygenerator: bundle fido2plugin.so when enable_fido2 is set— newenable_fido2flag inbooster.yaml; explicit opt-in needed for cmdline-configured LUKS since the generator doesn't parse kernel parameterstests: update TestSystemdFido2 for go-libfido2 pluginTest plan
cd init && go test -vcd tests && go test -v -run "TestSystemdFido2"