Introduction
A hardware security key is a great device for protecting your security online. They take the form of a small physical object, usually resembling a USB stick or dongle, and can be inserted in your computers USB port and tapped or pressed to authenticate to online services. The fact that the object is physical and works with advanced cryptography can dramatically increase your protection against remote attacks like phishing and MITM (Man in the middle) attacks.
FIDO2 and U2F (Universal 2 Factor) are two protocols that form the basis behind how your key authenticates with the web and other internet protocols.
In this tutorial we will be focusing on the support that OpenSSH has for these protocols, and getting it set up for you to authenticate to your server or cloud instance running Debian 10 (Stable).
To do this we'll need to compile and build the latest version (8.3 at time of writing) of OpenSSH from the next version of Debian (11/Bullseye) in a way that it will work nicely with Debian 10. It should behave fine since it is just a simple backport.
Keep in mind that building the software from source will mean that you won't receive updates for it via apt
like the rest of your software. To keep up to date with security issues you should subscribe to Debian's security mailing list or an equivalent for your distribution.
Since this tutorial gets involved in the build process of Debian packages, it may seem a little complicated or involved at times. That said, these instructions are designed to be used by people of any skill level, and it's likely you'll learn something new along the way! :)
Prerequisites
To follow this tutorial you will need:
- A server running Debian 10, either dedicated or virtual.
- A local Linux installation or Windows installation (With Ubuntu or Debian installed through Windows Subsystem for Linux)
- Arch Linux, Debian 10+ and Ubuntu 20.04+ are known to be compatible, anything else may work if it provides a version of OpenSSH 8.2 or later. macOS is untested.
- A U2F compatible hardware security key, for example a YubiKey or Solo Key.
This tutorial was tested on a fresh Hetzner Cloud CPX11 instance, though it should run perfectly fine on any other server.
Step 1 - Preparing the system
If you've just provisioned the server you'll want to upgrade the installed packages and prepare the system for the package building.
Step 1.1 - Upgrading and preparing the package repositories
You can upgrade your packages with:
root@<your_host>:~# apt update
root@<your_host>:~# apt upgrade
You will also need to add the buster-backports
repository if it's not already present.
Open /etc/apt/sources.list
and check for the follow lines in the list. If either is not there, add them:
/etc/apt/sources.list
:
deb http://deb.debian.org/debian buster-backports main
deb-src http://deb.debian.org/debian buster-backports main
You can then add the source repositories for Bullseye, the upcoming release of Debian which has the package we need. Please make sure you are only specifying deb-src
here, and not deb
.
/etc/apt/sources.list
:
deb-src http://deb.debian.org/debian bullseye main
Optionally if you're attempting this on a Hetzner server (Dedicated or cloud!), you can speed up the process of downloading these packages by adding an entry to download from the Hetzner APT Mirror:
/etc/apt/sources.list.d/hetzner-mirror.list
:deb http://mirror.hetzner.de/debian/packages buster main contrib non-free deb http://mirror.hetzner.de/debian/packages buster-updates main contrib non-free deb http://mirror.hetzner.de/debian/packages buster-backports main contrib non-free deb-src http://deb.debian.org/debian bullseye main
After all these changes you'll need to run apt update
one more time to download the package information.
You can verify you did it right by making sure both buster-backports
and bullseye
are mentioned in the output!
Step 1.2 - Creating a sudo user
A quick note at this point is to create a user account if you haven't already, and allow them to access the root command through sudo
.
For example:
root@<your_host>:~# adduser holu
Adding user `holu' ...
Adding new group `holu' (1001) ...
Adding new user `holu' (1001) with group `holu' ...
Creating home directory `/home/holu' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for holu
Enter the new value, or press ENTER for the default
Full Name []: Hetzner Online Live User
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n]
root@<your_host>:~# usermod -aG sudo username
You can then login as that user with a fresh login shell like so:
root@<your_host>:~# sudo -i -u holu
holu@<your_host>:~$
And quickly check sudo
is configured properly with the following command:
holu@<your_host>:~$ sudo whoami
[sudo] password for holu:
root
holu@<your_host>:~$
Step 2 - Installing development dependencies
First, we need to install the base Debian development dependencies:
holu@<your_host>:~$ sudo apt-get install build-essential fakeroot devscripts
There are 3 packages that we will need to build in order to successfully backport OpenSSH 8.3 to Buster. These are libfido2
, dh-runit
and of course openssh
.
Of these, libfido2
depends on a version of cmake
that is not present in Debian 10 Buster. Thankfully it is provided in the buster-backports
repository, and will need to be installed manually:
holu@<your_host>:~$ sudo apt-get install -t buster-backports cmake
From here, we can now download the build dependencies for libfido2
and dh-runit
. We will be fetching the openssh
build dependencies later, since it relies on these two.
holu@<your_host>:~$ sudo apt-get build-dep libfido2 dh-runit
That should be all the dependencies for now, we're ready to download the source code!
First, create a directory to store the code, then enter it:
holu@<your_host>:~$ mkdir -p src/debian
holu@<your_host>:~$ cd src/debian
holu@<your_host>:~/src/debian$
And use the helpful function of apt-get
to download the source code automatically!
holu@<your_host>:~/src/debian$ apt-get source libfido2 dh-runit openssh
holu@<your_host>:~/src/debian$ ls -1
dh-runit-2.8.15
dh-runit_2.8.15.dsc
dh-runit_2.8.15.tar.xz
libfido2-1.4.0
libfido2_1.4.0-2.debian.tar.xz
libfido2_1.4.0-2.dsc
libfido2_1.4.0.orig.tar.gz
libfido2_1.4.0.orig.tar.gz.asc
openssh-8.3p1
openssh_8.3p1-1.debian.tar.xz
openssh_8.3p1-1.dsc
openssh_8.3p1.orig.tar.gz
openssh_8.3p1.orig.tar.gz.asc
holu@<your_host>:~/src/debian$
That's all! Now we're ready for the build!
Step 3 - Building the packages
Of these packages, libfido2
should be installed first. Luckily it's quite a simple process.
First, enter the directory:
holu@<your_host>:~/src/debian$ cd libfido2-1.4.0/
holu@<your_host>:~/src/debian/libfido2-1.4.0$
(Make sure to replace the version number with the one you have downloaded. If you press the tab key after typing libfido2-
it should autocomplete the correct folder.)
And then build!
holu@<your_host>:~/src/debian/libfido2-1.4.0$ debuild -b -uc -us
These parameters will build a binary only package (.deb
file to install!), as well as instruct debuild
to not sign the package, since it's only needed for personal use (and I'm assuming you trust yourself).
The build should complete relatively quickly on most systems. Once it's done, you'll want to install the needed packages:
holu@<your_host>:~/src/debian/libfido2-1.4.0$ cd ..
holu@<your_host>:~/src/debian$ sudo dpkg -i libfido2-1_1.4.0-2_amd64.deb fido2-tools_1.4.0-2_amd64.deb libfido2-dev_1.4.0-2_amd64.deb
Again, please make sure to replace the version numbers with the ones from your packages. Tab completion should help with that. Additionally, please make sure to not install packages with dbgsym
in their name. These are for debugging purposes and are not needed for our purposes.
Next we can build the dh-runit
package. The process should be the same as before:
holu@<your_host>:~/src/debian$ cd dh-runit-2.8.15/
holu@<your_host>:~/src/debian/dh-runit-2.8.15$ debuild -b -uc -us
And install:
holu@<your_host>:~/src/debian/dh-runit-2.8.15$ cd ..
holu@<your_host>:~/src/debian$ sudo dpkg -i dh-runit_2.8.15_all.deb runit-helper_2.8.15_all.deb
With that out of the way we have all the dependencies we need to get started with building OpenSSH!
Same as before, we'll need to install its build dependencies:
holu@<your_host>:~/src/debian$ sudo apt-get build-dep openssh
Build the source code:
holu@<your_host>:~/src/debian$ cd openssh-8.3p1/
holu@<your_host>:~/src/debian/openssh-8.3p1$ debuild -b -uc -us
(This build might take a little while if your server doesn't have enough processing resources. Don't worry though, it should proceed fine.)
And install the packages!
holu@<your_host>:~/src/debian/openssh-8.3p1$ cd ..
holu@<your_host>:~/src/debian$ sudo dpkg -i ssh_8.3p1-1_all.deb openssh-client_8.3p1-1_amd64.deb openssh-server_8.3p1-1_amd64.deb openssh-sftp-server_8.3p1-1_amd64.deb
At this point it may ask you if you want to replace your /etc/ssh/sshd_config
file with a new one. This choice is up to you, I always personally keep the current configuration file since they're usually backwards and forwards compatible with each other.
Once you're done, check that it all worked!
holu@<your_host>:~/src/debian$ ssh -V
OpenSSH_8.3p1 Debian-1, OpenSSL 1.1.1d 10 Sep 2019
If the output includes a version of 8.3
or higher, congratulations! You have successfully built a newer version of OpenSSH for Debian 10 Buster!
Step 4 - Client setup
Next we will need to setup our client so that we can authenticate with our key!
Step 4.1 - Installing a supported OpenSSH
Windows
If you're running Windows 10, install the Windows Subsystem for Linux and then go through the following Linux package install instructions. There will be special instructions in Step 4.2 for setting up WSL to work with your key.
Versions of Windows older than Windows 10 do not support U2F security keys in a compatible way.
Ubuntu 20.04 or higher
Ubuntu 20.04 or higher have support for OpenSSH 8.3 out of the box, so simply sudo apt-get install openssh
if you haven't already! :)
You should skip over the following Debian section if that worked, and rejoin at step 4.2!
Debian 10 Buster
If you're running Debian 10 Buster on your client side, you'll need to install a backported version of OpenSSH again!
Thankfully, you've already done that. You can copy the resulting .deb
files and install them on your local machine.
First, put them in their own folder:
holu@<your_host>:~/src/debian$ mkdir debs
holu@<your_host>:~/src/debian$ mv *.deb debs/
(The *
character acts as a wildcard, so in this case of *.deb
it will move any file with a .deb
file extension.)
Then, from your local machine, copy them locally.
If you logged into your server as root originally, you may need to use root for this operation. Alternatively, you can setup your regular user with your SSH keys. In the example I will be using root.
erisa@<your_local_machine>:~$ scp root@<203.0.113.1>:/home/holu/src/debian/debs .
(In this example, erisa
is my local username on my computer. You'll also want to replace <203.0.113.1>
with your server's IP address.)
In that command, -r
will make it act recursively in order to download an entire directory, and .
refers to the current directory on your local machine.
You can now install the packages from the deb
folder you just downloaded locally:
erisa@<your_local_host>:~$ cd debs
erisa@<your_local_host>:~/debs$ sudo dpkg -i libfido2-1_1.4.0-2_amd64.deb fido2-tools_1.4.0-2_amd64.deb libfido2-dev_1.4.0-2_amd64.deb dh-runit_2.8.15_all.deb runit-helper_2.8.15_all.deb ssh_8.3p1-1_all.deb openssh-client_8.3p1-1_amd64.deb openssh-server_8.3p1-1_amd64.deb openssh-sftp-server_8.3p1-1_amd64.deb
This command is a bit of a big one. You may want to simplify it to sudo dpkg -i *.deb
however be aware that will install a few undesired packages and dependencies on your local machine. They will not however cause any harm, just take up storage space for no real reason.
If you get an error like this one:
dpkg: dependency problems prevent configuration of dh-runit:
dh-runit depends on debhelper (>= 9); however:
Package debhelper is not installed.
dh-runit depends on libtext-hogan-perl; however:
Package libtext-hogan-perl is not installed.
dh-runit depends on libfile-slurp-perl; however:
Package libfile-slurp-perl is not installed.
dh-runit depends on libfile-copy-recursive-perl; however:
Package libfile-copy-recursive-perl is not installed.
dpkg: error processing package dh-runit (--install):
dependency problems - leaving unconfigured
Don't worry! All you need to do is install some missing dependencies.
Simply run the following command:
erisa@<your_local_host>:~/debs$ sudo apt-get install -f
And apt
will find and install the required dependencies for you.
Now all that's left is to do a simple version check:
erisa@<your_local_host>:~/debs$ ssh -V
OpenSSH_8.3p1 Debian-1, OpenSSL 1.1.1d 10 Sep 2019
And you're done! Now for the fun part - getting the key to work with your new installations.
Step 4.2 - Key generation and interop
Once you're all set up with the appropriate OpenSSH versions of both server and client, you're ready to generate your SSH key pair!
If you're running Windows, skip over these instructions to the section on Windows below.
Linux
The format we'll be using here is ecdsa-sk
.
Issue the following command:
erisa@<your_local_host>:~$ ssh-keygen -t ecdsa-sk
It should ask you to touch your security key, enter a PIN if one is set up and save the key. You can also add a local passphrase if you want to further protect your SSH connections!
Once you're done, you can find out the public key:
erisa@<your_local_host>:~$ cat .ssh/id_ecdsa_sk.pub
sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBO5Uhm6IskKKlpu+waAlkZ79wE8hFBPpoPkEHb2V6sgCk+6UDbPCyU/siW6D4eHGMDzM4VVhvSkqrEpYa8samsQAAAAEc3NoOg== erisa@<your_local_host>
And add it to your server!
holu@<your_host>:~$ mkdir -p .ssh
holu@<your_host>:~$ echo '<your_public_key' >> .ssh/authorized_keys
At this point you should be able to successfully authenticate with your server!
If it's using an old or different SSH key, supply -i .ssh/id_ecdsa_sk
on the SSH commandline to use your security key instead.
Windows
If you're running Windows 10 with WSL, you'll need to setup your WSL instance to interact with your security key over the Windows U2F API.
To achieve this we'll be using a piece of open source software called windows-fido-bridge.
You can check out the installation and usage instructions on github.
Essentially it boils down to (at the time of writing)
sudo apt install build-essential cmake g++-mingw-w64-x86-64 git libfmt-dev libgtest-dev
git clone https://github.com/mgbowen/windows-fido-bridge.git
cd windows-fido-bridge
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j $(nproc)
make test
sudo make install
On your local WSL instance.
You can then generate a keypair with:
erisa@<your_local_host>:~$ SSH_SK_PROVIDER=/usr/local/lib/libwindowsfidobridge.so ssh-keygen -t ecdsa-sk
Add it to your server with:
erisa@<your_local_host>:~$ cat .ssh/id_ecdsa_sk.pub
sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBO5Uhm6IskKKlpu+waAlkZ79wE8hFBPpoPkEHb2V6sgCk+6UDbPCyU/siW6D4eHGMDzM4VVhvSkqrEpYa8samsQAAAAEc3NoOg== erisa@<your_local_host>
holu@<your_host>:~$ mkdir -p .ssh
holu@<your_host>:~$ echo '<your_public_key' >> .ssh/authorized_keys
And connect to your server with:
erisa@<your_local_host>:~$ ssh -oSecurityKeyProvider=/usr/local/lib/libwindowsfidobridge.so remote-server
If you are going to be using the Security Key for all of your authentications, you can instead add the following to .ssh/config
after creating a key pair:
Host *
SecurityKeyProvider /usr/local/lib/libwindowsfidobridge.so
IdentityFile ~/.ssh/id_ecdsa_sk
Conclusion
Phew, that was a long ride! Hopefully you learnt something, and are now able to securely authenticate with your server using your hardware security key!
If in the future you need to update to a newer version of OpenSSH (Security patches, etc.) you can simply redownload the source code and run build/install commands by following the above instructions again!