Skip to content
Merged
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
10 changes: 6 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ jobs:
go-version-file: go.mod
cache: true

# cgo hardware backends need their C libs: hardware/chalresp uses
# `pkg-config: ykpers-1`, hardware/fido2 uses keys-pub/go-libfido2 (libfido2).
# Without these, `go build ./...` fails compiling those packages.
# cgo hardware backends need their C libs: hardware/chalresp uses go-hid,
# whose Linux hidraw backend needs libudev (libudev-dev); hardware/fido2
# uses keys-pub/go-libfido2 (libfido2-dev). Without these, `go build ./...`
# fails compiling those packages. (The optional PC/SC chalresp backend is
# build-tagged `pcsc` and not built here, so libpcsclite isn't needed.)
- name: Install native deps
run: sudo apt-get update && sudo apt-get install -y pkg-config libykpers-1-dev libfido2-dev
run: sudo apt-get update && sudo apt-get install -y pkg-config libudev-dev libfido2-dev

- name: Build
run: go build ./...
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# CHANGELOG


**v0.1.5 - 09-10-2026**

- Migrated to hidapi (removed ykpers)
- Added nitrokey 1st pass proper (needs testing)


**v0.1.4 - 09-09-2026**

- Fixed Windows identity store
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ A Go library providing post-quantum ready encryption, signing, key management, a
| `sharing` | Transport for encrypted file shares — uploads/downloads pre-encrypted `.icfx` ciphertext (does no cryptography itself) |
| `cloud` | Go client SDK for Instacrypt Cloud: transport, auth, billing, encrypted blob CRUD, directory, notification sync |
| `keystore` | Abstract key storage — OS keychain or encrypted file backend |
| `hardware/chalresp` | Optional Yubikey/compatible HMAC-SHA1 challenge-response (libykpers) |
| `hardware/chalresp` | Optional YubiKey/compatible HMAC-SHA1 challenge-response over HID (go-hid); optional PC/SC backend for Nitrokey 3 |
| `hardware/fido2` | Optional FIDO2/WebAuthn security-key client (libfido2) — registration and login |
| `config` | TOML configuration and XDG-compliant path management |
| `profile` | Full profile backup/restore via encrypted tarballs |
Expand All @@ -72,12 +72,15 @@ A Go library providing post-quantum ready encryption, signing, key management, a
(On macOS the OS keychain still uses the system Security.framework; use file-based key storage for a fully
cgo-free build there.)
- OS keychain (optional — falls back to file-based storage)
- libykpers-1 (optional — required only if you import `hardware/chalresp` for hardware-key support)
- Linux (Debian/Ubuntu): `sudo apt install libykpers-1-dev`
- Linux (Arch): `sudo pacman -S yubikey-personalization`
- Linux (Fedora): `sudo dnf install ykpers-devel`
- macOS: `brew install ykpers`
- Windows: install from [Yubico's yubikey-personalization releases][ykpers-win]; link via mingw or vcpkg
- libudev (optional — required only if you import `hardware/chalresp` for hardware-key support). `hardware/chalresp`
talks to the device directly over HID via [go-hid](https://github.com/sstallion/go-hid); on Linux its hidraw
backend links libudev. macOS and Windows use the system HID APIs — no extra library needed there.
- Linux (Debian/Ubuntu): `sudo apt install libudev-dev`
- Linux (Arch): `sudo pacman -S systemd` (provides libudev)
- Linux (Fedora): `sudo dnf install systemd-devel`
- macOS / Windows: none (system HID)
- Optional PC/SC backend (Nitrokey 3): build with `-tags pcsc`. On Linux this additionally needs
`libpcsclite-dev` + a running `pcscd`; macOS/Windows use the system PC/SC service.
- See `hardware/chalresp/README.md` for details
- libfido2 (optional — required only if you import `hardware/fido2` for FIDO2/WebAuthn security keys)
- Linux (Debian/Ubuntu): `sudo apt install libfido2-dev`
Expand All @@ -92,8 +95,6 @@ A Go library providing post-quantum ready encryption, signing, key management, a
```
- Windows: install libfido2; link via mingw or vcpkg

[ykpers-win]: https://developers.yubico.com/yubikey-personalization/Releases/


### ⚗️ Tech Stack
---
Expand Down
17 changes: 17 additions & 0 deletions crypto/hardware.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ func DeriveHardwareKEKBytes(pass, hwResponse []byte) ([]byte, error) {
if len(hwResponse) == 0 {
return nil, fmt.Errorf("empty hardware response")
}
// Reject a degenerate all-zero response: it would nullify the hardware factor
// (the KEK would be derivable from the passphrase alone). A genuine HMAC-SHA1
// is never all-zero; an all-zero salt signals a device malfunction or a
// non-genuine token, so fail closed rather than seal/unlock with it.
if allZero(hwResponse) {
return nil, fmt.Errorf("degenerate (all-zero) hardware response")
}
// NOTE: an empty passphrase is intentionally allowed. Keychain-origin
// identities (HWKEKNone) derive the KEK from the hardware response alone,
// with the OS keychain (Android Keystore / macOS Keychain) as the second
Expand All @@ -68,3 +75,13 @@ func DeriveHardwareKEKBytes(pass, hwResponse []byte) ([]byte, error) {
}
return kek, nil
}

// allZero reports whether every byte of b is zero.
func allZero(b []byte) bool {
for _, x := range b {
if x != 0 {
return false
}
}
return true
}
15 changes: 15 additions & 0 deletions crypto/hardware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ func TestDeriveHardwareKEK_EmptyResponse(t *testing.T) {
}
}

// An all-zero hardware response is degenerate (it would nullify the hardware
// factor) and must be rejected — a genuine HMAC-SHA1 is never all-zero; an
// all-zero salt signals a malfunctioning or non-genuine device.
func TestDeriveHardwareKEK_AllZeroResponseRejected(t *testing.T) {
if _, err := DeriveHardwareKEK("pass", make([]byte, 20)); err == nil {
t.Errorf("expected error for all-zero hardware response")
}
// A single non-zero byte makes it non-degenerate and derives normally.
nonzero := make([]byte, 20)
nonzero[19] = 0x01
if _, err := DeriveHardwareKEK("pass", nonzero); err != nil {
t.Errorf("non-degenerate response should derive: %v", err)
}
}

// An EMPTY passphrase must be accepted: keychain-origin identities (HWKEKNone)
// derive the KEK from the hardware response alone, with the OS keychain as the
// second factor. Rejecting it here breaks HW-only keychain unlock (regression
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/makiuchi-d/gozxing v0.1.1
github.com/pquerna/otp v1.5.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/sstallion/go-hid v0.15.0
github.com/zalando/go-keyring v0.2.6
golang.org/x/crypto v0.50.0
)
Expand All @@ -25,6 +26,7 @@ require (
github.com/awnumar/memcall v0.4.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
github.com/ebfe/scard v0.0.0-20241214075232-7af069cabc25 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
Expand Down
7 changes: 4 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/descope/virtualwebauthn v1.0.5 h1:fMXji5UMepJC51Ge6d4v5IAjiJQRKmXE9hlo/B9SczQ=
github.com/descope/virtualwebauthn v1.0.5/go.mod h1:lLCfN+DpCM3iisM4bCILZlFEWkC1Zo7ZgsxC45CUapI=
github.com/ebfe/scard v0.0.0-20241214075232-7af069cabc25 h1:vXmXuiy1tgifTqWAAaU+ESu1goRp4B3fdhemWMMrS4g=
github.com/ebfe/scard v0.0.0-20241214075232-7af069cabc25/go.mod h1:BkYEeWL6FbT4Ek+TcOBnPzEKnL7kOq2g19tTQXkorHY=
github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
Expand All @@ -33,8 +35,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/keys-pub/go-libfido2 v1.5.3 h1:vtgHxlSB43u6lj0TSuA3VvT6z3E7VI+L1a2hvMFdECk=
github.com/keys-pub/go-libfido2 v1.5.3/go.mod h1:P0V19qHwJNY0htZwZDe9Ilvs/nokGhdFX7faKFyZ6+U=
github.com/keys-pub/go-libfido2 v1.5.4-0.20251021061633-bf2d0535e75c h1:KqxSEYvMTbdiv7KvnXMl83kfGBWfkcOr7jt+cRhwzlc=
github.com/keys-pub/go-libfido2 v1.5.4-0.20251021061633-bf2d0535e75c/go.mod h1:92J9LtSBl0UyUWljElJpTbMMNhC6VeY8dshsu40qjjo=
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
Expand All @@ -47,6 +47,8 @@ github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/sstallion/go-hid v0.15.0 h1:WERW/VW3Us6N73V2qa7HjdqWQvwHd0CoRDOP/N707/w=
github.com/sstallion/go-hid v0.15.0/go.mod h1:fPKp4rqx0xuoTV94gwKojsPG++KNKhxuU88goGuGM7I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
Expand All @@ -60,7 +62,6 @@ github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8u
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
Expand Down
84 changes: 47 additions & 37 deletions hardware/chalresp/README.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,55 @@
# chalresp

HMAC-SHA1 challenge-response against the OTP slot 2 of Yubikey-compatible
hardware keys (Yubikey, NitroKey Pro/Storage, OnlyKey). CGO wrapper around
[Yubico's libykpers][libykpers] — the same C library KeePassXC, ykman, and
pam_yubico build on. Implements `crypto.HardwareKey` for icfx.
HMAC-SHA1 challenge-response against OTP slot 2 of Yubico-compatible hardware
keys, implementing `crypto.HardwareKey` for icfx.

icfx does NOT program slots. Slot 2 must be pre-programmed for HMAC-SHA1
challenge-response by the user's preferred tool (`ykman otp chalresp
--generate 2`, KeePassXC's setup flow, `ykpersonalize`, etc.) before icfx
can use it.

## Build dependency

| Platform | Install |
|---|---|
| Linux (Arch / CachyOS) | `sudo pacman -S yubikey-personalization` |
| Linux (Debian / Ubuntu) | `sudo apt install libykpers-1-dev` |
| Linux (Fedora / RHEL) | `sudo dnf install ykpers-devel` |
| macOS | `brew install ykpers` |
| Windows | install the [yubikey-personalization Windows build][ykpers-win] (bundles the DLL); link via mingw or vcpkg |

`pkg-config --modversion ykpers-1` should report a version after install.
The `#cgo pkg-config: ykpers-1` directive in `chalresp.go` resolves the
include path and link flags from there.
It speaks the wire protocols directly — **no libykpers, no libusb** — via
[hidapi][gohid] (and optionally [PC/SC][scard]). This is what `ykman` and
KeePassXC do, and it works on macOS/Windows/Linux/BSD without claiming the USB
interface or any code-signing/entitlement.

**Don't need hardware keys?** Build the whole module with `-tags nohw` (see the top-level README) to compile the
pure-Go stub instead — then libykpers-1 is not required and cgo can be disabled entirely.

## Single-device today

`chalresp.List()` opens via libykpers' `yk_open_first_key`, which picks the
first detected device. Multi-device support (plug in two keys, choose one
by serial) is post-MVP — the existing `DeviceDescriptor.Serial` field is
populated for forward compatibility.
icfx does NOT program slots. Slot 2 must be pre-programmed for HMAC-SHA1
challenge-response by the user's tool (`ykman otp chalresp --generate 2`,
Yubico Authenticator, KeePassXC's setup, the OnlyKey app, `nitropy nk3 secrets
add-challenge-response 2 …`) before icfx can use it.

## Supported devices

| Device | Transport | Build | Notes |
|---|---|---|---|
| **YubiKey** (OTP-capable models) | HID (keyboard interface) | default | reference implementation; hardware-validated |
| **OnlyKey** (firmware ≥2.1.0) | HID (keyboard interface) | default | firmware mirrors the YubiKey protocol byte-for-byte |
| **Nitrokey 3** | CCID / PC-SC (Yubico OATH applet) | `-tags pcsc` | implemented to spec, **untested pending hardware** |
| YubiKey over CCID | CCID / PC-SC | `-tags pcsc` | bonus of the PC/SC path |

**Not supported:** Nitrokey Pro / Storage (they have no YubiKey-style HMAC-SHA1
challenge-response — the earlier "NitroKey Pro/Storage" claim was never real).

## Build dependencies

- **Default (HID: YubiKey + OnlyKey)** — cgo via `sstallion/go-hid`, which
bundles hidapi. Linux uses the hidraw backend (a `libudev` dev package may be
needed to build; a `/dev/hidraw*` udev rule for VID `1050`/`1d50` for runtime
access). macOS/Windows need no extra packages.
- **`-tags pcsc` (adds Nitrokey 3 + YubiKey-CCID)** — cgo via `ebfe/scard`.
macOS (`PCSC.framework`) and Windows (`WinSCard`) ship PC/SC in the system;
**Linux needs `libpcsclite`-dev to build and `pcscd` running at runtime.**
Keep `github.com/ebfe/scard` in `go.mod` — a bare `go mod tidy` (without the
`pcsc` tag) will prune it and break the tagged build.
- **No hardware keys?** Build the module with `-tags nohw` for the pure-Go stub
— cgo can be disabled entirely and neither dependency is required.

## Multi-transport enumeration

`chalresp.List()` merges HID and (when built with `-tags pcsc`) PC/SC results;
`Open`/`Challenge`/`IsSlot2Programmed` route to the transport the device was
found on. PC/SC enumeration is best-effort — a missing `pcscd` never breaks HID.

## Mobile (Android / iOS) is out of scope

Mobile USB / NFC access does not flow through libykpers — Android requires
the Yubico Android SDK (Java/Kotlin via JNI) and iOS requires the Yubico
iOS SDK (Swift, with NFC for non-MFi access). Each is a separate flugo
bridge effort. Desktop only here.
The desktop files are `//go:build !android && !ios && !nohw`; mobile uses the
`chalresp_mobile.go` stub. Actual mobile HW-key support lives in flugo
(`flugo/pkg/hardware/nfckey`) via the Yubico Android/iOS SDKs.

[libykpers]: https://github.com/Yubico/yubikey-personalization
[ykpers-win]: https://developers.yubico.com/yubikey-personalization/Releases/
[gohid]: https://github.com/sstallion/go-hid
[scard]: https://github.com/ebfe/scard
Loading
Loading