summaryrefslogtreecommitdiffstats
path: root/kex.c
diff options
context:
space:
mode:
Diffstat (limited to 'kex.c')
-rw-r--r--kex.c233
1 files changed, 187 insertions, 46 deletions
diff --git a/kex.c b/kex.c
index d478ff6e..cbb2af59 100644
--- a/kex.c
+++ b/kex.c
@@ -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");
}