From 6a740e7b92c8dba96e81ad3979849e7abcd26829 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 1 Dec 2010 12:01:51 +1100 Subject: - djm@cvs.openbsd.org 2010/11/23 02:35:50 [auth.c] use strict_modes already passed as function argument over referencing global options.strict_modes --- auth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'auth.c') diff --git a/auth.c b/auth.c index dba1e6555..6fe1b21a4 100644 --- a/auth.c +++ b/auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.c,v 1.89 2010/08/04 05:42:47 djm Exp $ */ +/* $OpenBSD: auth.c,v 1.90 2010/11/23 02:35:50 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -518,7 +518,7 @@ auth_openfile(const char *file, struct passwd *pw, int strict_modes, close(fd); return NULL; } - if (options.strict_modes && + if (strict_modes && secure_filename(f, file, pw, line, sizeof(line)) != 0) { fclose(f); logit("Authentication refused: %s", line); -- cgit v1.2.3 From d925dcd8a5d1a3070061006788352bed93260582 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 1 Dec 2010 12:21:51 +1100 Subject: - djm@cvs.openbsd.org 2010/11/29 23:45:51 [auth.c hostfile.c hostfile.h ssh.c ssh_config.5 sshconnect.c] [sshconnect.h sshconnect2.c] automatically order the hostkeys requested by the client based on which hostkeys are already recorded in known_hosts. This avoids hostkey warnings when connecting to servers with new ECDSA keys that are preferred by default; with markus@ --- ChangeLog | 7 ++ auth.c | 30 +++--- hostfile.c | 301 ++++++++++++++++++++++++++++++++++------------------------ hostfile.h | 30 ++++-- ssh.c | 4 +- ssh_config.5 | 7 +- sshconnect.c | 291 +++++++++++++++++++++++++++++--------------------------- sshconnect.h | 11 ++- sshconnect2.c | 62 +++++++++++- 9 files changed, 452 insertions(+), 291 deletions(-) (limited to 'auth.c') diff --git a/ChangeLog b/ChangeLog index 44e45eb8a..6ee7c0014 100644 --- a/ChangeLog +++ b/ChangeLog @@ -38,6 +38,13 @@ [authfile.c] correctly load comment for encrypted rsa1 keys; report/fix Joachim Schipper; ok djm@ + - djm@cvs.openbsd.org 2010/11/29 23:45:51 + [auth.c hostfile.c hostfile.h ssh.c ssh_config.5 sshconnect.c] + [sshconnect.h sshconnect2.c] + automatically order the hostkeys requested by the client based on + which hostkeys are already recorded in known_hosts. This avoids + hostkey warnings when connecting to servers with new ECDSA keys + that are preferred by default; with markus@ 20101124 - (dtucker) [platform.c session.c] Move the getluid call out of session.c and diff --git a/auth.c b/auth.c index 6fe1b21a4..33680b91b 100644 --- a/auth.c +++ b/auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.c,v 1.90 2010/11/23 02:35:50 djm Exp $ */ +/* $OpenBSD: auth.c,v 1.91 2010/11/29 23:45:51 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -379,16 +379,15 @@ HostStatus check_key_in_hostfiles(struct passwd *pw, Key *key, const char *host, const char *sysfile, const char *userfile) { - Key *found; char *user_hostfile; struct stat st; HostStatus host_status; + struct hostkeys *hostkeys; + const struct hostkey_entry *found; - /* Check if we know the host and its host key. */ - found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type); - host_status = check_host_in_hostfile(sysfile, host, key, found, NULL); - - if (host_status != HOST_OK && userfile != NULL) { + hostkeys = init_hostkeys(); + load_hostkeys(hostkeys, host, sysfile); + if (userfile != NULL) { user_hostfile = tilde_expand_filename(userfile, pw->pw_uid); if (options.strict_modes && (stat(user_hostfile, &st) == 0) && @@ -401,16 +400,23 @@ check_key_in_hostfiles(struct passwd *pw, Key *key, const char *host, user_hostfile); } else { temporarily_use_uid(pw); - host_status = check_host_in_hostfile(user_hostfile, - host, key, found, NULL); + load_hostkeys(hostkeys, host, user_hostfile); restore_uid(); } xfree(user_hostfile); } - key_free(found); + host_status = check_key_in_hostkeys(hostkeys, key, &found); + if (host_status == HOST_REVOKED) + error("WARNING: revoked key for %s attempted authentication", + found->host); + else if (host_status == HOST_OK) + debug("%s: key for %s found at %s:%ld", __func__, + found->host, found->file, found->line); + else + debug("%s: key for host %s not found", __func__, host); + + free_hostkeys(hostkeys); - debug2("check_key_in_hostfiles: key %s for %s", host_status == HOST_OK ? - "ok" : "not found", host); return host_status; } diff --git a/hostfile.c b/hostfile.c index afab6dad1..9145529cb 100644 --- a/hostfile.c +++ b/hostfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hostfile.c,v 1.48 2010/03/04 10:36:03 djm Exp $ */ +/* $OpenBSD: hostfile.c,v 1.49 2010/11/29 23:45:51 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -56,6 +56,12 @@ #include "key.h" #include "hostfile.h" #include "log.h" +#include "misc.h" + +struct hostkeys { + struct hostkey_entry *entries; + u_int num_entries; +}; static int extract_salt(const char *s, u_int l, char *salt, size_t salt_len) @@ -164,26 +170,28 @@ hostfile_read_key(char **cpp, u_int *bitsp, Key *ret) /* Return results. */ *cpp = cp; - *bitsp = key_size(ret); + if (bitsp != NULL) + *bitsp = key_size(ret); return 1; } static int -hostfile_check_key(int bits, const Key *key, const char *host, const char *filename, int linenum) +hostfile_check_key(int bits, const Key *key, const char *host, + const char *filename, u_long linenum) { if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) return 1; if (bits != BN_num_bits(key->rsa->n)) { - logit("Warning: %s, line %d: keysize mismatch for host %s: " + logit("Warning: %s, line %lu: keysize mismatch for host %s: " "actual %d vs. announced %d.", filename, linenum, host, BN_num_bits(key->rsa->n), bits); - logit("Warning: replace %d with %d in %s, line %d.", + logit("Warning: replace %d with %d in %s, line %lu.", bits, BN_num_bits(key->rsa->n), filename, linenum); } return 1; } -static enum { MRK_ERROR, MRK_NONE, MRK_REVOKE, MRK_CA } +static HostkeyMarker check_markers(char **cpp) { char marker[32], *sp, *cp = *cpp; @@ -218,49 +226,32 @@ check_markers(char **cpp) return ret; } -/* - * Checks whether the given host (which must be in all lowercase) is already - * in the list of our known hosts. Returns HOST_OK if the host is known and - * has the specified key, HOST_NEW if the host is not known, and HOST_CHANGED - * if the host is known but used to have a different host key. - * - * If no 'key' has been specified and a key of type 'keytype' is known - * for the specified host, then HOST_FOUND is returned. - */ +struct hostkeys * +init_hostkeys(void) +{ + struct hostkeys *ret = xcalloc(1, sizeof(*ret)); -static HostStatus -check_host_in_hostfile_by_key_or_type(const char *filename, - const char *host, const Key *key, int keytype, Key *found, - int want_revocation, int *numret) + ret->entries = NULL; + return ret; +} + +void +load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) { FILE *f; char line[8192]; - int want, have, linenum = 0, want_cert = key_is_cert(key); - u_int kbits; + u_long linenum = 0, num_loaded = 0; char *cp, *cp2, *hashed_host; - HostStatus end_return; - - debug3("check_host_in_hostfile: host %s filename %s", host, filename); - - if (want_revocation && (key == NULL || keytype != 0 || found != NULL)) - fatal("%s: invalid arguments", __func__); - - /* Open the file containing the list of known hosts. */ - f = fopen(filename, "r"); - if (!f) - return HOST_NEW; - - /* - * Return value when the loop terminates. This is set to - * HOST_CHANGED if we have seen a different key for the host and have - * not found the proper one. - */ - end_return = HOST_NEW; - - /* Go through the file. */ - while (fgets(line, sizeof(line), f)) { + HostkeyMarker marker; + Key *key; + int kbits; + + if ((f = fopen(path, "r")) == NULL) + return; + debug3("%s: loading entries for host \"%.100s\" from file \"%s\"", + __func__, host, path); + while (read_keyfile_line(f, path, line, sizeof(line), &linenum) == 0) { cp = line; - linenum++; /* Skip any leading whitespace, comments and empty lines. */ for (; *cp == ' ' || *cp == '\t'; cp++) @@ -268,19 +259,11 @@ check_host_in_hostfile_by_key_or_type(const char *filename, if (!*cp || *cp == '#' || *cp == '\n') continue; - if (want_revocation) - want = MRK_REVOKE; - else if (want_cert) - want = MRK_CA; - else - want = MRK_NONE; - - if ((have = check_markers(&cp)) == MRK_ERROR) { - verbose("%s: invalid marker at %s:%d", - __func__, filename, linenum); - continue; - } else if (want != have) + if ((marker = check_markers(&cp)) == MRK_ERROR) { + verbose("%s: invalid marker at %s:%lu", + __func__, path, linenum); continue; + } /* Find the end of the host name portion. */ for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) @@ -292,8 +275,8 @@ check_host_in_hostfile_by_key_or_type(const char *filename, continue; hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); if (hashed_host == NULL) { - debug("Invalid hashed host line %d of %s", - linenum, filename); + debug("Invalid hashed host line %lu of %s", + linenum, path); continue; } if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) @@ -303,98 +286,166 @@ check_host_in_hostfile_by_key_or_type(const char *filename, /* Got a match. Skip host name. */ cp = cp2; - if (want_revocation) - found = key_new(KEY_UNSPEC); - /* * Extract the key from the line. This will skip any leading * whitespace. Ignore badly formatted lines. */ - if (!hostfile_read_key(&cp, &kbits, found)) + key = key_new(KEY_UNSPEC); + if (!hostfile_read_key(&cp, &kbits, key)) { + key_free(key); + key = key_new(KEY_RSA1); + if (!hostfile_read_key(&cp, &kbits, key)) { + key_free(key); + continue; + } + } + if (!hostfile_check_key(kbits, key, host, path, linenum)) continue; - if (numret != NULL) - *numret = linenum; + debug3("%s: found %skey type %s in file %s:%lu", __func__, + marker == MRK_NONE ? "" : + (marker == MRK_CA ? "ca " : "revoked "), + key_type(key), path, linenum); + hostkeys->entries = xrealloc(hostkeys->entries, + hostkeys->num_entries + 1, sizeof(*hostkeys->entries)); + hostkeys->entries[hostkeys->num_entries].host = xstrdup(host); + hostkeys->entries[hostkeys->num_entries].file = xstrdup(path); + hostkeys->entries[hostkeys->num_entries].line = linenum; + hostkeys->entries[hostkeys->num_entries].key = key; + hostkeys->entries[hostkeys->num_entries].marker = marker; + hostkeys->num_entries++; + num_loaded++; + } + debug3("%s: loaded %lu keys", __func__, num_loaded); + return; +} - if (key == NULL) { - /* we found a key of the requested type */ - if (found->type == keytype) { - fclose(f); - return HOST_FOUND; - } - continue; - } +void +free_hostkeys(struct hostkeys *hostkeys) +{ + u_int i; + + for (i = 0; i < hostkeys->num_entries; i++) { + xfree(hostkeys->entries[i].host); + xfree(hostkeys->entries[i].file); + key_free(hostkeys->entries[i].key); + bzero(hostkeys->entries + i, sizeof(*hostkeys->entries)); + } + if (hostkeys->entries != NULL) + xfree(hostkeys->entries); + hostkeys->entries = NULL; + hostkeys->num_entries = 0; + xfree(hostkeys); +} - if (!hostfile_check_key(kbits, found, host, filename, linenum)) +static int +check_key_not_revoked(struct hostkeys *hostkeys, Key *k) +{ + int is_cert = key_is_cert(k); + u_int i; + + for (i = 0; i < hostkeys->num_entries; i++) { + if (hostkeys->entries[i].marker != MRK_REVOKE) continue; + if (key_equal_public(k, hostkeys->entries[i].key)) + return -1; + if (is_cert && + key_equal_public(k->cert->signature_key, + hostkeys->entries[i].key)) + return -1; + } + return 0; +} - if (want_revocation) { - if (key_is_cert(key) && - key_equal_public(key->cert->signature_key, found)) { - verbose("check_host_in_hostfile: revoked CA " - "line %d", linenum); - key_free(found); - return HOST_REVOKED; - } - if (key_equal_public(key, found)) { - verbose("check_host_in_hostfile: revoked key " - "line %d", linenum); - key_free(found); - return HOST_REVOKED; - } - key_free(found); +/* + * Match keys against a specified key, or look one up by key type. + * + * If looking for a keytype (key == NULL) and one is found then return + * HOST_FOUND, otherwise HOST_NEW. + * + * If looking for a key (key != NULL): + * 1. If the key is a cert and a matching CA is found, return HOST_OK + * 2. If the key is not a cert and a matching key is found, return HOST_OK + * 3. If no key matches but a key with a different type is found, then + * return HOST_CHANGED + * 4. If no matching keys are found, then return HOST_NEW. + * + * Finally, check any found key is not revoked. + */ +static HostStatus +check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, + Key *k, int keytype, const struct hostkey_entry **found) +{ + u_int i; + HostStatus end_return = HOST_NEW; + int want_cert = key_is_cert(k); + HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE; + int proto = (k ? k->type : keytype) == KEY_RSA1 ? 1 : 2; + + if (found != NULL) + *found = NULL; + + for (i = 0; i < hostkeys->num_entries; i++) { + if (proto == 1 && hostkeys->entries[i].key->type != KEY_RSA1) + continue; + if (proto == 2 && hostkeys->entries[i].key->type == KEY_RSA1) continue; + if (hostkeys->entries[i].marker != want_marker) + continue; + if (k == NULL) { + if (hostkeys->entries[i].key->type != keytype) + continue; + end_return = HOST_FOUND; + if (found != NULL) + *found = hostkeys->entries + i; + k = hostkeys->entries[i].key; + break; } - - /* Check if the current key is the same as the given key. */ - if (want_cert && key_equal(key->cert->signature_key, found)) { - /* Found CA cert for key */ - debug3("check_host_in_hostfile: CA match line %d", - linenum); - fclose(f); - return HOST_OK; - } else if (!want_cert && key_equal(key, found)) { - /* Found identical key */ - debug3("check_host_in_hostfile: match line %d", linenum); - fclose(f); - return HOST_OK; + if (want_cert) { + if (key_equal_public(k->cert->signature_key, + hostkeys->entries[i].key)) { + /* A matching CA exists */ + end_return = HOST_OK; + if (found != NULL) + *found = hostkeys->entries + i; + break; + } + } else { + if (key_equal(k, hostkeys->entries[i].key)) { + end_return = HOST_OK; + if (found != NULL) + *found = hostkeys->entries + i; + break; + } + /* A non-maching key exists */ + end_return = HOST_CHANGED; + if (found != NULL) + *found = hostkeys->entries + i; } - /* - * They do not match. We will continue to go through the - * file; however, we note that we will not return that it is - * new. - */ - end_return = HOST_CHANGED; } - /* Clear variables and close the file. */ - fclose(f); - - /* - * Return either HOST_NEW or HOST_CHANGED, depending on whether we - * saw a different key for the host. - */ + if (check_key_not_revoked(hostkeys, k) != 0) { + end_return = HOST_REVOKED; + if (found != NULL) + *found = NULL; + } return end_return; } - + HostStatus -check_host_in_hostfile(const char *filename, const char *host, const Key *key, - Key *found, int *numret) +check_key_in_hostkeys(struct hostkeys *hostkeys, Key *key, + const struct hostkey_entry **found) { if (key == NULL) fatal("no key to look up"); - if (check_host_in_hostfile_by_key_or_type(filename, host, - key, 0, NULL, 1, NULL) == HOST_REVOKED) - return HOST_REVOKED; - return check_host_in_hostfile_by_key_or_type(filename, host, key, 0, - found, 0, numret); + return check_hostkeys_by_key_or_type(hostkeys, key, 0, found); } int -lookup_key_in_hostfile_by_type(const char *filename, const char *host, - int keytype, Key *found, int *numret) +lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, + const struct hostkey_entry **found) { - return (check_host_in_hostfile_by_key_or_type(filename, host, NULL, - keytype, found, 0, numret) == HOST_FOUND); + return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype, + found) == HOST_FOUND); } /* diff --git a/hostfile.h b/hostfile.h index 1d460c1a9..d84d422ff 100644 --- a/hostfile.h +++ b/hostfile.h @@ -1,4 +1,4 @@ -/* $OpenBSD: hostfile.h,v 1.18 2010/03/04 10:36:03 djm Exp $ */ +/* $OpenBSD: hostfile.h,v 1.19 2010/11/29 23:45:51 djm Exp $ */ /* * Author: Tatu Ylonen @@ -18,12 +18,30 @@ typedef enum { HOST_OK, HOST_NEW, HOST_CHANGED, HOST_REVOKED, HOST_FOUND } HostStatus; +typedef enum { + MRK_ERROR, MRK_NONE, MRK_REVOKE, MRK_CA +} HostkeyMarker; + +struct hostkey_entry { + char *host; + char *file; + u_long line; + Key *key; + HostkeyMarker marker; +}; +struct hostkeys; + +struct hostkeys *init_hostkeys(void); +void load_hostkeys(struct hostkeys *, const char *, const char *); +void free_hostkeys(struct hostkeys *); + +HostStatus check_key_in_hostkeys(struct hostkeys *, Key *, + const struct hostkey_entry **); +int lookup_key_in_hostkeys_by_type(struct hostkeys *, int, + const struct hostkey_entry **); + int hostfile_read_key(char **, u_int *, Key *); -HostStatus check_host_in_hostfile(const char *, const char *, - const Key *, Key *, int *); -int add_host_to_hostfile(const char *, const char *, const Key *, int); -int lookup_key_in_hostfile_by_type(const char *, const char *, - int, Key *, int *); +int add_host_to_hostfile(const char *, const char *, const Key *, int); #define HASH_MAGIC "|1|" #define HASH_DELIM '|' diff --git a/ssh.c b/ssh.c index f413f8a5c..ec690ae38 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.354 2010/11/13 23:27:50 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.355 2010/11/29 23:45:51 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -880,7 +880,7 @@ main(int ac, char **av) /* Log into the remote system. Never returns if the login fails. */ ssh_login(&sensitive_data, host, (struct sockaddr *)&hostaddr, - pw, timeout_ms); + options.port, pw, timeout_ms); if (packet_connection_is_on_socket()) { verbose("Authenticated to %s ([%s]:%d).", host, diff --git a/ssh_config.5 b/ssh_config.5 index a51a37dde..5c6673de3 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.144 2010/11/15 07:40:14 jmc Exp $ -.Dd $Mdocdate: November 15 2010 $ +.\" $OpenBSD: ssh_config.5,v 1.145 2010/11/29 23:45:51 djm Exp $ +.Dd $Mdocdate: November 29 2010 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -555,6 +555,9 @@ ssh-rsa-cert-v00@openssh.com,ssh-dss-cert-v00@openssh.com, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, ssh-rsa,ssh-dss .Ed +.Pp +If hostkeys are known for the destination host then this default is modified +to prefer their algorithms. .It Cm HostKeyAlias Specifies an alias that should be used instead of the real host name when looking up or saving the host key diff --git a/sshconnect.c b/sshconnect.c index 78068c602..064bb74b3 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.228 2010/10/06 21:10:21 djm Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.229 2010/11/29 23:45:51 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -75,7 +75,7 @@ extern char *__progname; extern uid_t original_real_uid; extern uid_t original_effective_uid; -static int show_other_keys(const char *, Key *); +static int show_other_keys(struct hostkeys *, Key *); static void warn_changed_key(Key *); /* @@ -607,6 +607,79 @@ check_host_cert(const char *host, const Key *host_key) return 1; } +static int +sockaddr_is_local(struct sockaddr *hostaddr) +{ + switch (hostaddr->sa_family) { + case AF_INET: + return (ntohl(((struct sockaddr_in *)hostaddr)-> + sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; + case AF_INET6: + return IN6_IS_ADDR_LOOPBACK( + &(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); + default: + return 0; + } +} + +/* + * Prepare the hostname and ip address strings that are used to lookup + * host keys in known_hosts files. These may have a port number appended. + */ +void +get_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr, + u_short port, char **hostfile_hostname, char **hostfile_ipaddr) +{ + char ntop[NI_MAXHOST]; + socklen_t addrlen; + + switch (hostaddr == NULL ? -1 : hostaddr->sa_family) { + case -1: + addrlen = 0; + break; + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; + default: + addrlen = sizeof(struct sockaddr); + break; + } + + /* + * We don't have the remote ip-address for connections + * using a proxy command + */ + if (hostfile_ipaddr != NULL) { + if (options.proxy_command == NULL) { + if (getnameinfo(hostaddr, addrlen, + ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0) + fatal("check_host_key: getnameinfo failed"); + *hostfile_ipaddr = put_host_port(ntop, port); + } else { + *hostfile_ipaddr = xstrdup(""); + } + } + + /* + * Allow the user to record the key under a different name or + * differentiate a non-standard port. This is useful for ssh + * tunneling over forwarded connections or if you run multiple + * sshd's on different ports on the same machine. + */ + if (hostfile_hostname != NULL) { + if (options.host_key_alias != NULL) { + *hostfile_hostname = xstrdup(options.host_key_alias); + debug("using hostkeyalias: %s", *hostfile_hostname); + } else { + *hostfile_hostname = put_host_port(hostname, port); + } + } +} + /* * check whether the supplied host key is valid, return -1 if the key * is not valid. the user_hostfile will not be updated if 'readonly' is true. @@ -616,21 +689,21 @@ check_host_cert(const char *host, const Key *host_key) #define ROQUIET 2 static int check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, - Key *host_key, int readonly, const char *user_hostfile, - const char *system_hostfile) + Key *host_key, int readonly, char *user_hostfile, + char *system_hostfile) { - Key *file_key, *raw_key = NULL; + Key *raw_key = NULL; const char *type; char *ip = NULL, *host = NULL; char hostline[1000], *hostp, *fp, *ra; HostStatus host_status; HostStatus ip_status; - int r, want_cert, local = 0, host_ip_differ = 0; - int salen; - char ntop[NI_MAXHOST]; + int r, want_cert = key_is_cert(host_key), host_ip_differ = 0; + int local = sockaddr_is_local(hostaddr); char msg[1024]; - int len, host_line, ip_line, cancelled_forwarding = 0; - const char *host_file = NULL, *ip_file = NULL; + int len, cancelled_forwarding = 0; + struct hostkeys *host_hostkeys, *ip_hostkeys; + const struct hostkey_entry *host_found, *ip_found; /* * Force accepting of the host key for loopback/localhost. The @@ -640,23 +713,6 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, * essentially disables host authentication for localhost; however, * this is probably not a real problem. */ - /** hostaddr == 0! */ - switch (hostaddr->sa_family) { - case AF_INET: - local = (ntohl(((struct sockaddr_in *)hostaddr)-> - sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; - salen = sizeof(struct sockaddr_in); - break; - case AF_INET6: - local = IN6_IS_ADDR_LOOPBACK( - &(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); - salen = sizeof(struct sockaddr_in6); - break; - default: - local = 0; - salen = sizeof(struct sockaddr_storage); - break; - } if (options.no_host_authentication_for_localhost == 1 && local && options.host_key_alias == NULL) { debug("Forcing accepting of host key for " @@ -665,17 +721,10 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, } /* - * We don't have the remote ip-address for connections - * using a proxy command + * Prepare the hostname and address strings used for hostkey lookup. + * In some cases, these will have a port number appended. */ - if (options.proxy_command == NULL) { - if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop), - NULL, 0, NI_NUMERICHOST) != 0) - fatal("check_host_key: getnameinfo failed"); - ip = put_host_port(ntop, port); - } else { - ip = xstrdup(""); - } + get_hostfile_hostname_ipaddr(hostname, hostaddr, port, &host, &ip); /* * Turn off check_host_ip if the connection is to localhost, via proxy @@ -685,74 +734,52 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, strcmp(hostname, ip) == 0 || options.proxy_command != NULL)) options.check_host_ip = 0; - /* - * Allow the user to record the key under a different name or - * differentiate a non-standard port. This is useful for ssh - * tunneling over forwarded connections or if you run multiple - * sshd's on different ports on the same machine. - */ - if (options.host_key_alias != NULL) { - host = xstrdup(options.host_key_alias); - debug("using hostkeyalias: %s", host); - } else { - host = put_host_port(hostname, port); + host_hostkeys = init_hostkeys(); + load_hostkeys(host_hostkeys, host, user_hostfile); + load_hostkeys(host_hostkeys, host, system_hostfile); + + ip_hostkeys = NULL; + if (!want_cert && options.check_host_ip) { + ip_hostkeys = init_hostkeys(); + load_hostkeys(ip_hostkeys, ip, user_hostfile); + load_hostkeys(ip_hostkeys, ip, system_hostfile); } retry: + /* Reload these as they may have changed on cert->key downgrade */ want_cert = key_is_cert(host_key); type = key_type(host_key); - /* - * Store the host key from the known host file in here so that we can - * compare it with the key for the IP address. - */ - file_key = key_new(key_is_cert(host_key) ? KEY_UNSPEC : host_key->type); - /* * Check if the host key is present in the user's list of known * hosts or in the systemwide list. */ - host_file = user_hostfile; - host_status = check_host_in_hostfile(host_file, host, host_key, - file_key, &host_line); - if (host_status == HOST_NEW) { - host_file = system_hostfile; - host_status = check_host_in_hostfile(host_file, host, host_key, - file_key, &host_line); - } + host_status = check_key_in_hostkeys(host_hostkeys, host_key, + &host_found); + /* * Also perform check for the ip address, skip the check if we are * localhost, looking for a certificate, or the hostname was an ip * address to begin with. */ - if (!want_cert && options.check_host_ip) { - Key *ip_key = key_new(host_key->type); - - ip_file = user_hostfile; - ip_status = check_host_in_hostfile(ip_file, ip, host_key, - ip_key, &ip_line); - if (ip_status == HOST_NEW) { - ip_file = system_hostfile; - ip_status = check_host_in_hostfile(ip_file, ip, - host_key, ip_key, &ip_line); - } + if (!want_cert && ip_hostkeys != NULL) { + ip_status = check_key_in_hostkeys(ip_hostkeys, host_key, + &ip_found); if (host_status == HOST_CHANGED && - (ip_status != HOST_CHANGED || !key_equal(ip_key, file_key))) + (ip_status != HOST_CHANGED || + (ip_found != NULL && + !key_equal(ip_found->key, host_found->key)))) host_ip_differ = 1; - - key_free(ip_key); } else ip_status = host_status; - key_free(file_key); - switch (host_status) { case HOST_OK: /* The host is known and the key matches. */ debug("Host '%.200s' is known and matches the %s host %s.", host, type, want_cert ? "certificate" : "key"); - debug("Found %s in %s:%d", - want_cert ? "CA key" : "key", host_file, host_line); + debug("Found %s in %s:%lu", want_cert ? "CA key" : "key", + host_found->file, host_found->line); if (want_cert && !check_host_cert(hostname, host_key)) goto fail; if (options.check_host_ip && ip_status == HOST_NEW) { @@ -803,7 +830,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, } else if (options.strict_host_key_checking == 2) { char msg1[1024], msg2[1024]; - if (show_other_keys(host, host_key)) + if (show_other_keys(host_hostkeys, host_key)) snprintf(msg1, sizeof(msg1), "\nbut keys of different type are already" " known for this host."); @@ -844,8 +871,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, * local known_hosts file. */ if (options.check_host_ip && ip_status == HOST_NEW) { - snprintf(hostline, sizeof(hostline), "%s,%s", - host, ip); + snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); hostp = hostline; if (options.hash_known_hosts) { /* Add hash of host and IP separately */ @@ -899,8 +925,8 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, * all hosts that one might visit. */ debug("Host certificate authority does not " - "match %s in %s:%d", CA_MARKER, - host_file, host_line); + "match %s in %s:%lu", CA_MARKER, + host_found->file, host_found->line); goto fail; } if (readonly == ROQUIET) @@ -922,13 +948,15 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, error("DNS SPOOFING is happening or the IP address for the host"); error("and its host key have changed at the same time."); if (ip_status != HOST_NEW) - error("Offending key for IP in %s:%d", ip_file, ip_line); + error("Offending key for IP in %s:%lu", + ip_found->file, ip_found->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); + error("Offending %s key in %s:%lu", key_type(host_found->key), + host_found->file, host_found->line); /* * If strict host key checking is in use, the user will have @@ -1013,13 +1041,13 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, snprintf(msg, sizeof(msg), "Warning: the %s host key for '%.200s' " "differs from the key for the IP address '%.128s'" - "\nOffending key for IP in %s:%d", - type, host, ip, ip_file, ip_line); + "\nOffending key for IP in %s:%lu", + type, host, ip, ip_found->file, ip_found->line); if (host_status == HOST_OK) { len = strlen(msg); snprintf(msg + len, sizeof(msg) - len, - "\nMatching host key in %s:%d", - host_file, host_line); + "\nMatching host key in %s:%lu", + host_found->file, host_found->line); } if (options.strict_host_key_checking == 1) { logit("%s", msg); @@ -1037,6 +1065,10 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, xfree(ip); xfree(host); + if (host_hostkeys != NULL) + free_hostkeys(host_hostkeys); + if (ip_hostkeys != NULL) + free_hostkeys(ip_hostkeys); return 0; fail: @@ -1056,6 +1088,10 @@ fail: key_free(raw_key); xfree(ip); xfree(host); + if (host_hostkeys != NULL) + free_hostkeys(host_hostkeys); + if (ip_hostkeys != NULL) + free_hostkeys(ip_hostkeys); return -1; } @@ -1065,6 +1101,11 @@ verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) { struct stat st; int flags = 0; + char *fp; + + fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); + debug("Server host key: %s %s", key_type(host_key), fp); + xfree(fp); /* XXX certs are not yet supported for DNS */ if (!key_is_cert(host_key) && options.verify_host_key_dns && @@ -1108,7 +1149,7 @@ verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) */ void ssh_login(Sensitive *sensitive, const char *orighost, - struct sockaddr *hostaddr, struct passwd *pw, int timeout_ms) + struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms) { char *host, *cp; char *server_user, *local_user; @@ -1131,7 +1172,7 @@ ssh_login(Sensitive *sensitive, const char *orighost, /* key exchange */ /* authenticate user */ if (compat20) { - ssh_kex2(host, hostaddr); + ssh_kex2(host, hostaddr, port); ssh_userauth2(local_user, server_user, host, sensitive); } else { ssh_kex(host, hostaddr); @@ -1158,61 +1199,35 @@ ssh_put_password(char *password) xfree(padded); } -static int -show_key_from_file(const char *file, const char *host, int keytype) -{ - Key *found; - char *fp, *ra; - int line, ret; - - found = key_new(keytype); - if ((ret = lookup_key_in_hostfile_by_type(file, host, - keytype, found, &line))) { - fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); - ra = key_fingerprint(found, SSH_FP_MD5, SSH_FP_RANDOMART); - logit("WARNING: %s key found for host %s\n" - "in %s:%d\n" - "%s key fingerprint %s.\n%s\n", - key_type(found), host, file, line, - key_type(found), fp, ra); - xfree(ra); - xfree(fp); - } - key_free(found); - return (ret); -} - /* print all known host keys for a given host, but skip keys of given type */ static int -show_other_keys(const char *host, Key *key) +show_other_keys(struct hostkeys *hostkeys, Key *key) { int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, KEY_ECDSA, -1}; - int i, found = 0; + int i, ret = 0; + char *fp, *ra; + const struct hostkey_entry *found; for (i = 0; type[i] != -1; i++) { if (type[i] == key->type) continue; - if (type[i] != KEY_RSA1 && - show_key_from_file(options.user_hostfile2, host, type[i])) { - found = 1; - continue; - } - if (type[i] != KEY_RSA1 && - show_key_from_file(options.system_hostfile2, host, type[i])) { - found = 1; - continue; - } - if (show_key_from_file(options.user_hostfile, host, type[i])) { - found = 1; + if (!lookup_key_in_hostkeys_by_type(hostkeys, type[i], &found)) continue; - } - if (show_key_from_file(options.system_hostfile, host, type[i])) { - found = 1; - continue; - } - debug2("no key of type %d for host %s", type[i], host); + fp = key_fingerprint(found->key, SSH_FP_MD5, SSH_FP_HEX); + ra = key_fingerprint(found->key, SSH_FP_MD5, SSH_FP_RANDOMART); + logit("WARNING: %s key found for host %s\n" + "in %s:%lu\n" + "%s key fingerprint %s.", + key_type(found->key), + found->host, found->file, found->line, + key_type(found->key), fp); + if (options.visual_host_key) + logit("%s", ra); + xfree(ra); + xfree(fp); + ret = 1; } - return (found); + return ret; } static void diff --git a/sshconnect.h b/sshconnect.h index 69163afbc..fd7f7f7c6 100644 --- a/sshconnect.h +++ b/sshconnect.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.h,v 1.26 2010/10/06 06:39:28 djm Exp $ */ +/* $OpenBSD: sshconnect.h,v 1.27 2010/11/29 23:45:51 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -36,15 +36,18 @@ ssh_connect(const char *, struct sockaddr_storage *, u_short, int, int, int *, int, int, const char *); void ssh_kill_proxy_command(void); -void -ssh_login(Sensitive *, const char *, struct sockaddr *, struct passwd *, int); +void ssh_login(Sensitive *, const char *, struct sockaddr *, u_short, + struct passwd *, int); void ssh_exchange_identification(int); int verify_host_key(char *, struct sockaddr *, Key *); +void get_hostfile_hostname_ipaddr(char *, struct sockaddr *, u_short, + char **, char **); + void ssh_kex(char *, struct sockaddr *); -void ssh_kex2(char *, struct sockaddr *); +void ssh_kex2(char *, struct sockaddr *, u_short); void ssh_userauth1(const char *, const char *, char *, Sensitive *); void ssh_userauth2(const char *, const char *, char *, Sensitive *); diff --git a/sshconnect2.c b/sshconnect2.c index 6fe356cca..3cb9b101c 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect2.c,v 1.185 2010/09/22 05:01:29 djm Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.186 2010/11/29 23:45:51 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. @@ -69,6 +69,7 @@ #include "msg.h" #include "pathnames.h" #include "uidswap.h" +#include "hostfile.h" #include "schnorr.h" #include "jpake.h" @@ -101,8 +102,60 @@ verify_host_key_callback(Key *hostkey) return 0; } +static char * +order_hostkeyalgs(char *host, struct sockaddr *hostaddr, u_short port) +{ + char *oavail, *avail, *first, *last, *alg, *hostname, *ret; + size_t maxlen; + struct hostkeys *hostkeys; + int ktype; + + /* Find all hostkeys for this hostname */ + get_hostfile_hostname_ipaddr(host, hostaddr, port, &hostname, NULL); + hostkeys = init_hostkeys(); + load_hostkeys(hostkeys, hostname, options.user_hostfile2); + load_hostkeys(hostkeys, hostname, options.system_hostfile2); + load_hostkeys(hostkeys, hostname, options.user_hostfile); + load_hostkeys(hostkeys, hostname, options.system_hostfile); + + oavail = avail = xstrdup(KEX_DEFAULT_PK_ALG); + maxlen = strlen(avail) + 1; + first = xmalloc(maxlen); + last = xmalloc(maxlen); + *first = *last = '\0'; + +#define ALG_APPEND(to, from) \ + do { \ + if (*to != '\0') \ + strlcat(to, ",", maxlen); \ + strlcat(to, from, maxlen); \ + } while (0) + + while ((alg = strsep(&avail, ",")) && *alg != '\0') { + if ((ktype = key_type_from_name(alg)) == KEY_UNSPEC) + fatal("%s: unknown alg %s", __func__, alg); + if (lookup_key_in_hostkeys_by_type(hostkeys, + key_type_plain(ktype), NULL)) + ALG_APPEND(first, alg); + else + ALG_APPEND(last, alg); + } +#undef ALG_APPEND + xasprintf(&ret, "%s%s%s", first, *first == '\0' ? "" : ",", last); + if (*first != '\0') + debug3("%s: prefer hostkeyalgs: %s", __func__, first); + + xfree(first); + xfree(last); + xfree(hostname); + xfree(oavail); + free_hostkeys(hostkeys); + + return ret; +} + void -ssh_kex2(char *host, struct sockaddr *hostaddr) +ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) { Kex *kex; @@ -135,6 +188,11 @@ ssh_kex2(char *host, struct sockaddr *hostaddr) if (options.hostkeyalgorithms != NULL) myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = options.hostkeyalgorithms; + else { + /* Prefer algorithms that we already have keys for */ + myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = + order_hostkeyalgs(host, hostaddr, port); + } if (options.kex_algorithms != NULL) myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms; -- cgit v1.2.3