respect my authoritah

I enjoy trying to improve security in the environments I manage as a sysadmin. One of the hardest parts of improving security is not making it a hindrance to users. (There is a fine line between good security and hindrance, but that’s a rant for another time).

Recently I had been asked to improve the SSH security in an environment. SSH keys weren’t really being used, so that was my first suggestion, but I needed to make key management as simple as possible without forgoing any security benefits; so I decided to take a proper look at SSH Certificate Authorities (CA).

I’ve known about SSH CA for a while, though never played around with it. Before taking it into a production environment I decided to become familiar on my own turf.

There are two types of SSH CA, Host and User.

gracious host

Lets look at the Host side first. When you install OpenSSH (or other SSH servers) on a system a number of host keys are generated

/etc/ssh/ssh_host_dsa_key
/etc/ssh/ssh_host_dsa_key.pub
/etc/ssh/ssh_host_ecdsa_key
/etc/ssh/ssh_host_ecdsa_key.pub
/etc/ssh/ssh_host_ed25519_key
/etc/ssh/ssh_host_ed25519_key.pub
/etc/ssh/ssh_host_rsa_key
/etc/ssh/ssh_host_rsa_key.pub

When you first SSH to a server you are presented with a message that looks something like this

The authenticity of host '10.1.2.3 (10.1.2.3)' can't be established.
ECDSA key fingerprint is SHA256:CwrcHjdd9349u38rj392fr9j389rj3298rj23.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Now what should happen, if it isn’t your server, is the sysadmin would provide you with the fingerprint of the host key(s) so you can confirm it is in fact the correct server you are connecting to.

But we all know that doesn’t happen. Everyone types yes and moves on with their life.

When you accept the fingerprint SSH adds the host public key to your known_hosts file, usually located under ~/.ssh.

If the host key ever changed you would want to know. It could be the sysadmin has rotated the keys (unlikely), the server has been rebuilt or another has taken that IP (likely), or someone is attempted a Man in the Middle attack (scary!).

Thankfully SSH does warn you, have you ever seen this?

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256:CwrcHjdd9347u38fj392fr9f389rj289snjd.
Please contact your system administrator.
Add correct host key in /home/pyratebeard/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /home/pyratebeard/.ssh/known_hosts:1
ECDSA host key for 10.1.2.3 has changed and you have requested strict checking.
Host key verification failed.

Phew, now we can contact our sysadmin and make sure we’re not being intercepted.

Or, as is more common, we remove the “offending” line and reconnect, selecting to accept the new key

ssh-keygen -f ~/.ssh/known_hosts -R 10.1.2.3

An SSH CA can help improve security by removing this need to confirm the key fingerprints, because as we know few people do it anyway.

By signing all your server’s host keys with a CA key your users only need to trust one entity, the CA.

For SSH, a CA is actually just another SSH key pair. You then sign a hosts public key using the CA private key to produce a certificate. To generate a CA it’s as easy as creating a new key pair

ssh-keygen -t ed25519 -C hostca@pyratebeard.net -f hostca-key

Now I have hostca-key and hostca-key.pub. I prefer using ED25519 instead of RSA or ECDSA.

The public key is the entity we want to trust, so we add that to our known_hosts file, prefixed with @cert-authority and the domain your servers are in

cat >> EOF << ~/.ssh/known_hosts
> @cert-authority *.pyratebeard.net [hostca public key]
> EOF

In my example I am using the domain pyratebeard.net and a wildcard for the subdomain. This means if I ssh to server.pyratebeard.net then this CA will be used. You could use a wildcard (*) with no domain if you use the same CA for every system you need to access.

Next we want to take a copy of the host public key

# method 1
cat >> EOF << server-ssh_host_ed25519_key.pub
> [public key]
> EOF

# method 2
rsync server.pyratebeard.net:/etc/ssh/ssh_host_ed25519_key.pub server-ssh_host_ed25519_key.pub

# method 3
ssh-keyscan server.pyratebeard.net | awk '/ssh-ed25519/ {print $2" "$3}' | tee server-ssh_host_ed25519_key.pub

Now we can sign the host public key using our CA private key

ssh-keygen -s hostca-key -h -I server@pyratebeard.net -n server.pyratebeard.net,server -V +52w server-ssh_host_ed25519_key.pub

This command will produce a certificate ([key name]-cert.pub). To confirm the new server key cert details incant

ssh-keygen -L -f server-ssh_host_ed25519_key-cert.pub

Once the certificate has been generated it needs to be put on the server it is for, usually in the /etc/ssh directory

# method 1 (on server)
cat >> EOF << /etc/ssh/ssh_host_ed25519_key-cert.pub
> [server key cert]
> EOF

# method 2
rsync server-ssh_host_ed25519_key-cert.pub server:/etc/ssh/ssh_host_ed25519_key-cert.pub

So that SSH knows to use a certificate add the following to /etc/ssh/sshd_config and reload the SSH service

HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

If every server in the environment is signed with the same CA then only one line is needed in known_hosts, the @cert-authority entry. New servers can be signed and users won’t have to accept any fingerprints because they will already be trusted. Systems can be rebuilt, or keys regenerated, and it won’t cause any impact to users.

