diff options
-rw-r--r-- | PROTOCOL | 21 | ||||
-rw-r--r-- | auth2.c | 8 | ||||
-rw-r--r-- | kex.c | 233 | ||||
-rw-r--r-- | kex.h | 7 | ||||
-rw-r--r-- | monitor_wrap.c | 4 | ||||
-rw-r--r-- | sshconnect2.c | 50 | ||||
-rw-r--r-- | sshd.c | 4 |
7 files changed, 260 insertions, 67 deletions
@@ -163,6 +163,25 @@ b) After sending or receiving a SSH2_MSG_NEWKEYS message, reset the duration of the connection (i.e. not just the first SSH2_MSG_NEWKEYS). +1.10 transport: SSH2_MSG_EXT_INFO during user authentication + +This protocol extension allows the SSH2_MSG_EXT_INFO to be sent +during user authentication. RFC8308 does allow a second +SSH2_MSG_EXT_INFO notification, but it may only be sent at the end +of user authentication and this is too late to signal per-user +server signature algorithms. + +Support for receiving the SSH2_MSG_EXT_INFO message during user +authentication is signalled by the client including a +"ext-info-in-auth@openssh.com" key via its initial SSH2_MSG_EXT_INFO +set after the SSH2_MSG_NEWKEYS message. + +A server that supports this extension MAY send a second +SSH2_MSG_EXT_INFO message any time after the client's first +SSH2_MSG_USERAUTH_REQUEST, regardless of whether it succeed or fails. +The client SHOULD be prepared to update the server-sig-algs that +it received during an earlier SSH2_MSG_EXT_INFO with the later one. + 2. Connection protocol changes 2.1. connection: Channel write close extension "eow@openssh.com" @@ -771,4 +790,4 @@ master instance and later clients. OpenSSH extends the usual agent protocol. These changes are documented in the PROTOCOL.agent file. -$OpenBSD: PROTOCOL,v 1.50 2023/12/18 14:45:17 djm Exp $ +$OpenBSD: PROTOCOL,v 1.51 2023/12/18 14:45:49 djm Exp $ @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2.c,v 1.167 2023/08/28 09:48:11 djm Exp $ */ +/* $OpenBSD: auth2.c,v 1.168 2023/12/18 14:45:49 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -57,6 +57,7 @@ #endif #include "monitor_wrap.h" #include "digest.h" +#include "kex.h" /* import */ extern ServerOptions options; @@ -172,6 +173,8 @@ do_authentication2(struct ssh *ssh) Authctxt *authctxt = ssh->authctxt; ssh_dispatch_init(ssh, &dispatch_protocol_error); + if (ssh->kex->ext_info_c) + ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &kex_input_ext_info); ssh_dispatch_set(ssh, SSH2_MSG_SERVICE_REQUEST, &input_service_request); ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &authctxt->success); ssh->authctxt = NULL; @@ -211,6 +214,7 @@ input_service_request(int type, u_int32_t seq, struct ssh *ssh) debug("bad service request %s", service); ssh_packet_disconnect(ssh, "bad service request %s", service); } + ssh_dispatch_set(ssh, SSH2_MSG_EXT_INFO, &dispatch_protocol_error); r = 0; out: free(service); @@ -313,6 +317,8 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) if (use_privsep) mm_inform_authserv(service, style); userauth_banner(ssh); + if ((r = kex_server_update_ext_info(ssh)) != 0) + fatal_fr(r, "kex_server_update_ext_info failed"); if (auth2_setup_methods_lists(authctxt) != 0) ssh_packet_disconnect(ssh, "no authentication methods enabled"); @@ -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"); } @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.120 2023/12/18 14:45:17 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.121 2023/12/18 14:45:49 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -112,6 +112,7 @@ enum kex_exchange { #define KEX_RSA_SHA2_256_SUPPORTED 0x0008 /* only set in server for now */ #define KEX_RSA_SHA2_512_SUPPORTED 0x0010 /* only set in server for now */ #define KEX_HAS_PING 0x0020 +#define KEX_HAS_EXT_INFO_IN_AUTH 0x0040 struct sshenc { char *name; @@ -149,7 +150,9 @@ struct kex { u_int kex_type; char *server_sig_algs; int ext_info_c; + int ext_info_s; int kex_strict; + int ext_info_received; struct sshbuf *my; struct sshbuf *peer; struct sshbuf *client_version; @@ -209,6 +212,8 @@ int kex_protocol_error(int, u_int32_t, struct ssh *); int kex_derive_keys(struct ssh *, u_char *, u_int, const struct sshbuf *); int kex_send_newkeys(struct ssh *); int kex_start_rekex(struct ssh *); +int kex_server_update_ext_info(struct ssh *); +void kex_set_server_sig_algs(struct ssh *, const char *); int kexgex_client(struct ssh *); int kexgex_server(struct ssh *); diff --git a/monitor_wrap.c b/monitor_wrap.c index 3533cf06..6270d139 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.c,v 1.128 2023/03/31 00:44:29 dtucker Exp $ */ +/* $OpenBSD: monitor_wrap.c,v 1.129 2023/12/18 14:45:49 djm Exp $ */ /* * Copyright 2002 Niels Provos <provos@citi.umich.edu> * Copyright 2002 Markus Friedl <markus@openbsd.org> @@ -340,8 +340,8 @@ out: log_verbose_add(options.log_verbose[i]); process_permitopen(ssh, &options); process_channel_timeouts(ssh, &options); + kex_set_server_sig_algs(ssh, options.pubkey_accepted_algos); free(newopts); - sshbuf_free(m); return (pw); diff --git a/sshconnect2.c b/sshconnect2.c index 0cccbcc4..fab1e36b 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,4 +1,4 @@ -/* $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. @@ -459,10 +459,8 @@ ssh_userauth2(struct ssh *ssh, const char *local_user, 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 || @@ -521,7 +519,9 @@ input_userauth_service_accept(int type, u_int32_t seq, struct ssh *ssh) /* 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); @@ -1678,10 +1678,10 @@ pubkey_prepare(struct ssh *ssh, Authctxt *authctxt) 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 */ @@ -1799,16 +1799,30 @@ pubkey_prepare(struct ssh *ssh, Authctxt *authctxt) 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) { @@ -1854,6 +1868,12 @@ userauth_pubkey(struct ssh *ssh) 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++) @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.600 2023/03/08 04:43:12 guenther Exp $ */ +/* $OpenBSD: sshd.c,v 1.601 2023/12/18 14:45:49 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -2405,7 +2405,9 @@ do_ssh2_kex(struct ssh *ssh) /* start key exchange */ if ((r = kex_setup(ssh, myproposal)) != 0) fatal_r(r, "kex_setup"); + kex_set_server_sig_algs(ssh, options.pubkey_accepted_algos); kex = ssh->kex; + #ifdef WITH_OPENSSL kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server; kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server; |