diff options
Diffstat (limited to 'kex.c')
-rw-r--r-- | kex.c | 233 |
1 files changed, 187 insertions, 46 deletions
@@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.183 2023/12/18 14:45:17 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.184 2023/12/18 14:45:49 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -353,7 +353,7 @@ kex_proposal_populate_entries(struct ssh *ssh, char *prop[PROPOSAL_MAX], if (kexalgos == NULL) kexalgos = defprop[PROPOSAL_KEX_ALGS]; if ((cp = kex_names_cat(kexalgos, ssh->kex->server ? - "kex-strict-s-v00@openssh.com" : + "ext-info-s,kex-strict-s-v00@openssh.com" : "ext-info-c,kex-strict-c-v00@openssh.com")) == NULL) fatal_f("kex_names_cat"); @@ -505,37 +505,139 @@ kex_reset_dispatch(struct ssh *ssh) SSH2_MSG_TRANSPORT_MAX, &kex_protocol_error); } +void +kex_set_server_sig_algs(struct ssh *ssh, const char *allowed_algs) +{ + char *alg, *oalgs, *algs, *sigalgs; + const char *sigalg; + + /* + * NB. allowed algorithms may contain certificate algorithms that + * map to a specific plain signature type, e.g. + * rsa-sha2-512-cert-v01@openssh.com => rsa-sha2-512 + * We need to be careful here to match these, retain the mapping + * and only add each signature algorithm once. + */ + if ((sigalgs = sshkey_alg_list(0, 1, 1, ',')) == NULL) + fatal_f("sshkey_alg_list failed"); + oalgs = algs = xstrdup(allowed_algs); + free(ssh->kex->server_sig_algs); + ssh->kex->server_sig_algs = NULL; + for ((alg = strsep(&algs, ",")); alg != NULL && *alg != '\0'; + (alg = strsep(&algs, ","))) { + if ((sigalg = sshkey_sigalg_by_name(alg)) == NULL) + continue; + if (!has_any_alg(sigalg, sigalgs)) + continue; + /* Don't add an algorithm twice. */ + if (ssh->kex->server_sig_algs != NULL && + has_any_alg(sigalg, ssh->kex->server_sig_algs)) + continue; + xextendf(&ssh->kex->server_sig_algs, ",", "%s", sigalg); + } + free(oalgs); + free(sigalgs); + if (ssh->kex->server_sig_algs == NULL) + ssh->kex->server_sig_algs = xstrdup(""); +} + static int -kex_send_ext_info(struct ssh *ssh) +kex_compose_ext_info_server(struct ssh *ssh, struct sshbuf *m) { int r; - char *algs; - debug("Sending SSH2_MSG_EXT_INFO"); - if ((algs = sshkey_alg_list(0, 1, 1, ',')) == NULL) + if (ssh->kex->server_sig_algs == NULL && + (ssh->kex->server_sig_algs = sshkey_alg_list(0, 1, 1, ',')) == NULL) return SSH_ERR_ALLOC_FAIL; - /* XXX filter algs list by allowed pubkey/hostbased types */ - if ((r = sshpkt_start(ssh, SSH2_MSG_EXT_INFO)) != 0 || - (r = sshpkt_put_u32(ssh, 3)) != 0 || - (r = sshpkt_put_cstring(ssh, "server-sig-algs")) != 0 || - (r = sshpkt_put_cstring(ssh, algs)) != 0 || - (r = sshpkt_put_cstring(ssh, + if ((r = sshbuf_put_u32(m, 3)) != 0 || + (r = sshbuf_put_cstring(m, "server-sig-algs")) != 0 || + (r = sshbuf_put_cstring(m, ssh->kex->server_sig_algs)) != 0 || + (r = sshbuf_put_cstring(m, "publickey-hostbound@openssh.com")) != 0 || - (r = sshpkt_put_cstring(ssh, "0")) != 0 || - (r = sshpkt_put_cstring(ssh, "ping@openssh.com")) != 0 || - (r = sshpkt_put_cstring(ssh, "0")) != 0 || - (r = sshpkt_send(ssh)) != 0) { + (r = sshbuf_put_cstring(m, "0")) != 0 || + (r = sshbuf_put_cstring(m, "ping@openssh.com")) != 0 || + (r = sshbuf_put_cstring(m, "0")) != 0) { + error_fr(r, "compose"); + return r; + } + return 0; +} + +static int +kex_compose_ext_info_client(struct ssh *ssh, struct sshbuf *m) +{ + int r; + + if ((r = sshbuf_put_u32(m, 1)) != 0 || + (r = sshbuf_put_cstring(m, "ext-info-in-auth@openssh.com")) != 0 || + (r = sshbuf_put_cstring(m, "0")) != 0) { error_fr(r, "compose"); goto out; } /* success */ r = 0; out: - free(algs); + return r; +} + +static int +kex_maybe_send_ext_info(struct ssh *ssh) +{ + int r; + struct sshbuf *m = NULL; + + if ((ssh->kex->flags & KEX_INITIAL) == 0) + return 0; + if (!ssh->kex->ext_info_c && !ssh->kex->ext_info_s) + return 0; + + /* Compose EXT_INFO packet. */ + if ((m = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); + if (ssh->kex->ext_info_c && + (r = kex_compose_ext_info_server(ssh, m)) != 0) + goto fail; + if (ssh->kex->ext_info_s && + (r = kex_compose_ext_info_client(ssh, m)) != 0) + goto fail; + + /* Send the actual KEX_INFO packet */ + debug("Sending SSH2_MSG_EXT_INFO"); + if ((r = sshpkt_start(ssh, SSH2_MSG_EXT_INFO)) != 0 || + (r = sshpkt_putb(ssh, m)) != 0 || + (r = sshpkt_send(ssh)) != 0) { + error_f("send EXT_INFO"); + goto fail; + } + + r = 0; + + fail: + sshbuf_free(m); return r; } int +kex_server_update_ext_info(struct ssh *ssh) +{ + int r; + + if ((ssh->kex->flags & KEX_HAS_EXT_INFO_IN_AUTH) == 0) + return 0; + + debug_f("Sending SSH2_MSG_EXT_INFO"); + if ((r = sshpkt_start(ssh, SSH2_MSG_EXT_INFO)) != 0 || + (r = sshpkt_put_u32(ssh, 1)) != 0 || + (r = sshpkt_put_cstring(ssh, "server-sig-algs")) != 0 || + (r = sshpkt_put_cstring(ssh, ssh->kex->server_sig_algs)) != 0 || + (r = sshpkt_send(ssh)) != 0) { + error_f("send EXT_INFO"); + return r; + } + return 0; +} + +int kex_send_newkeys(struct ssh *ssh) { int r; @@ -546,9 +648,8 @@ kex_send_newkeys(struct ssh *ssh) return r; debug("SSH2_MSG_NEWKEYS sent"); ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); - if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) - if ((r = kex_send_ext_info(ssh)) != 0) - return r; + if ((r = kex_maybe_send_ext_info(ssh)) != 0) + return r; debug("expecting SSH2_MSG_NEWKEYS"); return 0; } @@ -570,10 +671,61 @@ kex_ext_info_check_ver(struct kex *kex, const char *name, return 0; } +static int +kex_ext_info_client_parse(struct ssh *ssh, const char *name, + const u_char *value, size_t vlen) +{ + int r; + + /* NB. some messages are only accepted in the initial EXT_INFO */ + if (strcmp(name, "server-sig-algs") == 0) { + /* Ensure no \0 lurking in value */ + if (memchr(value, '\0', vlen) != NULL) { + error_f("nul byte in %s", name); + return SSH_ERR_INVALID_FORMAT; + } + debug_f("%s=<%s>", name, value); + free(ssh->kex->server_sig_algs); + ssh->kex->server_sig_algs = xstrdup((const char *)value); + } else if (ssh->kex->ext_info_received == 1 && + strcmp(name, "publickey-hostbound@openssh.com") == 0) { + if ((r = kex_ext_info_check_ver(ssh->kex, name, value, vlen, + "0", KEX_HAS_PUBKEY_HOSTBOUND)) != 0) { + return r; + } + } else if (ssh->kex->ext_info_received == 1 && + strcmp(name, "ping@openssh.com") == 0) { + if ((r = kex_ext_info_check_ver(ssh->kex, name, value, vlen, + "0", KEX_HAS_PING)) != 0) { + return r; + } + } else + debug_f("%s (unrecognised)", name); + + return 0; +} + +static int +kex_ext_info_server_parse(struct ssh *ssh, const char *name, + const u_char *value, size_t vlen) +{ + int r; + + if (strcmp(name, "ext-info-in-auth@openssh.com") == 0) { + if ((r = kex_ext_info_check_ver(ssh->kex, name, value, vlen, + "0", KEX_HAS_EXT_INFO_IN_AUTH)) != 0) { + return r; + } + } else + debug_f("%s (unrecognised)", name); + return 0; +} + int kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) { struct kex *kex = ssh->kex; + const int max_ext_info = kex->server ? 1 : 2; u_int32_t i, ninfo; char *name; u_char *val; @@ -581,6 +733,10 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) int r; debug("SSH2_MSG_EXT_INFO received"); + if (++kex->ext_info_received > max_ext_info) { + error("too many SSH2_MSG_EXT_INFO messages sent by peer"); + return dispatch_protocol_error(type, seq, ssh); + } ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &kex_protocol_error); if ((r = sshpkt_get_u32(ssh, &ninfo)) != 0) return r; @@ -596,34 +752,16 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) free(name); return r; } - if (strcmp(name, "server-sig-algs") == 0) { - /* Ensure no \0 lurking in value */ - if (memchr(val, '\0', vlen) != NULL) { - error_f("nul byte in %s", name); - free(name); - free(val); - return SSH_ERR_INVALID_FORMAT; - } - debug_f("%s=<%s>", name, val); - kex->server_sig_algs = val; - val = NULL; - } else if (strcmp(name, - "publickey-hostbound@openssh.com") == 0) { - if ((r = kex_ext_info_check_ver(kex, name, val, vlen, - "0", KEX_HAS_PUBKEY_HOSTBOUND)) != 0) { - free(name); - free(val); + debug3_f("extension %s", name); + if (kex->server) { + if ((r = kex_ext_info_server_parse(ssh, name, + val, vlen)) != 0) return r; - } - } else if (strcmp(name, "ping@openssh.com") == 0) { - if ((r = kex_ext_info_check_ver(kex, name, val, vlen, - "0", KEX_HAS_PING)) != 0) { - free(name); - free(val); + } else { + if ((r = kex_ext_info_client_parse(ssh, name, + val, vlen)) != 0) return r; - } - } else - debug_f("%s (unrecognised)", name); + } free(name); free(val); } @@ -637,6 +775,8 @@ kex_input_newkeys(int type, u_int32_t seq, struct ssh *ssh) int r; debug("SSH2_MSG_NEWKEYS received"); + if (kex->ext_info_c && (kex->flags & KEX_INITIAL) != 0) + ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &kex_input_ext_info); ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_protocol_error); ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); if ((r = sshpkt_get_end(ssh)) != 0) @@ -1044,6 +1184,7 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq) kex->kex_strict = kexalgs_contains(peer, "kex-strict-c-v00@openssh.com"); } else { + kex->ext_info_s = kexalgs_contains(peer, "ext-info-s"); kex->kex_strict = kexalgs_contains(peer, "kex-strict-s-v00@openssh.com"); } |