72

How can I add a host key to the SSH known_hosts file securely?

I'm setting up a development machine, and I want to (e.g.) prevent git from prompting when I clone a repository from github.com using SSH.

I know that I can use StrictHostKeyChecking=no (e.g. this answer), but that's not secure.

So far, I've found...

  1. GitHub publishes their SSH key fingerprints at GitHub's SSH key fingerprints

  2. I can use ssh-keyscan to get the host key for github.com.

How do I combine these facts? Given a prepopulated list of fingerprints, how do I verify that the output of ssh-keyscan can be added to the known_hosts file?


I guess I'm asking the following:

How do I get the fingerprint for a key returned by ssh-keyscan?

Let's assume that I've already been MITM-ed for SSH, but that I can trust the GitHub HTTPS page (because it has a valid certificate chain).

That means that I've got some (suspect) SSH host keys (from ssh-keyscan) and some (trusted) key fingerprints. How do I verify one against the other?


Related: how do I hash the host portion of the output from ssh-keyscan? Or can I mix hashed/unhashed hosts in known_hosts?

  • Why wouldn't it be secure for your use case? – quadruplebucky Jun 16 '17 at 10:39
  • 1
    StrictHostKeyChecking=no is vulnerable to MITM. Is ssh-keyscan secure against MITM? – Roger Lipscombe Jun 16 '17 at 10:48
  • I fail to understand why I'm overly worried about somebody impersonating a stranger I've never met whom I'm trusting enough to write code I'm about to download and run... – quadruplebucky Jun 16 '17 at 11:23
  • 4
    Because this is my source code in a private repo on github, and I don't want a MITM (e.g.) introducing malicious changes when I push commits. That's just one example. – Roger Lipscombe Jun 16 '17 at 11:28
  • I (for better or worse) choose to trust github. I don't choose to trust every random network link between me and them. – Roger Lipscombe Jun 16 '17 at 11:28
  • Well that's unquestionably a different use case than mine. Makes perfect sense. – quadruplebucky Jun 16 '17 at 11:29
  • I don't fully understand why you want it so automated? Is it not possible for you to just hardcode Github's keys to your known_hosts/deployment scripts? And yes the keys that get returned from ssh-keyscan could be MITM'd. In the end, the point of known_hosts is "if keys match, proceed; otherwise give warning/prompt". So you should list the keys that you know are correct, and everything will work (until Github changes their keys, which hopefully won't happen for a while). HTH and that I didn't completely misunderstand it all :) – Iskar Jun 16 '17 at 11:35
  • I've updated the question. – Roger Lipscombe Jun 16 '17 at 11:37
  • You could ask github to put their ssh sigs on a separate web server in a static file, in a separate data-center, as a secondary means of validating them. I am sure they won't do this, but I have done this with certain orgs that were extra cautious and/or paranoid. You could also email them what you see in ssh-keyscan and say, "Does this look like your server?" You could even set up a repo where everyone checks in the sigs they see and compares. That might be a fun side project. – Aaron Jun 16 '17 at 13:01
  • "Is it not possible for you to just hardcode Github's keys to your known_hosts/deployment scripts?" -- no, because there might already be a MITM. So I can't (even manually) trust those keys until I've verified them against the fingerprints. – Roger Lipscombe Aug 08 '18 at 09:17
  • Note: Subject of the meta question Getting attention for answers that have aways been incorrect, about the two top answers being insecure. – Peter Mortensen Jun 24 '19 at 15:38
  • @PeterMortensen: It's not clear which answers that meta question refers to. The "top two" from June 24, 2019, might not be the same as the top two today. The current accepted answer is from June 18, 2019. Is that one of them? Or is this one considered secure? – djvg Oct 07 '21 at 08:01

8 Answers8

61

The most important part of "securely" adding a key to the known_hosts file is to get the key fingerprint from the server administrator. The key fingerprint should look something like this:

2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA)

In the case of GitHub, normally we can't talk directly to an administrator. However, they put the key on their web pages so we can recover the information from there.

Manual key installation

1) Take a copy of the key from the server and get its fingerprint. N.B.: Do this before checking the fingerprint.

$ ssh-keyscan -t rsa github.com | tee github-key-temp | ssh-keygen -lf -
# github.com:22 SSH-2.0-babeld-f3847d63
2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA)

2) Get a copy of the key fingerprint from the server administrator - in this case navigate to the page with the information on github.com

  1. Go to github.com
  2. Go to the help page (on the menu on the right if logged in; at the bottom of the homepage otherwise).
  3. In the Getting Started section go to Connecting to GitHub with SSH
  4. Go to Testing your SSH connection
  5. Copy the SHA256 fingerprint from that page into your text editor for later use.

3) Compare the keys from the two sources

By placing them directly one above the other in a text editor, it is easy to see if something has changed

2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA) #key recovered from github website
2048 SHA256:nThbg6kXUpJ3Gl7E1InsaspRomtxdcArLviKaEsTGY8 github.com (RSA) #key recovered with keyscan

(Note that the second key has been manipulated, but it looks quite similar to the original - if something like this happens you are under serious attack and should contact a trusted security expert.)

If the keys are different abort the procedure and get in touch with a security expert

4) If the keys compare correctly then you should install the key you already downloaded

cat github-key-temp >> ~/.ssh/known_hosts

Or to install for all users on a system (as root):

