Files
Thomas WaldmannandClaude Fable 5 eff90e0c39 docs: deployment guide for YubiKey challenge-response passphrases
New deployment chapter deriving the borg passphrase from a YubiKey OTP
slot's HMAC-SHA1 challenge-response via BORG_PASSCOMMAND: only a random
challenge is stored, the passphrase itself is computed by the token on
every unlock (the long-proven KeePassXC-style mechanism, as also
documented for restic in the Arch wiki).

Covers programming a slot with and without touch (touchless =
unattended backups, --touch = presence-gated interactive use),
redundant twin YubiKeys via a shared slot secret, adding the derived
passphrase as an additional borg key to an existing repository, the
paper copy of the derived passphrase as the recovery path, and honest
caveats: Yubico-proprietary (not on other vendors' keys nor the
FIDO2-only Security Key series - the age guide covers those), two
scarce slots, device-bound vs presence-gated threat model, macOS Input
Monitoring permission, and ykman instead of the deprecated ykpers
tooling.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-22 19:16:02 +02:00

118 lines
5.6 KiB
ReStructuredText

.. include:: ../global.rst.inc
.. highlight:: none
Passphrases from a YubiKey (challenge-response)
===============================================
The YubiKey's OTP application offers HMAC-SHA1 challenge-response: the YubiKey holds a
secret key and computes ``HMAC-SHA1(secret, challenge)`` for any challenge sent to it.
This can derive the borg passphrase on demand - the passphrase is never stored anywhere,
only a random challenge is - by wiring the derivation into ``BORG_PASSCOMMAND`` (see
:ref:`env_vars`). It is the same long-proven mechanism used by e.g. KeePassXC.
This is the most minimal hardware-backed setup for YubiKey owners: one tool
(``ykman``, from the yubikey-manager package), no other files than a challenge, and the
slot can be programmed with or without a touch requirement - so it covers both
unattended backups and presence-gated interactive use. The trade-offs: it is a
Yubico-proprietary mechanism (it does not exist on other vendors' security keys, nor on
Yubico's FIDO2-only "Security Key" series), and a YubiKey has only two OTP slots, which
may be shared with other uses of challenge-response. For a vendor-neutral approach that
also covers other hardware (any FIDO2 security key, TPM, Apple Secure Enclave), see the
age-based deployment guide.
Program a challenge-response slot
---------------------------------
Check which slots are free first - **programming a slot overwrites its previous
content** (slot 1 usually holds the factory Yubico OTP credential; slot 2 is typically
free)::
ykman otp info
Program slot 2 with a random secret::
ykman otp chalresp --generate 2
Without ``--touch``, responses are computed silently - that is what enables unattended
use. Add ``--touch`` instead if every unlock shall require a touch on the YubiKey
(presence-gated, for interactive use; not usable for unattended backups).
``--generate`` prints the generated secret once. Either store that hex secret offline
(it allows programming a *second* YubiKey with the same secret later, see below) or
discard it - afterwards it cannot be read back from the YubiKey.
For two YubiKeys as redundant twins, generate the secret yourself and program it into
both - they will then derive identical passphrases::
openssl rand -hex 20 # the slot secret; store it offline or destroy it afterwards
ykman otp chalresp 2 <SECRET> # run once per YubiKey
Derive the borg passphrase
--------------------------
Create a random challenge and keep it with your borg configuration (the HMAC challenge
is limited to 64 bytes, so 32 random bytes = 64 hex digits is a good choice)::
openssl rand -hex 32 > ~/.config/borg/yubikey-challenge
chmod 600 ~/.config/borg/yubikey-challenge
The passphrase is the response to that challenge::
ykman otp calculate 2 "$(cat ~/.config/borg/yubikey-challenge)"
d2f19d6c2b48e1665d2ea1042ee5d1c34e29bb2e
Keep an offline copy of this response (e.g. on paper, in a safe place): it is the
actual borg passphrase, and it is your recovery path if the YubiKey is lost - without
YubiKey and without the paper copy, this borg key cannot be unlocked.
The challenge file is not usable without the YubiKey holding the slot secret, but keep
it protected anyway: both "factors" are needed to derive the passphrase.
Wire it into borg
-----------------
``BORG_PASSCOMMAND`` runs its command *without* a shell, so ``$(...)`` substitution is
not available there. Put the derivation into a small script, e.g.
``~/.config/borg/yubikey-passphrase.sh`` (make it executable: ``chmod 700``)::
#!/bin/sh
exec ykman otp calculate 2 "$(cat ~/.config/borg/yubikey-challenge)"
and use it::
export BORG_PASSCOMMAND=~/.config/borg/yubikey-passphrase.sh
For a **new** repository, set ``BORG_PASSCOMMAND`` before ``borg repo-create``: the
command's output is then used as the new repository's passphrase.
For an **existing** repository, add the derived passphrase as an additional borg key
(keeping the existing passphrase as an independent way in, e.g. for recovery)::
BORG_NEW_PASSPHRASE="$(~/.config/borg/yubikey-passphrase.sh)" borg key add --label yubikey
With a touchless slot, scheduled backups (cron, systemd timers) now work while the
YubiKey is plugged in; with ``--touch``, every borg command asks for a touch.
Notes and caveats
-----------------
- A touchless slot makes the YubiKey act like an *uncopyable keyfile*: stealing the
challenge file (or any other file) is useless without the physical YubiKey, but code
running on the machine can derive the passphrase silently while the YubiKey is
plugged in - it is device-bound, not presence-gated. A ``--touch`` slot prevents
silent derivation, at the price of unattended use. In both cases, borg necessarily
obtains the passphrase and key material at unlock time - a compromised client still
gets them.
- Reprogramming the slot (or losing all YubiKeys programmed with its secret)
permanently breaks this derivation. Keep the paper copy of the derived passphrase,
or another borg key with its own passphrase, as the recovery path - and remember
that a repository is only as secure as its *weakest* borg key.
- The response is 160 bits (HMAC-SHA1). SHA-1's known weaknesses do not matter here
(it is used as a keyed PRF, not for collision resistance), and 160 random bits are
far beyond brute force.
- On macOS, the ``ykman otp`` commands access the YubiKey's keyboard (HID) interface,
which requires the *Input Monitoring* permission for your terminal application
(System Settings > Privacy & Security > Input Monitoring).
- The legacy ``ykchalresp -2 -i <challengefile>`` tool (from the deprecated ykpers
package) computes the same responses; prefer the maintained ``ykman``.