LUKS with Yubikey 2FA (chal-resp) on Qubes 4.2.0

Using a YubiKey can help protect your system from an attack that knows your LUKS password. The YubiKey Challenge-Response authentication doesn’t use a time component, if the attacker has access to both the key and your password they will be able to extract the permanent LUKS password.

Make sure you have a full backup of your system before you try to modify your LUKS authentication, if you mess something up, there might be no way to recover without a backup.

I will be using Debian 12, the yubikey-personalization installation might be different on Fedora, but setting up the key should be the same for both distributions.

You are going to be generating an LUKS key, you might want to do that step offline in a disposable qube, just to be sure it’s not possible to recover the key when you are done.

The following steps are done is an appvm

Clone repositories
$ git clone https://github.com/the2nd/ykluks
This repository has the code needed to use the YubiKey at the LUKS prompt

$ git clone https://github.com/Yubico/yubikey-personalization.git
This is needed one time to set up the key

Install dependencies
You don’t need to add the packages to a template, you only need to use them once.
$ sudo apt install libyubikey-dev dh-autoreconf libusb-dev asciidoc xsltproc make

Compile yubikey-personalization

cd yubikey-personalization
autoreconf --install
./configure
make

Setup key and password
Insert the YubiKey and attach the USB device to the qube with yubikey-personalization.
This will configure slot 2 of the YubiKey for the challenge response, make sure it’s not conflicting with how you use your key.

$ sudo ./ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible
$ sudo ./ykchalresp -2 <YOUPASSWORD>

The output from ykchalresp will later be used as the new LUKS password, make sure you keep a secure copy. YOURPASSWORD is the password you use at the password prompt.

Make a tar archive of the ykluks repository
$ tar czf ykluks.tar.gz ykluks

The following steps are done in dom0

Install ykpers
$ sudo qubes-dom0-update ykpers

Find a free key slot, normally only slot 0 will be in use.
$ sudo cryptsetup luksDump /dev/nvme0n1p3

Add the ykluks key to keyslot 1
$ sudo cryptsetup luksAddKey --key-slot 1 /dev/nvme0n1p3
The key you add is the output ykchalresp.

Copy ykluks to dom0 and install

qvm-run --pass-io <VM-NAME> 'cat ~/ykluks.tar.gz' > ykluks.tar.gz
tar xf ykluks.tar.gz
cd ykluks
sudo ./install

Edit /etc/default/grub
Changed the lines:

rd.luks.uuid=luks-...
rd.qubes.hide_all_usb

To:

rd.ykluks.uuid=luks-...
rd.ykluks.hide_all_usb

Update grub and dracut

sudo grub2-mkconfig -o /boot/grub2/grub.cfg
sudo dracut -f

Reboot and test if the key is working.
At the LUKS password prompt use ESC to get to the text console, both the normal LUKS prompt and the ykluks prompt will be asking for your password. Just type the ykluks password in all prompts and the system should eventually unlock. If the ykluks password doesn’t work, you should still be able to use your normal LUKS password.

You should now be able to unlock LUKS using the YubiKey and password, do not proceed if there are any issues using the key

Remove the old password in keyslot 0
$ sudo cryptsetup luksKillSlot /dev/nvme0n1p3 0

Disable the standard LUKS password prompt.
There might be a better way of doing this, but this worked for me.

cd /usr/lib/dracut/modules.d
sudo mv 90crypt x90crypt
sudo dracut -f

This should make it so there is only one password prompt when you unlock LUKS.

If you ever need to access the system without the YubiKey, you can boot into the emergency shell and unlock LUKS using the output from ykchalresp. From there you should be able to restore 90crypt, and add a new password with cryptsetup.

9 Likes

Hi Rene,

I might be missing something obvious, here, but I got an error while trying to run ‘ykpersonalize’:

user@yubikey:~/yubikey-personalization$ sudo ./ykpersonalize -2 -ochal-resp -ochal-hmac -hmac-lt64 -oserial-api-visible
Usage: ykpersonalize [options]

