-/* $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.
*
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");
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)
{
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;
}
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;
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;
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);
}
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)
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");
}
-/* $OpenBSD: sshconnect2.c,v 1.370 2023/12/18 14:45:17 djm Exp $ */
+/* $OpenBSD: sshconnect2.c,v 1.371 2023/12/18 14:45:49 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2008 Damien Miller. All rights reserved.
authctxt.mech_tried = 0;
#endif
authctxt.agent_fd = -1;
- pubkey_prepare(ssh, &authctxt);
- if (authctxt.method == NULL) {
+ if (authctxt.method == NULL)
fatal_f("internal error: cannot send userauth none request");
- }
if ((r = sshpkt_start(ssh, SSH2_MSG_SERVICE_REQUEST)) != 0 ||
(r = sshpkt_put_cstring(ssh, "ssh-userauth")) != 0 ||
/* initial userauth request */
userauth_none(ssh);
- ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &input_userauth_error);
+ /* accept EXT_INFO at any time during userauth */
+ ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, ssh->kex->ext_info_s ?
+ &kex_input_ext_info : &input_userauth_error);
ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_SUCCESS, &input_userauth_success);
ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_FAILURE, &input_userauth_failure);
ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_BANNER, &input_userauth_banner);
struct identity *id, *id2, *tmp;
struct idlist agent, files, *preferred;
struct sshkey *key;
- int agent_fd = -1, i, r, found;
+ int disallowed, agent_fd = -1, i, r, found;
size_t j;
struct ssh_identitylist *idlist;
- char *ident;
+ char *cp, *ident;
TAILQ_INIT(&agent); /* keys from the agent */
TAILQ_INIT(&files); /* keys from the config file */
TAILQ_CONCAT(preferred, &files, next);
/* finally, filter by PubkeyAcceptedAlgorithms */
TAILQ_FOREACH_SAFE(id, preferred, next, id2) {
- if (id->key != NULL && !key_type_allowed_by_config(id->key)) {
- debug("Skipping %s key %s - "
- "corresponding algo not in PubkeyAcceptedAlgorithms",
- sshkey_ssh_name(id->key), id->filename);
- TAILQ_REMOVE(preferred, id, next);
- sshkey_free(id->key);
- free(id->filename);
- memset(id, 0, sizeof(*id));
+ disallowed = 0;
+ cp = NULL;
+ if (id->key == NULL)
continue;
+ if (!key_type_allowed_by_config(id->key)) {
+ debug("Skipping %s key %s - corresponding algorithm "
+ "not in PubkeyAcceptedAlgorithms",
+ sshkey_ssh_name(id->key), id->filename);
+ disallowed = 1;
+ } else if (ssh->kex->server_sig_algs != NULL &&
+ (cp = key_sig_algorithm(ssh, id->key)) == NULL) {
+ debug("Skipping %s key %s - corresponding algorithm "
+ "not supported by server",
+ sshkey_ssh_name(id->key), id->filename);
+ disallowed = 1;
}
+ free(cp);
+ if (!disallowed)
+ continue;
+ /* remove key */
+ TAILQ_REMOVE(preferred, id, next);
+ sshkey_free(id->key);
+ free(id->filename);
+ memset(id, 0, sizeof(*id));
}
/* List the keys we plan on using */
TAILQ_FOREACH_SAFE(id, preferred, next, id2) {
Identity *id;
int sent = 0;
char *ident;
+ static int prepared;
+
+ if (!prepared) {
+ pubkey_prepare(ssh, authctxt);
+ prepared = 1;
+ }
while ((id = TAILQ_FIRST(&authctxt->keys))) {
if (id->tried++)