Title: Authenticate the SSH servers you are connecting to
       Author: Solène
       Date: 05 August 2023
       Tags: ssh security
       Description: In this article, you will learn how to use SSHFP DNS
       records in order to prevent TOFU when using SSH.
       
       # Introduction
       
       It's common knowledge that SSH connections are secure; however, they
       always had a flaw: when you connect to a remote host for the first
       time, how can you be sure it's the right one and not a tampered system?
       
       SSH uses what we call TOFU (Trust On First Use), when you connect to a
       remote server for the first time, you have a key fingerprint displayed,
       and you are asked if you want to trust it or not.  Without any other
       information, you can either blindly trust it or deny it and not
       connect.  If you trust it, the key's fingerprint is stored locally in
       the file `known_hosts`, and if the remote server offers you a different
       key later, you will be warned and the connection will be forbidden
       because the server may have been replaced by a malicious one.
       
       Let's try an analogy.  It's a bit like if you only had a post-it with,
       supposedly, your bank phone number on it, but you had no way to verify
       if it was really your bank on that number.  This would be pretty bad. 
       However, using an up-to-date trustable public reverse lookup directory,
       you could check that the phone number is genuine before calling.
       
       What we can do to improve the TOFU situation is to publish the server's
       SSH fingerprint over DNS, so when you connect, SSH will try to fetch
       the fingerprint if it exists and compare it with what the server is
       offering.  This only works if the DNS server uses DNSSEC, which
       guarantees the DNS answer hasn't been tampered with in the process. 
       It's unlikely that someone would be able to simultaneously hijack your
       SSH connection to a different server and also craft valid DNSSEC
       replies.
       
       # Setup
       
       The setup is really simple, we need to gather the fingerprints of each
       key (they exist in multiple different crypto) on a server, securely,
       and publish them as SSHFP DNS entries.
       
       If the server has new keys, you need to update its SSHFP entries.
       
       We will use the tool `ssh-keygen` which contains a feature to
       automatically generate the DNS records for the server on which the
       command is running.
       
       For example, on my server `interbus.perso.pw`, I will run `ssh-keygen
       -r interbus.perso.pw.` to get the records
       
       ```
       $ ssh-keygen -r interbus.perso.pw.
       interbus.perso.pw. IN SSHFP 1 1 d93504fdcb5a67f09d263d6cbf1fcf59b55c5a03
       interbus.perso.pw. IN SSHFP 1 2 1d677b3094170511297579836f5ef8d750dae8c481f464a0d2fb0943ad9f0430
       interbus.perso.pw. IN SSHFP 3 1 98350f8a3c4a6d94c8974df82144913fd478efd8
       interbus.perso.pw. IN SSHFP 3 2 ec67c81dd11f24f51da9560c53d7e3f21bf37b5436c3fd396ee7611cedf263c0
       interbus.perso.pw. IN SSHFP 4 1 cb5039e2d4ece538ebb7517cc4a9bba3c253ef3b
       interbus.perso.pw. IN SSHFP 4 2 adbcdfea2aee40345d1f28bc851158ed5a4b009f165ee6aa31cf6b6f62255612
       ```
       
       You certainly noted I used an extra dot, this is because they will be
       used as DNS records, so either:
       
       * Use the full domain name with an extra dot to indicate you are not
       giving a subdomain
       * Use only the subdomain part, this would be `interbus` in the example
       
       If you use `interbus.perso.pw` without the dot, this would be for the
       domain `interbus.perso.pw.perso.pw` because it would be treated as a
       subdomain.
       
       Note that `-r arg` isn't used for anything but the raw text in the
       output, this doesn't make `ssh-keygen` fetch the keys of a remote URL.
       
       Now, just add each of the generated entries in your DNS.
       
       # How to use SSHFP on your OpenSSH client
       
       By default, if you connect to my server, you should see this output:
       
       ```
       > ssh interbus.perso.pw
       The authenticity of host 'interbus.perso.pw (46.23.92.114)' can't be established.
       ED25519 key fingerprint is SHA256:rbzf6iruQDRdHyi8hRFY7VpLAJ8WXuaqMc9rb2IlVhI.
       This key is not known by any other names
       Are you sure you want to continue connecting (yes/no/[fingerprint])? 
       ```
       
       It's telling you the server isn't known in `known_hosts` yet, and you
       have to trust it (or not, but you wouldn't connect).
       
       However, with the option `VerifyHostKeyDNS` set to yes, the fingerprint
       will automatically be accepted if the one offered is found in an SSHFP
       entry.
       
       As I explained earlier, this only works if the DNS answer is valid with
       regard to DNSSEC, otherwise, the setting "VerifyHostKeyDNS"
       automatically falls back to "ask", asking you to manually check the DNS
       SSHFP found and if you want to accept or not.
       
       For example, without a working DNSSEC, the output would look like this:
       
       ```
       $ ssh -o VerifyHostKeyDNS=yes interbus.perso.pw
       The authenticity of host 'interbus.perso.pw (46.23.92.114)' can't be established.
       ED25519 key fingerprint is SHA256:rbzf6iruQDRdHyi8hRFY7VpLAJ8WXuaqMc9rb2IlVhI.
       Matching host key fingerprint found in DNS.
       This key is not known by any other names
       Are you sure you want to continue connecting (yes/no/[fingerprint])?
       ```
       
       With a working DNSSEC, you should immediately connect without any TOFU
       prompt, and the host fingerprint won't be stored in `known_hosts`.
       
       # Conclusion
       
       SSHFP is a simple mechanism to build a chain of trust using an external
       service to authenticate the server you are connecting to.  Another
       method to authenticate a remote server would be to use an SSH
       certificate, but I'll keep that one for later.
       
       # Going further
       
       We saw that VerifyHostKeyDNS is reliable, but doesn't save the
       fingerprint in the file `~/.ssh/known_hosts`, which can be an issue if
       you need to connect later to the same server if you don't have a
       working DNSSEC resolver, you would have to trust blindly the server.
       
       However, you could generate the required output from the server to be
       used by the known_hosts when you have DNSSEC working, so next time, you
       won't only rely on DNSSEC.
       
       Note that if the server is replaced by another one and its SSHFP
       records updated accordingly, this will ask you what to do if you have
       the old keys in known_hosts.
       
       To gather the fingerpints, connect on the remote server, which will be
       `remote-server.local` in the example and add the command output to your
       known_hosts file:
       
       ```
       ssh-keyscan localhost 2>/dev/null | sed 's/^localhost/remote-server/'
       ```
       
       We omit the `.local` in the `remote-server.local` hostname because it's
       a subdomain of the DNS zone. (thanks Francisco Gaitán for spotting
       it).
       
       Basically, `ssh-keyscan` can remotely gather keys, but we want the
       local keys of the server, then we need to modify its output to replace
       localhost by the actual server name used to ssh into it.