Server ssh certificate chain against MITM attacks?

During first contact with a server via ssh, the server's public key of the chosen key algorithm is presented to the user to validate it. After validation, the result is usually saved into the ~/.ssh/known_hosts file to counter later MITM attacks.

$ ssh host.example.com
The authenticity of host 'host.example.com (1.2.3.4)' can't be established.
ECDSA key fingerprint is SHA256:xxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no)?

This obviously won't help against a MITM attack on the first connection and does just move the problem to the user, who now has to have some process in place to verify the presented public key - and we all know how that ends.

Is it possible to distribute ssh server keys signed with a corporate CA to counter MITM-attacks on first contact? The public-private-key infrastructure with certificate chain supports this in general - but I have never seen it used in a corporate environment to secure ssh servers.

General idea would be to trust a CA key for a set of hosts:

trust *.example.com SHA256:<fingerprint(public key(corporate-ca))>

and every host that fist this and has been signed by the CA is trusted:

ca.example.com
+- host.example.com

This is similar to how HTTPS is secured, and since ssh uses the same underlying technology - is something like that already implemented in OpenSSH and I just didn't find it?


Solution 1:

This is possible.

Quoting the docs:

AUTHORIZED_KEYS FILE FORMAT
[...]
     cert-authority
             Specifies that the listed key is a certification authority (CA)
             that is trusted to validate signed certificates for user authentication.

             Certificates may encode access restrictions similar to these key options.
             If both certificate restrictions and key options are present, the most
             restrictive union of the two is applied.

Steps to achieve this:

  • Generate a SSH Server CA Key:
ssh-keygen -f server_ca
  • Generate a SSH Server Host Key:
ssh-keygen -t ecdsa -b 521 -N '' -C 'somehost.example.com' -f ssh-ecdsa.key
  • Sign the host key with your ca key:
ssh-keygen -s server_ca -I somehost.example.com -h -n 'somehost.example.com,somehost,<list of SANs>,<list of IPv4>,<list of IPv6>' -V +3650d ssh-ecdsa.key
  • Deploy the host key + certificate in your SSH Server (copy cert & key to /etc/ssh, edit /etc/ssh/sshd_config):
HostCertificate /etc/ssh/ssh-ecdsa.key-cert.pub
HostKey /etc/ssh/ssh-ecdsa.key
  • Set up your clients to trust your CA key to identify hosts (~/.ssh/known_hosts)
@cert-authority example.com ssh-rsa AAAA[...]

Notes:

  • It's not compatible to regular X.509 PKIs.
  • Maintaining (and consuming) a key revocation list (KRL) is needed. See the OpenSSH Cookbook for details.
  • Cumbersome, consider using something like Vault and a configuration management tool to automate this.
  • Have a look at the Monkeysphere project for a take on the "web of trust for SSH" idea.

Find more information in the links below, especially on how to set this up to sign keys for users.

Links:

  • https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Public_Key_Authentication
  • https://www.freebsd.org/cgi/man.cgi?sshd(8)
  • https://jameshfisher.com/2018/03/16/how-to-create-an-ssh-certificate-authority.html
  • https://web.monkeysphere.info/

Solution 2:

An different approach is to publish SSH key finger prints in DNS as SHFP records:
See RFC 4255 https://www.rfc-editor.org/rfc/rfc4255

That makes verification of the public key an automated process once you set VerifyHostKeyDNS to yes in the (global) ssh client defaults.

Solution 3:

If it's in a corporate environment, you can also have a centrally managed list of SSH keys of all your machines that you can then deploy to /etc/ssh/ssh_known_hosts on all clients using your favourite configuration management tool. This can also be used for direct host-based authentication between the machines. (Managing SSH host keys centrally is also useful to prevent unwanted host key changes when a server gets reinstalled.)

Of course, this approach only works for "internal" SSH access where both the client and the server is under your control. If you need arbitrary external users to SSH in (and you can't just give them the known_hosts file), SSHFP records in DNSSEC-secured DNS are probably the way to go.