This is really cool. After DNSSEC is used to sign a complete zone, SSH connections can be authenticated via checking the SSH fingerprint against the SSHFP resource record on the DNS server. With this way, administrators will never get the well-known “The authenticity of host ‘xyz’ can’t be established.” message again. Here we go:
The Problem
If you are an SSH admin you definitely know the following message:
1 2 3 4 |
pi@ntp1:~ $ ssh lx.weberdns.de The authenticity of host 'lx.weberdns.de (2003:51:6012:110::9)' can't be established. ECDSA key fingerprint is 49:f7:d1:ea:2c:4c:f8:0a:5b:26:08:b2:ce:7d:bb:76. Are you sure you want to continue connecting (yes/no)? ^C |
You are connecting to an SSH server the first time and you get the “can’t be established” message. And we all know: Nobody ever checks this fingerprint against a manually distributed list of fingerprints… ;) That is: If the first attempt to a new SSH server is spoofed by a man-in-the-middle attack (or a next-generation firewall with SSH decryption), you won’t recognize it!
The Solution: SSHFP
A technical solution to overcome this “whom can you trust” problem is the secure distribution of SSHFP (Secure Shell Key Fingerprints) within the Domain Name System (DNS). If the authoritative DNS server is signed via DNSSEC, the connecting SSH client can securely verify/authenticate the fingerprint of the SSH server it is connecting to.
The standard is defined in RFC 4255 “Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints” and RFC 6594 “Use of the SHA-256 Algorithm with RSA, Digital Signature Algorithm (DSA), and Elliptic Curve DSA (ECDSA) in SSHFP Resource Records” and RFC 7470 “Using Ed25519 in SSHFP Resource Records”. The DNS SSHFP Resource Record Parameters are listed by IANA.
The only required step is to distribute the SSH fingerprints within the DNS. To accomplish this, the fingerprints must be generated/listed on the SSH server itself via the ssh tool ssh-keygen -r name. This lists the fingerprints for all available public key algorithms (RSA, DSA, ECDSA, Ed25519) in SHA1 and SHA256:
1 2 3 4 5 6 7 8 9 |
weberjoh@jw-nb12:~$ ssh-keygen -r lx lx IN SSHFP 1 1 1449b6b574092366759d72c3617fd69b6093b22e lx IN SSHFP 1 2 9ad79ecde76840aad6c07dddd8463f95f121029b94181786f6135fb817152835 lx IN SSHFP 2 1 cb0b8a83ab63f37d475a5b44f9558c96b65f4d91 lx IN SSHFP 2 2 5d1c30f734c6b1ffdc9f5c058245db7b15591855cab0149508f58bf65ddfafb9 lx IN SSHFP 3 1 0e91f83edce8bfc7c72d4e538c28d1bbcd66fb15 lx IN SSHFP 3 2 4f4f7fa7f652b9b9e4ada1b7a2c94331e85f2222d17ba522567d5d199bed8d91 lx IN SSHFP 4 1 df623280cbffce18f0bb37fc8864df51fd3d7e90 lx IN SSHFP 4 2 4e125c92d0c3bf6162b3b253ea49a167c129b606c778a1d5940bfdbab0ae8668 |
After these records are placed into the DNS server zone (and signed via DNSSEC), they can be queried and validated via DNSSEC. Note the “AD” flag for authentic data within the DNS header. There is only one RRSIG record since all SSHFP records are signed at once.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
weberjoh@jw-nb12-lx:~$ dig lx.weberdns.de sshfp +dnssec +multi ; <<>> DiG 9.10.3-P4-Ubuntu <<>> lx.weberdns.de sshfp +dnssec +multi ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48421 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 9, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 4096 ;; QUESTION SECTION: ;lx.weberdns.de. IN SSHFP ;; ANSWER SECTION: lx.weberdns.de. 3589 IN SSHFP 1 1 ( 1449B6B574092366759D72C3617FD69B6093B22E ) lx.weberdns.de. 3589 IN SSHFP 2 1 ( CB0B8A83AB63F37D475A5B44F9558C96B65F4D91 ) lx.weberdns.de. 3589 IN SSHFP 2 2 ( 5D1C30F734C6B1FFDC9F5C058245DB7B15591855CAB0 149508F58BF65DDFAFB9 ) lx.weberdns.de. 3589 IN SSHFP 4 2 ( 4E125C92D0C3BF6162B3B253EA49A167C129B606C778 A1D5940BFDBAB0AE8668 ) lx.weberdns.de. 3589 IN SSHFP 1 2 ( 9AD79ECDE76840AAD6C07DDDD8463F95F121029B9418 1786F6135FB817152835 ) lx.weberdns.de. 3589 IN SSHFP 3 1 ( 0E91F83EDCE8BFC7C72D4E538C28D1BBCD66FB15 ) lx.weberdns.de. 3589 IN SSHFP 4 1 ( DF623280CBFFCE18F0BB37FC8864DF51FD3D7E90 ) lx.weberdns.de. 3589 IN SSHFP 3 2 ( 4F4F7FA7F652B9B9E4ADA1B7A2C94331E85F2222D17B A522567D5D199BED8D91) lx.weberdns.de. 3589 IN RRSIG SSHFP 8 3 3600 ( 20160911095338 20160812085338 57909 weberdns.de. p+r5oNKajiQXN/arL8nYtUCSGJ8kWOY/2ihxhNy/NOVB MkOXQn6XI5+eEXzyl8S6y6rQ6Zc7cRXXlodyaaZGGPGc vdtB+ooez7Nms2+q5t2qbpyjU/LJ9UF45iMc0cTMcJ6K XuUNjjJ/pDLv8IJkURrYDSka3AGB2itdtAGdyd8= ) ;; Query time: 1 msec ;; SERVER: 192.168.120.22#53(192.168.120.22) ;; WHEN: Tue Aug 30 13:58:50 CEST 2016 ;; MSG SIZE rcvd: 534 |
An SSH client that is configured to check the SSHFP record is now able to verify the fingerprint. If this client furthermore gets authentic data (DNSSEC validated “AD” flag), it will silently connect to the SSH server since it was able to authenticate the server. Great!
Tests
Currently, the VerifyHostKeyDNS option from the OpenSSH client is not enabled by default. That is, a connection to an unknown server will still result in the following message:
1 2 3 4 |
weberjoh@jw-vm08-rdns:~$ ssh lx.weberdns.de The authenticity of host 'lx.weberdns.de (2003:51:6012:110::9)' can't be established. ECDSA key fingerprint is SHA256:T09/p/ZSubnkraG3oslDMehfIiLRe6UiVn1dGZvtjZE. Are you sure you want to continue connecting (yes/no)? ^C |
But when used with the -o VerifyHostKeyDNS=yes option, it will not warn about an unauthenticated server, because it IS authenticated now:
1 2 |
weberjoh@jw-vm08-rdns:~$ ssh -o VerifyHostKeyDNS=yes weberjoh@lx.weberdns.de weberjoh@lx.weberdns.de's password: |
Using the -v flag you can see the following two lines that reveal that (1) SSHFP records are found in DNS (called “secure fingerprints” because DNSSEC is used), and (2) that they match the host key from the server:
1 2 3 4 5 6 |
weberjoh@jw-vm08-rdns:~$ ssh -v -o VerifyHostKeyDNS=yes weberjoh@lx.weberdns.de [...] debug1: found 8 secure fingerprints in DNS debug1: matching host key fingerprint found in DNS [...] weberjoh@lx.weberdns.de's password: |
Of course, this option can/should/must be set in the global ssh config, too:
1 2 |
sudo nano /etc/ssh/ssh_config VerifyHostKeyDNS yes |
This is great at all! Customers that have many servers and firewalls placed around the world can now connect from a jump host to any of them without the fear of man-in-the-middle decrypted SSH sessions. Yeah.
Test without DNSSEC Validation
Note that it is crucial that the DNS reply is DNSSEC validated (= “ad” flag when testing with dig). If not, OpenSSH will get the SSHFP record (“Matching host key fingerprint found in DNS.”) but will still warn such as:
1 2 3 4 5 |
pi@ntp2:~ $ ssh -o VerifyHostKeyDNS=yes weberjoh@lx.weberdns.de The authenticity of host 'lx.weberdns.de (2003:51:6012:110::9)' can't be established. ECDSA key fingerprint is 49:f7:d1:ea:2c:4c:f8:0a:5b:26:08:b2:ce:7d:bb:76. Matching host key fingerprint found in DNS. Are you sure you want to continue connecting (yes/no)? ^C |
Using the -v flag again, you can see some “insecure fingerprints“:
1 2 3 4 5 6 7 8 9 |
weberjoh@nb17-lx2:~$ ssh -v ns0.weberdns.de [...] debug1: found 4 insecure fingerprints in DNS debug1: matching host key fingerprint found in DNS [...] The authenticity of host 'ns0.weberdns.de (2001:470:765b::a24:53)' can't be established. ECDSA key fingerprint is SHA256:24EnTvX+vreXSvZvPKunjjqUBy5QGfe21cxQsQgodQc. Matching host key fingerprint found in DNS. Are you sure you want to continue connecting (yes/no)? ^C |
MD5, SHA256, Hex, Base64
What? “Why is the SSHFP fingerprint not the same as the log message from ssh?” This question took my a while to fully understand. And I am not the only one (click, click). In fact, there are several options to display fingerprints, e.g., with MD5, SHA256, either in hexadecimal or base64 notation. OpenSSH displays the fingerprint in MD5-hex or SHA256-base64 notation by default, whereas the SSHFP records list the SHA1 and SHA256 fingerprints, each in hex notation. That is: they are mutually exclusive. And since each of the four possible public keys (RSA, DSA, ECDSA, Ed25519) has its own fingerprint you’ll probably have MANY different fingerprints at all. ;) (Refer to OpenSSH/Cookbook/Authentication Keys. “The fingerprint can be forced to display as an MD5 hash in hexadecimal instead by passing FingerprintHash configuration directive as a runtime argument or in ssh_config. But the default is now SHA256 in base64. […] In OpenSSH 6.7 and earlier this fingerprint was a hexadecimal MD5 checksum instead a of the base64-encoded SHA256 checksum currently used.”)
The output of the fingerprint can be set to other hash algorithms, such as:
1 2 3 4 |
weberjoh@jw-vm08-rdns:~$ ssh -o FingerprintHash=sha256 lx.weberdns.de The authenticity of host 'lx.weberdns.de (2003:51:6012:110::9)' can't be established. ECDSA key fingerprint is SHA256:T09/p/ZSubnkraG3oslDMehfIiLRe6UiVn1dGZvtjZE. Are you sure you want to continue connecting (yes/no)? ^C |
Now, with some online tools, this SHA256 fingerprint can be converted from base64 to hex, which then compares to the SSHFP records. ;) Uff. That is: The the ECDSA fingerprint in SHA-256 in the hex variant (beginning with “4f:4f:7f” as seen in the SSHFP type 3 2 record) is the correct one as in the base64 variant (beginning with “T09/”).
What about PuTTY?
Unfortunately PuTTY is not supporting SSHFP yet. :( It is on the wishlist a few years now, but still not supported. Of course this is bad since many admins are using Windows machines with PuTTY to manage Linux servers. However, if a central (Linux) jump server is used for connecting to all other servers/firewall/routers/whatever, SSHFP is still very useful.
As a small workaround I placed a TXT record for my Linux server on the DNS to be able to compare the fingerprint with the ssh message, such as:
1 2 3 4 5 6 |
pi@ntp1:~ $ host -t txt lx.weberdns.de lx.weberdns.de descriptive text "ECDSA key fingerprint is 49:f7:d1:ea:2c:4c:f8:0a:5b:26:08:b2:ce:7d:bb:76" pi@ntp1:~ $ ssh lx.weberdns.de The authenticity of host 'lx.weberdns.de (2003:51:6012:110::9)' can't be established. ECDSA key fingerprint is 49:f7:d1:ea:2c:4c:f8:0a:5b:26:08:b2:ce:7d:bb:76. Are you sure you want to continue connecting (yes/no)? ^C |
Of course this is no automatic security, but at least I can manually check whether I am talking to the correct server. (But, you know, I won’t…)
Featured image “print” by jim hutchison is licensed under CC BY-NC-ND 2.0.
Hi Johannes,
thx for this article – but I do have some questions. We implemented DNSSEC here at RWTH Aachen University and also do use SSHFP resource records.
But one thing I could not verify was if DNSSEC is used during ssh searching and verifying SSHFP – “ssh -vvv -o VerifyHostKeyDNS=yes ${FQDN}” drops no information :(
So before writing my own wrapper script (using [0]) I wanted to ask you if you have additional information already knwo how tosolve this problem.
We use latest Ubuntu LTS but deaktivated lokal stub DNS Server (as we do have two central one) – yes one option is to reactivate this Bind daemon with “dnssec-validation auto;” (as you describe on [1]), but to be honest it is not our preferred way right now (maybe we will change our mind :D )
thx for your help
Bernd
[0]
https://www.cyberciti.biz/faq/unix-linux-test-and-validate-dnssec-using-dig-command-line/
[1]
https://weberblog.net/bind-dnssec-validation/
Hi Bernd,
I just updated this blog post a bit to answer your question. Using the -v flag with ssh you will get either:
debug1: found 3 secure fingerprints in DNS
OR
debug1: found 3 insecure fingerprints in DNS
–> The keyword is the “secure” or “insecure” fingerprints. It’s only “secure” if DNSSEC validation took place.
But, uh, wait a second. I am NOT sure whether ssh itself does the DNSSEC validation or whether it just trusts the recursive DNS server. In the second case, an attacker could spoof the “ad” flag forcing ssh to trust the answer. Hence you should do DNSSEC validation at your host itself. (Please ask the Internet whether the ssh client does DNSSEC validation by itself and post me the answer. Thanks. ;))
Cheers
Johannes
MacOS appears to have problems here. The SSHFP’s are always ‘insecure’, even though I’m behind a validating resolver.
Update: “brew install openssh” fixes this by the way.
Winderz users could resort to Cygwin