Hi list, I use ssh a lot and I often need to connect to hosts whose host key has changed. If a host key of the remote host changes ssh terminates and the user has to manually delete the offending host key from known_hosts. I had to do this so many times that I no longer like the idea ;-) I would really like ssh to ask me if the new host key is OK and if I want to add it to known_hosts. I talked to other people and they also seemed to be bothered by this behaviour, so I have just written a small patch that introduces a new config option: OffendingKeyOverride When OffendingKeyOverride is "true" and ssh finds an offending key in known_hosts it behaves just as if the key for the host were not there at all. I.e. If StrictHostKeyChecking is set to "ask" the user is prompted to accept the new key and so on... As a result, known_hosts may contain multiple keys for the same host - I find that very useful, for example when connecting to dualboot remote hosts, with different host key in each of their OSes. Is it possible that a similar patch could make it into openssh, or are there any major objections? The patch follows (it looks bigger than it is, because a large chunk of code is moved into a separate function...). Comments appreciated. I am willing to change the patch in any way or do whatever to get this functionality into openssh. Regards, Jirka Bohac diff -aur openssh-4.3p1/readconf.c openssh-4.3p1-patch/readconf.c --- openssh-4.3p1/readconf.c 2005-12-13 09:33:20.000000000 +0100 +++ openssh-4.3p1-patch/readconf.c 2006-02-04 16:41:10.000000000 +0100 @@ -112,7 +112,7 @@ oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oControlPath, oControlMaster, oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, - oDeprecated, oUnsupported + oDeprecated, oUnsupported, oOffendingKeyOverride } OpCodes; /* Textual representations of the tokens. */ @@ -175,6 +175,7 @@ { "batchmode", oBatchMode }, { "checkhostip", oCheckHostIP }, { "stricthostkeychecking", oStrictHostKeyChecking }, + { "offendingkeyoverride", oOffendingKeyOverride }, { "compression", oCompression }, { "compressionlevel", oCompressionLevel }, { "tcpkeepalive", oTCPKeepAlive }, @@ -434,6 +435,7 @@ case oStrictHostKeyChecking: intptr = &options->strict_host_key_checking; + parse_yesnoask: arg = strdelim(&s); if (!arg || *arg == '\0') @@ -452,6 +454,10 @@ *intptr = value; break; + case oOffendingKeyOverride: + intptr = &options->offending_key_override; + goto parse_flag; + case oCompression: intptr = &options->compression; goto parse_flag; @@ -979,6 +985,7 @@ options->batch_mode = -1; options->check_host_ip = -1; options->strict_host_key_checking = -1; + options->offending_key_override = -1; options->compression = -1; options->tcp_keep_alive = -1; options->compression_level = -1; @@ -1073,6 +1080,8 @@ options->check_host_ip = 1; if (options->strict_host_key_checking == -1) options->strict_host_key_checking = 2; /* 2 is default */ + if (options->offending_key_override == -1) + options->offending_key_override = 0; if (options->compression == -1) options->compression = 0; if (options->tcp_keep_alive == -1) diff -aur openssh-4.3p1/readconf.h openssh-4.3p1-patch/readconf.h --- openssh-4.3p1/readconf.h 2005-12-13 09:29:02.000000000 +0100 +++ openssh-4.3p1-patch/readconf.h 2006-02-04 15:07:38.000000000 +0100 @@ -53,6 +53,7 @@ int batch_mode; /* Batch mode: do not ask for passwords. */ int check_host_ip; /* Also keep track of keys for IP address */ int strict_host_key_checking; /* Strict host key checking. */ + int offending_key_override; /* Allow adding changed keys to hostfile */ int compression; /* Compress packets in both directions. */ int compression_level; /* Compression level 1 (fast) to 9 * (best). */ diff -aur openssh-4.3p1/sshconnect.c openssh-4.3p1-patch/sshconnect.c --- openssh-4.3p1/sshconnect.c 2005-12-13 09:29:03.000000000 +0100 +++ openssh-4.3p1-patch/sshconnect.c 2006-02-04 16:42:04.000000000 +0100 @@ -51,6 +51,9 @@ static int show_other_keys(const char *, Key *); static void warn_changed_key(Key *); +static int ask_connect_with_new_key(const char *host, Key *host_key, + const char* ip, const char* type, HostStatus ip_status, + const char *user_hostfile); /* * Connect to the given ssh server using a proxy command. @@ -524,10 +527,9 @@ Key *file_key; const char *type = key_type(host_key); char *ip = NULL; - char hostline[1000], *hostp, *fp; HostStatus host_status; HostStatus ip_status; - int r, local = 0, host_ip_differ = 0; + int local = 0, host_ip_differ = 0; int salen; char ntop[NI_MAXHOST]; char msg[1024]; @@ -674,71 +676,10 @@ error("No %s host key is known for %.200s and you " "have requested strict checking.", type, host); goto fail; - } else if (options.strict_host_key_checking == 2) { - char msg1[1024], msg2[1024]; - - if (show_other_keys(host, host_key)) - snprintf(msg1, sizeof(msg1), - "\nbut keys of different type are already" - " known for this host."); - else - snprintf(msg1, sizeof(msg1), "."); - /* The default */ - fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); - msg2[0] = '\0'; - if (options.verify_host_key_dns) { - if (matching_host_key_dns) - snprintf(msg2, sizeof(msg2), - "Matching host key fingerprint" - " found in DNS.\n"); - else - snprintf(msg2, sizeof(msg2), - "No matching host key fingerprint" - " found in DNS.\n"); - } - snprintf(msg, sizeof(msg), - "The authenticity of host '%.200s (%s)' can't be " - "established%s\n" - "%s key fingerprint is %s.\n%s" - "Are you sure you want to continue connecting " - "(yes/no)? ", - host, ip, msg1, type, fp, msg2); - xfree(fp); - if (!confirm(msg)) - goto fail; - } - /* - * If not in strict mode, add the key automatically to the - * local known_hosts file. - */ - if (options.check_host_ip && ip_status == HOST_NEW) { - snprintf(hostline, sizeof(hostline), "%s,%s", - host, ip); - hostp = hostline; - if (options.hash_known_hosts) { - /* Add hash of host and IP separately */ - r = add_host_to_hostfile(user_hostfile, host, - host_key, options.hash_known_hosts) && - add_host_to_hostfile(user_hostfile, ip, - host_key, options.hash_known_hosts); - } else { - /* Add unhashed "host,ip" */ - r = add_host_to_hostfile(user_hostfile, - hostline, host_key, - options.hash_known_hosts); - } - } else { - r = add_host_to_hostfile(user_hostfile, host, host_key, - options.hash_known_hosts); - hostp = host; - } + } + if (ask_connect_with_new_key(host, host_key, ip, type, ip_status, user_hostfile)) + goto fail; - if (!r) - logit("Failed to add the host to the list of known " - "hosts (%.500s).", user_hostfile); - else - logit("Warning: Permanently added '%.200s' (%s) to the " - "list of known hosts.", hostp, type); break; case HOST_CHANGED: if (options.check_host_ip && host_ip_differ) { @@ -760,21 +701,30 @@ if (ip_status != HOST_NEW) error("Offending key for IP in %s:%d", ip_file, ip_line); } + /* The host key has changed. */ warn_changed_key(host_key); error("Add correct host key in %.100s to get rid of this message.", user_hostfile); error("Offending key in %s:%d", host_file, host_line); - - /* - * If strict host key checking is in use, the user will have - * to edit the key manually and we can only abort. - */ - if (options.strict_host_key_checking) { + + /* Ask the user whether to accept the new host key */ + if (options.offending_key_override && options.strict_host_key_checking != 1) + { + if (ask_connect_with_new_key(host, host_key, ip, type, ip_status, user_hostfile)) + goto fail; + break; + } else if (options.strict_host_key_checking) { + /* + * If strict host key checking is in use, the user will have + * to edit the key manually and we can only abort. + */ error("%s host key for %.200s has changed and you have " "requested strict checking.", type, host); goto fail; } + + /* * If strict host key checking has not been requested, allow @@ -814,13 +764,6 @@ options.num_local_forwards options.num_remote_forwards = 0; } - /* - * XXX Should permit the user to change to use the new id. - * This could be done by converting the host key to an - * identifying sentence, tell that the host identifies itself - * by that sentence, and ask the user if he/she whishes to - * accept the authentication. - */ break; case HOST_FOUND: fatal("internal error"); @@ -1014,6 +957,83 @@ return (found); } +static int +ask_connect_with_new_key(const char *host, Key *host_key, const char* ip, + const char* type, HostStatus ip_status, const char *user_hostfile) +{ + char *fp; + const char *hostp; + int r; + char hostline[1000]; + char msg[1024]; + + if (options.strict_host_key_checking == 2) { + char msg1[1024], msg2[1024]; + + if (show_other_keys(host, host_key)) + snprintf(msg1, sizeof(msg1), + "\nbut keys of different type are already" + " known for this host."); + else + snprintf(msg1, sizeof(msg1), "."); + /* The default */ + fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); + msg2[0] = '\0'; + if (options.verify_host_key_dns) { + if (matching_host_key_dns) + snprintf(msg2, sizeof(msg2), + "Matching host key fingerprint" + " found in DNS.\n"); + else + snprintf(msg2, sizeof(msg2), + "No matching host key fingerprint" + " found in DNS.\n"); + } + snprintf(msg, sizeof(msg), + "The authenticity of host '%.200s (%s)' can't be " + "established%s\n" + "%s key fingerprint is %s.\n%s" + "Are you sure you want to continue connecting " + "(yes/no)? ", + host, ip, msg1, type, fp, msg2); + xfree(fp); + if (!confirm(msg)) + return -1; + } + /* + * If not in strict mode, add the key automatically to the + * local known_hosts file. + */ + if (options.check_host_ip && ip_status == HOST_NEW) { + snprintf(hostline, sizeof(hostline), "%s,%s", + host, ip); + hostp = hostline; + if (options.hash_known_hosts) { + /* Add hash of host and IP separately */ + r = add_host_to_hostfile(user_hostfile, host, + host_key, options.hash_known_hosts) && + add_host_to_hostfile(user_hostfile, ip, + host_key, options.hash_known_hosts); + } else { + /* Add unhashed "host,ip" */ + r = add_host_to_hostfile(user_hostfile, + hostline, host_key, + options.hash_known_hosts); + } + } else { + r = add_host_to_hostfile(user_hostfile, host, host_key, + options.hash_known_hosts); + hostp = host; + } + + if (!r) + logit("Failed to add the host to the list of known " + "hosts (%.500s).", user_hostfile); + else + logit("Warning: Permanently added '%.200s' (%s) to the " + "list of known hosts.", hostp, type); + return 0; +} static void warn_changed_key(Key *host_key) { @@ -1030,7 +1050,6 @@ error("It is also possible that the %s host key has just been changed.", type); error("The fingerprint for the %s key sent by the remote host is\n%s.", type, fp); - error("Please contact your system administrator."); xfree(fp); }
Roumen Petrov
2006-Feb-05 12:22 UTC
[PATCH] allow user to update changed key in known_hosts
Hi Jirca, Jirka Bohac wrote:> Hi list, > > I use ssh a lot and I often need to connect to hosts whose host key has > changed. If a host key of the remote host changes ssh terminates and the > user has to manually delete the offending host key from known_hosts.Use StrictHostKeyChecking=no for those hosts.> I talked to other people and they also seemed to be bothered by this > behaviourMay be people who don't read manual pages will bother other too ?>, so I have just written a small patch that introduces a new > config option: OffendingKeyOverridePlease, could you check ssh_config(5) and other manual pages for more usefull options for your environment.
Damien Miller
2006-Feb-06 00:35 UTC
[PATCH] allow user to update changed key in known_hosts
On Sat, 4 Feb 2006, Jirka Bohac wrote:> Hi list, > > > I use ssh a lot and I often need to connect to hosts whose host key has > changed. If a host key of the remote host changes ssh terminates and the > user has to manually delete the offending host key from known_hosts. I > had to do this so many times that I no longer like the idea ;-) > I would really like ssh to ask me if the new host key is OK and if I > want to add it to known_hosts. > > I talked to other people and they also seemed to be bothered by this > behaviour, so I have just written a small patch that introduces a new > config option: OffendingKeyOverrideI don't think we will add an option like this: part of the OpenSSH's "security UI" is that a changed key is a significant event that requires explicit manual intervention, not just answering "yes" to a question. We are very wary of convenience options like OffendingKeyOverride, as they tend to get turned on indiscriminately and never turned off. BTW, in recent releases, the manual intervention required on key change is as simple as the command: ssh-keygen -R offending.host.name This will remove the old key without editing, while still requiring a manual step. -d