If you’re an admin of a shared Linux system, or are able to manage users’ workstations, then drop the @cert-authority line into /etc/ssh/ssh_known_hosts for it to work system-wide without bothering the users.

total luser

Traditionally, to set up SSH key authentication as a user the public key is added to the ~/.ssh/authorized_keys file

ssh-copy-id -f -i ~/.ssh/pyratebeard-ssh_ed25519_key.pub pyratebeard@server

Using ssh-copy-id usually requires password authentication, or if you have console access, manually adding the public key to the authorized_keys file.

Depending on how you manage your SSH keys you may have a number of authorised keys available on the remote system. If your key was ever compromised, lost, or needed changing for some other reason, you would need to add a new public key to the remote system. Maybe you have multiple remote systems, and maybe you haven’t got an automated way to push out new keys. Or maybe your current key isn’t working and you can’t use password authentication as you have correctly locked down your SSH config.

By using a User CA the remote system(s) only have to trust the CA, then any user key that is signed will be granted access. This is especially useful in a production environment if you have multiple users across a magnitude of systems. When a new user joins the team they get their SSH key signed by the CA and they can instantly gain access to all the remote systems they require.

A User CA, the same Host, is an SSH keypair

ssh-keygen -t ed25519 -C userca@pyratebeard.net -f userca-key

Then we can sign a user public SSH key (note we don’t use the -h option)

ssh-keygen -s userca-key -I pyratebeard@pyratebeard.net -n pyratebeard -V +52w pyratebeard-ssh_ed25519_key.pub
* -s sign - tells `ssh-keygen` which key to sign with
* -I unique identifier - an identifier used for logging
* -n principal - username the certificate is valid for, comma-delimit multiple usernames
* -V validity - length of time the certificate is valid for, omit for never expiring

The above command will produce a certificate ([key-name]-cert.pub). To confirm the new key cert details incant

ssh-keygen -L -f pyratebeard-ssh_ed25519_key-cert.pub

Let SSH know which certificate to use by adding the following to ~/.ssh/config

Host *.pyratebeard.net
    CertificateFile /path/to/pyratebeard-ssh_host_ed25519_key-cert.pub

Just like we included the @cert-authority line in known_hosts, the remote system(s) need to know the User CA to trust. Copy the User CA public key to the remote system(s)

# method 1 (on server)
cat >> EOF << /etc/ssh/userca-key.pub
> [userca public key]
> EOF

# method 2
rsync userca-key.pub server:/etc/ssh/userca-key.pub

In the remote system(s) /etc/ssh/sshd_config add the following and reload sshd

TrustedUserCAKeys /etc/ssh/userca-key.pub

Setting up a User CA means that the authorized_keys file is no longer required for signed keys. If the user key changes, as long as it is signed by the trusted CA it will be granted access.

a problem shared is still a problem

All the benefits of SSH CA look great. Less messing around with known_hosts and authorized_keys. Quicker access for new user SSH keys and quicker access to new remote systems. But when it comes to security it can’t stay like that forever.

If a Host CA key is compromised a malicious system could be disguised as a trusted system, users wouldn’t be warned against a fingerprint change.

In order to revoke a Host CA change @cert-authority to @revoke in the relevant known_hosts file.

When this happens SSH will warn that the system authenticity can’t be established, as we saw before when logging in for the very first time.

If this is in a production environment the sysadmin would have to re-sign all the remote system keys and then inform users to update their known_hosts file.

A bit of overhead, but remedied easily enough.

What if the User CA key is compromised? A bad actor could gain access to all the remote systems by simply signing their SSH key (make sure you use passwords when generating CA keys!).

To revoke a User CA add the path to the CA in /etc/ssh/revoked_keys on the remote system(s)

cat << EOF >> /etc/ssh/revoked_keys
> /etc/ssh/userca-key.pub
> EOF

Let SSH know about the revocation file in /etc/ssh/sshd_config and reload sshd

RevokedKeys /etc/ssh/revoked_keys

This will revoke access to all user keys which have been signed by the compromised CA. This could be a lot of users, and could potentially cause great impact. Ideally you would have multiple CA certs configured, maybe group teams to a CA so the impact is reduced to a small number of users.

This is where SSH CA can fall down. I know from my own experience that not many environments use CA. There have been attempts to make things more dynamic and transparent, especially on the user side of things. There is BLESS, developed by Netflix, which has received much praise. It is based on using AWS Lambda functions, not always available to environments.

There is also sshrimp by Jeremy Stott, who gave an interesting talk (Zero Trust SSH) at LinuxConfAU in 2020. sshrimp also works using Lambda functions.

In my client’s environment I can’t use Lambda at the moment. Potentially I could get something like BLESS configured but that would be an uphill battle of approvals with various stakeholders, something I’m not ready for right now. I tried looking at getting the CAs set up first, at least as a proof of concept to showcase the client. All the users are running Windows, with MobaXTerm being the connection tool of choice. For a few days I fought with MobaXTerm, trying to get it to pick up the trusted Host CA and the signed User cert. I have since drawn a line under it until I can come up with a new plan.

The upside to all of this is that I have learned a lot about SSH Certificate Authorities, and I (finally) have SSH CA configured for my own systems.