Posted on :: Min Read :: 560 Words ::

I've been using an Onlykey security key for quite a while now, and while it definitely leaves quite a lot to be desired, it certainly has a few wins. I like that it's open source, that it offers a physical PIN entry, its derived key functionality, to name a few. It supports FIDO2/CTAP of course, but it also offers SSH and GPG agent functions.

Likewise, Qubes OS has a RPC proxying functions that let you separate untrusted guests (such as those you do dev work in) from more trusted ones which hold your secrets. Their split GPG function that leverages this is very popular, and many people have extended this to SSH, dm-crypt, and a bunch of other cool stuff.

Being forever unsatisfied as I am, I've put considerable energy into chaining as much of these things together as possible. I've wanted to have my Onlykey hold the private GPG keys, proxy it from the USB VM to another "vault VM" that holds the Onlykey tooling, and finally have my untrusted VMs proxy their requests to that; but I've struggled. The Onlykey's tooling doesn't expose a standard GPG agent API, and the Qubes split GPG implementation isn't supposed to support passwords on the keys...

But this evening I finally got it all to work, and it was surprisingly easy. I discovered a project that basically is another proxy between the generic GPG agent and the one offered by Onlykey: https://github.com/svareille/OnlyKey-gpg-agent

With all the pieces in place, we can get set up

Setting up

As with last time, I'll define a few variables up front that I'll reference throughout:

EXAMPLE_ONLYKEY_TEMPLATE=
EXAMPLE_ONLYKEY_APPVM=
EXAMPLE_GPG_CLIENT_VM=

dom0

First let's create everything we need in dom0:

  • Install qubes-gpg-split-dom0

  • Add the following policy to /etc/qubes/policy.d:

    qubes.Gpg   *   @tag:onlykey-gpg-client   @default  ask default_target=$EXAMPLE_ONLYKEY_APPVM
    
  • Create the server domains:

    # make the template
    qvm-create \
      --label=black \
      --class=TemplateVM \
      --memory=400 \
      --maxmem=800 \
      --vcpus=2 \
      --template=fedora-43-minimal \
      "${EXAMPLE_ONLYKEY_TEMPLATE}"
    
    # make the appvm
    qvm-create \
      --label=gray \
      --class=AppVM \
      --memory=400 \
      --maxmem=800 \
      --vcpus=2 \
      --netvm="" \
      --template="${EXAMPLE_ONLYKEY_TEMPLATE}" \
      "${EXAMPLE_ONLYKEY_APPVM}"
    
    # This can be done to reduce the AppVM's footprint
    qvm-features "${EXAMPLE_ONLYKEY_APPVM}" minimal-usbvm 1
    
  • Tag any qubes who should be allowed to proxy requests to it:

    qvm-tags "${EXAMPLE_GPG_CLIENT_VM}" a onlykey-gpg-client
    

Preparing the Onlykey Template Qube

  • The following will be done as root in the $EXAMPLE_ONLYKEY_TEMPLATE

  • First, install the required packages:

    dnf in -y \
      gnome-keying libusb1-devel pipx qubes-ctap qubes-usb-proxy systemd-devel
    
  • Next, install the Onlykey tools from pip, proxying as necessary:

    PIPX_GLOBAL_BIN_DIR=/opt https_proxy=http://127.0.0.1:8082 \
      pipx install --global onlykey onlykey-agent
    
  • Then install the other stuff you need for the Onlykey to work:

    https_proxy=http://127.0.0.1:8082 \
      curl -sL https://raw.githubusercontent.com/trustcrypto/trustcrypto.github.io/pages/49-onlykey.rules \
      > /etc/udev/rules.d/49-onlykey.rules
    
    udevadm control --reload
    udevadm trigger
    
  • Next, fetch the helper agent:

    cd /tmp
    https_proxy=http://127.0.0.1:8082 \
      curl -sLO https://github.com/svareille/OnlyKey-gpg-agent/releases/download/v1.1.2/ok-gpg-agent-linux.tar.gz
    
    echo "fd0d0dc493cef830132f84c7d66f5713db9cbf03195354bfe633ab1834b10340  ok-gpg-agent-linux.tar.gz" | sha256sum -c
    
    tar xzf ok-gpg-agent-linux.tar.gz
    
    mv bin/* /usr/bin/
    

Setting up the Onlykey AppVM

  • First, you'll wanna set up the ok-gpg-agent service which will take over for the generic GPG agent. (We don't use the Onlykey GPG agent at all actually)

    # As root
    cat <<-EOF | /usr/local/share/systemd/user/ok-gpg-agent.service
    [Unit]
    Description=GnuPG cryptographic agent and passphrase cache
    Requires=gpg-agent.socket
    Conflicts=gpg-agent.service
    
    [Service]
    ExecStart=/usr/bin/ok-gpg-agent
    ExecReload=/usr/bin/gpgconf --reload gpg-agent
    Environment=GNUPGHOME=/home/user/.gnupg/onlykey
    EOF
    
    # As uid 1000
    systemctl --user enable --now ok-gpg-agent.service
    

    [!NOTE] Of course, you can do this in a DispVM and transfer the bins after

  • Loading the keypairs is out of the scope of today's article, but I'll write a guide soon cause there's a ton of misinfo out there. You'll have to set up your ok-agent.toml in any case, so refer to the repo on how to do that.

Setting up the client AppVM

  • In their templates, ensure that qubes-gpg-split is installed

  • From the desired AppVMs, simply set QUBES_GPG_DOMAIN to @default, and then use qubes-gpg-client or qubes-gpg-client-wrapper as described in the documentation

Wrapping up

With all that done, you should be able to plug in your Onlykey, proxy it to your Onlykey vault/sys qube, and start using it as you would the usual split GPG once it's unlocked! It even works with the physical tap!

Table of Contents