Skip to content

Commit 0dd454d

Browse files
Merge pull request #10405 from ThomasWaldmann/docs-yubikey
docs: derive the borg passphrase from a YubiKey (challenge-response)
2 parents 285aefb + eff90e0 commit 0dd454d

2 files changed

Lines changed: 118 additions & 0 deletions

File tree

‎docs/deployment.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ This chapter details deployment strategies for the following scenarios.
1616
deployment/image-backup
1717
deployment/pull-backup
1818
deployment/non-root-user
19+
deployment/yubikey

‎docs/deployment/yubikey.rst‎

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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

Comments
 (0)