From f69b69b8625be447b8826b21d87713874dac25a6 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 22 Dec 2014 07:51:30 +0000 Subject: upstream commit remember which public keys have been used for authentication and refuse to accept previously-used keys. This allows AuthenticationMethods=publickey,publickey to require that users authenticate using two _different_ pubkeys. ok markus@ --- auth.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'auth.h') diff --git a/auth.h b/auth.h index d081c94a6..c67e4a4d4 100644 --- a/auth.h +++ b/auth.h @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.h,v 1.78 2014/07/03 11:16:55 djm Exp $ */ +/* $OpenBSD: auth.h,v 1.79 2014/12/22 07:51:30 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -42,6 +42,8 @@ #include #endif +struct sshkey; + typedef struct Authctxt Authctxt; typedef struct Authmethod Authmethod; typedef struct KbdintDevice KbdintDevice; @@ -75,6 +77,9 @@ struct Authctxt { #endif Buffer *loginmsg; void *methoddata; + + struct sshkey **prev_userkeys; + u_int nprev_userkeys; }; /* * Every authentication method has to handle authentication requests for @@ -123,6 +128,8 @@ int hostbased_key_allowed(struct passwd *, const char *, char *, Key *); int user_key_allowed(struct passwd *, Key *); void pubkey_auth_info(Authctxt *, const Key *, const char *, ...) __attribute__((__format__ (printf, 3, 4))); +void auth2_record_userkey(Authctxt *, struct sshkey *); +int auth2_userkey_already_used(Authctxt *, struct sshkey *); struct stat; int auth_secure_path(const char *, struct stat *, const char *, uid_t, -- cgit v1.2.3 From 57d10cbe861a235dd269c74fb2fe248469ecee9d Mon Sep 17 00:00:00 2001 From: "markus@openbsd.org" Date: Mon, 19 Jan 2015 20:16:15 +0000 Subject: upstream commit adapt kex to sshbuf and struct ssh; ok djm@ --- auth.h | 13 +- clientloop.c | 11 +- dh.c | 59 +++--- dh.h | 6 +- kex.c | 575 +++++++++++++++++++++++++++++++++------------------------ kex.h | 127 +++++++------ kexc25519.c | 94 +++++----- kexc25519c.c | 159 ++++++++++------ kexc25519s.c | 133 ++++++++----- kexdh.c | 87 ++++----- kexdhc.c | 194 +++++++++++-------- kexdhs.c | 186 ++++++++++++------- kexecdh.c | 81 ++++---- kexecdhc.c | 207 ++++++++++++++------- kexecdhs.c | 190 ++++++++++++------- kexgex.c | 105 +++++------ kexgexc.c | 287 +++++++++++++++++----------- kexgexs.c | 257 ++++++++++++++++---------- monitor.c | 4 +- monitor_wrap.c | 6 +- serverloop.c | 6 +- ssh-keyscan.c | 14 +- sshconnect2.c | 12 +- sshd.c | 47 ++--- 24 files changed, 1704 insertions(+), 1156 deletions(-) (limited to 'auth.h') diff --git a/auth.h b/auth.h index c67e4a4d4..60d1c3355 100644 --- a/auth.h +++ b/auth.h @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.h,v 1.79 2014/12/22 07:51:30 djm Exp $ */ +/* $OpenBSD: auth.h,v 1.80 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -42,6 +42,7 @@ #include #endif +struct ssh; struct sshkey; typedef struct Authctxt Authctxt; @@ -202,12 +203,12 @@ check_key_in_hostfiles(struct passwd *, Key *, const char *, /* hostkey handling */ Key *get_hostkey_by_index(int); -Key *get_hostkey_public_by_index(int); -Key *get_hostkey_public_by_type(int); -Key *get_hostkey_private_by_type(int); -int get_hostkey_index(Key *); +Key *get_hostkey_public_by_index(int, struct ssh *); +Key *get_hostkey_public_by_type(int, struct ssh *); +Key *get_hostkey_private_by_type(int, struct ssh *); +int get_hostkey_index(Key *, struct ssh *); int ssh1_session_key(BIGNUM *); -void sshd_hostkey_sign(Key *, Key *, u_char **, u_int *, u_char *, u_int); +int sshd_hostkey_sign(Key *, Key *, u_char **, size_t *, u_char *, size_t, u_int); /* debug messages during authentication */ void auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2))); diff --git a/clientloop.c b/clientloop.c index 672d06989..5a018ac79 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.264 2015/01/19 20:07:45 markus Exp $ */ +/* $OpenBSD: clientloop.c,v 1.265 2015/01/19 20:16:15 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1413,8 +1413,7 @@ client_process_output(fd_set *writeset) static void client_process_buffered_input_packets(void) { - dispatch_run(DISPATCH_NONBLOCK, &quit_pending, - compat20 ? active_state->kex : NULL); + dispatch_run(DISPATCH_NONBLOCK, &quit_pending, active_state); } /* scan buf[] for '~' before sending data to the peer */ @@ -1468,7 +1467,7 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) { fd_set *readset = NULL, *writeset = NULL; double start_time, total_time; - int max_fd = 0, max_fd2 = 0, len, rekeying = 0; + int r, max_fd = 0, max_fd2 = 0, len, rekeying = 0; u_int64_t ibytes, obytes; u_int nalloc = 0; char buf[100]; @@ -1598,7 +1597,9 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) if (need_rekeying || packet_need_rekeying()) { debug("need rekeying"); active_state->kex->done = 0; - kex_send_kexinit(active_state->kex); + if ((r = kex_send_kexinit(active_state)) != 0) + fatal("%s: kex_send_kexinit: %s", + __func__, ssh_err(r)); need_rekeying = 0; } } diff --git a/dh.c b/dh.c index 3331cda6c..38ad615c5 100644 --- a/dh.c +++ b/dh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dh.c,v 1.53 2013/11/21 00:45:44 djm Exp $ */ +/* $OpenBSD: dh.c,v 1.54 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * @@ -39,6 +39,7 @@ #include "pathnames.h" #include "log.h" #include "misc.h" +#include "ssherr.h" static int parse_prime(int linenum, char *line, struct dhgroup *dhg) @@ -107,10 +108,11 @@ parse_prime(int linenum, char *line, struct dhgroup *dhg) goto fail; } - if ((dhg->g = BN_new()) == NULL) - fatal("parse_prime: BN_new failed"); - if ((dhg->p = BN_new()) == NULL) - fatal("parse_prime: BN_new failed"); + if ((dhg->g = BN_new()) == NULL || + (dhg->p = BN_new()) == NULL) { + error("parse_prime: BN_new failed"); + goto fail; + } if (BN_hex2bn(&dhg->g, gen) == 0) { error("moduli:%d: could not parse generator value", linenum); goto fail; @@ -128,7 +130,6 @@ parse_prime(int linenum, char *line, struct dhgroup *dhg) error("moduli:%d: generator is invalid", linenum); goto fail; } - return 1; fail: @@ -137,7 +138,6 @@ parse_prime(int linenum, char *line, struct dhgroup *dhg) if (dhg->p != NULL) BN_clear_free(dhg->p); dhg->g = dhg->p = NULL; - error("Bad prime description in line %d", linenum); return 0; } @@ -200,9 +200,11 @@ choose_dh(int min, int wantbits, int max) break; } fclose(f); - if (linenum != which+1) - fatal("WARNING: line %d disappeared in %s, giving up", + if (linenum != which+1) { + logit("WARNING: line %d disappeared in %s, giving up", which, _PATH_DH_PRIMES); + return (dh_new_group14()); + } return (dh_new_group(dhg.g, dhg.p)); } @@ -251,22 +253,22 @@ dh_pub_is_valid(DH *dh, BIGNUM *dh_pub) return 0; } -void +int dh_gen_key(DH *dh, int need) { int pbits; - if (need <= 0) - fatal("%s: need <= 0", __func__); - if (dh->p == NULL) - fatal("%s: dh->p == NULL", __func__); - if ((pbits = BN_num_bits(dh->p)) <= 0) - fatal("%s: bits(p) <= 0", __func__); + if (need < 0 || dh->p == NULL || + (pbits = BN_num_bits(dh->p)) <= 0 || + need > INT_MAX / 2 || 2 * need >= pbits) + return SSH_ERR_INVALID_ARGUMENT; dh->length = MIN(need * 2, pbits - 1); - if (DH_generate_key(dh) == 0) - fatal("%s: key generation failed", __func__); - if (!dh_pub_is_valid(dh, dh->pub_key)) - fatal("%s: generated invalid key", __func__); + if (DH_generate_key(dh) == 0 || + !dh_pub_is_valid(dh, dh->pub_key)) { + BN_clear_free(dh->priv_key); + return SSH_ERR_LIBCRYPTO_ERROR; + } + return 0; } DH * @@ -275,13 +277,12 @@ dh_new_group_asc(const char *gen, const char *modulus) DH *dh; if ((dh = DH_new()) == NULL) - fatal("dh_new_group_asc: DH_new"); - - if (BN_hex2bn(&dh->p, modulus) == 0) - fatal("BN_hex2bn p"); - if (BN_hex2bn(&dh->g, gen) == 0) - fatal("BN_hex2bn g"); - + return NULL; + if (BN_hex2bn(&dh->p, modulus) == 0 || + BN_hex2bn(&dh->g, gen) == 0) { + DH_free(dh); + return NULL; + } return (dh); } @@ -296,7 +297,7 @@ dh_new_group(BIGNUM *gen, BIGNUM *modulus) DH *dh; if ((dh = DH_new()) == NULL) - fatal("dh_new_group: DH_new"); + return NULL; dh->p = modulus; dh->g = gen; @@ -344,7 +345,7 @@ dh_new_group14(void) * from RFC4419 section 3. */ -int +u_int dh_estimate(int bits) { if (bits <= 112) diff --git a/dh.h b/dh.h index 48f7b68ea..63a1b1477 100644 --- a/dh.h +++ b/dh.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dh.h,v 1.11 2013/10/08 11:42:13 dtucker Exp $ */ +/* $OpenBSD: dh.h,v 1.12 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. @@ -38,10 +38,10 @@ DH *dh_new_group(BIGNUM *, BIGNUM *); DH *dh_new_group1(void); DH *dh_new_group14(void); -void dh_gen_key(DH *, int); +int dh_gen_key(DH *, int); int dh_pub_is_valid(DH *, BIGNUM *); -int dh_estimate(int); +u_int dh_estimate(int); /* Min and max values from RFC4419. */ #define DH_GRP_MIN 1024 diff --git a/kex.c b/kex.c index 92ebaf732..9280dd3f4 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.101 2015/01/19 20:07:45 markus Exp $ */ +/* $OpenBSD: kex.c,v 1.102 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -37,20 +37,22 @@ #include #endif -#include "xmalloc.h" #include "ssh2.h" -#include "buffer.h" #include "packet.h" #include "compat.h" #include "cipher.h" -#include "key.h" +#include "sshkey.h" #include "kex.h" #include "log.h" #include "mac.h" #include "match.h" +#include "misc.h" #include "dispatch.h" #include "monitor.h" #include "roaming.h" + +#include "ssherr.h" +#include "sshbuf.h" #include "digest.h" #if OPENSSL_VERSION_NUMBER >= 0x00907000L @@ -62,12 +64,12 @@ extern const EVP_MD *evp_ssh_sha256(void); #endif /* prototype */ -static void kex_kexinit_finish(Kex *); -static void kex_choose_conf(Kex *); +static int kex_choose_conf(struct ssh *); +static int kex_input_newkeys(int, u_int32_t, void *); struct kexalg { char *name; - int type; + u_int type; int ec_nid; int hash_alg; }; @@ -99,7 +101,7 @@ static const struct kexalg kexalgs[] = { char * kex_alg_list(char sep) { - char *ret = NULL; + char *ret = NULL, *tmp; size_t nlen, rlen = 0; const struct kexalg *k; @@ -107,7 +109,11 @@ kex_alg_list(char sep) if (ret != NULL) ret[rlen++] = sep; nlen = strlen(k->name); - ret = xrealloc(ret, 1, rlen + nlen + 2); + if ((tmp = realloc(ret, rlen + nlen + 2)) == NULL) { + free(ret); + return NULL; + } + ret = tmp; memcpy(ret + rlen, k->name, nlen + 1); rlen += nlen; } @@ -134,7 +140,8 @@ kex_names_valid(const char *names) if (names == NULL || strcmp(names, "") == 0) return 0; - s = cp = xstrdup(names); + if ((s = cp = strdup(names)) == NULL) + return 0; for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) { if (kex_alg_by_name(p) == NULL) { @@ -149,56 +156,75 @@ kex_names_valid(const char *names) } /* put algorithm proposal into buffer */ -static void -kex_prop2buf(Buffer *b, char *proposal[PROPOSAL_MAX]) +int +kex_prop2buf(struct sshbuf *b, char *proposal[PROPOSAL_MAX]) { u_int i; + int r; + + sshbuf_reset(b); - buffer_clear(b); /* * add a dummy cookie, the cookie will be overwritten by * kex_send_kexinit(), each time a kexinit is set */ - for (i = 0; i < KEX_COOKIE_LEN; i++) - buffer_put_char(b, 0); - for (i = 0; i < PROPOSAL_MAX; i++) - buffer_put_cstring(b, proposal[i]); - buffer_put_char(b, 0); /* first_kex_packet_follows */ - buffer_put_int(b, 0); /* uint32 reserved */ + for (i = 0; i < KEX_COOKIE_LEN; i++) { + if ((r = sshbuf_put_u8(b, 0)) != 0) + return r; + } + for (i = 0; i < PROPOSAL_MAX; i++) { + if ((r = sshbuf_put_cstring(b, proposal[i])) != 0) + return r; + } + if ((r = sshbuf_put_u8(b, 0)) != 0 || /* first_kex_packet_follows */ + (r = sshbuf_put_u32(b, 0)) != 0) /* uint32 reserved */ + return r; + return 0; } /* parse buffer and return algorithm proposal */ -static char ** -kex_buf2prop(Buffer *raw, int *first_kex_follows) +int +kex_buf2prop(struct sshbuf *raw, int *first_kex_follows, char ***propp) { - Buffer b; + struct sshbuf *b = NULL; + u_char v; u_int i; - char **proposal; - - proposal = xcalloc(PROPOSAL_MAX, sizeof(char *)); - - buffer_init(&b); - buffer_append(&b, buffer_ptr(raw), buffer_len(raw)); - /* skip cookie */ - for (i = 0; i < KEX_COOKIE_LEN; i++) - buffer_get_char(&b); + char **proposal = NULL; + int r; + + *propp = NULL; + if ((proposal = calloc(PROPOSAL_MAX, sizeof(char *))) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((b = sshbuf_fromb(raw)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = sshbuf_consume(b, KEX_COOKIE_LEN)) != 0) /* skip cookie */ + goto out; /* extract kex init proposal strings */ for (i = 0; i < PROPOSAL_MAX; i++) { - proposal[i] = buffer_get_cstring(&b,NULL); + if ((r = sshbuf_get_cstring(b, &(proposal[i]), NULL)) != 0) + goto out; debug2("kex_parse_kexinit: %s", proposal[i]); } /* first kex follows / reserved */ - i = buffer_get_char(&b); + if ((r = sshbuf_get_u8(b, &v)) != 0 || + (r = sshbuf_get_u32(b, &i)) != 0) + goto out; if (first_kex_follows != NULL) *first_kex_follows = i; - debug2("kex_parse_kexinit: first_kex_follows %d ", i); - i = buffer_get_int(&b); + debug2("kex_parse_kexinit: first_kex_follows %d ", v); debug2("kex_parse_kexinit: reserved %u ", i); - buffer_free(&b); - return proposal; + r = 0; + *propp = proposal; + out: + if (r != 0 && proposal != NULL) + kex_prop_free(proposal); + sshbuf_free(b); + return r; } -static void +void kex_prop_free(char **proposal) { u_int i; @@ -217,91 +243,103 @@ kex_protocol_error(int type, u_int32_t seq, void *ctxt) } static void -kex_reset_dispatch(void) +kex_reset_dispatch(struct ssh *ssh) { - dispatch_range(SSH2_MSG_TRANSPORT_MIN, + ssh_dispatch_range(ssh, SSH2_MSG_TRANSPORT_MIN, SSH2_MSG_TRANSPORT_MAX, &kex_protocol_error); - dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit); + ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); } -void -kex_finish(Kex *kex) +int +kex_send_newkeys(struct ssh *ssh) { - kex_reset_dispatch(); + int r; - packet_start(SSH2_MSG_NEWKEYS); - packet_send(); - /* packet_write_wait(); */ + kex_reset_dispatch(ssh); + if ((r = sshpkt_start(ssh, SSH2_MSG_NEWKEYS)) != 0 || + (r = sshpkt_send(ssh)) != 0) + return r; debug("SSH2_MSG_NEWKEYS sent"); - debug("expecting SSH2_MSG_NEWKEYS"); - packet_read_expect(SSH2_MSG_NEWKEYS); - packet_check_eom(); - debug("SSH2_MSG_NEWKEYS received"); + ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); + return 0; +} + +static int +kex_input_newkeys(int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + int r; + debug("SSH2_MSG_NEWKEYS received"); + ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_protocol_error); + if ((r = sshpkt_get_end(ssh)) != 0) + return r; kex->done = 1; - buffer_clear(kex->peer); - /* buffer_clear(kex->my); */ + sshbuf_reset(kex->peer); + /* sshbuf_reset(kex->my); */ kex->flags &= ~KEX_INIT_SENT; free(kex->name); kex->name = NULL; + return 0; } -void -kex_send_kexinit(Kex *kex) +int +kex_send_kexinit(struct ssh *ssh) { - u_int32_t rnd = 0; u_char *cookie; - u_int i; + struct kex *kex = ssh->kex; + int r; - if (kex == NULL) { - error("kex_send_kexinit: no kex, cannot rekey"); - return; - } - if (kex->flags & KEX_INIT_SENT) { - debug("KEX_INIT_SENT"); - return; - } + if (kex == NULL) + return SSH_ERR_INTERNAL_ERROR; + if (kex->flags & KEX_INIT_SENT) + return 0; kex->done = 0; /* generate a random cookie */ - if (buffer_len(kex->my) < KEX_COOKIE_LEN) - fatal("kex_send_kexinit: kex proposal too short"); - cookie = buffer_ptr(kex->my); - for (i = 0; i < KEX_COOKIE_LEN; i++) { - if (i % 4 == 0) - rnd = arc4random(); - cookie[i] = rnd; - rnd >>= 8; - } - packet_start(SSH2_MSG_KEXINIT); - packet_put_raw(buffer_ptr(kex->my), buffer_len(kex->my)); - packet_send(); + if (sshbuf_len(kex->my) < KEX_COOKIE_LEN) + return SSH_ERR_INVALID_FORMAT; + if ((cookie = sshbuf_mutable_ptr(kex->my)) == NULL) + return SSH_ERR_INTERNAL_ERROR; + arc4random_buf(cookie, KEX_COOKIE_LEN); + + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXINIT)) != 0 || + (r = sshpkt_putb(ssh, kex->my)) != 0 || + (r = sshpkt_send(ssh)) != 0) + return r; debug("SSH2_MSG_KEXINIT sent"); kex->flags |= KEX_INIT_SENT; + return 0; } /* ARGSUSED */ int kex_input_kexinit(int type, u_int32_t seq, void *ctxt) { - const char *ptr; + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + const u_char *ptr; u_int i; size_t dlen; - Kex *kex = (Kex *)ctxt; + int r; debug("SSH2_MSG_KEXINIT received"); if (kex == NULL) - fatal("kex_input_kexinit: no kex, cannot rekey"); + return SSH_ERR_INVALID_ARGUMENT; - ptr = packet_get_raw(&dlen); - buffer_append(kex->peer, ptr, dlen); + ptr = sshpkt_ptr(ssh, &dlen); + if ((r = sshbuf_put(kex->peer, ptr, dlen)) != 0) + return r; /* discard packet */ for (i = 0; i < KEX_COOKIE_LEN; i++) - packet_get_char(); + if ((r = sshpkt_get_u8(ssh, NULL)) != 0) + return r; for (i = 0; i < PROPOSAL_MAX; i++) - free(packet_get_string(NULL)); + if ((r = sshpkt_get_string(ssh, NULL, NULL)) != 0) + return r; /* * XXX RFC4253 sec 7: "each side MAY guess" - currently no supported * KEX method has the server move first, but a server might be using @@ -312,12 +350,47 @@ kex_input_kexinit(int type, u_int32_t seq, void *ctxt) * for cases where the server *doesn't* go first. I guess we should * ignore it when it is set for these cases, which is what we do now. */ - (void) packet_get_char(); /* first_kex_follows */ - (void) packet_get_int(); /* reserved */ - packet_check_eom(); + if ((r = sshpkt_get_u8(ssh, NULL)) != 0 || /* first_kex_follows */ + (r = sshpkt_get_u32(ssh, NULL)) != 0 || /* reserved */ + (r = sshpkt_get_end(ssh)) != 0) + return r; - kex_kexinit_finish(kex); - return 0; + if (!(kex->flags & KEX_INIT_SENT)) + if ((r = kex_send_kexinit(ssh)) != 0) + return r; + if ((r = kex_choose_conf(ssh)) != 0) + return r; + + if (kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL) + return (kex->kex[kex->kex_type])(ssh); + + return SSH_ERR_INTERNAL_ERROR; +} + +int +kex_new(struct ssh *ssh, char *proposal[PROPOSAL_MAX], struct kex **kexp) +{ + struct kex *kex; + int r; + + *kexp = NULL; + if ((kex = calloc(1, sizeof(*kex))) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((kex->peer = sshbuf_new()) == NULL || + (kex->my = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = kex_prop2buf(kex->my, proposal)) != 0) + goto out; + kex->done = 0; + kex_reset_dispatch(ssh); + r = 0; + *kexp = kex; + out: + if (r != 0) + kex_free(kex); + return r; } void @@ -351,51 +424,53 @@ kex_free_newkeys(struct newkeys *newkeys) free(newkeys); } -Kex * -kex_setup(char *proposal[PROPOSAL_MAX]) +void +kex_free(struct kex *kex) { - struct kex *kex; + u_int mode; - if ((kex = calloc(1, sizeof(*kex))) == NULL) - fatal("%s: calloc", __func__); - if ((kex->peer = sshbuf_new()) == NULL || - (kex->my = sshbuf_new()) == NULL) { - fatal("%s: sshbuf_new", __func__); +#ifdef WITH_OPENSSL + if (kex->dh) + DH_free(kex->dh); + if (kex->ec_client_key) + EC_KEY_free(kex->ec_client_key); +#endif + for (mode = 0; mode < MODE_MAX; mode++) { + kex_free_newkeys(kex->newkeys[mode]); + kex->newkeys[mode] = NULL; } - kex_prop2buf(kex->my, proposal); - kex->done = 0; - - kex_send_kexinit(kex); /* we start */ - kex_reset_dispatch(); - - return kex; + sshbuf_free(kex->peer); + sshbuf_free(kex->my); + free(kex->session_id); + free(kex->client_version_string); + free(kex->server_version_string); + free(kex); } -static void -kex_kexinit_finish(Kex *kex) +int +kex_setup(struct ssh *ssh, char *proposal[PROPOSAL_MAX]) { - if (!(kex->flags & KEX_INIT_SENT)) - kex_send_kexinit(kex); - - kex_choose_conf(kex); - - if (kex->kex_type >= 0 && kex->kex_type < KEX_MAX && - kex->kex[kex->kex_type] != NULL) { - (kex->kex[kex->kex_type])(kex); - } else { - fatal("Unsupported key exchange %d", kex->kex_type); + int r; + + if ((r = kex_new(ssh, proposal, &ssh->kex)) != 0) + return r; + if ((r = kex_send_kexinit(ssh)) != 0) { /* we start */ + kex_free(ssh->kex); + ssh->kex = NULL; + return r; } + return 0; } -static void -choose_enc(Enc *enc, char *client, char *server) +static int +choose_enc(struct sshenc *enc, char *client, char *server) { char *name = match_list(client, server, NULL); + if (name == NULL) - fatal("no matching cipher found: client %s server %s", - client, server); + return SSH_ERR_NO_CIPHER_ALG_MATCH; if ((enc->cipher = cipher_by_name(name)) == NULL) - fatal("matching cipher is not supported: %s", name); + return SSH_ERR_INTERNAL_ERROR; enc->name = name; enc->enabled = 0; enc->iv = NULL; @@ -403,31 +478,34 @@ choose_enc(Enc *enc, char *client, char *server) enc->key = NULL; enc->key_len = cipher_keylen(enc->cipher); enc->block_size = cipher_blocksize(enc->cipher); + return 0; } -static void -choose_mac(Mac *mac, char *client, char *server) +static int +choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server) { char *name = match_list(client, server, NULL); + if (name == NULL) - fatal("no matching mac found: client %s server %s", - client, server); + return SSH_ERR_NO_MAC_ALG_MATCH; if (mac_setup(mac, name) < 0) - fatal("unsupported mac %s", name); + return SSH_ERR_INTERNAL_ERROR; /* truncate the key */ - if (datafellows & SSH_BUG_HMAC) + if (ssh->compat & SSH_BUG_HMAC) mac->key_len = 16; mac->name = name; mac->key = NULL; mac->enabled = 0; + return 0; } -static void -choose_comp(Comp *comp, char *client, char *server) +static int +choose_comp(struct sshcomp *comp, char *client, char *server) { char *name = match_list(client, server, NULL); + if (name == NULL) - fatal("no matching comp found: client %s server %s", client, server); + return SSH_ERR_NO_COMPRESS_ALG_MATCH; if (strcmp(name, "zlib@openssh.com") == 0) { comp->type = COMP_DELAYED; } else if (strcmp(name, "zlib") == 0) { @@ -435,36 +513,41 @@ choose_comp(Comp *comp, char *client, char *server) } else if (strcmp(name, "none") == 0) { comp->type = COMP_NONE; } else { - fatal("unsupported comp %s", name); + return SSH_ERR_INTERNAL_ERROR; } comp->name = name; + return 0; } -static void -choose_kex(Kex *k, char *client, char *server) +static int +choose_kex(struct kex *k, char *client, char *server) { const struct kexalg *kexalg; k->name = match_list(client, server, NULL); + if (k->name == NULL) - fatal("Unable to negotiate a key exchange method"); + return SSH_ERR_NO_KEX_ALG_MATCH; if ((kexalg = kex_alg_by_name(k->name)) == NULL) - fatal("unsupported kex alg %s", k->name); + return SSH_ERR_INTERNAL_ERROR; k->kex_type = kexalg->type; k->hash_alg = kexalg->hash_alg; k->ec_nid = kexalg->ec_nid; + return 0; } -static void -choose_hostkeyalg(Kex *k, char *client, char *server) +static int +choose_hostkeyalg(struct kex *k, char *client, char *server) { char *hostkeyalg = match_list(client, server, NULL); + if (hostkeyalg == NULL) - fatal("no hostkey alg"); - k->hostkey_type = key_type_from_name(hostkeyalg); + return SSH_ERR_NO_HOSTKEY_ALG_MATCH; + k->hostkey_type = sshkey_type_from_name(hostkeyalg); if (k->hostkey_type == KEY_UNSPEC) - fatal("bad hostkey alg '%s'", hostkeyalg); + return SSH_ERR_INTERNAL_ERROR; free(hostkeyalg); + return 0; } static int @@ -491,18 +574,20 @@ proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX]) return (1); } -static void -kex_choose_conf(Kex *kex) +static int +kex_choose_conf(struct ssh *ssh) { - Newkeys *newkeys; - char **my, **peer; + struct kex *kex = ssh->kex; + struct newkeys *newkeys; + char **my = NULL, **peer = NULL; char **cprop, **sprop; int nenc, nmac, ncomp; u_int mode, ctos, need, dh_need, authlen; - int first_kex_follows, type; + int r, first_kex_follows; - my = kex_buf2prop(kex->my, NULL); - peer = kex_buf2prop(kex->peer, &first_kex_follows); + if ((r = kex_buf2prop(kex->my, NULL, &my)) != 0 || + (r = kex_buf2prop(kex->peer, &first_kex_follows, &peer)) != 0) + goto out; if (kex->server) { cprop=peer; @@ -514,8 +599,9 @@ kex_choose_conf(Kex *kex) /* Check whether server offers roaming */ if (!kex->server) { - char *roaming; - roaming = match_list(KEX_RESUME, peer[PROPOSAL_KEX_ALGS], NULL); + char *roaming = match_list(KEX_RESUME, + peer[PROPOSAL_KEX_ALGS], NULL); + if (roaming) { kex->roaming = 1; free(roaming); @@ -524,28 +610,39 @@ kex_choose_conf(Kex *kex) /* Algorithm Negotiation */ for (mode = 0; mode < MODE_MAX; mode++) { - newkeys = xcalloc(1, sizeof(*newkeys)); + if ((newkeys = calloc(1, sizeof(*newkeys))) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } kex->newkeys[mode] = newkeys; ctos = (!kex->server && mode == MODE_OUT) || (kex->server && mode == MODE_IN); nenc = ctos ? PROPOSAL_ENC_ALGS_CTOS : PROPOSAL_ENC_ALGS_STOC; nmac = ctos ? PROPOSAL_MAC_ALGS_CTOS : PROPOSAL_MAC_ALGS_STOC; ncomp = ctos ? PROPOSAL_COMP_ALGS_CTOS : PROPOSAL_COMP_ALGS_STOC; - choose_enc(&newkeys->enc, cprop[nenc], sprop[nenc]); - /* ignore mac for authenticated encryption */ + if ((r = choose_enc(&newkeys->enc, cprop[nenc], + sprop[nenc])) != 0) + goto out; authlen = cipher_authlen(newkeys->enc.cipher); - if (authlen == 0) - choose_mac(&newkeys->mac, cprop[nmac], sprop[nmac]); - choose_comp(&newkeys->comp, cprop[ncomp], sprop[ncomp]); + /* ignore mac for authenticated encryption */ + if (authlen == 0 && + (r = choose_mac(ssh, &newkeys->mac, cprop[nmac], + sprop[nmac])) != 0) + goto out; + if ((r = choose_comp(&newkeys->comp, cprop[ncomp], + sprop[ncomp])) != 0) + goto out; debug("kex: %s %s %s %s", ctos ? "client->server" : "server->client", newkeys->enc.name, authlen == 0 ? newkeys->mac.name : "", newkeys->comp.name); } - choose_kex(kex, cprop[PROPOSAL_KEX_ALGS], sprop[PROPOSAL_KEX_ALGS]); - choose_hostkeyalg(kex, cprop[PROPOSAL_SERVER_HOST_KEY_ALGS], - sprop[PROPOSAL_SERVER_HOST_KEY_ALGS]); + if ((r = choose_kex(kex, cprop[PROPOSAL_KEX_ALGS], + sprop[PROPOSAL_KEX_ALGS])) != 0 || + (r = choose_hostkeyalg(kex, cprop[PROPOSAL_SERVER_HOST_KEY_ALGS], + sprop[PROPOSAL_SERVER_HOST_KEY_ALGS])) != 0) + goto out; need = dh_need = 0; for (mode = 0; mode < MODE_MAX; mode++) { newkeys = kex->newkeys[mode]; @@ -564,45 +661,47 @@ kex_choose_conf(Kex *kex) /* ignore the next message if the proposals do not match */ if (first_kex_follows && !proposals_match(my, peer) && - !(datafellows & SSH_BUG_FIRSTKEX)) { - type = packet_read(); - debug2("skipping next packet (type %u)", type); - } - + !(ssh->compat & SSH_BUG_FIRSTKEX)) + ssh->dispatch_skip_packets = 1; + r = 0; + out: kex_prop_free(my); kex_prop_free(peer); + return r; } -static u_char * -derive_key(Kex *kex, int id, u_int need, u_char *hash, u_int hashlen, - const u_char *shared_secret, u_int slen) +static int +derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen, + const struct sshbuf *shared_secret, u_char **keyp) { - Buffer b; - struct ssh_digest_ctx *hashctx; + struct kex *kex = ssh->kex; + struct ssh_digest_ctx *hashctx = NULL; char c = id; u_int have; size_t mdsz; u_char *digest; + int r; if ((mdsz = ssh_digest_bytes(kex->hash_alg)) == 0) - fatal("bad kex md size %zu", mdsz); - digest = xmalloc(roundup(need, mdsz)); - - buffer_init(&b); - buffer_append(&b, shared_secret, slen); + return SSH_ERR_INVALID_ARGUMENT; + if ((digest = calloc(1, roundup(need, mdsz))) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } /* K1 = HASH(K || H || "A" || session_id) */ - if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL) - fatal("%s: ssh_digest_start failed", __func__); - if (ssh_digest_update_buffer(hashctx, &b) != 0 || + if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL || + ssh_digest_update_buffer(hashctx, shared_secret) != 0 || ssh_digest_update(hashctx, hash, hashlen) != 0 || ssh_digest_update(hashctx, &c, 1) != 0 || ssh_digest_update(hashctx, kex->session_id, - kex->session_id_len) != 0) - fatal("%s: ssh_digest_update failed", __func__); - if (ssh_digest_final(hashctx, digest, mdsz) != 0) - fatal("%s: ssh_digest_final failed", __func__); + kex->session_id_len) != 0 || + ssh_digest_final(hashctx, digest, mdsz) != 0) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } ssh_digest_free(hashctx); + hashctx = NULL; /* * expand key: @@ -610,38 +709,49 @@ derive_key(Kex *kex, int id, u_int need, u_char *hash, u_int hashlen, * Key = K1 || K2 || ... || Kn */ for (have = mdsz; need > have; have += mdsz) { - if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL) - fatal("%s: ssh_digest_start failed", __func__); - if (ssh_digest_update_buffer(hashctx, &b) != 0 || + if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL || + ssh_digest_update_buffer(hashctx, shared_secret) != 0 || ssh_digest_update(hashctx, hash, hashlen) != 0 || - ssh_digest_update(hashctx, digest, have) != 0) - fatal("%s: ssh_digest_update failed", __func__); - if (ssh_digest_final(hashctx, digest + have, mdsz) != 0) - fatal("%s: ssh_digest_final failed", __func__); + ssh_digest_update(hashctx, digest, have) != 0 || + ssh_digest_final(hashctx, digest + have, mdsz) != 0) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } ssh_digest_free(hashctx); + hashctx = NULL; } - buffer_free(&b); #ifdef DEBUG_KEX fprintf(stderr, "key '%c'== ", c); dump_digest("key", digest, need); #endif - return digest; + *keyp = digest; + digest = NULL; + r = 0; + out: + if (digest) + free(digest); + ssh_digest_free(hashctx); + return r; } #define NKEYS 6 -void -kex_derive_keys(Kex *kex, u_char *hash, u_int hashlen, - const u_char *shared_secret, u_int slen) +int +kex_derive_keys(struct ssh *ssh, u_char *hash, u_int hashlen, + const struct sshbuf *shared_secret) { + struct kex *kex = ssh->kex; u_char *keys[NKEYS]; - u_int i, mode, ctos; + u_int i, j, mode, ctos; + int r; for (i = 0; i < NKEYS; i++) { - keys[i] = derive_key(kex, 'A'+i, kex->we_need, hash, hashlen, - shared_secret, slen); + if ((r = derive_key(ssh, 'A'+i, kex->we_need, hash, hashlen, + shared_secret, &keys[i])) != 0) { + for (j = 0; j < i; j++) + free(keys[j]); + return r; + } } - - debug2("kex_derive_keys"); for (mode = 0; mode < MODE_MAX; mode++) { ctos = (!kex->server && mode == MODE_OUT) || (kex->server && mode == MODE_IN); @@ -649,54 +759,54 @@ kex_derive_keys(Kex *kex, u_char *hash, u_int hashlen, kex->newkeys[mode]->enc.key = keys[ctos ? 2 : 3]; kex->newkeys[mode]->mac.key = keys[ctos ? 4 : 5]; } + return 0; } #ifdef WITH_OPENSSL -void -kex_derive_keys_bn(Kex *kex, u_char *hash, u_int hashlen, const BIGNUM *secret) +int +kex_derive_keys_bn(struct ssh *ssh, u_char *hash, u_int hashlen, + const BIGNUM *secret) { - Buffer shared_secret; - - buffer_init(&shared_secret); - buffer_put_bignum2(&shared_secret, secret); - kex_derive_keys(kex, hash, hashlen, - buffer_ptr(&shared_secret), buffer_len(&shared_secret)); - buffer_free(&shared_secret); + struct sshbuf *shared_secret; + int r; + + if ((shared_secret = sshbuf_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((r = sshbuf_put_bignum2(shared_secret, secret)) == 0) + r = kex_derive_keys(ssh, hash, hashlen, shared_secret); + sshbuf_free(shared_secret); + return r; } #endif #ifdef WITH_SSH1 -void +int derive_ssh1_session_id(BIGNUM *host_modulus, BIGNUM *server_modulus, u_int8_t cookie[8], u_int8_t id[16]) { u_int8_t nbuf[2048], obuf[SSH_DIGEST_MAX_LENGTH]; - int len; - struct ssh_digest_ctx *hashctx; - - if ((hashctx = ssh_digest_start(SSH_DIGEST_MD5)) == NULL) - fatal("%s: ssh_digest_start", __func__); + struct ssh_digest_ctx *hashctx = NULL; + size_t len; + int r; len = BN_num_bytes(host_modulus); if (len < (512 / 8) || (u_int)len > sizeof(nbuf)) - fatal("%s: bad host modulus (len %d)", __func__, len); - BN_bn2bin(host_modulus, nbuf); - if (ssh_digest_update(hashctx, nbuf, len) != 0) - fatal("%s: ssh_digest_update failed", __func__); - - len = BN_num_bytes(server_modulus); - if (len < (512 / 8) || (u_int)len > sizeof(nbuf)) - fatal("%s: bad server modulus (len %d)", __func__, len); - BN_bn2bin(server_modulus, nbuf); - if (ssh_digest_update(hashctx, nbuf, len) != 0 || - ssh_digest_update(hashctx, cookie, 8) != 0) - fatal("%s: ssh_digest_update failed", __func__); - if (ssh_digest_final(hashctx, obuf, sizeof(obuf)) != 0) - fatal("%s: ssh_digest_final failed", __func__); + return SSH_ERR_KEY_BITS_MISMATCH; + if (BN_bn2bin(host_modulus, nbuf) <= 0 || + (hashctx = ssh_digest_start(SSH_DIGEST_MD5)) == NULL || + ssh_digest_update(hashctx, nbuf, len) != 0 || + ssh_digest_update(hashctx, cookie, 8) != 0 || + ssh_digest_final(hashctx, obuf, sizeof(obuf)) != 0) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } memcpy(id, obuf, ssh_digest_bytes(SSH_DIGEST_MD5)); - + r = 0; + out: + ssh_digest_free(hashctx); explicit_bzero(nbuf, sizeof(nbuf)); explicit_bzero(obuf, sizeof(obuf)); + return r; } #endif @@ -704,16 +814,7 @@ derive_ssh1_session_id(BIGNUM *host_modulus, BIGNUM *server_modulus, void dump_digest(char *msg, u_char *digest, int len) { - int i; - fprintf(stderr, "%s\n", msg); - for (i = 0; i < len; i++) { - fprintf(stderr, "%02x", digest[i]); - if (i%32 == 31) - fprintf(stderr, "\n"); - else if (i%8 == 7) - fprintf(stderr, " "); - } - fprintf(stderr, "\n"); + sshbuf_dump_data(digest, len, stderr); } #endif diff --git a/kex.h b/kex.h index 1f7c824b6..1798eea4d 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.68 2015/01/19 20:07:45 markus Exp $ */ +/* $OpenBSD: kex.h,v 1.69 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -30,8 +30,8 @@ #include "buffer.h" /* XXX for typedef */ #include "key.h" /* XXX for typedef */ -#if defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC) -#include +#ifdef WITH_LEAKMALLOC +#include "leakmalloc.h" #endif #define KEX_COOKIE_LEN 16 @@ -50,6 +50,8 @@ #define COMP_ZLIB 1 #define COMP_DELAYED 2 +#define CURVE25519_SIZE 32 + enum kex_init_proposals { PROPOSAL_KEX_ALGS, PROPOSAL_SERVER_HOST_KEY_ALGS, @@ -82,12 +84,6 @@ enum kex_exchange { #define KEX_INIT_SENT 0x0001 -typedef struct kex Kex; -typedef struct sshcomp Comp; -typedef struct sshmac Mac; -typedef struct sshenc Enc; -typedef struct newkeys Newkeys; - struct sshenc { char *name; const struct sshcipher *cipher; @@ -106,8 +102,11 @@ struct sshcomp { struct newkeys { struct sshenc enc; struct sshmac mac; - struct sshcomp comp; + struct sshcomp comp; }; + +struct ssh; + struct kex { u_char *session_id; size_t session_id_len; @@ -117,73 +116,87 @@ struct kex { int server; char *name; int hostkey_type; - int kex_type; + u_int kex_type; int roaming; struct sshbuf *my; struct sshbuf *peer; sig_atomic_t done; - int flags; + u_int flags; int hash_alg; int ec_nid; char *client_version_string; char *server_version_string; - int (*verify_host_key)(Key *); - Key *(*load_host_public_key)(int); - Key *(*load_host_private_key)(int); - int (*host_key_index)(Key *); - void (*sign)(Key *, Key *, u_char **, u_int *, u_char *, u_int); - void (*kex[KEX_MAX])(Kex *); + int (*verify_host_key)(struct sshkey *, struct ssh *); + struct sshkey *(*load_host_public_key)(int, struct ssh *); + struct sshkey *(*load_host_private_key)(int, struct ssh *); + int (*host_key_index)(struct sshkey *, struct ssh *); + int (*sign)(struct sshkey *, struct sshkey *, + u_char **, size_t *, u_char *, size_t, u_int); + int (*kex[KEX_MAX])(struct ssh *); + /* kex specific state */ + DH *dh; /* DH */ + u_int min, max, nbits; /* GEX */ + EC_KEY *ec_client_key; /* ECDH */ + const EC_GROUP *ec_group; /* ECDH */ + u_char c25519_client_key[CURVE25519_SIZE]; /* 25519 */ + u_char c25519_client_pubkey[CURVE25519_SIZE]; /* 25519 */ }; int kex_names_valid(const char *); char *kex_alg_list(char); -Kex *kex_setup(char *[PROPOSAL_MAX]); -void kex_finish(Kex *); -void kex_free_newkeys(struct newkeys *); +int kex_new(struct ssh *, char *[PROPOSAL_MAX], struct kex **); +int kex_setup(struct ssh *, char *[PROPOSAL_MAX]); +void kex_free_newkeys(struct newkeys *); +void kex_free(struct kex *); -void kex_send_kexinit(Kex *); -int kex_input_kexinit(int, u_int32_t, void *); -void kex_derive_keys(Kex *, u_char *, u_int, const u_char *, u_int); -void kex_derive_keys_bn(Kex *, u_char *, u_int, const BIGNUM *); - -void kexdh_client(Kex *); -void kexdh_server(Kex *); -void kexgex_client(Kex *); -void kexgex_server(Kex *); -void kexecdh_client(Kex *); -void kexecdh_server(Kex *); -void kexc25519_client(Kex *); -void kexc25519_server(Kex *); - -void -kex_dh_hash(char *, char *, char *, int, char *, int, u_char *, int, - BIGNUM *, BIGNUM *, BIGNUM *, u_char **, u_int *); -void -kexgex_hash(int, char *, char *, char *, int, char *, - int, u_char *, int, int, int, int, BIGNUM *, BIGNUM *, BIGNUM *, - BIGNUM *, BIGNUM *, u_char **, u_int *); -#ifdef OPENSSL_HAS_ECC -void -kex_ecdh_hash(int, const EC_GROUP *, char *, char *, char *, int, - char *, int, u_char *, int, const EC_POINT *, const EC_POINT *, - const BIGNUM *, u_char **, u_int *); -#endif -void -kex_c25519_hash(int, char *, char *, char *, int, - char *, int, u_char *, int, const u_char *, const u_char *, - const u_char *, u_int, u_char **, u_int *); +int kex_buf2prop(struct sshbuf *, int *, char ***); +int kex_prop2buf(struct sshbuf *, char *proposal[PROPOSAL_MAX]); +void kex_prop_free(char **); -#define CURVE25519_SIZE 32 -void kexc25519_keygen(u_char[CURVE25519_SIZE], u_char[CURVE25519_SIZE]) +int kex_send_kexinit(struct ssh *); +int kex_input_kexinit(int, u_int32_t, void *); +int kex_derive_keys(struct ssh *, u_char *, u_int, const struct sshbuf *); +int kex_derive_keys_bn(struct ssh *, u_char *, u_int, const BIGNUM *); +int kex_send_newkeys(struct ssh *); + +int kexdh_client(struct ssh *); +int kexdh_server(struct ssh *); +int kexgex_client(struct ssh *); +int kexgex_server(struct ssh *); +int kexecdh_client(struct ssh *); +int kexecdh_server(struct ssh *); +int kexc25519_client(struct ssh *); +int kexc25519_server(struct ssh *); + +int kex_dh_hash(const char *, const char *, + const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, + const BIGNUM *, const BIGNUM *, const BIGNUM *, u_char *, size_t *); + +int kexgex_hash(int, const char *, const char *, + const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, + int, int, int, + const BIGNUM *, const BIGNUM *, const BIGNUM *, + const BIGNUM *, const BIGNUM *, + u_char *, size_t *); + +int kex_ecdh_hash(int, const EC_GROUP *, const char *, const char *, + const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, + const EC_POINT *, const EC_POINT *, const BIGNUM *, u_char *, size_t *); + +int kex_c25519_hash(int, const char *, const char *, const char *, size_t, + const char *, size_t, const u_char *, size_t, const u_char *, const u_char *, + const u_char *, size_t, u_char *, size_t *); + +void kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); -void kexc25519_shared_key(const u_char key[CURVE25519_SIZE], - const u_char pub[CURVE25519_SIZE], Buffer *out) +int kexc25519_shared_key(const u_char key[CURVE25519_SIZE], + const u_char pub[CURVE25519_SIZE], struct sshbuf *out) __attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE))) __attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE))); -void +int derive_ssh1_session_id(BIGNUM *, BIGNUM *, u_int8_t[8], u_int8_t[16]); #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH) diff --git a/kexc25519.c b/kexc25519.c index e3afa0055..b6e6c4010 100644 --- a/kexc25519.c +++ b/kexc25519.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519.c,v 1.7 2014/05/02 03:27:54 djm Exp $ */ +/* $OpenBSD: kexc25519.c,v 1.8 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2001, 2013 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -35,13 +35,14 @@ #include #include -#include "buffer.h" +#include "sshbuf.h" #include "ssh2.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" #include "kex.h" #include "log.h" #include "digest.h" +#include "ssherr.h" extern int crypto_scalarmult_curve25519(u_char a[CURVE25519_SIZE], const u_char b[CURVE25519_SIZE], const u_char c[CURVE25519_SIZE]) @@ -58,65 +59,70 @@ kexc25519_keygen(u_char key[CURVE25519_SIZE], u_char pub[CURVE25519_SIZE]) crypto_scalarmult_curve25519(pub, key, basepoint); } -void +int kexc25519_shared_key(const u_char key[CURVE25519_SIZE], - const u_char pub[CURVE25519_SIZE], Buffer *out) + const u_char pub[CURVE25519_SIZE], struct sshbuf *out) { u_char shared_key[CURVE25519_SIZE]; + int r; crypto_scalarmult_curve25519(shared_key, key, pub); #ifdef DEBUG_KEXECDH dump_digest("shared secret", shared_key, CURVE25519_SIZE); #endif - buffer_clear(out); - buffer_put_bignum2_from_string(out, shared_key, CURVE25519_SIZE); + sshbuf_reset(out); + r = sshbuf_put_bignum2_bytes(out, shared_key, CURVE25519_SIZE); explicit_bzero(shared_key, CURVE25519_SIZE); + return r; } -void +int kex_c25519_hash( int hash_alg, - char *client_version_string, - char *server_version_string, - char *ckexinit, int ckexinitlen, - char *skexinit, int skexinitlen, - u_char *serverhostkeyblob, int sbloblen, + const char *client_version_string, + const char *server_version_string, + const char *ckexinit, size_t ckexinitlen, + const char *skexinit, size_t skexinitlen, + const u_char *serverhostkeyblob, size_t sbloblen, const u_char client_dh_pub[CURVE25519_SIZE], const u_char server_dh_pub[CURVE25519_SIZE], - const u_char *shared_secret, u_int secretlen, - u_char **hash, u_int *hashlen) + const u_char *shared_secret, size_t secretlen, + u_char *hash, size_t *hashlen) { - Buffer b; - static u_char digest[SSH_DIGEST_MAX_LENGTH]; - - buffer_init(&b); - buffer_put_cstring(&b, client_version_string); - buffer_put_cstring(&b, server_version_string); - - /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ - buffer_put_int(&b, ckexinitlen+1); - buffer_put_char(&b, SSH2_MSG_KEXINIT); - buffer_append(&b, ckexinit, ckexinitlen); - buffer_put_int(&b, skexinitlen+1); - buffer_put_char(&b, SSH2_MSG_KEXINIT); - buffer_append(&b, skexinit, skexinitlen); - - buffer_put_string(&b, serverhostkeyblob, sbloblen); - buffer_put_string(&b, client_dh_pub, CURVE25519_SIZE); - buffer_put_string(&b, server_dh_pub, CURVE25519_SIZE); - buffer_append(&b, shared_secret, secretlen); - + struct sshbuf *b; + int r; + + if (*hashlen < ssh_digest_bytes(hash_alg)) + return SSH_ERR_INVALID_ARGUMENT; + if ((b = sshbuf_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((r = sshbuf_put_cstring(b, client_version_string)) < 0 || + (r = sshbuf_put_cstring(b, server_version_string)) < 0 || + /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ + (r = sshbuf_put_u32(b, ckexinitlen+1)) < 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) < 0 || + (r = sshbuf_put(b, ckexinit, ckexinitlen)) < 0 || + (r = sshbuf_put_u32(b, skexinitlen+1)) < 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) < 0 || + (r = sshbuf_put(b, skexinit, skexinitlen)) < 0 || + (r = sshbuf_put_string(b, serverhostkeyblob, sbloblen)) < 0 || + (r = sshbuf_put_string(b, client_dh_pub, CURVE25519_SIZE)) < 0 || + (r = sshbuf_put_string(b, server_dh_pub, CURVE25519_SIZE)) < 0 || + (r = sshbuf_put(b, shared_secret, secretlen)) < 0) { + sshbuf_free(b); + return r; + } #ifdef DEBUG_KEX - buffer_dump(&b); + sshbuf_dump(b, stderr); #endif - if (ssh_digest_buffer(hash_alg, &b, digest, sizeof(digest)) != 0) - fatal("%s: digest_buffer failed", __func__); - - buffer_free(&b); - + if (ssh_digest_buffer(hash_alg, b, hash, *hashlen) != 0) { + sshbuf_free(b); + return SSH_ERR_LIBCRYPTO_ERROR; + } + sshbuf_free(b); + *hashlen = ssh_digest_bytes(hash_alg); #ifdef DEBUG_KEX - dump_digest("hash", digest, ssh_digest_bytes(hash_alg)); + dump_digest("hash", hash, *hashlen); #endif - *hash = digest; - *hashlen = ssh_digest_bytes(hash_alg); + return 0; } diff --git a/kexc25519c.c b/kexc25519c.c index ffb537ef6..833ce0544 100644 --- a/kexc25519c.c +++ b/kexc25519c.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519c.c,v 1.5 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: kexc25519c.c,v 1.6 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -33,97 +33,136 @@ #include #include -#include "xmalloc.h" -#include "buffer.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" #include "kex.h" #include "log.h" #include "packet.h" #include "ssh2.h" +#include "sshbuf.h" +#include "digest.h" +#include "ssherr.h" -void -kexc25519_client(Kex *kex) -{ - Key *server_host_key; - u_char client_key[CURVE25519_SIZE]; - u_char client_pubkey[CURVE25519_SIZE]; - u_char *server_pubkey = NULL; - u_char *server_host_key_blob = NULL, *signature = NULL; - u_char *hash; - u_int slen, sbloblen, hashlen; - Buffer shared_secret; - - kexc25519_keygen(client_key, client_pubkey); +static int +input_kex_c25519_reply(int type, u_int32_t seq, void *ctxt); - packet_start(SSH2_MSG_KEX_ECDH_INIT); - packet_put_string(client_pubkey, sizeof(client_pubkey)); - packet_send(); - debug("sending SSH2_MSG_KEX_ECDH_INIT"); +int +kexc25519_client(struct ssh *ssh) +{ + struct kex *kex = ssh->kex; + int r; + kexc25519_keygen(kex->c25519_client_key, kex->c25519_client_pubkey); #ifdef DEBUG_KEXECDH - dump_digest("client private key:", client_key, sizeof(client_key)); + dump_digest("client private key:", kex->c25519_client_key, + sizeof(kex->c25519_client_key)); #endif + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_ECDH_INIT)) != 0 || + (r = sshpkt_put_string(ssh, kex->c25519_client_pubkey, + sizeof(kex->c25519_client_pubkey))) != 0 || + (r = sshpkt_send(ssh)) != 0) + return r; debug("expecting SSH2_MSG_KEX_ECDH_REPLY"); - packet_read_expect(SSH2_MSG_KEX_ECDH_REPLY); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_ECDH_REPLY, &input_kex_c25519_reply); + return 0; +} + +static int +input_kex_c25519_reply(int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + struct sshkey *server_host_key = NULL; + struct sshbuf *shared_secret = NULL; + u_char *server_pubkey = NULL; + u_char *server_host_key_blob = NULL, *signature = NULL; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t slen, pklen, sbloblen, hashlen; + int r; + + if (kex->verify_host_key == NULL) { + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } /* hostkey */ - server_host_key_blob = packet_get_string(&sbloblen); - server_host_key = key_from_blob(server_host_key_blob, sbloblen); - if (server_host_key == NULL) - fatal("cannot decode server_host_key_blob"); - if (server_host_key->type != kex->hostkey_type) - fatal("type mismatch for decoded server_host_key_blob"); - if (kex->verify_host_key == NULL) - fatal("cannot verify server_host_key"); - if (kex->verify_host_key(server_host_key) == -1) - fatal("server_host_key verification failed"); + if ((r = sshpkt_get_string(ssh, &server_host_key_blob, + &sbloblen)) != 0 || + (r = sshkey_from_blob(server_host_key_blob, sbloblen, + &server_host_key)) != 0) + goto out; + if (server_host_key->type != kex->hostkey_type) { + r = SSH_ERR_KEY_TYPE_MISMATCH; + goto out; + } + if (kex->verify_host_key(server_host_key, ssh) == -1) { + r = SSH_ERR_SIGNATURE_INVALID; + goto out; + } /* Q_S, server public key */ - server_pubkey = packet_get_string(&slen); - if (slen != CURVE25519_SIZE) - fatal("Incorrect size for server Curve25519 pubkey: %d", slen); + /* signed H */ + if ((r = sshpkt_get_string(ssh, &server_pubkey, &pklen)) != 0 || + (r = sshpkt_get_string(ssh, &signature, &slen)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; + if (pklen != CURVE25519_SIZE) { + r = SSH_ERR_SIGNATURE_INVALID; + goto out; + } #ifdef DEBUG_KEXECDH dump_digest("server public key:", server_pubkey, CURVE25519_SIZE); #endif - /* signed H */ - signature = packet_get_string(&slen); - packet_check_eom(); - - buffer_init(&shared_secret); - kexc25519_shared_key(client_key, server_pubkey, &shared_secret); + if ((shared_secret = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = kexc25519_shared_key(kex->c25519_client_key, server_pubkey, + shared_secret)) < 0) + goto out; /* calc and verify H */ - kex_c25519_hash( + hashlen = sizeof(hash); + if ((r = kex_c25519_hash( kex->hash_alg, kex->client_version_string, kex->server_version_string, - buffer_ptr(kex->my), buffer_len(kex->my), - buffer_ptr(kex->peer), buffer_len(kex->peer), + sshbuf_ptr(kex->my), sshbuf_len(kex->my), + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), server_host_key_blob, sbloblen, - client_pubkey, + kex->c25519_client_pubkey, server_pubkey, - buffer_ptr(&shared_secret), buffer_len(&shared_secret), - &hash, &hashlen - ); - free(server_host_key_blob); - free(server_pubkey); - if (key_verify(server_host_key, signature, slen, hash, hashlen) != 1) - fatal("key_verify failed for server_host_key"); - key_free(server_host_key); - free(signature); + sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), + hash, &hashlen)) < 0) + goto out; + + if ((r = sshkey_verify(server_host_key, signature, slen, hash, hashlen, + ssh->compat)) != 0) + goto out; /* save session id */ if (kex->session_id == NULL) { kex->session_id_len = hashlen; - kex->session_id = xmalloc(kex->session_id_len); + kex->session_id = malloc(kex->session_id_len); + if (kex->session_id == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } memcpy(kex->session_id, hash, kex->session_id_len); } - kex_derive_keys(kex, hash, hashlen, - buffer_ptr(&shared_secret), buffer_len(&shared_secret)); - buffer_free(&shared_secret); - kex_finish(kex); + + if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); +out: + explicit_bzero(hash, sizeof(hash)); + explicit_bzero(kex->c25519_client_key, sizeof(kex->c25519_client_key)); + free(server_host_key_blob); + free(server_pubkey); + free(signature); + sshkey_free(server_host_key); + sshbuf_free(shared_secret); + return r; } diff --git a/kexc25519s.c b/kexc25519s.c index ba6f546f4..912b0afb1 100644 --- a/kexc25519s.c +++ b/kexc25519s.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519s.c,v 1.5 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: kexc25519s.c,v 1.6 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -30,97 +30,128 @@ #include #include -#include "xmalloc.h" -#include "buffer.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" +#include "digest.h" #include "kex.h" #include "log.h" #include "packet.h" #include "ssh2.h" +#include "sshbuf.h" +#include "ssherr.h" -void -kexc25519_server(Kex *kex) +static int input_kex_c25519_init(int, u_int32_t, void *); + +int +kexc25519_server(struct ssh *ssh) +{ + debug("expecting SSH2_MSG_KEX_ECDH_INIT"); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_ECDH_INIT, &input_kex_c25519_init); + return 0; +} + +static int +input_kex_c25519_init(int type, u_int32_t seq, void *ctxt) { - Key *server_host_private, *server_host_public; + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + struct sshkey *server_host_private, *server_host_public; + struct sshbuf *shared_secret = NULL; u_char *server_host_key_blob = NULL, *signature = NULL; u_char server_key[CURVE25519_SIZE]; u_char *client_pubkey = NULL; u_char server_pubkey[CURVE25519_SIZE]; - u_char *hash; - u_int slen, sbloblen, hashlen; - Buffer shared_secret; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t slen, pklen, sbloblen, hashlen; + int r; /* generate private key */ kexc25519_keygen(server_key, server_pubkey); #ifdef DEBUG_KEXECDH dump_digest("server private key:", server_key, sizeof(server_key)); #endif - if (kex->load_host_public_key == NULL || - kex->load_host_private_key == NULL) - fatal("Cannot load hostkey"); - server_host_public = kex->load_host_public_key(kex->hostkey_type); - if (server_host_public == NULL) - fatal("Unsupported hostkey type %d", kex->hostkey_type); - server_host_private = kex->load_host_private_key(kex->hostkey_type); - - debug("expecting SSH2_MSG_KEX_ECDH_INIT"); - packet_read_expect(SSH2_MSG_KEX_ECDH_INIT); - client_pubkey = packet_get_string(&slen); - if (slen != CURVE25519_SIZE) - fatal("Incorrect size for server Curve25519 pubkey: %d", slen); - packet_check_eom(); + kex->load_host_private_key == NULL) { + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } + if ((server_host_public = kex->load_host_public_key(kex->hostkey_type, + ssh)) == NULL || + (server_host_private = kex->load_host_private_key(kex->hostkey_type, + ssh)) == NULL) { + r = SSH_ERR_NO_HOSTKEY_LOADED; + goto out; + } + if ((r = sshpkt_get_string(ssh, &client_pubkey, &pklen)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; + if (pklen != CURVE25519_SIZE) { + r = SSH_ERR_SIGNATURE_INVALID; + goto out; + } #ifdef DEBUG_KEXECDH dump_digest("client public key:", client_pubkey, CURVE25519_SIZE); #endif - buffer_init(&shared_secret); - kexc25519_shared_key(server_key, client_pubkey, &shared_secret); + if ((shared_secret = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = kexc25519_shared_key(server_key, client_pubkey, + shared_secret)) < 0) + goto out; /* calc H */ - key_to_blob(server_host_public, &server_host_key_blob, &sbloblen); - kex_c25519_hash( + if ((r = sshkey_to_blob(server_host_public, &server_host_key_blob, + &sbloblen)) != 0) + goto out; + hashlen = sizeof(hash); + if ((r = kex_c25519_hash( kex->hash_alg, kex->client_version_string, kex->server_version_string, - buffer_ptr(kex->peer), buffer_len(kex->peer), - buffer_ptr(kex->my), buffer_len(kex->my), + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), + sshbuf_ptr(kex->my), sshbuf_len(kex->my), server_host_key_blob, sbloblen, client_pubkey, server_pubkey, - buffer_ptr(&shared_secret), buffer_len(&shared_secret), - &hash, &hashlen - ); + sshbuf_ptr(shared_secret), sshbuf_len(shared_secret), + hash, &hashlen)) < 0) + goto out; /* save session id := H */ if (kex->session_id == NULL) { kex->session_id_len = hashlen; - kex->session_id = xmalloc(kex->session_id_len); + kex->session_id = malloc(kex->session_id_len); + if (kex->session_id == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } memcpy(kex->session_id, hash, kex->session_id_len); } /* sign H */ - kex->sign(server_host_private, server_host_public, &signature, &slen, - hash, hashlen); - - /* destroy_sensitive_data(); */ + if ((r = kex->sign(server_host_private, server_host_public, + &signature, &slen, hash, hashlen, ssh->compat)) < 0) + goto out; /* send server hostkey, ECDH pubkey 'Q_S' and signed H */ - packet_start(SSH2_MSG_KEX_ECDH_REPLY); - packet_put_string(server_host_key_blob, sbloblen); - packet_put_string(server_pubkey, sizeof(server_pubkey)); - packet_put_string(signature, slen); - packet_send(); - - free(signature); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_ECDH_REPLY)) != 0 || + (r = sshpkt_put_string(ssh, server_host_key_blob, sbloblen)) != 0 || + (r = sshpkt_put_string(ssh, server_pubkey, sizeof(server_pubkey))) != 0 || + (r = sshpkt_put_string(ssh, signature, slen)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; + + if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); +out: + explicit_bzero(hash, sizeof(hash)); + explicit_bzero(server_key, sizeof(server_key)); free(server_host_key_blob); - /* have keys, free server key */ + free(signature); free(client_pubkey); - - kex_derive_keys(kex, hash, hashlen, - buffer_ptr(&shared_secret), buffer_len(&shared_secret)); - buffer_free(&shared_secret); - kex_finish(kex); + sshbuf_free(shared_secret); + return r; } diff --git a/kexdh.c b/kexdh.c index 2c1dfb6f5..feea6697d 100644 --- a/kexdh.c +++ b/kexdh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdh.c,v 1.24 2014/01/09 23:20:00 djm Exp $ */ +/* $OpenBSD: kexdh.c,v 1.25 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -33,58 +33,61 @@ #include -#include "buffer.h" #include "ssh2.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" #include "kex.h" +#include "ssherr.h" +#include "sshbuf.h" #include "digest.h" -#include "log.h" -void +int kex_dh_hash( - char *client_version_string, - char *server_version_string, - char *ckexinit, int ckexinitlen, - char *skexinit, int skexinitlen, - u_char *serverhostkeyblob, int sbloblen, - BIGNUM *client_dh_pub, - BIGNUM *server_dh_pub, - BIGNUM *shared_secret, - u_char **hash, u_int *hashlen) + const char *client_version_string, + const char *server_version_string, + const u_char *ckexinit, size_t ckexinitlen, + const u_char *skexinit, size_t skexinitlen, + const u_char *serverhostkeyblob, size_t sbloblen, + const BIGNUM *client_dh_pub, + const BIGNUM *server_dh_pub, + const BIGNUM *shared_secret, + u_char *hash, size_t *hashlen) { - Buffer b; - static u_char digest[SSH_DIGEST_MAX_LENGTH]; - - buffer_init(&b); - buffer_put_cstring(&b, client_version_string); - buffer_put_cstring(&b, server_version_string); - - /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ - buffer_put_int(&b, ckexinitlen+1); - buffer_put_char(&b, SSH2_MSG_KEXINIT); - buffer_append(&b, ckexinit, ckexinitlen); - buffer_put_int(&b, skexinitlen+1); - buffer_put_char(&b, SSH2_MSG_KEXINIT); - buffer_append(&b, skexinit, skexinitlen); - - buffer_put_string(&b, serverhostkeyblob, sbloblen); - buffer_put_bignum2(&b, client_dh_pub); - buffer_put_bignum2(&b, server_dh_pub); - buffer_put_bignum2(&b, shared_secret); + struct sshbuf *b; + int r; + if (*hashlen < ssh_digest_bytes(SSH_DIGEST_SHA1)) + return SSH_ERR_INVALID_ARGUMENT; + if ((b = sshbuf_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((r = sshbuf_put_cstring(b, client_version_string)) != 0 || + (r = sshbuf_put_cstring(b, server_version_string)) != 0 || + /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ + (r = sshbuf_put_u32(b, ckexinitlen+1)) != 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || + (r = sshbuf_put(b, ckexinit, ckexinitlen)) != 0 || + (r = sshbuf_put_u32(b, skexinitlen+1)) != 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || + (r = sshbuf_put(b, skexinit, skexinitlen)) != 0 || + (r = sshbuf_put_string(b, serverhostkeyblob, sbloblen)) != 0 || + (r = sshbuf_put_bignum2(b, client_dh_pub)) != 0 || + (r = sshbuf_put_bignum2(b, server_dh_pub)) != 0 || + (r = sshbuf_put_bignum2(b, shared_secret)) != 0) { + sshbuf_free(b); + return r; + } #ifdef DEBUG_KEX - buffer_dump(&b); + sshbuf_dump(b, stderr); #endif - if (ssh_digest_buffer(SSH_DIGEST_SHA1, &b, digest, sizeof(digest)) != 0) - fatal("%s: ssh_digest_buffer failed", __func__); - - buffer_free(&b); - + if (ssh_digest_buffer(SSH_DIGEST_SHA1, b, hash, *hashlen) != 0) { + sshbuf_free(b); + return SSH_ERR_LIBCRYPTO_ERROR; + } + sshbuf_free(b); + *hashlen = ssh_digest_bytes(SSH_DIGEST_SHA1); #ifdef DEBUG_KEX - dump_digest("hash", digest, ssh_digest_bytes(SSH_DIGEST_SHA1)); + dump_digest("hash", hash, *hashlen); #endif - *hash = digest; - *hashlen = ssh_digest_bytes(SSH_DIGEST_SHA1); + return 0; } #endif /* WITH_OPENSSL */ diff --git a/kexdhc.c b/kexdhc.c index cd12df33d..52b752295 100644 --- a/kexdhc.c +++ b/kexdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhc.c,v 1.16 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: kexdhc.c,v 1.17 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -36,129 +36,175 @@ #include #include -#include "xmalloc.h" -#include "buffer.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" +#include "digest.h" #include "kex.h" #include "log.h" #include "packet.h" #include "dh.h" #include "ssh2.h" +#include "dispatch.h" +#include "compat.h" +#include "ssherr.h" +#include "sshbuf.h" -void -kexdh_client(Kex *kex) +static int input_kex_dh(int, u_int32_t, void *); + +int +kexdh_client(struct ssh *ssh) { - BIGNUM *dh_server_pub = NULL, *shared_secret = NULL; - DH *dh; - Key *server_host_key; - u_char *server_host_key_blob = NULL, *signature = NULL; - u_char *kbuf, *hash; - u_int klen, slen, sbloblen, hashlen; - int kout; + struct kex *kex = ssh->kex; + int r; /* generate and send 'e', client DH public key */ switch (kex->kex_type) { case KEX_DH_GRP1_SHA1: - dh = dh_new_group1(); + kex->dh = dh_new_group1(); break; case KEX_DH_GRP14_SHA1: - dh = dh_new_group14(); + kex->dh = dh_new_group14(); break; default: - fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } + if (kex->dh == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; } - dh_gen_key(dh, kex->we_need * 8); - packet_start(SSH2_MSG_KEXDH_INIT); - packet_put_bignum2(dh->pub_key); - packet_send(); - debug("sending SSH2_MSG_KEXDH_INIT"); + if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0 || + (r = sshpkt_start(ssh, SSH2_MSG_KEXDH_INIT)) != 0 || + (r = sshpkt_put_bignum2(ssh, kex->dh->pub_key)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; #ifdef DEBUG_KEXDH - DHparams_print_fp(stderr, dh); + DHparams_print_fp(stderr, kex->dh); fprintf(stderr, "pub= "); - BN_print_fp(stderr, dh->pub_key); + BN_print_fp(stderr, kex->dh->pub_key); fprintf(stderr, "\n"); #endif - debug("expecting SSH2_MSG_KEXDH_REPLY"); - packet_read_expect(SSH2_MSG_KEXDH_REPLY); + ssh_dispatch_set(ssh, SSH2_MSG_KEXDH_REPLY, &input_kex_dh); + r = 0; + out: + return r; +} +static int +input_kex_dh(int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + BIGNUM *dh_server_pub = NULL, *shared_secret = NULL; + struct sshkey *server_host_key = NULL; + u_char *kbuf = NULL, *server_host_key_blob = NULL, *signature = NULL; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t klen = 0, slen, sbloblen, hashlen; + int kout, r; + + if (kex->verify_host_key == NULL) { + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } /* key, cert */ - server_host_key_blob = packet_get_string(&sbloblen); - server_host_key = key_from_blob(server_host_key_blob, sbloblen); - if (server_host_key == NULL) - fatal("cannot decode server_host_key_blob"); - if (server_host_key->type != kex->hostkey_type) - fatal("type mismatch for decoded server_host_key_blob"); - if (kex->verify_host_key == NULL) - fatal("cannot verify server_host_key"); - if (kex->verify_host_key(server_host_key) == -1) - fatal("server_host_key verification failed"); - + if ((r = sshpkt_get_string(ssh, &server_host_key_blob, + &sbloblen)) != 0 || + (r = sshkey_from_blob(server_host_key_blob, sbloblen, + &server_host_key)) != 0) + goto out; + if (server_host_key->type != kex->hostkey_type) { + r = SSH_ERR_KEY_TYPE_MISMATCH; + goto out; + } + if (kex->verify_host_key(server_host_key, ssh) == -1) { + r = SSH_ERR_SIGNATURE_INVALID; + goto out; + } /* DH parameter f, server public DH key */ - if ((dh_server_pub = BN_new()) == NULL) - fatal("dh_server_pub == NULL"); - packet_get_bignum2(dh_server_pub); - + if ((dh_server_pub = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + /* signed H */ + if ((r = sshpkt_get_bignum2(ssh, dh_server_pub)) != 0 || + (r = sshpkt_get_string(ssh, &signature, &slen)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; #ifdef DEBUG_KEXDH fprintf(stderr, "dh_server_pub= "); BN_print_fp(stderr, dh_server_pub); fprintf(stderr, "\n"); debug("bits %d", BN_num_bits(dh_server_pub)); #endif + if (!dh_pub_is_valid(kex->dh, dh_server_pub)) { + sshpkt_disconnect(ssh, "bad server public DH value"); + r = SSH_ERR_MESSAGE_INCOMPLETE; + goto out; + } - /* signed H */ - signature = packet_get_string(&slen); - packet_check_eom(); - - if (!dh_pub_is_valid(dh, dh_server_pub)) - packet_disconnect("bad server public DH value"); - - klen = DH_size(dh); - kbuf = xmalloc(klen); - if ((kout = DH_compute_key(kbuf, dh_server_pub, dh)) < 0) - fatal("DH_compute_key: failed"); + klen = DH_size(kex->dh); + if ((kbuf = malloc(klen)) == NULL || + (shared_secret = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((kout = DH_compute_key(kbuf, dh_server_pub, kex->dh)) < 0 || + BN_bin2bn(kbuf, kout, shared_secret) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } #ifdef DEBUG_KEXDH dump_digest("shared secret", kbuf, kout); #endif - if ((shared_secret = BN_new()) == NULL) - fatal("kexdh_client: BN_new failed"); - if (BN_bin2bn(kbuf, kout, shared_secret) == NULL) - fatal("kexdh_client: BN_bin2bn failed"); - explicit_bzero(kbuf, klen); - free(kbuf); /* calc and verify H */ - kex_dh_hash( + hashlen = sizeof(hash); + if ((r = kex_dh_hash( kex->client_version_string, kex->server_version_string, - buffer_ptr(kex->my), buffer_len(kex->my), - buffer_ptr(kex->peer), buffer_len(kex->peer), + sshbuf_ptr(kex->my), sshbuf_len(kex->my), + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), server_host_key_blob, sbloblen, - dh->pub_key, + kex->dh->pub_key, dh_server_pub, shared_secret, - &hash, &hashlen - ); - free(server_host_key_blob); - BN_clear_free(dh_server_pub); - DH_free(dh); + hash, &hashlen)) != 0) + goto out; - if (key_verify(server_host_key, signature, slen, hash, hashlen) != 1) - fatal("key_verify failed for server_host_key"); - key_free(server_host_key); - free(signature); + if ((r = sshkey_verify(server_host_key, signature, slen, hash, hashlen, + ssh->compat)) != 0) + goto out; /* save session id */ if (kex->session_id == NULL) { kex->session_id_len = hashlen; - kex->session_id = xmalloc(kex->session_id_len); + kex->session_id = malloc(kex->session_id_len); + if (kex->session_id == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } memcpy(kex->session_id, hash, kex->session_id_len); } - kex_derive_keys_bn(kex, hash, hashlen, shared_secret); - BN_clear_free(shared_secret); - kex_finish(kex); + if ((r = kex_derive_keys_bn(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); + out: + explicit_bzero(hash, sizeof(hash)); + DH_free(kex->dh); + kex->dh = NULL; + if (dh_server_pub) + BN_clear_free(dh_server_pub); + if (kbuf) { + explicit_bzero(kbuf, klen); + free(kbuf); + } + if (shared_secret) + BN_clear_free(shared_secret); + sshkey_free(server_host_key); + free(server_host_key_blob); + free(signature); + return r; } #endif /* WITH_OPENSSL */ diff --git a/kexdhs.c b/kexdhs.c index 34a215f8c..182657ac9 100644 --- a/kexdhs.c +++ b/kexdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhs.c,v 1.19 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: kexdhs.c,v 1.20 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -35,55 +35,88 @@ #include -#include "xmalloc.h" -#include "buffer.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" +#include "digest.h" #include "kex.h" #include "log.h" #include "packet.h" #include "dh.h" #include "ssh2.h" -void -kexdh_server(Kex *kex) +#include "dispatch.h" +#include "compat.h" +#include "ssherr.h" +#include "sshbuf.h" + +static int input_kex_dh_init(int, u_int32_t, void *); + +int +kexdh_server(struct ssh *ssh) { - BIGNUM *shared_secret = NULL, *dh_client_pub = NULL; - DH *dh; - Key *server_host_public, *server_host_private; - u_char *kbuf, *hash, *signature = NULL, *server_host_key_blob = NULL; - u_int sbloblen, klen, hashlen, slen; - int kout; + struct kex *kex = ssh->kex; + int r; /* generate server DH public key */ switch (kex->kex_type) { case KEX_DH_GRP1_SHA1: - dh = dh_new_group1(); + kex->dh = dh_new_group1(); break; case KEX_DH_GRP14_SHA1: - dh = dh_new_group14(); + kex->dh = dh_new_group14(); break; default: - fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); + r = SSH_ERR_INVALID_ARGUMENT; + goto out; } - dh_gen_key(dh, kex->we_need * 8); + if (kex->dh == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) + goto out; debug("expecting SSH2_MSG_KEXDH_INIT"); - packet_read_expect(SSH2_MSG_KEXDH_INIT); + ssh_dispatch_set(ssh, SSH2_MSG_KEXDH_INIT, &input_kex_dh_init); + r = 0; + out: + return r; +} + +int +input_kex_dh_init(int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + BIGNUM *shared_secret = NULL, *dh_client_pub = NULL; + struct sshkey *server_host_public, *server_host_private; + u_char *kbuf = NULL, *signature = NULL, *server_host_key_blob = NULL; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t sbloblen, slen; + size_t klen = 0, hashlen; + int kout, r; if (kex->load_host_public_key == NULL || - kex->load_host_private_key == NULL) - fatal("Cannot load hostkey"); - server_host_public = kex->load_host_public_key(kex->hostkey_type); - if (server_host_public == NULL) - fatal("Unsupported hostkey type %d", kex->hostkey_type); - server_host_private = kex->load_host_private_key(kex->hostkey_type); + kex->load_host_private_key == NULL) { + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } + if ((server_host_public = kex->load_host_public_key(kex->hostkey_type, + ssh)) == NULL || + (server_host_private = kex->load_host_private_key(kex->hostkey_type, + ssh)) == NULL) { + r = SSH_ERR_NO_HOSTKEY_LOADED; + goto out; + } /* key, cert */ - if ((dh_client_pub = BN_new()) == NULL) - fatal("dh_client_pub == NULL"); - packet_get_bignum2(dh_client_pub); - packet_check_eom(); + if ((dh_client_pub = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = sshpkt_get_bignum2(ssh, dh_client_pub)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; #ifdef DEBUG_KEXDH fprintf(stderr, "dh_client_pub= "); @@ -93,71 +126,90 @@ kexdh_server(Kex *kex) #endif #ifdef DEBUG_KEXDH - DHparams_print_fp(stderr, dh); + DHparams_print_fp(stderr, kex->dh); fprintf(stderr, "pub= "); - BN_print_fp(stderr, dh->pub_key); + BN_print_fp(stderr, kex->dh->pub_key); fprintf(stderr, "\n"); #endif - if (!dh_pub_is_valid(dh, dh_client_pub)) - packet_disconnect("bad client public DH value"); + if (!dh_pub_is_valid(kex->dh, dh_client_pub)) { + sshpkt_disconnect(ssh, "bad client public DH value"); + r = SSH_ERR_MESSAGE_INCOMPLETE; + goto out; + } - klen = DH_size(dh); - kbuf = xmalloc(klen); - if ((kout = DH_compute_key(kbuf, dh_client_pub, dh)) < 0) - fatal("DH_compute_key: failed"); + klen = DH_size(kex->dh); + if ((kbuf = malloc(klen)) == NULL || + (shared_secret = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((kout = DH_compute_key(kbuf, dh_client_pub, kex->dh)) < 0 || + BN_bin2bn(kbuf, kout, shared_secret) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } #ifdef DEBUG_KEXDH dump_digest("shared secret", kbuf, kout); #endif - if ((shared_secret = BN_new()) == NULL) - fatal("kexdh_server: BN_new failed"); - if (BN_bin2bn(kbuf, kout, shared_secret) == NULL) - fatal("kexdh_server: BN_bin2bn failed"); - explicit_bzero(kbuf, klen); - free(kbuf); - - key_to_blob(server_host_public, &server_host_key_blob, &sbloblen); - + if ((r = sshkey_to_blob(server_host_public, &server_host_key_blob, + &sbloblen)) != 0) + goto out; /* calc H */ - kex_dh_hash( + hashlen = sizeof(hash); + if ((r = kex_dh_hash( kex->client_version_string, kex->server_version_string, - buffer_ptr(kex->peer), buffer_len(kex->peer), - buffer_ptr(kex->my), buffer_len(kex->my), + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), + sshbuf_ptr(kex->my), sshbuf_len(kex->my), server_host_key_blob, sbloblen, dh_client_pub, - dh->pub_key, + kex->dh->pub_key, shared_secret, - &hash, &hashlen - ); - BN_clear_free(dh_client_pub); + hash, &hashlen)) != 0) + goto out; /* save session id := H */ if (kex->session_id == NULL) { kex->session_id_len = hashlen; - kex->session_id = xmalloc(kex->session_id_len); + kex->session_id = malloc(kex->session_id_len); + if (kex->session_id == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } memcpy(kex->session_id, hash, kex->session_id_len); } /* sign H */ - kex->sign(server_host_private, server_host_public, &signature, &slen, - hash, hashlen); + if ((r = kex->sign(server_host_private, server_host_public, + &signature, &slen, hash, hashlen, ssh->compat)) < 0) + goto out; /* destroy_sensitive_data(); */ /* send server hostkey, DH pubkey 'f' and singed H */ - packet_start(SSH2_MSG_KEXDH_REPLY); - packet_put_string(server_host_key_blob, sbloblen); - packet_put_bignum2(dh->pub_key); /* f */ - packet_put_string(signature, slen); - packet_send(); - - free(signature); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEXDH_REPLY)) != 0 || + (r = sshpkt_put_string(ssh, server_host_key_blob, sbloblen)) != 0 || + (r = sshpkt_put_bignum2(ssh, kex->dh->pub_key)) != 0 || /* f */ + (r = sshpkt_put_string(ssh, signature, slen)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; + + if ((r = kex_derive_keys_bn(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); + out: + explicit_bzero(hash, sizeof(hash)); + DH_free(kex->dh); + kex->dh = NULL; + if (dh_client_pub) + BN_clear_free(dh_client_pub); + if (kbuf) { + explicit_bzero(kbuf, klen); + free(kbuf); + } + if (shared_secret) + BN_clear_free(shared_secret); free(server_host_key_blob); - /* have keys, free DH */ - DH_free(dh); - - kex_derive_keys_bn(kex, hash, hashlen, shared_secret); - BN_clear_free(shared_secret); - kex_finish(kex); + free(signature); + return r; } #endif /* WITH_OPENSSL */ diff --git a/kexecdh.c b/kexecdh.c index 3115d13d1..2a4fec6b1 100644 --- a/kexecdh.c +++ b/kexecdh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdh.c,v 1.5 2014/01/09 23:20:00 djm Exp $ */ +/* $OpenBSD: kexecdh.c,v 1.6 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -38,60 +38,63 @@ #include #include -#include "buffer.h" #include "ssh2.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" #include "kex.h" -#include "log.h" +#include "sshbuf.h" #include "digest.h" +#include "ssherr.h" -void +int kex_ecdh_hash( int hash_alg, const EC_GROUP *ec_group, - char *client_version_string, - char *server_version_string, - char *ckexinit, int ckexinitlen, - char *skexinit, int skexinitlen, - u_char *serverhostkeyblob, int sbloblen, + const char *client_version_string, + const char *server_version_string, + const u_char *ckexinit, size_t ckexinitlen, + const u_char *skexinit, size_t skexinitlen, + const u_char *serverhostkeyblob, size_t sbloblen, const EC_POINT *client_dh_pub, const EC_POINT *server_dh_pub, const BIGNUM *shared_secret, - u_char **hash, u_int *hashlen) + u_char *hash, size_t *hashlen) { - Buffer b; - static u_char digest[SSH_DIGEST_MAX_LENGTH]; - - buffer_init(&b); - buffer_put_cstring(&b, client_version_string); - buffer_put_cstring(&b, server_version_string); - - /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ - buffer_put_int(&b, ckexinitlen+1); - buffer_put_char(&b, SSH2_MSG_KEXINIT); - buffer_append(&b, ckexinit, ckexinitlen); - buffer_put_int(&b, skexinitlen+1); - buffer_put_char(&b, SSH2_MSG_KEXINIT); - buffer_append(&b, skexinit, skexinitlen); - - buffer_put_string(&b, serverhostkeyblob, sbloblen); - buffer_put_ecpoint(&b, ec_group, client_dh_pub); - buffer_put_ecpoint(&b, ec_group, server_dh_pub); - buffer_put_bignum2(&b, shared_secret); + struct sshbuf *b; + int r; + if (*hashlen < ssh_digest_bytes(hash_alg)) + return SSH_ERR_INVALID_ARGUMENT; + if ((b = sshbuf_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((r = sshbuf_put_cstring(b, client_version_string)) != 0 || + (r = sshbuf_put_cstring(b, server_version_string)) != 0 || + /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ + (r = sshbuf_put_u32(b, ckexinitlen+1)) != 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || + (r = sshbuf_put(b, ckexinit, ckexinitlen)) != 0 || + (r = sshbuf_put_u32(b, skexinitlen+1)) != 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || + (r = sshbuf_put(b, skexinit, skexinitlen)) != 0 || + (r = sshbuf_put_string(b, serverhostkeyblob, sbloblen)) != 0 || + (r = sshbuf_put_ec(b, client_dh_pub, ec_group)) != 0 || + (r = sshbuf_put_ec(b, server_dh_pub, ec_group)) != 0 || + (r = sshbuf_put_bignum2(b, shared_secret)) != 0) { + sshbuf_free(b); + return r; + } #ifdef DEBUG_KEX - buffer_dump(&b); + sshbuf_dump(b, stderr); #endif - if (ssh_digest_buffer(hash_alg, &b, digest, sizeof(digest)) != 0) - fatal("%s: ssh_digest_buffer failed", __func__); - - buffer_free(&b); - + if (ssh_digest_buffer(hash_alg, b, hash, *hashlen) != 0) { + sshbuf_free(b); + return SSH_ERR_LIBCRYPTO_ERROR; + } + sshbuf_free(b); + *hashlen = ssh_digest_bytes(hash_alg); #ifdef DEBUG_KEX - dump_digest("hash", digest, ssh_digest_bytes(hash_alg)); + dump_digest("hash", hash, *hashlen); #endif - *hash = digest; - *hashlen = ssh_digest_bytes(hash_alg); + return 0; } #endif /* defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC) */ diff --git a/kexecdhc.c b/kexecdhc.c index df811c1c8..3f362c5b1 100644 --- a/kexecdhc.c +++ b/kexecdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhc.c,v 1.8 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: kexecdhc.c,v 1.9 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -34,126 +34,193 @@ #include #include -#include "xmalloc.h" -#include "buffer.h" -#include "key.h" +#include + +#include "sshkey.h" #include "cipher.h" +#include "digest.h" #include "kex.h" #include "log.h" #include "packet.h" #include "dh.h" #include "ssh2.h" +#include "dispatch.h" +#include "compat.h" +#include "ssherr.h" +#include "sshbuf.h" -#include +static int input_kex_ecdh_reply(int, u_int32_t, void *); -void -kexecdh_client(Kex *kex) +int +kexecdh_client(struct ssh *ssh) { - EC_KEY *client_key; - EC_POINT *server_public; + struct kex *kex = ssh->kex; + EC_KEY *client_key = NULL; const EC_GROUP *group; - BIGNUM *shared_secret; - Key *server_host_key; - u_char *server_host_key_blob = NULL, *signature = NULL; - u_char *kbuf, *hash; - u_int klen, slen, sbloblen, hashlen; + const EC_POINT *public_key; + int r; - if ((client_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) - fatal("%s: EC_KEY_new_by_curve_name failed", __func__); - if (EC_KEY_generate_key(client_key) != 1) - fatal("%s: EC_KEY_generate_key failed", __func__); + if ((client_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if (EC_KEY_generate_key(client_key) != 1) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } group = EC_KEY_get0_group(client_key); + public_key = EC_KEY_get0_public_key(client_key); - packet_start(SSH2_MSG_KEX_ECDH_INIT); - packet_put_ecpoint(group, EC_KEY_get0_public_key(client_key)); - packet_send(); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_ECDH_INIT)) != 0 || + (r = sshpkt_put_ec(ssh, public_key, group)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; debug("sending SSH2_MSG_KEX_ECDH_INIT"); #ifdef DEBUG_KEXECDH fputs("client private key:\n", stderr); - key_dump_ec_key(client_key); + sshkey_dump_ec_key(client_key); #endif + kex->ec_client_key = client_key; + kex->ec_group = group; + client_key = NULL; /* owned by the kex */ debug("expecting SSH2_MSG_KEX_ECDH_REPLY"); - packet_read_expect(SSH2_MSG_KEX_ECDH_REPLY); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_ECDH_REPLY, &input_kex_ecdh_reply); + r = 0; + out: + if (client_key) + EC_KEY_free(client_key); + return r; +} + +static int +input_kex_ecdh_reply(int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + const EC_GROUP *group; + EC_POINT *server_public = NULL; + EC_KEY *client_key; + BIGNUM *shared_secret = NULL; + struct sshkey *server_host_key = NULL; + u_char *server_host_key_blob = NULL, *signature = NULL; + u_char *kbuf = NULL; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t slen, sbloblen; + size_t klen = 0, hashlen; + int r; + + if (kex->verify_host_key == NULL) { + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } + group = kex->ec_group; + client_key = kex->ec_client_key; /* hostkey */ - server_host_key_blob = packet_get_string(&sbloblen); - server_host_key = key_from_blob(server_host_key_blob, sbloblen); - if (server_host_key == NULL) - fatal("cannot decode server_host_key_blob"); - if (server_host_key->type != kex->hostkey_type) - fatal("type mismatch for decoded server_host_key_blob"); - if (kex->verify_host_key == NULL) - fatal("cannot verify server_host_key"); - if (kex->verify_host_key(server_host_key) == -1) - fatal("server_host_key verification failed"); + if ((r = sshpkt_get_string(ssh, &server_host_key_blob, + &sbloblen)) != 0 || + (r = sshkey_from_blob(server_host_key_blob, sbloblen, + &server_host_key)) != 0) + goto out; + if (server_host_key->type != kex->hostkey_type) { + r = SSH_ERR_KEY_TYPE_MISMATCH; + goto out; + } + if (kex->verify_host_key(server_host_key, ssh) == -1) { + r = SSH_ERR_SIGNATURE_INVALID; + goto out; + } /* Q_S, server public key */ - if ((server_public = EC_POINT_new(group)) == NULL) - fatal("%s: EC_POINT_new failed", __func__); - packet_get_ecpoint(group, server_public); - - if (key_ec_validate_public(group, server_public) != 0) - fatal("%s: invalid server public key", __func__); + /* signed H */ + if ((server_public = EC_POINT_new(group)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = sshpkt_get_ec(ssh, server_public, group)) != 0 || + (r = sshpkt_get_string(ssh, &signature, &slen)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; #ifdef DEBUG_KEXECDH fputs("server public key:\n", stderr); - key_dump_ec_point(group, server_public); + sshkey_dump_ec_point(group, server_public); #endif - - /* signed H */ - signature = packet_get_string(&slen); - packet_check_eom(); + if (sshkey_ec_validate_public(group, server_public) != 0) { + sshpkt_disconnect(ssh, "invalid server public key"); + r = SSH_ERR_MESSAGE_INCOMPLETE; + goto out; + } klen = (EC_GROUP_get_degree(group) + 7) / 8; - kbuf = xmalloc(klen); + if ((kbuf = malloc(klen)) == NULL || + (shared_secret = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } if (ECDH_compute_key(kbuf, klen, server_public, - client_key, NULL) != (int)klen) - fatal("%s: ECDH_compute_key failed", __func__); + client_key, NULL) != (int)klen || + BN_bin2bn(kbuf, klen, shared_secret) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } #ifdef DEBUG_KEXECDH dump_digest("shared secret", kbuf, klen); #endif - if ((shared_secret = BN_new()) == NULL) - fatal("%s: BN_new failed", __func__); - if (BN_bin2bn(kbuf, klen, shared_secret) == NULL) - fatal("%s: BN_bin2bn failed", __func__); - explicit_bzero(kbuf, klen); - free(kbuf); - /* calc and verify H */ - kex_ecdh_hash( + hashlen = sizeof(hash); + if ((r = kex_ecdh_hash( kex->hash_alg, group, kex->client_version_string, kex->server_version_string, - buffer_ptr(kex->my), buffer_len(kex->my), - buffer_ptr(kex->peer), buffer_len(kex->peer), + sshbuf_ptr(kex->my), sshbuf_len(kex->my), + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), server_host_key_blob, sbloblen, EC_KEY_get0_public_key(client_key), server_public, shared_secret, - &hash, &hashlen - ); - free(server_host_key_blob); - EC_POINT_clear_free(server_public); - EC_KEY_free(client_key); + hash, &hashlen)) != 0) + goto out; - if (key_verify(server_host_key, signature, slen, hash, hashlen) != 1) - fatal("key_verify failed for server_host_key"); - key_free(server_host_key); - free(signature); + if ((r = sshkey_verify(server_host_key, signature, slen, hash, + hashlen, ssh->compat)) != 0) + goto out; /* save session id */ if (kex->session_id == NULL) { kex->session_id_len = hashlen; - kex->session_id = xmalloc(kex->session_id_len); + kex->session_id = malloc(kex->session_id_len); + if (kex->session_id == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } memcpy(kex->session_id, hash, kex->session_id_len); } - kex_derive_keys_bn(kex, hash, hashlen, shared_secret); - BN_clear_free(shared_secret); - kex_finish(kex); + if ((r = kex_derive_keys_bn(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); + out: + explicit_bzero(hash, sizeof(hash)); + if (kex->ec_client_key) { + EC_KEY_free(kex->ec_client_key); + kex->ec_client_key = NULL; + } + if (server_public) + EC_POINT_clear_free(server_public); + if (kbuf) { + explicit_bzero(kbuf, klen); + free(kbuf); + } + if (shared_secret) + BN_clear_free(shared_secret); + sshkey_free(server_host_key); + free(server_host_key_blob); + free(signature); + return r; } #endif /* defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC) */ + diff --git a/kexecdhs.c b/kexecdhs.c index 6bfad04cf..6b8d95d9c 100644 --- a/kexecdhs.c +++ b/kexecdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhs.c,v 1.11 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: kexecdhs.c,v 1.12 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -32,124 +32,176 @@ #include #include -#include "xmalloc.h" -#include "buffer.h" -#include "key.h" +#include + +#include "sshkey.h" #include "cipher.h" +#include "digest.h" #include "kex.h" #include "log.h" #include "packet.h" #include "ssh2.h" -#include +#include "dispatch.h" +#include "compat.h" +#include "ssherr.h" +#include "sshbuf.h" + +static int input_kex_ecdh_init(int, u_int32_t, void *); + +int +kexecdh_server(struct ssh *ssh) +{ + debug("expecting SSH2_MSG_KEX_ECDH_INIT"); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_ECDH_INIT, &input_kex_ecdh_init); + return 0; +} -void -kexecdh_server(Kex *kex) +static int +input_kex_ecdh_init(int type, u_int32_t seq, void *ctxt) { + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; EC_POINT *client_public; - EC_KEY *server_key; + EC_KEY *server_key = NULL; const EC_GROUP *group; - BIGNUM *shared_secret; - Key *server_host_private, *server_host_public; + const EC_POINT *public_key; + BIGNUM *shared_secret = NULL; + struct sshkey *server_host_private, *server_host_public; u_char *server_host_key_blob = NULL, *signature = NULL; - u_char *kbuf, *hash; - u_int klen, slen, sbloblen, hashlen; - - if ((server_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) - fatal("%s: EC_KEY_new_by_curve_name failed", __func__); - if (EC_KEY_generate_key(server_key) != 1) - fatal("%s: EC_KEY_generate_key failed", __func__); + u_char *kbuf = NULL; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t slen, sbloblen; + size_t klen = 0, hashlen; + int r; + + if ((server_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if (EC_KEY_generate_key(server_key) != 1) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } group = EC_KEY_get0_group(server_key); #ifdef DEBUG_KEXECDH fputs("server private key:\n", stderr); - key_dump_ec_key(server_key); + sshkey_dump_ec_key(server_key); #endif if (kex->load_host_public_key == NULL || - kex->load_host_private_key == NULL) - fatal("Cannot load hostkey"); - server_host_public = kex->load_host_public_key(kex->hostkey_type); - if (server_host_public == NULL) - fatal("Unsupported hostkey type %d", kex->hostkey_type); - server_host_private = kex->load_host_private_key(kex->hostkey_type); - - debug("expecting SSH2_MSG_KEX_ECDH_INIT"); - packet_read_expect(SSH2_MSG_KEX_ECDH_INIT); - if ((client_public = EC_POINT_new(group)) == NULL) - fatal("%s: EC_POINT_new failed", __func__); - packet_get_ecpoint(group, client_public); - packet_check_eom(); - - if (key_ec_validate_public(group, client_public) != 0) - fatal("%s: invalid client public key", __func__); + kex->load_host_private_key == NULL) { + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } + if ((server_host_public = kex->load_host_public_key(kex->hostkey_type, + ssh)) == NULL || + (server_host_private = kex->load_host_private_key(kex->hostkey_type, + ssh)) == NULL) { + r = SSH_ERR_NO_HOSTKEY_LOADED; + goto out; + } + if ((client_public = EC_POINT_new(group)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = sshpkt_get_ec(ssh, client_public, group)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; #ifdef DEBUG_KEXECDH fputs("client public key:\n", stderr); - key_dump_ec_point(group, client_public); + sshkey_dump_ec_point(group, client_public); #endif + if (sshkey_ec_validate_public(group, client_public) != 0) { + sshpkt_disconnect(ssh, "invalid client public key"); + r = SSH_ERR_MESSAGE_INCOMPLETE; + goto out; + } /* Calculate shared_secret */ klen = (EC_GROUP_get_degree(group) + 7) / 8; - kbuf = xmalloc(klen); + if ((kbuf = malloc(klen)) == NULL || + (shared_secret = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } if (ECDH_compute_key(kbuf, klen, client_public, - server_key, NULL) != (int)klen) - fatal("%s: ECDH_compute_key failed", __func__); + server_key, NULL) != (int)klen || + BN_bin2bn(kbuf, klen, shared_secret) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } -#ifdef DEBUG_KEXDH +#ifdef DEBUG_KEXECDH dump_digest("shared secret", kbuf, klen); #endif - if ((shared_secret = BN_new()) == NULL) - fatal("%s: BN_new failed", __func__); - if (BN_bin2bn(kbuf, klen, shared_secret) == NULL) - fatal("%s: BN_bin2bn failed", __func__); - explicit_bzero(kbuf, klen); - free(kbuf); - /* calc H */ - key_to_blob(server_host_public, &server_host_key_blob, &sbloblen); - kex_ecdh_hash( + if ((r = sshkey_to_blob(server_host_public, &server_host_key_blob, + &sbloblen)) != 0) + goto out; + hashlen = sizeof(hash); + if ((r = kex_ecdh_hash( kex->hash_alg, group, kex->client_version_string, kex->server_version_string, - buffer_ptr(kex->peer), buffer_len(kex->peer), - buffer_ptr(kex->my), buffer_len(kex->my), + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), + sshbuf_ptr(kex->my), sshbuf_len(kex->my), server_host_key_blob, sbloblen, client_public, EC_KEY_get0_public_key(server_key), shared_secret, - &hash, &hashlen - ); - EC_POINT_clear_free(client_public); + hash, &hashlen)) != 0) + goto out; /* save session id := H */ if (kex->session_id == NULL) { kex->session_id_len = hashlen; - kex->session_id = xmalloc(kex->session_id_len); + kex->session_id = malloc(kex->session_id_len); + if (kex->session_id == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } memcpy(kex->session_id, hash, kex->session_id_len); } /* sign H */ - kex->sign(server_host_private, server_host_public, &signature, &slen, - hash, hashlen); + if ((r = kex->sign(server_host_private, server_host_public, + &signature, &slen, hash, hashlen, ssh->compat)) < 0) + goto out; /* destroy_sensitive_data(); */ + public_key = EC_KEY_get0_public_key(server_key); /* send server hostkey, ECDH pubkey 'Q_S' and signed H */ - packet_start(SSH2_MSG_KEX_ECDH_REPLY); - packet_put_string(server_host_key_blob, sbloblen); - packet_put_ecpoint(group, EC_KEY_get0_public_key(server_key)); - packet_put_string(signature, slen); - packet_send(); - - free(signature); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_ECDH_REPLY)) != 0 || + (r = sshpkt_put_string(ssh, server_host_key_blob, sbloblen)) != 0 || + (r = sshpkt_put_ec(ssh, public_key, group)) != 0 || + (r = sshpkt_put_string(ssh, signature, slen)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; + + if ((r = kex_derive_keys_bn(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); + out: + explicit_bzero(hash, sizeof(hash)); + if (kex->ec_client_key) { + EC_KEY_free(kex->ec_client_key); + kex->ec_client_key = NULL; + } + if (server_key) + EC_KEY_free(server_key); + if (kbuf) { + explicit_bzero(kbuf, klen); + free(kbuf); + } + if (shared_secret) + BN_clear_free(shared_secret); free(server_host_key_blob); - /* have keys, free server key */ - EC_KEY_free(server_key); - - kex_derive_keys_bn(kex, hash, hashlen, shared_secret); - BN_clear_free(shared_secret); - kex_finish(kex); + free(signature); + return r; } #endif /* defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC) */ + diff --git a/kexgex.c b/kexgex.c index d1fa1a063..8b0d83332 100644 --- a/kexgex.c +++ b/kexgex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgex.c,v 1.28 2014/01/09 23:20:00 djm Exp $ */ +/* $OpenBSD: kexgex.c,v 1.29 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -33,69 +33,70 @@ #include #include -#include "buffer.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" #include "kex.h" #include "ssh2.h" +#include "ssherr.h" +#include "sshbuf.h" #include "digest.h" -#include "log.h" -void +int kexgex_hash( int hash_alg, - char *client_version_string, - char *server_version_string, - char *ckexinit, int ckexinitlen, - char *skexinit, int skexinitlen, - u_char *serverhostkeyblob, int sbloblen, - int min, int wantbits, int max, BIGNUM *prime, BIGNUM *gen, - BIGNUM *client_dh_pub, - BIGNUM *server_dh_pub, - BIGNUM *shared_secret, - u_char **hash, u_int *hashlen) + const char *client_version_string, + const char *server_version_string, + const u_char *ckexinit, size_t ckexinitlen, + const u_char *skexinit, size_t skexinitlen, + const u_char *serverhostkeyblob, size_t sbloblen, + int min, int wantbits, int max, + const BIGNUM *prime, + const BIGNUM *gen, + const BIGNUM *client_dh_pub, + const BIGNUM *server_dh_pub, + const BIGNUM *shared_secret, + u_char *hash, size_t *hashlen) { - Buffer b; - static u_char digest[SSH_DIGEST_MAX_LENGTH]; + struct sshbuf *b; + int r; - buffer_init(&b); - buffer_put_cstring(&b, client_version_string); - buffer_put_cstring(&b, server_version_string); - - /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ - buffer_put_int(&b, ckexinitlen+1); - buffer_put_char(&b, SSH2_MSG_KEXINIT); - buffer_append(&b, ckexinit, ckexinitlen); - buffer_put_int(&b, skexinitlen+1); - buffer_put_char(&b, SSH2_MSG_KEXINIT); - buffer_append(&b, skexinit, skexinitlen); - - buffer_put_string(&b, serverhostkeyblob, sbloblen); - if (min == -1 || max == -1) - buffer_put_int(&b, wantbits); - else { - buffer_put_int(&b, min); - buffer_put_int(&b, wantbits); - buffer_put_int(&b, max); + if (*hashlen < ssh_digest_bytes(SSH_DIGEST_SHA1)) + return SSH_ERR_INVALID_ARGUMENT; + if ((b = sshbuf_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((r = sshbuf_put_cstring(b, client_version_string)) != 0 || + (r = sshbuf_put_cstring(b, server_version_string)) != 0 || + /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ + (r = sshbuf_put_u32(b, ckexinitlen+1)) != 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || + (r = sshbuf_put(b, ckexinit, ckexinitlen)) != 0 || + (r = sshbuf_put_u32(b, skexinitlen+1)) != 0 || + (r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 || + (r = sshbuf_put(b, skexinit, skexinitlen)) != 0 || + (r = sshbuf_put_string(b, serverhostkeyblob, sbloblen)) != 0 || + (min != -1 && (r = sshbuf_put_u32(b, min)) != 0) || + (r = sshbuf_put_u32(b, wantbits)) != 0 || + (max != -1 && (r = sshbuf_put_u32(b, max)) != 0) || + (r = sshbuf_put_bignum2(b, prime)) != 0 || + (r = sshbuf_put_bignum2(b, gen)) != 0 || + (r = sshbuf_put_bignum2(b, client_dh_pub)) != 0 || + (r = sshbuf_put_bignum2(b, server_dh_pub)) != 0 || + (r = sshbuf_put_bignum2(b, shared_secret)) != 0) { + sshbuf_free(b); + return r; } - buffer_put_bignum2(&b, prime); - buffer_put_bignum2(&b, gen); - buffer_put_bignum2(&b, client_dh_pub); - buffer_put_bignum2(&b, server_dh_pub); - buffer_put_bignum2(&b, shared_secret); - #ifdef DEBUG_KEXDH - buffer_dump(&b); + sshbuf_dump(b, stderr); #endif - if (ssh_digest_buffer(hash_alg, &b, digest, sizeof(digest)) != 0) - fatal("%s: ssh_digest_buffer failed", __func__); - - buffer_free(&b); - -#ifdef DEBUG_KEX - dump_digest("hash", digest, ssh_digest_bytes(hash_alg)); -#endif - *hash = digest; + if (ssh_digest_buffer(hash_alg, b, hash, *hashlen) != 0) { + sshbuf_free(b); + return SSH_ERR_LIBCRYPTO_ERROR; + } + sshbuf_free(b); *hashlen = ssh_digest_bytes(hash_alg); +#ifdef DEBUG_KEXDH + dump_digest("hash", hash, *hashlen); +#endif + return 0; } #endif /* WITH_OPENSSL */ diff --git a/kexgexc.c b/kexgexc.c index 18d09cfb0..0898824f3 100644 --- a/kexgexc.c +++ b/kexgexc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexc.c,v 1.18 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: kexgexc.c,v 1.19 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -37,174 +37,237 @@ #include #include -#include "xmalloc.h" -#include "buffer.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" +#include "digest.h" #include "kex.h" #include "log.h" #include "packet.h" #include "dh.h" #include "ssh2.h" #include "compat.h" +#include "dispatch.h" +#include "ssherr.h" +#include "sshbuf.h" -void -kexgex_client(Kex *kex) +static int input_kex_dh_gex_group(int, u_int32_t, void *); +static int input_kex_dh_gex_reply(int, u_int32_t, void *); + +int +kexgex_client(struct ssh *ssh) { - BIGNUM *dh_server_pub = NULL, *shared_secret = NULL; - BIGNUM *p = NULL, *g = NULL; - Key *server_host_key; - u_char *kbuf, *hash, *signature = NULL, *server_host_key_blob = NULL; - u_int klen, slen, sbloblen, hashlen; - int kout; - int min, max, nbits; - DH *dh; + struct kex *kex = ssh->kex; + int r; + u_int nbits; nbits = dh_estimate(kex->dh_need * 8); - if (datafellows & SSH_OLD_DHGEX) { + kex->min = DH_GRP_MIN; + kex->max = DH_GRP_MAX; + kex->nbits = nbits; + if (ssh->compat & SSH_OLD_DHGEX) { /* Old GEX request */ - packet_start(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); - packet_put_int(nbits); - min = DH_GRP_MIN; - max = DH_GRP_MAX; - - debug("SSH2_MSG_KEX_DH_GEX_REQUEST_OLD(%u) sent", nbits); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD)) + != 0 || + (r = sshpkt_put_u32(ssh, kex->nbits)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; + debug("SSH2_MSG_KEX_DH_GEX_REQUEST_OLD(%u) sent", kex->nbits); } else { /* New GEX request */ - min = DH_GRP_MIN; - max = DH_GRP_MAX; - packet_start(SSH2_MSG_KEX_DH_GEX_REQUEST); - packet_put_int(min); - packet_put_int(nbits); - packet_put_int(max); - + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_DH_GEX_REQUEST)) != 0 || + (r = sshpkt_put_u32(ssh, kex->min)) != 0 || + (r = sshpkt_put_u32(ssh, kex->nbits)) != 0 || + (r = sshpkt_put_u32(ssh, kex->max)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; debug("SSH2_MSG_KEX_DH_GEX_REQUEST(%u<%u<%u) sent", - min, nbits, max); + kex->min, kex->nbits, kex->max); } #ifdef DEBUG_KEXDH fprintf(stderr, "\nmin = %d, nbits = %d, max = %d\n", - min, nbits, max); + kex->min, kex->nbits, kex->max); #endif - packet_send(); - - debug("expecting SSH2_MSG_KEX_DH_GEX_GROUP"); - packet_read_expect(SSH2_MSG_KEX_DH_GEX_GROUP); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_DH_GEX_GROUP, + &input_kex_dh_gex_group); + r = 0; + out: + return r; +} - if ((p = BN_new()) == NULL) - fatal("BN_new"); - packet_get_bignum2(p); - if ((g = BN_new()) == NULL) - fatal("BN_new"); - packet_get_bignum2(g); - packet_check_eom(); +static int +input_kex_dh_gex_group(int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + BIGNUM *p = NULL, *g = NULL; + int r, bits; - if (BN_num_bits(p) < min || BN_num_bits(p) > max) - fatal("DH_GEX group out of range: %d !< %d !< %d", - min, BN_num_bits(p), max); + debug("got SSH2_MSG_KEX_DH_GEX_GROUP"); - dh = dh_new_group(g, p); - dh_gen_key(dh, kex->we_need * 8); + if ((p = BN_new()) == NULL || + (g = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = sshpkt_get_bignum2(ssh, p)) != 0 || + (r = sshpkt_get_bignum2(ssh, g)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; + if ((bits = BN_num_bits(p)) < 0 || + (u_int)bits < kex->min || (u_int)bits > kex->max) { + r = SSH_ERR_DH_GEX_OUT_OF_RANGE; + goto out; + } + if ((kex->dh = dh_new_group(g, p)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + p = g = NULL; /* belong to kex->dh now */ + /* generate and send 'e', client DH public key */ + if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0 || + (r = sshpkt_start(ssh, SSH2_MSG_KEX_DH_GEX_INIT)) != 0 || + (r = sshpkt_put_bignum2(ssh, kex->dh->pub_key)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; + debug("SSH2_MSG_KEX_DH_GEX_INIT sent"); #ifdef DEBUG_KEXDH - DHparams_print_fp(stderr, dh); + DHparams_print_fp(stderr, kex->dh); fprintf(stderr, "pub= "); - BN_print_fp(stderr, dh->pub_key); + BN_print_fp(stderr, kex->dh->pub_key); fprintf(stderr, "\n"); #endif + ssh_dispatch_set(ssh, SSH2_MSG_KEX_DH_GEX_GROUP, NULL); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_DH_GEX_REPLY, &input_kex_dh_gex_reply); + r = 0; +out: + if (p) + BN_clear_free(p); + if (g) + BN_clear_free(g); + return r; +} - debug("SSH2_MSG_KEX_DH_GEX_INIT sent"); - /* generate and send 'e', client DH public key */ - packet_start(SSH2_MSG_KEX_DH_GEX_INIT); - packet_put_bignum2(dh->pub_key); - packet_send(); - - debug("expecting SSH2_MSG_KEX_DH_GEX_REPLY"); - packet_read_expect(SSH2_MSG_KEX_DH_GEX_REPLY); +static int +input_kex_dh_gex_reply(int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + BIGNUM *dh_server_pub = NULL, *shared_secret = NULL; + struct sshkey *server_host_key = NULL; + u_char *kbuf = NULL, *signature = NULL, *server_host_key_blob = NULL; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t klen = 0, slen, sbloblen, hashlen; + int kout, r; + debug("got SSH2_MSG_KEX_DH_GEX_REPLY"); + if (kex->verify_host_key == NULL) { + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } /* key, cert */ - server_host_key_blob = packet_get_string(&sbloblen); - server_host_key = key_from_blob(server_host_key_blob, sbloblen); - if (server_host_key == NULL) - fatal("cannot decode server_host_key_blob"); - if (server_host_key->type != kex->hostkey_type) - fatal("type mismatch for decoded server_host_key_blob"); - if (kex->verify_host_key == NULL) - fatal("cannot verify server_host_key"); - if (kex->verify_host_key(server_host_key) == -1) - fatal("server_host_key verification failed"); - + if ((r = sshpkt_get_string(ssh, &server_host_key_blob, + &sbloblen)) != 0 || + (r = sshkey_from_blob(server_host_key_blob, sbloblen, + &server_host_key)) != 0) + goto out; + if (server_host_key->type != kex->hostkey_type) { + r = SSH_ERR_KEY_TYPE_MISMATCH; + goto out; + } + if (kex->verify_host_key(server_host_key, ssh) == -1) { + r = SSH_ERR_SIGNATURE_INVALID; + goto out; + } /* DH parameter f, server public DH key */ - if ((dh_server_pub = BN_new()) == NULL) - fatal("dh_server_pub == NULL"); - packet_get_bignum2(dh_server_pub); - + if ((dh_server_pub = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + /* signed H */ + if ((r = sshpkt_get_bignum2(ssh, dh_server_pub)) != 0 || + (r = sshpkt_get_string(ssh, &signature, &slen)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; #ifdef DEBUG_KEXDH fprintf(stderr, "dh_server_pub= "); BN_print_fp(stderr, dh_server_pub); fprintf(stderr, "\n"); debug("bits %d", BN_num_bits(dh_server_pub)); #endif + if (!dh_pub_is_valid(kex->dh, dh_server_pub)) { + sshpkt_disconnect(ssh, "bad server public DH value"); + r = SSH_ERR_MESSAGE_INCOMPLETE; + goto out; + } - /* signed H */ - signature = packet_get_string(&slen); - packet_check_eom(); - - if (!dh_pub_is_valid(dh, dh_server_pub)) - packet_disconnect("bad server public DH value"); - - klen = DH_size(dh); - kbuf = xmalloc(klen); - if ((kout = DH_compute_key(kbuf, dh_server_pub, dh)) < 0) - fatal("DH_compute_key: failed"); + klen = DH_size(kex->dh); + if ((kbuf = malloc(klen)) == NULL || + (shared_secret = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((kout = DH_compute_key(kbuf, dh_server_pub, kex->dh)) < 0 || + BN_bin2bn(kbuf, kout, shared_secret) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } #ifdef DEBUG_KEXDH dump_digest("shared secret", kbuf, kout); #endif - if ((shared_secret = BN_new()) == NULL) - fatal("kexgex_client: BN_new failed"); - if (BN_bin2bn(kbuf, kout, shared_secret) == NULL) - fatal("kexgex_client: BN_bin2bn failed"); - explicit_bzero(kbuf, klen); - free(kbuf); - - if (datafellows & SSH_OLD_DHGEX) - min = max = -1; + if (ssh->compat & SSH_OLD_DHGEX) + kex->min = kex->max = -1; /* calc and verify H */ - kexgex_hash( + hashlen = sizeof(hash); + if ((r = kexgex_hash( kex->hash_alg, kex->client_version_string, kex->server_version_string, - buffer_ptr(kex->my), buffer_len(kex->my), - buffer_ptr(kex->peer), buffer_len(kex->peer), + sshbuf_ptr(kex->my), sshbuf_len(kex->my), + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), server_host_key_blob, sbloblen, - min, nbits, max, - dh->p, dh->g, - dh->pub_key, + kex->min, kex->nbits, kex->max, + kex->dh->p, kex->dh->g, + kex->dh->pub_key, dh_server_pub, shared_secret, - &hash, &hashlen - ); - - /* have keys, free DH */ - DH_free(dh); - free(server_host_key_blob); - BN_clear_free(dh_server_pub); + hash, &hashlen)) != 0) + goto out; - if (key_verify(server_host_key, signature, slen, hash, hashlen) != 1) - fatal("key_verify failed for server_host_key"); - key_free(server_host_key); - free(signature); + if ((r = sshkey_verify(server_host_key, signature, slen, hash, + hashlen, ssh->compat)) != 0) + goto out; /* save session id */ if (kex->session_id == NULL) { kex->session_id_len = hashlen; - kex->session_id = xmalloc(kex->session_id_len); + kex->session_id = malloc(kex->session_id_len); + if (kex->session_id == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } memcpy(kex->session_id, hash, kex->session_id_len); } - kex_derive_keys_bn(kex, hash, hashlen, shared_secret); - BN_clear_free(shared_secret); - kex_finish(kex); + if ((r = kex_derive_keys_bn(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); + out: + explicit_bzero(hash, sizeof(hash)); + DH_free(kex->dh); + kex->dh = NULL; + if (dh_server_pub) + BN_clear_free(dh_server_pub); + if (kbuf) { + explicit_bzero(kbuf, klen); + free(kbuf); + } + if (shared_secret) + BN_clear_free(shared_secret); + sshkey_free(server_host_key); + free(server_host_key_blob); + free(signature); + return r; } #endif /* WITH_OPENSSL */ diff --git a/kexgexs.c b/kexgexs.c index 1021e0bf6..6e2b009b5 100644 --- a/kexgexs.c +++ b/kexgexs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexs.c,v 1.20 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: kexgexs.c,v 1.21 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -37,10 +37,9 @@ #include -#include "xmalloc.h" -#include "buffer.h" -#include "key.h" +#include "sshkey.h" #include "cipher.h" +#include "digest.h" #include "kex.h" #include "log.h" #include "packet.h" @@ -51,33 +50,43 @@ #include "ssh-gss.h" #endif #include "monitor_wrap.h" +#include "dispatch.h" +#include "ssherr.h" +#include "sshbuf.h" -void -kexgex_server(Kex *kex) +static int input_kex_dh_gex_request(int, u_int32_t, void *); +static int input_kex_dh_gex_init(int, u_int32_t, void *); + +int +kexgex_server(struct ssh *ssh) { - BIGNUM *shared_secret = NULL, *dh_client_pub = NULL; - Key *server_host_public, *server_host_private; - DH *dh; - u_char *kbuf, *hash, *signature = NULL, *server_host_key_blob = NULL; - u_int sbloblen, klen, slen, hashlen; - int omin = -1, min = -1, omax = -1, max = -1, onbits = -1, nbits = -1; - int type, kout; + ssh_dispatch_set(ssh, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, + &input_kex_dh_gex_request); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_DH_GEX_REQUEST, + &input_kex_dh_gex_request); + debug("expecting SSH2_MSG_KEX_DH_GEX_REQUEST"); + return 0; +} + +static int +input_kex_dh_gex_request(int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + int r; + u_int min = 0, max = 0, nbits = 0; - if (kex->load_host_public_key == NULL || - kex->load_host_private_key == NULL) - fatal("Cannot load hostkey"); - server_host_public = kex->load_host_public_key(kex->hostkey_type); - if (server_host_public == NULL) - fatal("Unsupported hostkey type %d", kex->hostkey_type); - server_host_private = kex->load_host_private_key(kex->hostkey_type); - - type = packet_read(); switch (type) { case SSH2_MSG_KEX_DH_GEX_REQUEST: debug("SSH2_MSG_KEX_DH_GEX_REQUEST received"); - omin = min = packet_get_int(); - onbits = nbits = packet_get_int(); - omax = max = packet_get_int(); + if ((r = sshpkt_get_u32(ssh, &min)) != 0 || + (r = sshpkt_get_u32(ssh, &nbits)) != 0 || + (r = sshpkt_get_u32(ssh, &max)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; + kex->nbits = nbits; + kex->min = min; + kex->max = max; min = MAX(DH_GRP_MIN, min); max = MIN(DH_GRP_MAX, max); nbits = MAX(DH_GRP_MIN, nbits); @@ -85,45 +94,88 @@ kexgex_server(Kex *kex) break; case SSH2_MSG_KEX_DH_GEX_REQUEST_OLD: debug("SSH2_MSG_KEX_DH_GEX_REQUEST_OLD received"); - onbits = nbits = packet_get_int(); + if ((r = sshpkt_get_u32(ssh, &nbits)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; + kex->nbits = nbits; /* unused for old GEX */ - omin = min = DH_GRP_MIN; - omax = max = DH_GRP_MAX; + kex->min = min = DH_GRP_MIN; + kex->max = max = DH_GRP_MAX; break; default: - fatal("protocol error during kex, no DH_GEX_REQUEST: %d", type); + r = SSH_ERR_INVALID_ARGUMENT; + goto out; } - packet_check_eom(); - if (omax < omin || onbits < omin || omax < onbits) - fatal("DH_GEX_REQUEST, bad parameters: %d !< %d !< %d", - omin, onbits, omax); + if (kex->max < kex->min || kex->nbits < kex->min || + kex->max < kex->nbits) { + r = SSH_ERR_DH_GEX_OUT_OF_RANGE; + goto out; + } /* Contact privileged parent */ - dh = PRIVSEP(choose_dh(min, nbits, max)); - if (dh == NULL) - packet_disconnect("Protocol error: no matching DH grp found"); - + kex->dh = PRIVSEP(choose_dh(min, nbits, max)); + if (kex->dh == NULL) { + sshpkt_disconnect(ssh, "no matching DH grp found"); + r = SSH_ERR_ALLOC_FAIL; + goto out; + } debug("SSH2_MSG_KEX_DH_GEX_GROUP sent"); - packet_start(SSH2_MSG_KEX_DH_GEX_GROUP); - packet_put_bignum2(dh->p); - packet_put_bignum2(dh->g); - packet_send(); - - /* flush */ - packet_write_wait(); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_DH_GEX_GROUP)) != 0 || + (r = sshpkt_put_bignum2(ssh, kex->dh->p)) != 0 || + (r = sshpkt_put_bignum2(ssh, kex->dh->g)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; /* Compute our exchange value in parallel with the client */ - dh_gen_key(dh, kex->we_need * 8); + if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) + goto out; + + /* old KEX does not use min/max in kexgex_hash() */ + if (type == SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) + kex->min = kex->max = -1; debug("expecting SSH2_MSG_KEX_DH_GEX_INIT"); - packet_read_expect(SSH2_MSG_KEX_DH_GEX_INIT); + ssh_dispatch_set(ssh, SSH2_MSG_KEX_DH_GEX_INIT, &input_kex_dh_gex_init); + r = 0; + out: + return r; +} + +static int +input_kex_dh_gex_init(int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = ctxt; + struct kex *kex = ssh->kex; + BIGNUM *shared_secret = NULL, *dh_client_pub = NULL; + struct sshkey *server_host_public, *server_host_private; + u_char *kbuf = NULL, *signature = NULL, *server_host_key_blob = NULL; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t sbloblen, slen; + size_t klen = 0, hashlen; + int kout, r; + + if (kex->load_host_public_key == NULL || + kex->load_host_private_key == NULL) { + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } + if ((server_host_public = kex->load_host_public_key(kex->hostkey_type, + ssh)) == NULL || + (server_host_private = kex->load_host_private_key(kex->hostkey_type, + ssh)) == NULL) { + r = SSH_ERR_NO_HOSTKEY_LOADED; + goto out; + } /* key, cert */ - if ((dh_client_pub = BN_new()) == NULL) - fatal("dh_client_pub == NULL"); - packet_get_bignum2(dh_client_pub); - packet_check_eom(); + if ((dh_client_pub = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = sshpkt_get_bignum2(ssh, dh_client_pub)) != 0 || + (r = sshpkt_get_end(ssh)) != 0) + goto out; #ifdef DEBUG_KEXDH fprintf(stderr, "dh_client_pub= "); @@ -133,79 +185,92 @@ kexgex_server(Kex *kex) #endif #ifdef DEBUG_KEXDH - DHparams_print_fp(stderr, dh); + DHparams_print_fp(stderr, kex->dh); fprintf(stderr, "pub= "); - BN_print_fp(stderr, dh->pub_key); + BN_print_fp(stderr, kex->dh->pub_key); fprintf(stderr, "\n"); #endif - if (!dh_pub_is_valid(dh, dh_client_pub)) - packet_disconnect("bad client public DH value"); + if (!dh_pub_is_valid(kex->dh, dh_client_pub)) { + sshpkt_disconnect(ssh, "bad client public DH value"); + r = SSH_ERR_MESSAGE_INCOMPLETE; + goto out; + } - klen = DH_size(dh); - kbuf = xmalloc(klen); - if ((kout = DH_compute_key(kbuf, dh_client_pub, dh)) < 0) - fatal("DH_compute_key: failed"); + klen = DH_size(kex->dh); + if ((kbuf = malloc(klen)) == NULL || + (shared_secret = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((kout = DH_compute_key(kbuf, dh_client_pub, kex->dh)) < 0 || + BN_bin2bn(kbuf, kout, shared_secret) == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } #ifdef DEBUG_KEXDH dump_digest("shared secret", kbuf, kout); #endif - if ((shared_secret = BN_new()) == NULL) - fatal("kexgex_server: BN_new failed"); - if (BN_bin2bn(kbuf, kout, shared_secret) == NULL) - fatal("kexgex_server: BN_bin2bn failed"); - explicit_bzero(kbuf, klen); - free(kbuf); - - key_to_blob(server_host_public, &server_host_key_blob, &sbloblen); - - if (type == SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) - omin = min = omax = max = -1; - + if ((r = sshkey_to_blob(server_host_public, &server_host_key_blob, + &sbloblen)) != 0) + goto out; /* calc H */ - kexgex_hash( + hashlen = sizeof(hash); + if ((r = kexgex_hash( kex->hash_alg, kex->client_version_string, kex->server_version_string, - buffer_ptr(kex->peer), buffer_len(kex->peer), - buffer_ptr(kex->my), buffer_len(kex->my), + sshbuf_ptr(kex->peer), sshbuf_len(kex->peer), + sshbuf_ptr(kex->my), sshbuf_len(kex->my), server_host_key_blob, sbloblen, - omin, onbits, omax, - dh->p, dh->g, + kex->min, kex->nbits, kex->max, + kex->dh->p, kex->dh->g, dh_client_pub, - dh->pub_key, + kex->dh->pub_key, shared_secret, - &hash, &hashlen - ); - BN_clear_free(dh_client_pub); + hash, &hashlen)) != 0) + goto out; /* save session id := H */ if (kex->session_id == NULL) { kex->session_id_len = hashlen; - kex->session_id = xmalloc(kex->session_id_len); + kex->session_id = malloc(kex->session_id_len); + if (kex->session_id == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } memcpy(kex->session_id, hash, kex->session_id_len); } /* sign H */ - kex->sign(server_host_private, server_host_public, &signature, &slen, - hash, hashlen); + if ((r = kex->sign(server_host_private, server_host_public, + &signature, &slen, hash, hashlen, ssh->compat)) < 0) + goto out; /* destroy_sensitive_data(); */ /* send server hostkey, DH pubkey 'f' and singed H */ - debug("SSH2_MSG_KEX_DH_GEX_REPLY sent"); - packet_start(SSH2_MSG_KEX_DH_GEX_REPLY); - packet_put_string(server_host_key_blob, sbloblen); - packet_put_bignum2(dh->pub_key); /* f */ - packet_put_string(signature, slen); - packet_send(); + if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_DH_GEX_REPLY)) != 0 || + (r = sshpkt_put_string(ssh, server_host_key_blob, sbloblen)) != 0 || + (r = sshpkt_put_bignum2(ssh, kex->dh->pub_key)) != 0 || /* f */ + (r = sshpkt_put_string(ssh, signature, slen)) != 0 || + (r = sshpkt_send(ssh)) != 0) + goto out; - free(signature); + if ((r = kex_derive_keys_bn(ssh, hash, hashlen, shared_secret)) == 0) + r = kex_send_newkeys(ssh); + out: + DH_free(kex->dh); + kex->dh = NULL; + if (dh_client_pub) + BN_clear_free(dh_client_pub); + if (kbuf) { + explicit_bzero(kbuf, klen); + free(kbuf); + } + if (shared_secret) + BN_clear_free(shared_secret); free(server_host_key_blob); - /* have keys, free DH */ - DH_free(dh); - - kex_derive_keys_bn(kex, hash, hashlen, shared_secret); - BN_clear_free(shared_secret); - - kex_finish(kex); + free(signature); + return r; } #endif /* WITH_OPENSSL */ diff --git a/monitor.c b/monitor.c index 6ee291128..40fff097d 100644 --- a/monitor.c +++ b/monitor.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.c,v 1.139 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: monitor.c,v 1.140 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -717,7 +717,7 @@ mm_answer_sign(int sock, Buffer *m) datafellows)) != 0) fatal("%s: sshkey_sign failed: %s", __func__, ssh_err(r)); - } else if ((key = get_hostkey_public_by_index(keyid)) != NULL && + } else if ((key = get_hostkey_public_by_index(keyid, active_state)) != NULL && auth_sock > 0) { if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen, p, datlen, datafellows)) != 0) { diff --git a/monitor_wrap.c b/monitor_wrap.c index 84df481d8..c0935dc69 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.c,v 1.82 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: monitor_wrap.c,v 1.83 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -221,13 +221,13 @@ mm_choose_dh(int min, int nbits, int max) int mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) { - Kex *kex = *pmonitor->m_pkex; + struct kex *kex = *pmonitor->m_pkex; Buffer m; debug3("%s entering", __func__); buffer_init(&m); - buffer_put_int(&m, kex->host_key_index(key)); + buffer_put_int(&m, kex->host_key_index(key, active_state)); buffer_put_string(&m, data, datalen); mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SIGN, &m); diff --git a/serverloop.c b/serverloop.c index edf6a813f..83a1e010d 100644 --- a/serverloop.c +++ b/serverloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: serverloop.c,v 1.174 2015/01/19 20:07:45 markus Exp $ */ +/* $OpenBSD: serverloop.c,v 1.175 2015/01/19 20:16:15 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -544,7 +544,7 @@ drain_output(void) static void process_buffered_input_packets(void) { - dispatch_run(DISPATCH_NONBLOCK, NULL, compat20 ? active_state->kex : NULL); + dispatch_run(DISPATCH_NONBLOCK, NULL, active_state); } /* @@ -874,7 +874,7 @@ server_loop2(Authctxt *authctxt) if (packet_need_rekeying()) { debug("need rekeying"); active_state->kex->done = 0; - kex_send_kexinit(active_state->kex); + kex_send_kexinit(active_state); } } process_input(readset); diff --git a/ssh-keyscan.c b/ssh-keyscan.c index 84301b6ff..223ac58f1 100644 --- a/ssh-keyscan.c +++ b/ssh-keyscan.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keyscan.c,v 1.93 2014/12/11 08:20:09 djm Exp $ */ +/* $OpenBSD: ssh-keyscan.c,v 1.94 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright 1995, 1996 by David Mazieres . * @@ -100,7 +100,7 @@ typedef struct Connection { char *c_namelist; /* Pointer to other possible addresses */ char *c_output_name; /* Hostname of connection for output */ char *c_data; /* Data read from this fd */ - Kex *c_kex; /* The key-exchange struct for ssh2 */ + struct kex *c_kex; /* The key-exchange struct for ssh2 */ struct timeval c_tv; /* Time at which connection gets aborted */ TAILQ_ENTRY(Connection) c_link; /* List of connections in timeout order. */ } con; @@ -221,7 +221,7 @@ keygrab_ssh1(con *c) #endif static int -hostjump(Key *hostkey) +hostjump(Key *hostkey, struct ssh *ssh) { kexjmp_key = hostkey; longjmp(kexjmp, 1); @@ -247,7 +247,7 @@ static Key * keygrab_ssh2(con *c) { char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; - int j; + int r, j; packet_set_connection(c->c_fd, c->c_fd); enable_compat20(); @@ -256,7 +256,9 @@ keygrab_ssh2(con *c) (c->c_keytype == KT_RSA ? "ssh-rsa" : (c->c_keytype == KT_ED25519 ? "ssh-ed25519" : "ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521")); - c->c_kex = kex_setup(myproposal); + if ((r = kex_setup(active_state, myproposal)) < 0) + fatal("%s: kex_setup: %s", __func__, ssh_err(r)); + c->c_kex = active_state->kex; #ifdef WITH_OPENSSL c->c_kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client; c->c_kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client; @@ -269,7 +271,7 @@ keygrab_ssh2(con *c) if (!(j = setjmp(kexjmp))) { nonfatal_fatal = 1; - dispatch_run(DISPATCH_BLOCK, &c->c_kex->done, c->c_kex); + dispatch_run(DISPATCH_BLOCK, &c->c_kex->done, active_state); fprintf(stderr, "Impossible! dispatch_run() returned!\n"); exit(1); } diff --git a/sshconnect2.c b/sshconnect2.c index e0d129996..e5802abcc 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect2.c,v 1.218 2015/01/19 20:07:45 markus Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.219 2015/01/19 20:16:15 markus Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. @@ -92,7 +92,7 @@ char *xxx_host; struct sockaddr *xxx_hostaddr; static int -verify_host_key_callback(Key *hostkey) +verify_host_key_callback(Key *hostkey, struct ssh *ssh) { if (verify_host_key(xxx_host, xxx_hostaddr, hostkey) == -1) fatal("Host key verification failed."); @@ -157,7 +157,7 @@ void ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) { char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; - Kex *kex; + struct kex *kex; xxx_host = host; xxx_hostaddr = hostaddr; @@ -204,8 +204,8 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) (time_t)options.rekey_interval); /* start key exchange */ - kex = kex_setup(myproposal); - active_state->kex = kex; + kex_setup(active_state, myproposal); + kex = active_state->kex; #ifdef WITH_OPENSSL kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client; @@ -218,7 +218,7 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) kex->server_version_string=server_version_string; kex->verify_host_key=&verify_host_key_callback; - dispatch_run(DISPATCH_BLOCK, &kex->done, kex); + dispatch_run(DISPATCH_BLOCK, &kex->done, active_state); if (options.use_roaming && !kex->roaming) { debug("Roaming not allowed by server"); diff --git a/sshd.c b/sshd.c index e45303988..21d8dd699 100644 --- a/sshd.c +++ b/sshd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.434 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: sshd.c,v 1.435 2015/01/19 20:16:15 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -836,7 +836,7 @@ list_hostkey_types(void) } static Key * -get_hostkey_by_type(int type, int need_private) +get_hostkey_by_type(int type, int need_private, struct ssh *ssh) { int i; Key *key; @@ -865,15 +865,15 @@ get_hostkey_by_type(int type, int need_private) } Key * -get_hostkey_public_by_type(int type) +get_hostkey_public_by_type(int type, struct ssh *ssh) { - return get_hostkey_by_type(type, 0); + return get_hostkey_by_type(type, 0, ssh); } Key * -get_hostkey_private_by_type(int type) +get_hostkey_private_by_type(int type, struct ssh *ssh) { - return get_hostkey_by_type(type, 1); + return get_hostkey_by_type(type, 1, ssh); } Key * @@ -885,7 +885,7 @@ get_hostkey_by_index(int ind) } Key * -get_hostkey_public_by_index(int ind) +get_hostkey_public_by_index(int ind, struct ssh *ssh) { if (ind < 0 || ind >= options.num_host_key_files) return (NULL); @@ -893,7 +893,7 @@ get_hostkey_public_by_index(int ind) } int -get_hostkey_index(Key *key) +get_hostkey_index(Key *key, struct ssh *ssh) { int i; @@ -2432,29 +2432,30 @@ do_ssh1_kex(void) } #endif -void -sshd_hostkey_sign(Key *privkey, Key *pubkey, u_char **signature, u_int *slen, - u_char *data, u_int dlen) +int +sshd_hostkey_sign(Key *privkey, Key *pubkey, u_char **signature, size_t *slen, + u_char *data, size_t dlen, u_int flag) { int r; + u_int xxx_slen, xxx_dlen = dlen; if (privkey) { - if (PRIVSEP(key_sign(privkey, signature, slen, data, dlen) < 0)) + if (PRIVSEP(key_sign(privkey, signature, &xxx_slen, data, xxx_dlen) < 0)) fatal("%s: key_sign failed", __func__); + if (slen) + *slen = xxx_slen; } else if (use_privsep) { - if (mm_key_sign(pubkey, signature, slen, data, dlen) < 0) + if (mm_key_sign(pubkey, signature, &xxx_slen, data, xxx_dlen) < 0) fatal("%s: pubkey_sign failed", __func__); + if (slen) + *slen = xxx_slen; } else { - size_t xxx_slen; - - if ((r = ssh_agent_sign(auth_sock, pubkey, signature, &xxx_slen, + if ((r = ssh_agent_sign(auth_sock, pubkey, signature, slen, data, dlen, datafellows)) != 0) fatal("%s: ssh_agent_sign failed: %s", __func__, ssh_err(r)); - /* XXX: Old API is u_int; new size_t */ - if (slen != NULL) - *slen = xxx_slen; } + return 0; } /* @@ -2464,7 +2465,7 @@ static void do_ssh2_kex(void) { char *myproposal[PROPOSAL_MAX] = { KEX_SERVER }; - Kex *kex; + struct kex *kex; if (options.ciphers != NULL) { myproposal[PROPOSAL_ENC_ALGS_CTOS] = @@ -2500,8 +2501,8 @@ do_ssh2_kex(void) list_hostkey_types()); /* start key exchange */ - kex = kex_setup(myproposal); - active_state->kex = kex; + kex_setup(active_state, myproposal); + kex = active_state->kex; #ifdef WITH_OPENSSL kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; @@ -2518,7 +2519,7 @@ do_ssh2_kex(void) kex->host_key_index=&get_hostkey_index; kex->sign = sshd_hostkey_sign; - dispatch_run(DISPATCH_BLOCK, &kex->done, kex); + dispatch_run(DISPATCH_BLOCK, &kex->done, active_state); session_id2 = kex->session_id; session_id2_len = kex->session_id_len; -- cgit v1.2.3 From 5104db7cbd6cdd9c5971f4358e74414862fc1022 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 26 Jan 2015 06:10:03 +0000 Subject: upstream commit correctly match ECDSA subtype (== curve) for offered/recevied host keys. Fixes connection-killing host key mismatches when a server offers multiple ECDSA keys with different curve type (an extremely unlikely configuration). ok markus, "looks mechanical" deraadt@ --- auth.h | 6 +++--- kex.c | 3 ++- kex.h | 7 ++++--- kexc25519c.c | 6 ++++-- kexc25519s.c | 8 +++++--- kexdhc.c | 6 ++++-- kexdhs.c | 8 +++++--- kexecdhc.c | 6 ++++-- kexecdhs.c | 8 +++++--- kexgexc.c | 8 +++++++- kexgexs.c | 8 +++++--- ssh_api.c | 16 +++++++++------- sshconnect.c | 5 +++-- sshd.c | 15 ++++++++------- 14 files changed, 68 insertions(+), 42 deletions(-) (limited to 'auth.h') diff --git a/auth.h b/auth.h index 60d1c3355..d28261929 100644 --- a/auth.h +++ b/auth.h @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.h,v 1.80 2015/01/19 20:16:15 markus Exp $ */ +/* $OpenBSD: auth.h,v 1.81 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -204,8 +204,8 @@ check_key_in_hostfiles(struct passwd *, Key *, const char *, /* hostkey handling */ Key *get_hostkey_by_index(int); Key *get_hostkey_public_by_index(int, struct ssh *); -Key *get_hostkey_public_by_type(int, struct ssh *); -Key *get_hostkey_private_by_type(int, struct ssh *); +Key *get_hostkey_public_by_type(int, int, struct ssh *); +Key *get_hostkey_private_by_type(int, int, struct ssh *); int get_hostkey_index(Key *, struct ssh *); int ssh1_session_key(BIGNUM *); int sshd_hostkey_sign(Key *, Key *, u_char **, size_t *, u_char *, size_t, u_int); diff --git a/kex.c b/kex.c index 5b7b1e86a..7eb3185e6 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.103 2015/01/20 23:14:00 deraadt Exp $ */ +/* $OpenBSD: kex.c,v 1.104 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -546,6 +546,7 @@ choose_hostkeyalg(struct kex *k, char *client, char *server) k->hostkey_type = sshkey_type_from_name(hostkeyalg); if (k->hostkey_type == KEY_UNSPEC) return SSH_ERR_INTERNAL_ERROR; + k->hostkey_nid = sshkey_ecdsa_nid_from_name(hostkeyalg); free(hostkeyalg); return 0; } diff --git a/kex.h b/kex.h index 1798eea4d..45d35773c 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.69 2015/01/19 20:16:15 markus Exp $ */ +/* $OpenBSD: kex.h,v 1.70 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -116,6 +116,7 @@ struct kex { int server; char *name; int hostkey_type; + int hostkey_nid; u_int kex_type; int roaming; struct sshbuf *my; @@ -127,8 +128,8 @@ struct kex { char *client_version_string; char *server_version_string; int (*verify_host_key)(struct sshkey *, struct ssh *); - struct sshkey *(*load_host_public_key)(int, struct ssh *); - struct sshkey *(*load_host_private_key)(int, struct ssh *); + struct sshkey *(*load_host_public_key)(int, int, struct ssh *); + struct sshkey *(*load_host_private_key)(int, int, struct ssh *); int (*host_key_index)(struct sshkey *, struct ssh *); int (*sign)(struct sshkey *, struct sshkey *, u_char **, size_t *, u_char *, size_t, u_int); diff --git a/kexc25519c.c b/kexc25519c.c index 833ce0544..b7ef65dc3 100644 --- a/kexc25519c.c +++ b/kexc25519c.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519c.c,v 1.6 2015/01/19 20:16:15 markus Exp $ */ +/* $OpenBSD: kexc25519c.c,v 1.7 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -92,7 +92,9 @@ input_kex_c25519_reply(int type, u_int32_t seq, void *ctxt) (r = sshkey_from_blob(server_host_key_blob, sbloblen, &server_host_key)) != 0) goto out; - if (server_host_key->type != kex->hostkey_type) { + if (server_host_key->type != kex->hostkey_type || + (kex->hostkey_type == KEY_ECDSA && + server_host_key->ecdsa_nid != kex->hostkey_nid)) { r = SSH_ERR_KEY_TYPE_MISMATCH; goto out; } diff --git a/kexc25519s.c b/kexc25519s.c index d840856d2..b2d2c858f 100644 --- a/kexc25519s.c +++ b/kexc25519s.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexc25519s.c,v 1.7 2015/01/20 07:55:33 djm Exp $ */ +/* $OpenBSD: kexc25519s.c,v 1.8 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -75,8 +75,10 @@ input_kex_c25519_init(int type, u_int32_t seq, void *ctxt) r = SSH_ERR_INVALID_ARGUMENT; goto out; } - server_host_public = kex->load_host_public_key(kex->hostkey_type, ssh); - server_host_private = kex->load_host_private_key(kex->hostkey_type, ssh); + server_host_public = kex->load_host_public_key(kex->hostkey_type, + kex->hostkey_nid, ssh); + server_host_private = kex->load_host_private_key(kex->hostkey_type, + kex->hostkey_nid, ssh); if (server_host_public == NULL) { r = SSH_ERR_NO_HOSTKEY_LOADED; goto out; diff --git a/kexdhc.c b/kexdhc.c index 52b752295..af259f16a 100644 --- a/kexdhc.c +++ b/kexdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhc.c,v 1.17 2015/01/19 20:16:15 markus Exp $ */ +/* $OpenBSD: kexdhc.c,v 1.18 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -114,7 +114,9 @@ input_kex_dh(int type, u_int32_t seq, void *ctxt) (r = sshkey_from_blob(server_host_key_blob, sbloblen, &server_host_key)) != 0) goto out; - if (server_host_key->type != kex->hostkey_type) { + if (server_host_key->type != kex->hostkey_type || + (kex->hostkey_type == KEY_ECDSA && + server_host_key->ecdsa_nid != kex->hostkey_nid)) { r = SSH_ERR_KEY_TYPE_MISMATCH; goto out; } diff --git a/kexdhs.c b/kexdhs.c index 0bfa08b07..de7c05b17 100644 --- a/kexdhs.c +++ b/kexdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexdhs.c,v 1.21 2015/01/20 07:55:33 djm Exp $ */ +/* $OpenBSD: kexdhs.c,v 1.22 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * @@ -101,8 +101,10 @@ input_kex_dh_init(int type, u_int32_t seq, void *ctxt) r = SSH_ERR_INVALID_ARGUMENT; goto out; } - server_host_public = kex->load_host_public_key(kex->hostkey_type, ssh); - server_host_private = kex->load_host_private_key(kex->hostkey_type, ssh); + server_host_public = kex->load_host_public_key(kex->hostkey_type, + kex->hostkey_nid, ssh); + server_host_private = kex->load_host_private_key(kex->hostkey_type, + kex->hostkey_nid, ssh); if (server_host_public == NULL) { r = SSH_ERR_NO_HOSTKEY_LOADED; goto out; diff --git a/kexecdhc.c b/kexecdhc.c index 3f362c5b1..90220ce82 100644 --- a/kexecdhc.c +++ b/kexecdhc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhc.c,v 1.9 2015/01/19 20:16:15 markus Exp $ */ +/* $OpenBSD: kexecdhc.c,v 1.10 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -124,7 +124,9 @@ input_kex_ecdh_reply(int type, u_int32_t seq, void *ctxt) (r = sshkey_from_blob(server_host_key_blob, sbloblen, &server_host_key)) != 0) goto out; - if (server_host_key->type != kex->hostkey_type) { + if (server_host_key->type != kex->hostkey_type || + (kex->hostkey_type == KEY_ECDSA && + server_host_key->ecdsa_nid != kex->hostkey_nid)) { r = SSH_ERR_KEY_TYPE_MISMATCH; goto out; } diff --git a/kexecdhs.c b/kexecdhs.c index f47a7b207..0adb80e6a 100644 --- a/kexecdhs.c +++ b/kexecdhs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexecdhs.c,v 1.13 2015/01/20 07:55:33 djm Exp $ */ +/* $OpenBSD: kexecdhs.c,v 1.14 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2010 Damien Miller. All rights reserved. @@ -95,8 +95,10 @@ input_kex_ecdh_init(int type, u_int32_t seq, void *ctxt) r = SSH_ERR_INVALID_ARGUMENT; goto out; } - server_host_public = kex->load_host_public_key(kex->hostkey_type, ssh); - server_host_private = kex->load_host_private_key(kex->hostkey_type, ssh); + server_host_public = kex->load_host_public_key(kex->hostkey_type, + kex->hostkey_nid, ssh); + server_host_private = kex->load_host_private_key(kex->hostkey_type, + kex->hostkey_nid, ssh); if (server_host_public == NULL) { r = SSH_ERR_NO_HOSTKEY_LOADED; goto out; diff --git a/kexgexc.c b/kexgexc.c index 0898824f3..e8e059a88 100644 --- a/kexgexc.c +++ b/kexgexc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexc.c,v 1.19 2015/01/19 20:16:15 markus Exp $ */ +/* $OpenBSD: kexgexc.c,v 1.20 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -176,6 +176,12 @@ input_kex_dh_gex_reply(int type, u_int32_t seq, void *ctxt) r = SSH_ERR_KEY_TYPE_MISMATCH; goto out; } + if (server_host_key->type != kex->hostkey_type || + (kex->hostkey_type == KEY_ECDSA && + server_host_key->ecdsa_nid != kex->hostkey_nid)) { + r = SSH_ERR_KEY_TYPE_MISMATCH; + goto out; + } if (kex->verify_host_key(server_host_key, ssh) == -1) { r = SSH_ERR_SIGNATURE_INVALID; goto out; diff --git a/kexgexs.c b/kexgexs.c index ca5ee13e9..9c281d288 100644 --- a/kexgexs.c +++ b/kexgexs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kexgexs.c,v 1.23 2015/01/20 23:14:00 deraadt Exp $ */ +/* $OpenBSD: kexgexs.c,v 1.24 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2000 Niels Provos. All rights reserved. * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -160,8 +160,10 @@ input_kex_dh_gex_init(int type, u_int32_t seq, void *ctxt) r = SSH_ERR_INVALID_ARGUMENT; goto out; } - server_host_public = kex->load_host_public_key(kex->hostkey_type, ssh); - server_host_private = kex->load_host_private_key(kex->hostkey_type, ssh); + server_host_public = kex->load_host_public_key(kex->hostkey_type, + kex->hostkey_nid, ssh); + server_host_private = kex->load_host_private_key(kex->hostkey_type, + kex->hostkey_nid, ssh); if (server_host_public == NULL) { r = SSH_ERR_NO_HOSTKEY_LOADED; goto out; diff --git a/ssh_api.c b/ssh_api.c index 1df995c94..9794e0e57 100644 --- a/ssh_api.c +++ b/ssh_api.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh_api.c,v 1.1 2015/01/19 20:30:23 markus Exp $ */ +/* $OpenBSD: ssh_api.c,v 1.2 2015/01/26 06:10:03 djm Exp $ */ /* * Copyright (c) 2012 Markus Friedl. All rights reserved. * @@ -38,8 +38,8 @@ int _ssh_send_banner(struct ssh *, char **); int _ssh_read_banner(struct ssh *, char **); int _ssh_order_hostkeyalgs(struct ssh *); int _ssh_verify_host_key(struct sshkey *, struct ssh *); -struct sshkey *_ssh_host_public_key(int, struct ssh *); -struct sshkey *_ssh_host_private_key(int, struct ssh *); +struct sshkey *_ssh_host_public_key(int, int, struct ssh *); +struct sshkey *_ssh_host_private_key(int, int, struct ssh *); int _ssh_host_key_sign(struct sshkey *, struct sshkey *, u_char **, size_t *, u_char *, size_t, u_int); @@ -425,28 +425,30 @@ _ssh_exchange_banner(struct ssh *ssh) } struct sshkey * -_ssh_host_public_key(int type, struct ssh *ssh) +_ssh_host_public_key(int type, int nid, struct ssh *ssh) { struct key_entry *k; debug3("%s: need %d", __func__, type); TAILQ_FOREACH(k, &ssh->public_keys, next) { debug3("%s: check %s", __func__, sshkey_type(k->key)); - if (k->key->type == type) + if (k->key->type == type && + (type != KEY_ECDSA || k->key->ecdsa_nid == nid)) return (k->key); } return (NULL); } struct sshkey * -_ssh_host_private_key(int type, struct ssh *ssh) +_ssh_host_private_key(int type, int nid, struct ssh *ssh) { struct key_entry *k; debug3("%s: need %d", __func__, type); TAILQ_FOREACH(k, &ssh->private_keys, next) { debug3("%s: check %s", __func__, sshkey_type(k->key)); - if (k->key->type == type) + if (k->key->type == type && + (type != KEY_ECDSA || k->key->ecdsa_nid == nid)) return (k->key); } return (NULL); diff --git a/sshconnect.c b/sshconnect.c index ae3b642cb..df921bec1 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.257 2015/01/26 03:04:46 djm Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.258 2015/01/26 06:10:03 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1243,7 +1243,8 @@ verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) goto out; } - debug("Server host key: %s %s", sshkey_type(host_key), fp); + debug("Server host key: %s %s", + compat20 ? sshkey_ssh_name(host_key) : sshkey_type(host_key), fp); if (sshkey_equal(previous_host_key, host_key)) { debug2("%s: server host key %s %s matches cached key", diff --git a/sshd.c b/sshd.c index f2ee10d2c..004ddd4a5 100644 --- a/sshd.c +++ b/sshd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.439 2015/01/26 03:04:46 djm Exp $ */ +/* $OpenBSD: sshd.c,v 1.440 2015/01/26 06:10:03 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -836,7 +836,7 @@ list_hostkey_types(void) } static Key * -get_hostkey_by_type(int type, int need_private, struct ssh *ssh) +get_hostkey_by_type(int type, int nid, int need_private, struct ssh *ssh) { int i; Key *key; @@ -857,7 +857,8 @@ get_hostkey_by_type(int type, int need_private, struct ssh *ssh) key = sensitive_data.host_pubkeys[i]; break; } - if (key != NULL && key->type == type) + if (key != NULL && key->type == type && + (key->type != KEY_ECDSA || key->ecdsa_nid == nid)) return need_private ? sensitive_data.host_keys[i] : key; } @@ -865,15 +866,15 @@ get_hostkey_by_type(int type, int need_private, struct ssh *ssh) } Key * -get_hostkey_public_by_type(int type, struct ssh *ssh) +get_hostkey_public_by_type(int type, int nid, struct ssh *ssh) { - return get_hostkey_by_type(type, 0, ssh); + return get_hostkey_by_type(type, nid, 0, ssh); } Key * -get_hostkey_private_by_type(int type, struct ssh *ssh) +get_hostkey_private_by_type(int type, int nid, struct ssh *ssh) { - return get_hostkey_by_type(type, 1, ssh); + return get_hostkey_by_type(type, nid, 1, ssh); } Key * -- cgit v1.2.3 From 523463a3a2a9bfc6cfc5afa01bae9147f76a37cc Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 16 Feb 2015 22:13:32 +0000 Subject: upstream commit Revise hostkeys@openssh.com hostkey learning extension. The client will not ask the server to prove ownership of the private halves of any hitherto-unseen hostkeys it offers to the client. Allow UpdateHostKeys option to take an 'ask' argument to let the user manually review keys offered. ok markus@ --- PROTOCOL | 53 ++++++--- auth.h | 7 +- clientloop.c | 353 ++++++++++++++++++++++++++++++++++++++++++++++++++------- kex.h | 6 +- monitor.c | 45 +++++++- monitor_wrap.c | 7 +- monitor_wrap.h | 4 +- readconf.c | 6 +- readconf.h | 8 +- serverloop.c | 88 +++++++++++++- ssh_api.c | 7 +- ssh_config.5 | 15 ++- sshd.c | 35 ++++-- ssherr.c | 4 +- 14 files changed, 537 insertions(+), 101 deletions(-) (limited to 'auth.h') diff --git a/PROTOCOL b/PROTOCOL index 8150c577b..f9560839e 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -40,8 +40,8 @@ http://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt "ecdsa-sha2-nistp521-cert-v01@openssh.com" OpenSSH introduces new public key algorithms to support certificate -authentication for users and hostkeys. These methods are documented in -the file PROTOCOL.certkeys +authentication for users and host keys. These methods are documented +in the file PROTOCOL.certkeys 1.4. transport: Elliptic Curve cryptography @@ -283,26 +283,51 @@ by the client cancel the forwarding of a Unix domain socket. string socket path 2.5. connection: hostkey update and rotation "hostkeys@openssh.com" +and "hostkeys-prove@openssh.com" OpenSSH supports a protocol extension allowing a server to inform -a client of all its protocol v.2 hostkeys after user-authentication +a client of all its protocol v.2 host keys after user-authentication has completed. byte SSH_MSG_GLOBAL_REQUEST string "hostkeys@openssh.com" string[] hostkeys -Upon receiving this message, a client may update its known_hosts -file, adding keys that it has not seen before and deleting keys -for the server host that are no longer offered. +Upon receiving this message, a client should check which of the +supplied host keys are present in known_hosts. For keys that are +not present, it should send a "hostkeys-prove@openssh.com" message +to request the server prove ownership of the private half of the +key. -This extension allows a client to learn key types that it had -not previously encountered, thereby allowing it to potentially -upgrade from weaker key algorithms to better ones. It also -supports graceful key rotation: a server may offer multiple keys -of the same type for a period (to give clients an opportunity to -learn them using this extension) before removing the deprecated -key from those offered. + byte SSH_MSG_GLOBAL_REQUEST + string "hostkeys-prove@openssh.com" + char 1 /* want-reply */ + string[] hostkeys + +When a server receives this message, it should generate a signature +using each requested key over the following: + + string session identifier + string "hostkeys-prove@openssh.com" + string hostkey + +These signatures should be included in the reply, in the order matching +the hostkeys in the request: + + byte SSH_MSG_REQUEST_SUCCESS + string[] signatures + +When the client receives this reply (and not a failure), it should +validate the signatures and may update its known_hosts file, adding keys +that it has not seen before and deleting keys for the server host that +are no longer offered. + +These extensions let a client learn key types that it had not previously +encountered, thereby allowing it to potentially upgrade from weaker +key algorithms to better ones. It also supports graceful key rotation: +a server may offer multiple keys of the same type for a period (to +give clients an opportunity to learn them using this extension) before +removing the deprecated key from those offered. 3. SFTP protocol changes @@ -428,4 +453,4 @@ respond with a SSH_FXP_STATUS message. This extension is advertised in the SSH_FXP_VERSION hello with version "1". -$OpenBSD: PROTOCOL,v 1.25 2015/01/26 03:04:45 djm Exp $ +$OpenBSD: PROTOCOL,v 1.26 2015/02/16 22:13:32 djm Exp $ diff --git a/auth.h b/auth.h index d28261929..db8603760 100644 --- a/auth.h +++ b/auth.h @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.h,v 1.81 2015/01/26 06:10:03 djm Exp $ */ +/* $OpenBSD: auth.h,v 1.82 2015/02/16 22:13:32 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -206,9 +206,10 @@ Key *get_hostkey_by_index(int); Key *get_hostkey_public_by_index(int, struct ssh *); Key *get_hostkey_public_by_type(int, int, struct ssh *); Key *get_hostkey_private_by_type(int, int, struct ssh *); -int get_hostkey_index(Key *, struct ssh *); +int get_hostkey_index(Key *, int, struct ssh *); int ssh1_session_key(BIGNUM *); -int sshd_hostkey_sign(Key *, Key *, u_char **, size_t *, u_char *, size_t, u_int); +int sshd_hostkey_sign(Key *, Key *, u_char **, size_t *, + const u_char *, size_t, u_int); /* debug messages during authentication */ void auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2))); diff --git a/clientloop.c b/clientloop.c index c6f8e9dc1..a19d9d06f 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.268 2015/02/16 22:08:57 djm Exp $ */ +/* $OpenBSD: clientloop.c,v 1.269 2015/02/16 22:13:32 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2089,6 +2089,216 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt) return 0; } +struct hostkeys_update_ctx { + /* The hostname and (optionally) IP address string for the server */ + char *host_str, *ip_str; + + /* + * Keys received from the server and a flag for each indicating + * whether they already exist in known_hosts. + * keys_seen is filled in by hostkeys_find() and later (for new + * keys) by client_global_hostkeys_private_confirm(). + */ + struct sshkey **keys; + int *keys_seen; + size_t nkeys; + + size_t nnew; + + /* + * Keys that are in known_hosts, but were not present in the update + * from the server (i.e. scheduled to be deleted). + * Filled in by hostkeys_find(). + */ + struct sshkey **old_keys; + size_t nold; +}; + +static void +hostkeys_update_ctx_free(struct hostkeys_update_ctx *ctx) +{ + size_t i; + + if (ctx == NULL) + return; + for (i = 0; i < ctx->nkeys; i++) + sshkey_free(ctx->keys[i]); + free(ctx->keys); + free(ctx->keys_seen); + for (i = 0; i < ctx->nold; i++) + sshkey_free(ctx->old_keys[i]); + free(ctx->old_keys); + free(ctx->host_str); + free(ctx->ip_str); + free(ctx); +} + +static int +hostkeys_find(struct hostkey_foreach_line *l, void *_ctx) +{ + struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx; + size_t i; + struct sshkey **tmp; + + if (l->status != HKF_STATUS_MATCHED || l->key == NULL || + l->key->type == KEY_RSA1) + return 0; + + /* Mark off keys we've already seen for this host */ + for (i = 0; i < ctx->nkeys; i++) { + if (sshkey_equal(l->key, ctx->keys[i])) { + debug3("%s: found %s key at %s:%ld", __func__, + sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum); + ctx->keys_seen[i] = 1; + return 0; + } + } + /* This line contained a key that not offered by the server */ + debug3("%s: deprecated %s key at %s:%ld", __func__, + sshkey_ssh_name(l->key), l->path, l->linenum); + if ((tmp = reallocarray(ctx->old_keys, ctx->nold + 1, + sizeof(*ctx->old_keys))) == NULL) + fatal("%s: reallocarray failed nold = %zu", + __func__, ctx->nold); + ctx->old_keys = tmp; + ctx->old_keys[ctx->nold++] = l->key; + l->key = NULL; + + return 0; +} + +static void +update_known_hosts(struct hostkeys_update_ctx *ctx) +{ + int r, loglevel = options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK ? + SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_VERBOSE; + char *fp, *response; + size_t i; + + for (i = 0; i < ctx->nkeys; i++) { + if (ctx->keys_seen[i] != 2) + continue; + if ((fp = sshkey_fingerprint(ctx->keys[i], + options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) + fatal("%s: sshkey_fingerprint failed", __func__); + do_log2(loglevel, "Learned new hostkey: %s %s", + sshkey_type(ctx->keys[i]), fp); + free(fp); + } + for (i = 0; i < ctx->nold; i++) { + if ((fp = sshkey_fingerprint(ctx->old_keys[i], + options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) + fatal("%s: sshkey_fingerprint failed", __func__); + do_log2(loglevel, "Deprecating obsolete hostkey: %s %s", + sshkey_type(ctx->old_keys[i]), fp); + free(fp); + } + if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK) { + leave_raw_mode(options.request_tty == REQUEST_TTY_FORCE); + response = NULL; + for (i = 0; !quit_pending && i < 3; i++) { + free(response); + response = read_passphrase("Accept updated hostkeys? " + "(yes/no): ", RP_ECHO); + if (strcasecmp(response, "yes") == 0) + break; + else if (quit_pending || response == NULL || + strcasecmp(response, "no") == 0) { + options.update_hostkeys = 0; + break; + } else { + do_log2(loglevel, "Please enter " + "\"yes\" or \"no\""); + } + } + if (quit_pending || i >= 3 || response == NULL) + options.update_hostkeys = 0; + free(response); + enter_raw_mode(options.request_tty == REQUEST_TTY_FORCE); + } + + /* + * Now that all the keys are verified, we can go ahead and replace + * them in known_hosts (assuming SSH_UPDATE_HOSTKEYS_ASK didn't + * cancel the operation). + */ + if (options.update_hostkeys != 0 && + (r = hostfile_replace_entries(options.user_hostfiles[0], + ctx->host_str, ctx->ip_str, ctx->keys, ctx->nkeys, + options.hash_known_hosts, 0, + options.fingerprint_hash)) != 0) + error("%s: hostfile_replace_entries failed: %s", + __func__, ssh_err(r)); +} + +static void +client_global_hostkeys_private_confirm(int type, u_int32_t seq, void *_ctx) +{ + struct ssh *ssh = active_state; /* XXX */ + struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx; + size_t i, ndone; + struct sshbuf *signdata; + int r; + const u_char *sig; + size_t siglen; + + if (ctx->nnew == 0) + fatal("%s: ctx->nnew == 0", __func__); /* sanity */ + if (type != SSH2_MSG_REQUEST_SUCCESS) { + error("Server failed to confirm ownership of " + "private host keys"); + hostkeys_update_ctx_free(ctx); + return; + } + if ((signdata = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + /* Don't want to accidentally accept an unbound signature */ + if (ssh->kex->session_id_len == 0) + fatal("%s: ssh->kex->session_id_len == 0", __func__); + /* + * Expect a signature for each of the ctx->nnew private keys we + * haven't seen before. They will be in the same order as the + * ctx->keys where the corresponding ctx->keys_seen[i] == 0. + */ + for (ndone = i = 0; i < ctx->nkeys; i++) { + if (ctx->keys_seen[i]) + continue; + /* Prepare data to be signed: session ID, unique string, key */ + sshbuf_reset(signdata); + if ((r = sshbuf_put_string(signdata, ssh->kex->session_id, + ssh->kex->session_id_len)) != 0 || + (r = sshbuf_put_cstring(signdata, + "hostkeys-prove@openssh.com")) != 0 || + (r = sshkey_puts(ctx->keys[i], signdata)) != 0) + fatal("%s: failed to prepare signature: %s", + __func__, ssh_err(r)); + /* Extract and verify signature */ + if ((r = sshpkt_get_string_direct(ssh, &sig, &siglen)) != 0) { + error("%s: couldn't parse message: %s", + __func__, ssh_err(r)); + goto out; + } + if ((r = sshkey_verify(ctx->keys[i], sig, siglen, + sshbuf_ptr(signdata), sshbuf_len(signdata), 0)) != 0) { + error("%s: server gave bad signature for %s key %zu", + __func__, sshkey_type(ctx->keys[i]), i); + goto out; + } + /* Key is good. Mark it as 'seen' */ + ctx->keys_seen[i] = 2; + ndone++; + } + if (ndone != ctx->nnew) + fatal("%s: ndone != ctx->nnew (%zu / %zu)", __func__, + ndone, ctx->nnew); /* Shouldn't happen */ + ssh_packet_check_eom(ssh); + + /* Make the edits to known_hosts */ + update_known_hosts(ctx); + out: + hostkeys_update_ctx_free(ctx); +} + /* * Handle hostkeys@openssh.com global request to inform the client of all * the server's hostkeys. The keys are checked against the user's @@ -2097,34 +2307,35 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt) static int client_input_hostkeys(void) { + struct ssh *ssh = active_state; /* XXX */ const u_char *blob = NULL; - u_int i, len = 0, nkeys = 0; + size_t i, len = 0; struct sshbuf *buf = NULL; - struct sshkey *key = NULL, **tmp, **keys = NULL; - int r, success = 1; - char *fp, *host_str = NULL, *ip_str = NULL; + struct sshkey *key = NULL, **tmp; + int r; + char *fp; static int hostkeys_seen = 0; /* XXX use struct ssh */ extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */ + struct hostkeys_update_ctx *ctx; - /* - * NB. Return success for all cases other than protocol error. The - * server doesn't need to know what the client does with its hosts - * file. - */ - - blob = packet_get_string_ptr(&len); - packet_check_eom(); + ctx = xcalloc(1, sizeof(*ctx)); if (hostkeys_seen) fatal("%s: server already sent hostkeys", __func__); + if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK && + options.batch_mode) + return 1; /* won't ask in batchmode, so don't even try */ if (!options.update_hostkeys || options.num_user_hostfiles <= 0) return 1; - if ((buf = sshbuf_from(blob, len)) == NULL) - fatal("%s: sshbuf_from failed", __func__); - while (sshbuf_len(buf) > 0) { + while (ssh_packet_remaining(ssh) > 0) { sshkey_free(key); key = NULL; - if ((r = sshkey_froms(buf, &key)) != 0) + if ((r = sshpkt_get_string_direct(ssh, &blob, &len)) != 0) { + error("%s: couldn't parse message: %s", + __func__, ssh_err(r)); + goto out; + } + if ((r = sshkey_from_blob(blob, len, &key)) != 0) fatal("%s: parse key: %s", __func__, ssh_err(r)); fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT); @@ -2140,47 +2351,107 @@ client_input_hostkeys(void) __func__, sshkey_ssh_name(key)); continue; } - if ((tmp = reallocarray(keys, nkeys + 1, - sizeof(*keys))) == NULL) - fatal("%s: reallocarray failed nkeys = %u", - __func__, nkeys); - keys = tmp; - keys[nkeys++] = key; + /* Skip certs */ + if (sshkey_is_cert(key)) { + debug3("%s: %s key is a certificate; skipping", + __func__, sshkey_ssh_name(key)); + continue; + } + /* Ensure keys are unique */ + for (i = 0; i < ctx->nkeys; i++) { + if (sshkey_equal(key, ctx->keys[i])) { + error("%s: received duplicated %s host key", + __func__, sshkey_ssh_name(key)); + goto out; + } + } + /* Key is good, record it */ + if ((tmp = reallocarray(ctx->keys, ctx->nkeys + 1, + sizeof(*ctx->keys))) == NULL) + fatal("%s: reallocarray failed nkeys = %zu", + __func__, ctx->nkeys); + ctx->keys = tmp; + ctx->keys[ctx->nkeys++] = key; key = NULL; } - if (nkeys == 0) { + if (ctx->nkeys == 0) { error("%s: server sent no hostkeys", __func__); goto out; } + if ((ctx->keys_seen = calloc(ctx->nkeys, + sizeof(*ctx->keys_seen))) == NULL) + fatal("%s: calloc failed", __func__); get_hostfile_hostname_ipaddr(host, options.check_host_ip ? (struct sockaddr *)&hostaddr : NULL, - options.port, &host_str, options.check_host_ip ? &ip_str : NULL); + options.port, &ctx->host_str, + options.check_host_ip ? &ctx->ip_str : NULL); + + /* Find which keys we already know about. */ + if ((r = hostkeys_foreach(options.user_hostfiles[0], hostkeys_find, + ctx, ctx->host_str, ctx->ip_str, + HKF_WANT_PARSE_KEY|HKF_WANT_MATCH)) != 0) { + error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); + goto out; + } + + /* Figure out if we have any new keys to add */ + ctx->nnew = 0; + for (i = 0; i < ctx->nkeys; i++) { + if (!ctx->keys_seen[i]) + ctx->nnew++; + } - debug3("%s: update known hosts for %s%s%s with %u keys from server", - __func__, host_str, - options.check_host_ip ? " " : "", - options.check_host_ip ? ip_str : "", nkeys); + debug3("%s: %zu keys from server: %zu new, %zu retained. %zu to remove", + __func__, ctx->nkeys, ctx->nnew, ctx->nkeys - ctx->nnew, ctx->nold); - if ((r = hostfile_replace_entries(options.user_hostfiles[0], - host_str, options.check_host_ip ? ip_str : NULL, - keys, nkeys, options.hash_known_hosts, 0, - options.fingerprint_hash)) != 0) { - error("%s: hostfile_replace_entries failed: %s", - __func__, ssh_err(r)); - goto out; + if (ctx->nnew == 0 && ctx->nold != 0) { + /* We have some keys to remove. Just do it. */ + update_known_hosts(ctx); + } else if (ctx->nnew != 0) { + /* + * We have received hitherto-unseen keys from the server. + * Ask the server to confirm ownership of the private halves. + */ + debug3("%s: asking server to prove ownership for %zu keys", + __func__, ctx->nnew); + if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || + (r = sshpkt_put_cstring(ssh, + "hostkeys-prove@openssh.com")) != 0 || + (r = sshpkt_put_u8(ssh, 1)) != 0) /* bool: want reply */ + fatal("%s: cannot prepare packet: %s", + __func__, ssh_err(r)); + if ((buf = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new", __func__); + for (i = 0; i < ctx->nkeys; i++) { + if (ctx->keys_seen[i]) + continue; + sshbuf_reset(buf); + if ((r = sshkey_putb(ctx->keys[i], buf)) != 0) + fatal("%s: sshkey_putb: %s", + __func__, ssh_err(r)); + if ((r = sshpkt_put_stringb(ssh, buf)) != 0) + fatal("%s: sshpkt_put_string: %s", + __func__, ssh_err(r)); + } + if ((r = sshpkt_send(ssh)) != 0) + fatal("%s: sshpkt_send: %s", __func__, ssh_err(r)); + client_register_global_confirm( + client_global_hostkeys_private_confirm, ctx); + ctx = NULL; /* will be freed in callback */ } /* Success */ out: - free(host_str); - free(ip_str); + hostkeys_update_ctx_free(ctx); sshkey_free(key); - for (i = 0; i < nkeys; i++) - sshkey_free(keys[i]); sshbuf_free(buf); - return success; + /* + * NB. Return success for all cases. The server doesn't need to know + * what the client does with its hosts file. + */ + return 1; } static int diff --git a/kex.h b/kex.h index 45d35773c..99a7d55bf 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.70 2015/01/26 06:10:03 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.71 2015/02/16 22:13:32 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -130,9 +130,9 @@ struct kex { int (*verify_host_key)(struct sshkey *, struct ssh *); struct sshkey *(*load_host_public_key)(int, int, struct ssh *); struct sshkey *(*load_host_private_key)(int, int, struct ssh *); - int (*host_key_index)(struct sshkey *, struct ssh *); + int (*host_key_index)(struct sshkey *, int, struct ssh *); int (*sign)(struct sshkey *, struct sshkey *, - u_char **, size_t *, u_char *, size_t, u_int); + u_char **, size_t *, const u_char *, size_t, u_int); int (*kex[KEX_MAX])(struct ssh *); /* kex specific state */ DH *dh; /* DH */ diff --git a/monitor.c b/monitor.c index e97b20ef0..6e97def1c 100644 --- a/monitor.c +++ b/monitor.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.c,v 1.143 2015/02/13 18:57:00 markus Exp $ */ +/* $OpenBSD: monitor.c,v 1.144 2015/02/16 22:13:32 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -685,12 +685,15 @@ mm_answer_moduli(int sock, Buffer *m) int mm_answer_sign(int sock, Buffer *m) { + struct ssh *ssh = active_state; /* XXX */ extern int auth_sock; /* XXX move to state struct? */ struct sshkey *key; + struct sshbuf *sigbuf; u_char *p; u_char *signature; size_t datlen, siglen; - int r, keyid; + int r, keyid, is_proof = 0; + const char proof_req[] = "hostkeys-prove@openssh.com"; debug3("%s", __func__); @@ -701,9 +704,38 @@ mm_answer_sign(int sock, Buffer *m) /* * Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes), * SHA384 (48 bytes) and SHA512 (64 bytes). + * + * Otherwise, verify the signature request is for a hostkey + * proof. + * + * XXX perform similar check for KEX signature requests too? + * it's not trivial, since what is signed is the hash, rather + * than the full kex structure... */ - if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64) - fatal("%s: data length incorrect: %zu", __func__, datlen); + if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64) { + /* + * Construct expected hostkey proof and compare it to what + * the client sent us. + */ + if (session_id2_len == 0) /* hostkeys is never first */ + fatal("%s: bad data length: %zu", __func__, datlen); + if ((key = get_hostkey_public_by_index(keyid, ssh)) == NULL) + fatal("%s: no hostkey for index %d", __func__, keyid); + if ((sigbuf = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new", __func__); + if ((r = sshbuf_put_string(sigbuf, session_id2, + session_id2_len) != 0) || + (r = sshbuf_put_cstring(sigbuf, proof_req)) != 0 || + (r = sshkey_puts(key, sigbuf)) != 0) + fatal("%s: couldn't prepare private key " + "proof buffer: %s", __func__, ssh_err(r)); + if (datlen != sshbuf_len(sigbuf) || + memcmp(p, sshbuf_ptr(sigbuf), sshbuf_len(sigbuf)) != 0) + fatal("%s: bad data length: %zu, hostkey proof len %zu", + __func__, datlen, sshbuf_len(sigbuf)); + sshbuf_free(sigbuf); + is_proof = 1; + } /* save session id, it will be passed on the first call */ if (session_id2_len == 0) { @@ -717,7 +749,7 @@ mm_answer_sign(int sock, Buffer *m) datafellows)) != 0) fatal("%s: sshkey_sign failed: %s", __func__, ssh_err(r)); - } else if ((key = get_hostkey_public_by_index(keyid, active_state)) != NULL && + } else if ((key = get_hostkey_public_by_index(keyid, ssh)) != NULL && auth_sock > 0) { if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen, p, datlen, datafellows)) != 0) { @@ -727,7 +759,8 @@ mm_answer_sign(int sock, Buffer *m) } else fatal("%s: no hostkey from index %d", __func__, keyid); - debug3("%s: signature %p(%zu)", __func__, signature, siglen); + debug3("%s: %s signature %p(%zu)", __func__, + is_proof ? "KEX" : "hostkey proof", signature, siglen); sshbuf_reset(m); if ((r = sshbuf_put_string(m, signature, siglen)) != 0) diff --git a/monitor_wrap.c b/monitor_wrap.c index c0935dc69..b379f0555 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.c,v 1.83 2015/01/19 20:16:15 markus Exp $ */ +/* $OpenBSD: monitor_wrap.c,v 1.84 2015/02/16 22:13:32 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -219,7 +219,8 @@ mm_choose_dh(int min, int nbits, int max) #endif int -mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) +mm_key_sign(Key *key, u_char **sigp, u_int *lenp, + const u_char *data, u_int datalen) { struct kex *kex = *pmonitor->m_pkex; Buffer m; @@ -227,7 +228,7 @@ mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) debug3("%s entering", __func__); buffer_init(&m); - buffer_put_int(&m, kex->host_key_index(key, active_state)); + buffer_put_int(&m, kex->host_key_index(key, 0, active_state)); buffer_put_string(&m, data, datalen); mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SIGN, &m); diff --git a/monitor_wrap.h b/monitor_wrap.h index d97e8db1e..e18784ac4 100644 --- a/monitor_wrap.h +++ b/monitor_wrap.h @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.h,v 1.25 2015/01/19 19:52:16 markus Exp $ */ +/* $OpenBSD: monitor_wrap.h,v 1.26 2015/02/16 22:13:32 djm Exp $ */ /* * Copyright 2002 Niels Provos @@ -40,7 +40,7 @@ struct Authctxt; void mm_log_handler(LogLevel, const char *, void *); int mm_is_monitor(void); DH *mm_choose_dh(int, int, int); -int mm_key_sign(Key *, u_char **, u_int *, u_char *, u_int); +int mm_key_sign(Key *, u_char **, u_int *, const u_char *, u_int); void mm_inform_authserv(char *, char *); struct passwd *mm_getpwnamallow(const char *); char *mm_auth2_read_banner(void); diff --git a/readconf.c b/readconf.c index a5bb4a25e..42a2961fa 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.231 2015/02/02 07:41:40 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.232 2015/02/16 22:13:32 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1480,7 +1480,8 @@ parse_int: case oUpdateHostkeys: intptr = &options->update_hostkeys; - goto parse_flag; + multistate_ptr = multistate_yesnoask; + goto parse_multistate; case oHostbasedKeyTypes: charptr = &options->hostbased_key_types; @@ -2107,6 +2108,7 @@ fmt_intarg(OpCodes code, int val) return fmt_multistate_int(val, multistate_addressfamily); case oVerifyHostKeyDNS: case oStrictHostKeyChecking: + case oUpdateHostkeys: return fmt_multistate_int(val, multistate_yesnoask); case oControlMaster: return fmt_multistate_int(val, multistate_controlmaster); diff --git a/readconf.h b/readconf.h index 701b9c696..576b9e352 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.108 2015/01/30 11:43:14 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.109 2015/02/16 22:13:32 djm Exp $ */ /* * Author: Tatu Ylonen @@ -148,7 +148,7 @@ typedef struct { int fingerprint_hash; - int update_hostkeys; + int update_hostkeys; /* one of SSH_UPDATE_HOSTKEYS_* */ char *hostbased_key_types; @@ -174,6 +174,10 @@ typedef struct { #define SSHCONF_USERCONF 2 /* user provided config file not system */ #define SSHCONF_POSTCANON 4 /* After hostname canonicalisation */ +#define SSH_UPDATE_HOSTKEYS_NO 0 +#define SSH_UPDATE_HOSTKEYS_YES 1 +#define SSH_UPDATE_HOSTKEYS_ASK 2 + void initialize_options(Options *); void fill_default_options(Options *); void fill_default_options_for_canonicalization(Options *); diff --git a/serverloop.c b/serverloop.c index 48bb3f631..5633ceb41 100644 --- a/serverloop.c +++ b/serverloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: serverloop.c,v 1.176 2015/01/20 23:14:00 deraadt Exp $ */ +/* $OpenBSD: serverloop.c,v 1.177 2015/02/16 22:13:32 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -79,6 +79,7 @@ #include "auth-options.h" #include "serverloop.h" #include "roaming.h" +#include "ssherr.h" extern ServerOptions options; @@ -1149,12 +1150,83 @@ server_input_channel_open(int type, u_int32_t seq, void *ctxt) return 0; } +static int +server_input_hostkeys_prove(struct sshbuf **respp) +{ + struct ssh *ssh = active_state; /* XXX */ + struct sshbuf *resp = NULL; + struct sshbuf *sigbuf = NULL; + struct sshkey *key = NULL, *key_pub = NULL, *key_prv = NULL; + int r, ndx, success = 0; + const u_char *blob; + u_char *sig = 0; + size_t blen, slen; + + if ((resp = sshbuf_new()) == NULL || (sigbuf = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new", __func__); + + while (ssh_packet_remaining(ssh) > 0) { + sshkey_free(key); + key = NULL; + if ((r = sshpkt_get_string_direct(ssh, &blob, &blen)) != 0 || + (r = sshkey_from_blob(blob, blen, &key)) != 0) { + error("%s: couldn't parse key: %s", + __func__, ssh_err(r)); + goto out; + } + /* + * Better check that this is actually one of our hostkeys + * before attempting to sign anything with it. + */ + if ((ndx = ssh->kex->host_key_index(key, 1, ssh)) == -1) { + error("%s: unknown host %s key", + __func__, sshkey_type(key)); + goto out; + } + /* + * XXX refactor: make kex->sign just use an index rather + * than passing in public and private keys + */ + if ((key_prv = get_hostkey_by_index(ndx)) == NULL && + (key_pub = get_hostkey_public_by_index(ndx, ssh)) == NULL) { + error("%s: can't retrieve hostkey %d", __func__, ndx); + goto out; + } + sshbuf_reset(sigbuf); + free(sig); + sig = NULL; + if ((r = sshbuf_put_string(sigbuf, + ssh->kex->session_id, ssh->kex->session_id_len)) != 0 || + (r = sshbuf_put_cstring(sigbuf, + "hostkeys-prove@openssh.com")) != 0 || + (r = sshkey_puts(key, sigbuf)) != 0 || + (r = ssh->kex->sign(key_prv, key_pub, &sig, &slen, + sshbuf_ptr(sigbuf), sshbuf_len(sigbuf), 0)) != 0 || + (r = sshbuf_put_string(resp, sig, slen)) != 0) { + error("%s: couldn't prepare signature: %s", + __func__, ssh_err(r)); + goto out; + } + } + /* Success */ + *respp = resp; + resp = NULL; /* don't free it */ + success = 1; + out: + free(sig); + sshbuf_free(resp); + sshbuf_free(sigbuf); + sshkey_free(key); + return success; +} + static int server_input_global_request(int type, u_int32_t seq, void *ctxt) { char *rtype; int want_reply; - int success = 0, allocated_listen_port = 0; + int r, success = 0, allocated_listen_port = 0; + struct sshbuf *resp = NULL; rtype = packet_get_string(NULL); want_reply = packet_get_char(); @@ -1191,6 +1263,10 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt) &allocated_listen_port, &options.fwd_opts); } free(fwd.listen_host); + if ((resp = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new", __func__); + if ((r = sshbuf_put_u32(resp, allocated_listen_port)) != 0) + fatal("%s: sshbuf_put_u32: %s", __func__, ssh_err(r)); } else if (strcmp(rtype, "cancel-tcpip-forward") == 0) { struct Forward fwd; @@ -1234,16 +1310,20 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt) } else if (strcmp(rtype, "no-more-sessions@openssh.com") == 0) { no_more_sessions = 1; success = 1; + } else if (strcmp(rtype, "hostkeys-prove@openssh.com") == 0) { + success = server_input_hostkeys_prove(&resp); } if (want_reply) { packet_start(success ? SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); - if (success && allocated_listen_port > 0) - packet_put_int(allocated_listen_port); + if (success && resp != NULL) + ssh_packet_put_raw(active_state, sshbuf_ptr(resp), + sshbuf_len(resp)); packet_send(); packet_write_wait(); } free(rtype); + sshbuf_free(resp); return 0; } diff --git a/ssh_api.c b/ssh_api.c index 7097c063c..265a3e639 100644 --- a/ssh_api.c +++ b/ssh_api.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh_api.c,v 1.3 2015/01/30 01:13:33 djm Exp $ */ +/* $OpenBSD: ssh_api.c,v 1.4 2015/02/16 22:13:32 djm Exp $ */ /* * Copyright (c) 2012 Markus Friedl. All rights reserved. * @@ -41,7 +41,7 @@ int _ssh_verify_host_key(struct sshkey *, struct ssh *); struct sshkey *_ssh_host_public_key(int, int, struct ssh *); struct sshkey *_ssh_host_private_key(int, int, struct ssh *); int _ssh_host_key_sign(struct sshkey *, struct sshkey *, u_char **, - size_t *, u_char *, size_t, u_int); + size_t *, const u_char *, size_t, u_int); /* * stubs for the server side implementation of kex. @@ -524,7 +524,8 @@ _ssh_order_hostkeyalgs(struct ssh *ssh) int _ssh_host_key_sign(struct sshkey *privkey, struct sshkey *pubkey, - u_char **signature, size_t *slen, u_char *data, size_t dlen, u_int compat) + u_char **signature, size_t *slen, + const u_char *data, size_t dlen, u_int compat) { return sshkey_sign(privkey, signature, slen, data, dlen, compat); } diff --git a/ssh_config.5 b/ssh_config.5 index ce79fe03f..fa59c518e 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.203 2015/02/02 07:41:40 djm Exp $ -.Dd $Mdocdate: February 2 2015 $ +.\" $OpenBSD: ssh_config.5,v 1.204 2015/02/16 22:13:32 djm Exp $ +.Dd $Mdocdate: February 16 2015 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1510,15 +1510,20 @@ should accept notifications of additional hostkeys from the server sent after authentication has completed and add them to .Cm UserKnownHostsFile . The argument must be -.Dq yes -or +.Dq yes , .Dq no -(the default). +(the default) or +.Dq ask . Enabling this option allows learning alternate hostkeys for a server and supports graceful key rotation by allowing a server to send replacement public keys before old ones are removed. Additional hostkeys are only accepted if the key used to authenticate the host was already trusted or explicity accepted by the user. +If +.Cm UpdateHostKeys +is set to +.Dq ask , +then the user is asked to confirm the modifications to the known_hosts file. .Pp Presently, only .Xr sshd 8 diff --git a/sshd.c b/sshd.c index 4282bdc1b..aaa63d497 100644 --- a/sshd.c +++ b/sshd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.441 2015/01/31 20:30:05 djm Exp $ */ +/* $OpenBSD: sshd.c,v 1.442 2015/02/16 22:13:32 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -894,18 +894,25 @@ get_hostkey_public_by_index(int ind, struct ssh *ssh) } int -get_hostkey_index(Key *key, struct ssh *ssh) +get_hostkey_index(Key *key, int compare, struct ssh *ssh) { int i; for (i = 0; i < options.num_host_key_files; i++) { if (key_is_cert(key)) { - if (key == sensitive_data.host_certificates[i]) + if (key == sensitive_data.host_certificates[i] || + (compare && sensitive_data.host_certificates[i] && + sshkey_equal(key, + sensitive_data.host_certificates[i]))) return (i); } else { - if (key == sensitive_data.host_keys[i]) + if (key == sensitive_data.host_keys[i] || + (compare && sensitive_data.host_keys[i] && + sshkey_equal(key, sensitive_data.host_keys[i]))) return (i); - if (key == sensitive_data.host_pubkeys[i]) + if (key == sensitive_data.host_pubkeys[i] || + (compare && sensitive_data.host_pubkeys[i] && + sshkey_equal(key, sensitive_data.host_pubkeys[i]))) return (i); } } @@ -933,19 +940,23 @@ notify_hostkeys(struct ssh *ssh) debug3("%s: key %d: %s %s", __func__, i, sshkey_ssh_name(key), fp); free(fp); - if ((r = sshkey_puts(key, buf)) != 0) + if (nkeys == 0) { + packet_start(SSH2_MSG_GLOBAL_REQUEST); + packet_put_cstring("hostkeys@openssh.com"); + packet_put_char(0); /* want-reply */ + } + sshbuf_reset(buf); + if ((r = sshkey_putb(key, buf)) != 0) fatal("%s: couldn't put hostkey %d: %s", __func__, i, ssh_err(r)); + packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf)); nkeys++; } + debug3("%s: sent %d hostkeys", __func__, nkeys); if (nkeys == 0) fatal("%s: no hostkeys", __func__); - debug3("%s: send %d hostkeys", __func__, nkeys); - packet_start(SSH2_MSG_GLOBAL_REQUEST); - packet_put_cstring("hostkeys@openssh.com"); - packet_put_char(0); /* want-reply */ - packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf)); packet_send(); + sshbuf_free(buf); } /* @@ -2484,7 +2495,7 @@ do_ssh1_kex(void) int sshd_hostkey_sign(Key *privkey, Key *pubkey, u_char **signature, size_t *slen, - u_char *data, size_t dlen, u_int flag) + const u_char *data, size_t dlen, u_int flag) { int r; u_int xxx_slen, xxx_dlen = dlen; diff --git a/ssherr.c b/ssherr.c index 5c29c467c..4ca793992 100644 --- a/ssherr.c +++ b/ssherr.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssherr.c,v 1.3 2015/01/30 01:13:33 djm Exp $ */ +/* $OpenBSD: ssherr.c,v 1.4 2015/02/16 22:13:32 djm Exp $ */ /* * Copyright (c) 2011 Damien Miller * @@ -121,6 +121,8 @@ ssh_err(int n) return "agent not present"; case SSH_ERR_AGENT_NO_IDENTITIES: return "agent contains no identities"; + case SSH_ERR_BUFFER_READ_ONLY: + return "internal error: buffer is read-only"; case SSH_ERR_KRL_BAD_MAGIC: return "KRL file has invalid magic number"; case SSH_ERR_KEY_REVOKED: -- cgit v1.2.3