Do you have any ideas?

I just created an AppVM from debian-12 template, and I don’t have a sys-usb on this box.

Sincerely
Max

-hmac-lt64 should be -ohmac-lt64

If you don’t have sys-usb you need to run ykpersonalize in the vm that has the usb controller, it needs to be able to write to the key.

1 Like

IT seems that Dom0 has the controller when no sys-usb exists?

I can list the yubikey in Dom0 only:
[max@dom0 ~]$ lsusb | grep Yubikey
Bus 001 Device 021: ID 1050:0407 Yubico.com Yubikey 4/5 OTP+U2F+CCID
[max@dom0 ~]$

Tried installing sys-usb per documentation, but it fails. Has this changed in 4.2? I do believe I have installed sys-sub before on other systems with succes, but that was ages ago.

[max@dom0 ~]$ sudo qubesctl state.sls.qvm.sys-usb
‘state.sls.qvm.sys-usb’ is not available.
DOM0 configuration failed, not continuing
[max@dom0 ~]$

Sincerely
Max

One dot too many between sls and qvm!

1 Like

Goddamnit :slight_smile: - Covid ruined my brain, sorry.

Interesting fail though, sys-net-usb failed prerequsite :

[max@dom0 ~]$ sudo qubesctl state.sls qvm.sys-usb
local:
----------
          ID: default-dvm
    Function: qvm.vm
      Result: True
     Comment: ====== ['present'] ======
              /usr/bin/qvm-create default-dvm --class=AppVM --label=red 
              
              ====== ['prefs'] ======
              [SKIP] label              : red
              
              ====== ['features'] ======
     Started: 10:32:57.547444
    Duration: 7425.904 ms
     Changes:   
              ----------
              qvm.features:
                  ----------
                  qvm.features:
                      ----------
                      appmenus-dispvm:
                          ----------
                          new:
                              1
                          old:
                              None
              qvm.prefs:
                  ----------
                  qvm.create:
                      ----------
                      template_for_dispvms:
                          ----------
                          new:
                              True
                          old:
                              *default*
----------
          ID: qvm-appmenus --get-default-whitelist fedora-39 | grep -i 'firefox\|term' | qvm-appmenus --set-whitelist=- --update default-dvm
    Function: cmd.run
      Result: True
     Comment: Command "qvm-appmenus --get-default-whitelist fedora-39 | grep -i 'firefox\|term' | qvm-appmenus --set-whitelist=- --update default-dvm" run
     Started: 10:33:04.980041
    Duration: 2730.937 ms
     Changes:   
              ----------
              pid:
                  18222
              retcode:
                  0
              stderr:
                  default-dvm: Creating appmenus
                  default-dvm: Removing appmenu '/home/max/.local/share/qubes-appmenus/default-dvm/apps/org.qubes-os.vm._default_ddvm.org.gnome.Nautilus.desktop'
                  default-dvm: Removing appmenu '/home/max/.local/share/qubes-appmenus/default-dvm/apps/org.qubes-os.dispvm._default_ddvm.org.gnome.Nautilus.desktop'
              stdout:
----------
          ID: hide-usb-from-dom0-uefi
    Function: cmd.run
        Name: sed -i -e 's/^kernel=.*/\0 rd.qubes.hide_all_usb/' /boot/efi/EFI/qubes/xen.cfg
      Result: True
     Comment: onlyif condition is false
              unless condition is false
     Started: 10:33:07.712577
    Duration: 7759.761 ms
     Changes:   
----------
          ID: hide-usb-from-dom0-grub
    Function: file.append
        Name: /etc/default/grub
      Result: True
     Comment: Appended 1 lines
     Started: 10:33:15.472765
    Duration: 42.083 ms
     Changes:   
              ----------
              diff:
                  --- 
                  
                  +++ 
                  
                  @@ -9,3 +9,4 @@
                  
                   GRUB_CMDLINE_XEN_DEFAULT="console=none dom0_mem=min:1024M dom0_mem=max:4096M ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096"
                   GRUB_DISABLE_OS_PROBER="true"
                   . /etc/default/grub.qubes-kernel-vm-support
                  +GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX rd.qubes.hide_all_usb"