cat github-key-temp >> /etc/ssh/ssh_known_hosts

Automated key installation

If you need to add a key during a build process then you should follow steps 1-3 of the manual process above.

Having done that, examine the contents of your github-key-temp file and make a script to add those contents to your known hosts file.

if ! grep github.com ~/.ssh/known_hosts > /dev/null
then
     echo "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" >> ~/.ssh/known_hosts
fi

You should now get rid of any ssh commands which have StrictHostKeyChecking disabled.

Michael
  • 966
  • Why is the order of step 1 and 2 important? – Daniel Goldberg Dec 31 '20 at 10:46
  • 3
  • @DanielGoldberg - if you check on the web server before you take a copy of the key then an attacker may realise you did the check. If they do that then they can turn off their man in the middle attack before you detect it. You will be safe this time, but they will keep trying until, some time in future, you forget to check. – Michael Sep 13 '21 at 17:21
  • @x-yuri that only works if you already connected to the host and you trust the connection you made before. If in doubt, follow the above procedure anyway. – Michael Sep 13 '21 at 17:22
  • 1
    I mean, you do grep github.com ~/.ssh/known_hosts. Why not use a more specialized tool (ssh-keygen) that does just that, but also knows the format of the file? – x-yuri Sep 14 '21 at 03:13
  • okay @x-yuri I now understand your suggestion. I think it's a correct and reasonable improvement. I'm going to have to think about it since it's got the disadvantage that the line in the file would then not match the line being added making things more difficult to check. I can't think of any clear risk from that right now. – Michael Sep 16 '21 at 20:13
  • 1
    To be honest, I'm not sure what lines you're talking about. You add the line with echo. ssh-keygen only checks the ip/domain part, and won't change ~/.ssh/known_hosts. I'm only suggesting to change the grep part. – x-yuri Sep 17 '21 at 03:18
  • According to man ssh-keygen the -F option does the following: "Search for the specified hostname in a known_hosts file, listing any occurrences found. This option is useful to find hashed host names or addresses ..." (my emphasis) – djvg Oct 07 '21 at 08:24
  • 1
    If you need to replace (possibly) stale host keys, you could use, for example, [ -f ~/.ssh/known_hosts ] && ssh-keygen -R github.com to remove any existing github keys, followed by echo "$GITHUB_HOST_KEY" >> ~/.ssh/known_hosts (assuming the key is stored in a variable called GITHUB_HOST_KEY). – djvg Oct 07 '21 at 09:55
48

You can mix hashed/unhashed entries in your known_hosts file.

So if you want to add github key, you can just do :

ssh-keyscan github.com >> ~/.ssh/known_hosts

If you want it hashed, add -H

ssh-keyscan -H github.com >> ~/.ssh/known_hosts

Note: this is vulnerable to MITM attack, it answers to the "Related" part of the question only.

Wee
  • 712
  • 1
    What is the difference between hashed/unhashed? I know what hashing is, just not why it is applied in this context. – Glen Thomas Feb 14 '19 at 10:22
  • 5
    Hashing your entries allows you to hide information about the hosts you are used to connect to. Check the accepted answer at https://security.stackexchange.com/questions/56268/ssh-benefits-of-using-hashed-known-hosts – Wee Feb 15 '19 at 11:16
  • 10
    This doesn't answer the question. Using ssh-keyscan is subject to a man in the middle attack which means that the key you store could be a key belonging to the attacker trying to break into your system. Please see my answer for a way to check things. – Michael Jun 24 '19 at 07:26
  • 1
  • 1
    This answer was written at the very beginning when the question was not very clear. It is subject to the man in the middle yes, but I left it here as people keep voting +1, I guess it helps some. It also answers the "related" part of the question. – Wee Mar 29 '22 at 10:17
9

Now that GitHub provides their SSH keys and fingerprints via their metadata API endpoint (as of January 2022), you can leverage the trust you have in GitHub's TLS certificate used on api.github.com (due to it being signed by a certificate authority (CA) which is in your system's trusted root certificate store) to securely fetch their SSH host keys.

If you have jq installed you can do it with this one-liner

curl --silent https://api.github.com/meta \
  | jq --raw-output '"github.com "+.ssh_keys[]' >> ~/.ssh/known_hosts

Or if you want to use Python

curl --silent https://api.github.com/meta | \
  python3 -c 'import json,sys;print(*["github.com " + x for x in json.load(sys.stdin)["ssh_keys"]], sep="\n")' \
  >> ~/.ssh/known_hosts
gene_wood
  • 555
  • 7
  • 15
  • This answer relies on curl being correctly configured to reject fraudulent certificates. You can check that your curl is working right by doing curl https://expired.badssl.com/ which should fail.

    As long as that fails then this should be a great answer for the special case of github.

    – Michael Apr 17 '22 at 10:23
  • To quote curl's man page By default, every SSL connection curl makes is verified to be secure. So as long as you don't add a -k or --insecure argument to the curl command above or a curl config file you're fine. – gene_wood Apr 18 '22 at 14:53
  • I also saw that, however that's not the guaranteed default on all installs of curl, just the default install. If the user, or someone else, has turned off the SSL check in the ~/.curlrc file then these checks will be turned off silently and then there will be a vulnerability in this process.
  • The original answer specifically asks for a "secure" way of doing this so I think that a certain level of extra care is important in answers to this question.

    – Michael Apr 21 '22 at 12:33