diff options
author | Damien Miller <djm@cvs.openbsd.org> | 2020-10-03 08:11:29 +0000 |
---|---|---|
committer | Damien Miller <djm@cvs.openbsd.org> | 2020-10-03 08:11:29 +0000 |
commit | 19b5e7ec5feb6e6037e8587dbfc6525b3ce08305 (patch) | |
tree | bd2a290eff1e16a60dbc74aa4b4faffe8abe9924 | |
parent | 84928f6c16cce6e8ea694fb22aae7d16f7c75e21 (diff) |
record when the host key checking code downgrades a certificate host
key to a plain key. This occurs when the user connects to a host with
a certificate host key but no corresponding CA key configured in
known_hosts; feedback and ok markus@
-rw-r--r-- | usr.bin/ssh/kex.h | 8 | ||||
-rw-r--r-- | usr.bin/ssh/sshconnect.c | 55 | ||||
-rw-r--r-- | usr.bin/ssh/sshconnect.h | 4 | ||||
-rw-r--r-- | usr.bin/ssh/sshconnect2.c | 9 |
4 files changed, 57 insertions, 19 deletions
diff --git a/usr.bin/ssh/kex.h b/usr.bin/ssh/kex.h index 50decba6e4c..e7e01e34de2 100644 --- a/usr.bin/ssh/kex.h +++ b/usr.bin/ssh/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.109 2019/09/06 05:23:55 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.110 2020/10/03 08:11:28 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -98,8 +98,10 @@ enum kex_exchange { KEX_MAX }; -#define KEX_INIT_SENT 0x0001 -#define KEX_INITIAL 0x0002 +/* kex->flags values */ +#define KEX_INIT_SENT 0x0001 /* KEXINIT sent */ +#define KEX_INITIAL 0x0002 /* Initial KEX, not rekey */ +#define KEX_HOSTCERT_CONVERT 0x0004 /* Client downgraded hostcert->plain */ struct sshenc { char *name; diff --git a/usr.bin/ssh/sshconnect.c b/usr.bin/ssh/sshconnect.c index fdf0c2d9c66..96e776b1b71 100644 --- a/usr.bin/ssh/sshconnect.c +++ b/usr.bin/ssh/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.332 2020/09/09 21:57:27 djm Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.333 2020/10/03 08:11:28 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -669,6 +669,10 @@ get_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr, /* * check whether the supplied host key is valid, return -1 if the key * is not valid. user_hostfile[0] will not be updated if 'readonly' is true. + * + * If cert_fallbackp is not NULL then will attempt to convert certificate host + * keys to plain keys if no certificate match was found and will return + * non-zero via *cert_fallbackp if this fall-back was used. */ #define RDRW 0 #define RDONLY 1 @@ -677,7 +681,7 @@ static int check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, struct sshkey *host_key, int readonly, char **user_hostfiles, u_int num_user_hostfiles, - char **system_hostfiles, u_int num_system_hostfiles) + char **system_hostfiles, u_int num_system_hostfiles, int *cert_fallbackp) { HostStatus host_status; HostStatus ip_status; @@ -688,12 +692,15 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, const char *type; const struct hostkey_entry *host_found, *ip_found; int len, cancelled_forwarding = 0, confirmed; - int local = sockaddr_is_local(hostaddr); + int local = sockaddr_is_local(hostaddr), cert_fallback = 0; int r, want_cert = sshkey_is_cert(host_key), host_ip_differ = 0; int hostkey_trusted = 0; /* Known or explicitly accepted by user */ struct hostkeys *host_hostkeys, *ip_hostkeys; u_int i; + if (cert_fallbackp != NULL) + *cert_fallbackp = 0; + /* * Force accepting of the host key for loopback/localhost. The * problem is that if the home directory is NFS-mounted to multiple @@ -809,9 +816,15 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, if (options.host_key_alias == NULL && port != 0 && port != SSH_DEFAULT_PORT) { debug("checking without port identifier"); + /* + * NB. do not perform cert->key fallback in this + * recursive call. Fallback will only be performed in + * the top-level call. + */ if (check_host_key(hostname, hostaddr, 0, host_key, ROQUIET, user_hostfiles, num_user_hostfiles, - system_hostfiles, num_system_hostfiles) == 0) { + system_hostfiles, num_system_hostfiles, + NULL) == 0) { debug("found matching key w/out port"); break; } @@ -1088,10 +1101,13 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, free_hostkeys(host_hostkeys); if (ip_hostkeys != NULL) free_hostkeys(ip_hostkeys); + if (cert_fallbackp != NULL) + *cert_fallbackp = cert_fallback; return 0; fail: - if (want_cert && host_status != HOST_REVOKED) { + if (cert_fallbackp != NULL && want_cert && + host_status != HOST_REVOKED) { /* * No matching certificate. Downgrade cert to raw key and * search normally. @@ -1103,6 +1119,7 @@ fail: if ((r = sshkey_drop_cert(raw_key)) != 0) fatal("Couldn't drop certificate: %s", ssh_err(r)); host_key = raw_key; + cert_fallback = 1; goto retry; } sshkey_free(raw_key); @@ -1115,15 +1132,24 @@ fail: return -1; } -/* returns 0 if key verifies or -1 if key does NOT verify */ +/* + * returns 0 if key verifies or -1 if key does NOT verify. + * + * If the host key was a certificate that was downgraded to a plain key in + * the process of matching, then cert_fallbackp will be non-zero. + */ int -verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key) +verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key, + int *cert_fallbackp) { u_int i; - int r = -1, flags = 0; + int r = -1, flags = 0, cert_fallback = 0; char valid[64], *fp = NULL, *cafp = NULL; struct sshkey *plain = NULL; + if (cert_fallbackp != NULL) + *cert_fallbackp = 0; + if ((fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { error("%s: fingerprint host key: %s", __func__, ssh_err(r)); @@ -1214,15 +1240,20 @@ verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key) } r = check_host_key(host, hostaddr, options.port, host_key, RDRW, options.user_hostfiles, options.num_user_hostfiles, - options.system_hostfiles, options.num_system_hostfiles); + options.system_hostfiles, options.num_system_hostfiles, + &cert_fallback); out: sshkey_free(plain); free(fp); free(cafp); - if (r == 0 && host_key != NULL) { - sshkey_free(previous_host_key); - r = sshkey_from_private(host_key, &previous_host_key); + if (r == 0) { + if (host_key != NULL) { + sshkey_free(previous_host_key); + r = sshkey_from_private(host_key, &previous_host_key); + } + if (r == 0 && cert_fallbackp != NULL) + *cert_fallbackp = cert_fallback; } return r; diff --git a/usr.bin/ssh/sshconnect.h b/usr.bin/ssh/sshconnect.h index 7c091e2b1bf..6d63075e963 100644 --- a/usr.bin/ssh/sshconnect.h +++ b/usr.bin/ssh/sshconnect.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.h,v 1.40 2020/01/25 07:17:18 djm Exp $ */ +/* $OpenBSD: sshconnect.h,v 1.41 2020/10/03 08:11:28 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -41,7 +41,7 @@ void ssh_kill_proxy_command(void); void ssh_login(struct ssh *, Sensitive *, const char *, struct sockaddr *, u_short, struct passwd *, int); -int verify_host_key(char *, struct sockaddr *, struct sshkey *); +int verify_host_key(char *, struct sockaddr *, struct sshkey *, int *); void get_hostfile_hostname_ipaddr(char *, struct sockaddr *, u_short, char **, char **); diff --git a/usr.bin/ssh/sshconnect2.c b/usr.bin/ssh/sshconnect2.c index b2699a92bed..62fb2f5b834 100644 --- a/usr.bin/ssh/sshconnect2.c +++ b/usr.bin/ssh/sshconnect2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect2.c,v 1.326 2020/09/18 05:23:03 djm Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.327 2020/10/03 08:11:28 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. @@ -92,8 +92,13 @@ struct sockaddr *xxx_hostaddr; static int verify_host_key_callback(struct sshkey *hostkey, struct ssh *ssh) { - if (verify_host_key(xxx_host, xxx_hostaddr, hostkey) == -1) + int cert_downgraded = 0; + + if (verify_host_key(xxx_host, xxx_hostaddr, hostkey, + &cert_downgraded) == -1) fatal("Host key verification failed."); + if (cert_downgraded) + ssh->kex->flags |= KEX_HOSTCERT_CONVERT; return 0; } |