----------
          ID: grub2-mkconfig -o /boot/grub2/grub.cfg
    Function: cmd.run
      Result: True
     Comment: Command "grub2-mkconfig -o /boot/grub2/grub.cfg" run
     Started: 10:33:15.515727
    Duration: 20284.601 ms
     Changes:   
              ----------
              pid:
                  18580
              retcode:
                  0
              stderr:
                  Generating grub configuration file ...
                  Found theme: /boot/grub2/themes/qubes/theme.txt
                  Found linux image: /boot/vmlinuz-6.6.2-1.qubes.fc37.x86_64
                  Found initrd image: /boot/initramfs-6.6.2-1.qubes.fc37.x86_64.img
                  Found linux image: /boot/vmlinuz-6.6.2-1.qubes.fc32.x86_64
                  Found initrd image: /boot/initramfs-6.6.2-1.qubes.fc32.x86_64.img
                  Found linux image: /boot/vmlinuz-6.5.8-1.qubes.fc32.x86_64
                  Found initrd image: /boot/initramfs-6.5.8-1.qubes.fc32.x86_64.img
                  Found linux image: /boot/vmlinuz-6.1.62-1.qubes.fc37.x86_64
                  Found initrd image: /boot/initramfs-6.1.62-1.qubes.fc37.x86_64.img
                  Found linux image: /boot/vmlinuz-6.1.62-1.qubes.fc32.x86_64
                  Found initrd image: /boot/initramfs-6.1.62-1.qubes.fc32.x86_64.img
                  Found linux image: /boot/vmlinuz-6.1.57-1.qubes.fc32.x86_64
                  Found initrd image: /boot/initramfs-6.1.57-1.qubes.fc32.x86_64.img
                  Adding boot menu entry for UEFI Firmware Settings ...
                  done
              stdout:
----------
          ID: sys-net-usb
    Function: qvm.prefs
        Name: sys-net
      Result: False
     Comment: The following requisites were not found:
                                 require:
                                     sls: qvm.sys-net
     Started: 10:33:35.801237
    Duration: 0.01 ms
     Changes:   
----------
          ID: qubes-input-proxy
    Function: pkg.installed
      Result: True
     Comment: All specified packages are already installed
     Started: 10:33:35.840750
    Duration: 1986.569 ms
     Changes:   
----------
          ID: sys-usb-input-proxy
    Function: file.managed
        Name: /etc/qubes/policy.d/50-config-input.policy
      Result: True
     Comment: File /etc/qubes/policy.d/50-config-input.policy updated
     Started: 10:33:37.828217
    Duration: 7.458 ms
     Changes:   
              ----------
              diff:
                  New file
----------
          ID: /etc/systemd/system/qubes-vm@sys-net.service.d/50_autostart.conf
    Function: file.managed
      Result: True
     Comment: File /etc/systemd/system/qubes-vm@sys-net.service.d/50_autostart.conf updated
     Started: 10:33:37.836062
    Duration: 7.308 ms
     Changes:   
              ----------
              diff:
                  New file

Summary for local
------------
Succeeded: 8 (changed=6)
Failed:    1
------------
Total states run:     9
Total run time:  40.245 s
DOM0 configuration failed, not continuing
[max@dom0 ~]$

Do you have a sys-net qube?
From the log, it looks like some QVM prefs are meant to be set on sys-net, and that can’t happen because the qube is missing.

Take this with a grain of salt (no pun intended), I’m taking a somewhat educated guess from the log and file names, but I am not familiar with those specific states.

Note writing three backticks on the line before and after any logs or code block makes them a lot more readable!

