|
| 1 | +.. include:: ../global.rst.inc |
| 2 | +.. highlight:: none |
| 3 | + |
| 4 | +Passphrases from a YubiKey (challenge-response) |
| 5 | +=============================================== |
| 6 | + |
| 7 | +The YubiKey's OTP application offers HMAC-SHA1 challenge-response: the YubiKey holds a |
| 8 | +secret key and computes ``HMAC-SHA1(secret, challenge)`` for any challenge sent to it. |
| 9 | +This can derive the borg passphrase on demand - the passphrase is never stored anywhere, |
| 10 | +only a random challenge is - by wiring the derivation into ``BORG_PASSCOMMAND`` (see |
| 11 | +:ref:`env_vars`). It is the same long-proven mechanism used by e.g. KeePassXC. |
| 12 | + |
| 13 | +This is the most minimal hardware-backed setup for YubiKey owners: one tool |
| 14 | +(``ykman``, from the yubikey-manager package), no other files than a challenge, and the |
| 15 | +slot can be programmed with or without a touch requirement - so it covers both |
| 16 | +unattended backups and presence-gated interactive use. The trade-offs: it is a |
| 17 | +Yubico-proprietary mechanism (it does not exist on other vendors' security keys, nor on |
| 18 | +Yubico's FIDO2-only "Security Key" series), and a YubiKey has only two OTP slots, which |
| 19 | +may be shared with other uses of challenge-response. For a vendor-neutral approach that |
| 20 | +also covers other hardware (any FIDO2 security key, TPM, Apple Secure Enclave), see the |
| 21 | +age-based deployment guide. |
| 22 | + |
| 23 | +Program a challenge-response slot |
| 24 | +--------------------------------- |
| 25 | + |
| 26 | +Check which slots are free first - **programming a slot overwrites its previous |
| 27 | +content** (slot 1 usually holds the factory Yubico OTP credential; slot 2 is typically |
| 28 | +free):: |
| 29 | + |
| 30 | + ykman otp info |
| 31 | + |
| 32 | +Program slot 2 with a random secret:: |
| 33 | + |
| 34 | + ykman otp chalresp --generate 2 |
| 35 | + |
| 36 | +Without ``--touch``, responses are computed silently - that is what enables unattended |
| 37 | +use. Add ``--touch`` instead if every unlock shall require a touch on the YubiKey |
| 38 | +(presence-gated, for interactive use; not usable for unattended backups). |
| 39 | + |
| 40 | +``--generate`` prints the generated secret once. Either store that hex secret offline |
| 41 | +(it allows programming a *second* YubiKey with the same secret later, see below) or |
| 42 | +discard it - afterwards it cannot be read back from the YubiKey. |
| 43 | + |
| 44 | +For two YubiKeys as redundant twins, generate the secret yourself and program it into |
| 45 | +both - they will then derive identical passphrases:: |
| 46 | + |
| 47 | + openssl rand -hex 20 # the slot secret; store it offline or destroy it afterwards |
| 48 | + ykman otp chalresp 2 <SECRET> # run once per YubiKey |
| 49 | + |
| 50 | +Derive the borg passphrase |
| 51 | +-------------------------- |
| 52 | + |
| 53 | +Create a random challenge and keep it with your borg configuration (the HMAC challenge |
| 54 | +is limited to 64 bytes, so 32 random bytes = 64 hex digits is a good choice):: |
| 55 | + |
| 56 | + openssl rand -hex 32 > ~/.config/borg/yubikey-challenge |
| 57 | + chmod 600 ~/.config/borg/yubikey-challenge |
| 58 | + |
| 59 | +The passphrase is the response to that challenge:: |
| 60 | + |
| 61 | + ykman otp calculate 2 "$(cat ~/.config/borg/yubikey-challenge)" |
| 62 | + d2f19d6c2b48e1665d2ea1042ee5d1c34e29bb2e |
| 63 | + |
| 64 | +Keep an offline copy of this response (e.g. on paper, in a safe place): it is the |
| 65 | +actual borg passphrase, and it is your recovery path if the YubiKey is lost - without |
| 66 | +YubiKey and without the paper copy, this borg key cannot be unlocked. |
| 67 | + |
| 68 | +The challenge file is not usable without the YubiKey holding the slot secret, but keep |
| 69 | +it protected anyway: both "factors" are needed to derive the passphrase. |
| 70 | + |
| 71 | +Wire it into borg |
| 72 | +----------------- |
| 73 | + |
| 74 | +``BORG_PASSCOMMAND`` runs its command *without* a shell, so ``$(...)`` substitution is |
| 75 | +not available there. Put the derivation into a small script, e.g. |
| 76 | +``~/.config/borg/yubikey-passphrase.sh`` (make it executable: ``chmod 700``):: |
| 77 | + |
| 78 | + #!/bin/sh |
| 79 | + exec ykman otp calculate 2 "$(cat ~/.config/borg/yubikey-challenge)" |
| 80 | + |
| 81 | +and use it:: |
| 82 | + |
| 83 | + export BORG_PASSCOMMAND=~/.config/borg/yubikey-passphrase.sh |
| 84 | + |
| 85 | +For a **new** repository, set ``BORG_PASSCOMMAND`` before ``borg repo-create``: the |
| 86 | +command's output is then used as the new repository's passphrase. |
| 87 | + |
| 88 | +For an **existing** repository, add the derived passphrase as an additional borg key |
| 89 | +(keeping the existing passphrase as an independent way in, e.g. for recovery):: |
| 90 | + |
| 91 | + BORG_NEW_PASSPHRASE="$(~/.config/borg/yubikey-passphrase.sh)" borg key add --label yubikey |
| 92 | + |
| 93 | +With a touchless slot, scheduled backups (cron, systemd timers) now work while the |
| 94 | +YubiKey is plugged in; with ``--touch``, every borg command asks for a touch. |
| 95 | + |
| 96 | +Notes and caveats |
| 97 | +----------------- |
| 98 | + |
| 99 | +- A touchless slot makes the YubiKey act like an *uncopyable keyfile*: stealing the |
| 100 | + challenge file (or any other file) is useless without the physical YubiKey, but code |
| 101 | + running on the machine can derive the passphrase silently while the YubiKey is |
| 102 | + plugged in - it is device-bound, not presence-gated. A ``--touch`` slot prevents |
| 103 | + silent derivation, at the price of unattended use. In both cases, borg necessarily |
| 104 | + obtains the passphrase and key material at unlock time - a compromised client still |
| 105 | + gets them. |
| 106 | +- Reprogramming the slot (or losing all YubiKeys programmed with its secret) |
| 107 | + permanently breaks this derivation. Keep the paper copy of the derived passphrase, |
| 108 | + or another borg key with its own passphrase, as the recovery path - and remember |
| 109 | + that a repository is only as secure as its *weakest* borg key. |
| 110 | +- The response is 160 bits (HMAC-SHA1). SHA-1's known weaknesses do not matter here |
| 111 | + (it is used as a keyed PRF, not for collision resistance), and 160 random bits are |
| 112 | + far beyond brute force. |
| 113 | +- On macOS, the ``ykman otp`` commands access the YubiKey's keyboard (HID) interface, |
| 114 | + which requires the *Input Monitoring* permission for your terminal application |
| 115 | + (System Settings > Privacy & Security > Input Monitoring). |
| 116 | +- The legacy ``ykchalresp -2 -i <challengefile>`` tool (from the deprecated ykpers |
| 117 | + package) computes the same responses; prefer the maintained ``ykman``. |
0 commit comments