SSH trust and keys

November 10, 2019 - Reading time: 5 minutes

Summary

In this post I'll show practical examples on how to verify that we are connecting to the correct SSH endpoint and how to exchange keys for password-less authentication.

Who are you?

When we try to connect to an SSH server for the first time, we get a warning about the authenticity of the host we are about to connect, followed by a fingerprint and a question.. but where does the signature comes from? and how can we be certain that it comes from the right host?

root@chromeos:~# ssh 192.168.1.200
The authenticity of host '192.168.1.200 (192.168.1.200)' can't be established.
ECDSA key fingerprint is SHA256:DqEhLL8M8YjVJk8XDIdlOJQ235faGOjkL8MmZ+yPxWM.
Are you sure you want to continue connecting (yes/no)? 

In older versions of the ssh client we used to get an MD5 fingerprint

root@chromeos:~# ssh -o FingerprintHash=md5 192.168.1.200
The authenticity of host '192.168.1.200 (192.168.1.200)' can't be established.
ECDSA key fingerprint is MD5:8a:4a:b9:84:81:f8:0f:de:cb:89:be:85:42:1a:5c:30.
Are you sure you want to continue connecting (yes/no)?

In this example we are getting the ECDSA (Elliptic Curve Digital Signature Algorithm) fingerprint, but we could specify a different key algorithm

root@chromeos:~# ssh -o HostKeyAlgorithms=ssh-rsa 192.168.1.200
The authenticity of host '192.168.1.200 (192.168.1.200)' can't be established.
RSA key fingerprint is SHA256:4skDV2xIXiQLwe0EsAdCmUjXm8CteDN2DGQcrx61lQk.
Are you sure you want to continue connecting (yes/no)?

A full list of supported key algorithms is available by running the following command:

root@chromeos:~# ssh -Q key
ssh-ed25519
ssh-ed25519-cert-v01@openssh.com
ssh-rsa
ssh-dss
ecdsa-sha2-nistp256
ecdsa-sha2-nistp384
ecdsa-sha2-nistp521
ssh-rsa-cert-v01@openssh.com
ssh-dss-cert-v01@openssh.com
ecdsa-sha2-nistp256-cert-v01@openssh.com
ecdsa-sha2-nistp384-cert-v01@openssh.com
ecdsa-sha2-nistp521-cert-v01@openssh.com

These fingerprints are generated in the client, from the public keys in the server. In this case, the server has:

root@raspberrypi:/etc/ssh# ll ssh_host_*pub
-rw-r--r-- 1 root root 606 Sep 26 01:24 ssh_host_dsa_key.pub
-rw-r--r-- 1 root root 178 Sep 26 01:24 ssh_host_ecdsa_key.pub
-rw-r--r-- 1 root root  98 Sep 26 01:24 ssh_host_ed25519_key.pub
-rw-r--r-- 1 root root 398 Sep 26 01:24 ssh_host_rsa_key.pub

To verify that we are connecting to the right server, we need to compare the public key in the server and the one that we are receiving in the client.

root@raspberrypi:/etc/ssh# cat ssh_host_ecdsa_key.pub 
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFfsub7U+Sb6h4obExzsxzXanqzVfE4sNozlkl2Bu0j/f5ZomIv5ieY+5oTZvutW/e/eLrZ6LL5McNJJ8WR2eIo= root@raspberrypi

Then we can get the full key in the client

root@chromeos:~# ssh-keyscan -t ecdsa  192.168.1.200 
# 192.168.1.200:22 SSH-2.0-OpenSSH_7.9p1 Raspbian-10+deb10u1
192.168.1.200 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFfsub7U+Sb6h4obExzsxzXanqzVfE4sNozlkl2Bu0j/f5ZomIv5ieY+5oTZvutW/e/eLrZ6LL5McNJJ8WR2eIo=

And finally, to go full circle, and check that the digest of the key is the same as the one we get in the question, we can use key-gen

root@chromeos:~# ssh-keyscan -t ecdsa 192.168.1.200 | ssh-keygen -lf -
# 192.168.1.200:22 SSH-2.0-OpenSSH_7.9p1 Raspbian-10+deb10u1
256 SHA256:DqEhLL8M8YjVJk8XDIdlOJQ235faGOjkL8MmZ+yPxWM 192.168.1.200 (ECDSA)

I trust you... now what?

Your client keeps a list of all the hosts where the authenticity has been verified. When we answer the question with yes, we add this new host to the list.

root@chromeos:~# tail -n1 .ssh/known_hosts
|1|ZCjQ5DVz31CIjAcqJlhL6/PBce0=|1Bzfnbf0BUBYmsmSYHXtmIICRqY= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFfsub7U+Sb6h4obExzsxzXanqzVfE4sNozlkl2Bu0j/f5ZomIv5ieY+5oTZvutW/e/eLrZ6LL5McNJJ8WR2eIo=

We can see the public ECDSA key of the server in that output. But what are those strings that precede the key? This is the result of an ssh option called HashKnownHosts. When the option is enabled, instead of storing the IP address or hostname, we take this value, add some salt to it, and then hash it. So, what we have is:

HASH_MAGIC Salt Hashed value
1 ZCjQ5DVz31CIjAcqJlhL6/PBce0= 1Bzfnbf0BUBYmsmSYHXtmIICRqY=