``` <— like this.

Yes, I do have a sys-net qube, just not a sys-net-usb qube.

qvm-ls:

[max@dom0 ~]$ qvm-ls | grep sys-net
sys-firewall           Running  DispVM      green   fedora-39-dvm          sys-net
sys-net                Running  AppVM       red     fedora-39              -
[max@dom0 ~]$ 

Does sudo qubesctl state.sls qvm.usb-keyboard give you the same error?

ID: sys-net-usb is only a state name. By that I mean it’s an arbitrary name that doesn’t imply a sys-net-usb qubes does or should exist.

Here is the state definition indeed: qubes-mgmt-salt-dom0-virtual-machines/qvm/sys-usb.sls at ca4eda04d643be90d801db7cabe26e575cc7fe07 · QubesOS/qubes-mgmt-salt-dom0-virtual-machines · GitHub

Which renders roughly to:

sys-net-usb:  # just a (unique) name
  qvm.prefs:  # this state will ensure preferences are set
    - name: sys-net  # on the qube called sys-net
    - pcidevs: "some value, I've no clue what it looks like"  # pref. 1
    - pci_strictreset: False  # pref. 2
    - require:
      - sls: qvm.sys-net  # this state makes no sense unless the qvm.sys-net is successful first

The qvm.sys-net state is defined here: qubes-mgmt-salt-dom0-virtual-machines/qvm/sys-net.sls at ca4eda04d643be90d801db7cabe26e575cc7fe07 · QubesOS/qubes-mgmt-salt-dom0-virtual-machines · GitHub and as far as I can tell after skimmimg through it, it does ensure that the sys-net qube exists.

From memory: this error doesn’t mean that the qvm.sys-net state failed, but rather that Salt couldn’t find its definition when it needed it.

Salt needs the states that are defined in different files to be included when they are required… I’m a bit surprised because it looks like a bug, but if it was I would expect it to have been identified long ago.

I don’t see qvm.sys-net in the include block of qvm.sys-usb though, and I would have expected it: qubes-mgmt-salt-dom0-virtual-machines/qvm/sys-usb.sls at ca4eda04d643be90d801db7cabe26e575cc7fe07 · QubesOS/qubes-mgmt-salt-dom0-virtual-machines · GitHub

Taking a step back: I don’t have a way to test this myself now, so I’ll stop here, assuming I’m probably either missimg something, or making a mistake, or repeating something that’s already described in the issue tracker.

If you want to chase this error down @MilitantDK, I’d suggest searching the GitHub issues first. If you don’t find anything, I can open one with these findings.

Yes, it does.

I did upgrade from 4.1, so there might have been issues I have overseen. I am using the fedora-39 template for sys-net.

[max@dom0 ~]$ sudo qubesctl state.sls qvm.usb-keyboard
local:
----------
          ID: default-dvm
    Function: qvm.vm
      Result: True
     Comment: ====== ['present'] ======
              [SKIP] A VM with the name 'default-dvm' already exists.
              
              ====== ['prefs'] ======
              [SKIP] template_for_dispvms: True
              [SKIP] label              : red
              
              ====== ['features'] ======
              [SKIP] Feature already in desired state: ENABLE 'appmenus-dispvm' = Enabled
     Started: 12:58:42.127507
    Duration: 978.07 ms
     Changes:   
----------
          ID: qvm-appmenus --get-default-whitelist fedora-39 | grep -i 'firefox\|term' | qvm-appmenus --set-whitelist=- --update default-dvm
    Function: cmd.run
      Result: True
     Comment: Command "qvm-appmenus --get-default-whitelist fedora-39 | grep -i 'firefox\|term' | qvm-appmenus --set-whitelist=- --update default-dvm" run
     Started: 12:58:43.113703
    Duration: 2126.021 ms
     Changes:   
              ----------
              pid:
                  21222
              retcode:
                  0
              stderr:
                  default-dvm: Creating appmenus
              stdout:
----------
          ID: hide-usb-from-dom0-uefi
    Function: cmd.run
        Name: sed -i -e 's/^kernel=.*/\0 rd.qubes.hide_all_usb/' /boot/efi/EFI/qubes/xen.cfg
      Result: True
     Comment: onlyif condition is false
              unless condition is false
     Started: 12:58:45.241291
    Duration: 5480.621 ms
     Changes:   
----------
          ID: hide-usb-from-dom0-grub
    Function: file.append
        Name: /etc/default/grub
      Result: True
     Comment: File /etc/default/grub is in correct state
     Started: 12:58:50.722334
    Duration: 40.836 ms
     Changes:   
----------
          ID: grub2-mkconfig -o /boot/grub2/grub.cfg
    Function: cmd.run
      Result: True
     Comment: State was not run because none of the onchanges reqs changed
     Started: 12:58:50.764080
    Duration: 0.009 ms
     Changes:   
----------
          ID: sys-net-usb
    Function: qvm.prefs
        Name: sys-net
      Result: False
     Comment: The following requisites were not found:
                                 require:
                                     sls: qvm.sys-net
     Started: 12:58:50.764691
    Duration: 0.008 ms
     Changes:   
----------
          ID: qubes-input-proxy
    Function: pkg.installed
      Result: True
     Comment: All specified packages are already installed
     Started: 12:58:50.804017
    Duration: 1991.79 ms
     Changes:   
----------
          ID: sys-usb-input-proxy
    Function: file.managed
        Name: /etc/qubes/policy.d/50-config-input.policy
      Result: True
     Comment: File /etc/qubes/policy.d/50-config-input.policy is in the correct state
     Started: 12:58:52.796695
    Duration: 235.101 ms
     Changes:   
----------
          ID: /etc/systemd/system/qubes-vm@sys-net.service.d/50_autostart.conf
    Function: file.managed
      Result: True
     Comment: File /etc/systemd/system/qubes-vm@sys-net.service.d/50_autostart.conf is in the correct state
     Started: 12:58:53.032203
    Duration: 208.262 ms
     Changes:   
----------
          ID: sys-usb-input-proxy-keyboard
    Function: file.prepend
        Name: /etc/qubes/policy.d/50-config-input.policy
      Result: True
     Comment: Prepended 1 lines
     Started: 12:58:53.241110
    Duration: 5.828 ms
     Changes:   
              ----------
              diff:
                  --- 
                  +++ 
                  @@ -1,3 +1,4 @@
                  +qubes.InputKeyboard * sys-net dom0 allow
                   qubes.InputMouse * sys-net dom0 ask default_target=dom0
                   qubes.InputKeyboard * sys-net dom0 deny
                   # not configurable by this state
----------
          ID: unhide-usb-from-dom0-uefi
    Function: file.replace
        Name: /boot/efi/EFI/qubes/xen.cfg
      Result: True
     Comment: onlyif condition is false
     Started: 12:58:53.247310
    Duration: 36.882 ms
     Changes:   
----------
          ID: unhide-usb-from-dom0-grub
    Function: file.replace
        Name: /etc/default/grub
      Result: True
     Comment: Changes were made
     Started: 12:58:53.284519
    Duration: 42.125 ms
     Changes:   
              ----------
              diff:
                  --- 
                  +++ 
                  @@ -9,4 +9,4 @@
                   GRUB_CMDLINE_XEN_DEFAULT="console=none dom0_mem=min:1024M dom0_mem=max:4096M ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096"
                   GRUB_DISABLE_OS_PROBER="true"
                   . /etc/default/grub.qubes-kernel-vm-support
                  -GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX rd.qubes.hide_all_usb"
                  +GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX usbcore.authorized_default=0"
----------
          ID: grub-regenerate-unhide
    Function: cmd.run
        Name: grub2-mkconfig -o /boot/grub2/grub.cfg
      Result: True
     Comment: Command "grub2-mkconfig -o /boot/grub2/grub.cfg" run
     Started: 12:58:53.327477
    Duration: 21385.559 ms
     Changes:   
              ----------
              pid:
                  21417
              retcode:
                  0
              stderr:
                  Generating grub configuration file ...
                  Found theme: /boot/grub2/themes/qubes/theme.txt
                  Found linux image: /boot/vmlinuz-6.6.2-1.qubes.fc37.x86_64
                  Found initrd image: /boot/initramfs-6.6.2-1.qubes.fc37.x86_64.img
                  Found linux image: /boot/vmlinuz-6.6.2-1.qubes.fc32.x86_64
                  Found initrd image: /boot/initramfs-6.6.2-1.qubes.fc32.x86_64.img
                  Found linux image: /boot/vmlinuz-6.5.8-1.qubes.fc32.x86_64
                  Found initrd image: /boot/initramfs-6.5.8-1.qubes.fc32.x86_64.img
                  Found linux image: /boot/vmlinuz-6.1.62-1.qubes.fc37.x86_64
                  Found initrd image: /boot/initramfs-6.1.62-1.qubes.fc37.x86_64.img
                  Found linux image: /boot/vmlinuz-6.1.62-1.qubes.fc32.x86_64
                  Found initrd image: /boot/initramfs-6.1.62-1.qubes.fc32.x86_64.img
                  Found linux image: /boot/vmlinuz-6.1.57-1.qubes.fc32.x86_64
                  Found initrd image: /boot/initramfs-6.1.57-1.qubes.fc32.x86_64.img
                  Adding boot menu entry for UEFI Firmware Settings ...
                  done
              stdout:

Summary for local
-------------
Succeeded: 12 (changed=4)
Failed:     1
-------------
Total states run:     13
Total run time:   32.531 s
DOM0 configuration failed, not continuing
[max@dom0 ~]$

Tried searching the issues and found a possible solution, thoguh it did not work: qvm.sys-net-with-usb no longer exists · Issue #6507 · QubesOS/qubes-issues · GitHub

I will search further

Sincerely
Max

1 Like

Can someone provide screenshots of the completed Security Key LUKS login? I have seen what HEADS-Nitokey login looks like.

In addition to your chalresp method, there is also the GRUB-script page and the dracut-cryptenroll method for locking LUKS with a security key. Still many unanswered questions.

You are told to insert the key

You get the normal password prompt, but you need to enter the password to the key

The system unlocks if you entered the correct password.

2 Likes

Super cool! So ykchalresp is the way.

Do you know if it is possible to bypass the Yubikey through GRUB boot parameters? That would defeat the purpose of the key unless the disk is bound to TPM2.

In attempting to lock LUKS with dracut and crypttab, testing first on Fedora before on Dom0, I found out that if the disk nomenclature isn’t perfect then the system drops to emergency shell. But supposedly that isn’t a problem if you change the boot parameters in GRUB to ignore the key, or so I was told…

What do you mean?

If the passphrase that unlocks LUKS on the drive is stored in the Yubikey, and the only way to get the Yubikey to spit out the passphrase is to provide a secret correctly… then no matter how much you tinker with GRUB you won’t be able to unlock the LUKS volume. I mean… GRUB has no hand in unlocking LUKS or am I missimg something?

OMG THIS IS SO COOL :sunglasses:

thanks

It is really great to unlock the laptob with a yubikey. Now I want to set up a spare yubikey as backup. Is it possible to use this backup yubikey with luks keyslot 0 ?

I don’t know if you can make a backup of the challenge response authentication, maybe there is some way you can define the secret in the key, and repeat the process on both keys.

You could take a second YubiKey, and take the generated LUKS password and add it as a static password on the second key. The downside would be, anyone who gets access to the second key can just export the LUKS password directly from the key.

The safest backup would be to keep an encrypted copy of the LUKS password generated by the YubiKey, if you lose the key you can decrypt the password and enter it manually.

So I was playing around with the yubikey a little more to find out how to produce a backup yubikey with the same challenge response authentication. I used the YUBICO AUTHETICATOR app https://www.yubico.com/products/yubico-authenticator/ and it was super easy. Just use the secret key from the first yubikey for the second yubikey as well and you have two yubikeys with the same response when triggered with the same challenge.

Oh and I installed yubikey-personalization from the dabian repo. It seems to run flawless compared to the git-install. When using the git-install several times when triggering a response with different challenges I received the response from the first challenge although they should be different.

1 Like