Mercurial > dropbear
changeset 1676:d5cdc60db08e
ext-info handling for server-sig-algs
only client side is handled
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Tue, 19 May 2020 00:31:41 +0800 |
parents | ae41624c2198 |
children | e05c0e394f1d |
files | algo.h buffer.c buffer.h cli-authpubkey.c cli-kex.c cli-main.c cli-session.c common-algo.c common-kex.c fuzzer-verify.c kex.h session.h signkey.c signkey.h ssh.h svr-session.c |
diffstat | 16 files changed, 256 insertions(+), 91 deletions(-) [+] |
line wrap: on
line diff
--- a/algo.h Sun May 17 23:58:31 2020 +0800 +++ b/algo.h Tue May 19 00:31:41 2020 +0800 @@ -114,18 +114,11 @@ void buf_put_algolist(buffer * buf, const algo_type localalgos[]); -enum kexguess2_used { - KEXGUESS2_LOOK, - KEXGUESS2_NO, - KEXGUESS2_YES, -}; +#define KEXGUESS2_ALGO_NAME "[email protected]" -#define KEXGUESS2_ALGO_NAME "[email protected]" -#define KEXGUESS2_ALGO_ID 99 - - +int buf_has_algo(buffer *buf, const char *algo); algo_type * buf_match_algo(buffer* buf, algo_type localalgos[], - enum kexguess2_used *kexguess2, int *goodguess); + int kexguess2, int *goodguess); #if DROPBEAR_USER_ALGO_LIST int check_user_algos(const char* user_algo_list, algo_type * algos,
--- a/buffer.c Sun May 17 23:58:31 2020 +0800 +++ b/buffer.c Tue May 19 00:31:41 2020 +0800 @@ -228,19 +228,37 @@ } /* Return a string as a newly allocated buffer */ -buffer * buf_getstringbuf(buffer *buf) { +static buffer * buf_getstringbuf_int(buffer *buf, int incllen) { buffer *ret = NULL; unsigned int len = buf_getint(buf); + int extra = 0; if (len > MAX_STRING_LEN) { dropbear_exit("String too long"); } - ret = buf_new(len); + if (incllen) { + extra = 4; + } + ret = buf_new(len+extra); + if (incllen) { + buf_putint(ret, len); + } memcpy(buf_getwriteptr(ret, len), buf_getptr(buf, len), len); buf_incrpos(buf, len); buf_incrlen(ret, len); + buf_setpos(ret, 0); return ret; } +/* Return a string as a newly allocated buffer */ +buffer * buf_getstringbuf(buffer *buf) { + return buf_getstringbuf_int(buf, 0); +} + +/* Returns a string in a new buffer, including the length */ +buffer * buf_getbuf(buffer *buf) { + return buf_getstringbuf_int(buf, 1); +} + /* Just increment the buffer position the same as if we'd used buf_getstring, * but don't bother copying/malloc()ing for it */ void buf_eatstring(buffer *buf) {
--- a/buffer.h Sun May 17 23:58:31 2020 +0800 +++ b/buffer.h Tue May 19 00:31:41 2020 +0800 @@ -58,6 +58,7 @@ unsigned char* buf_getwriteptr(const buffer* buf, unsigned int len); char* buf_getstring(buffer* buf, unsigned int *retlen); buffer * buf_getstringbuf(buffer *buf); +buffer * buf_getbuf(buffer *buf); void buf_eatstring(buffer *buf); void buf_putint(buffer* buf, unsigned int val); void buf_putstring(buffer* buf, const char* str, unsigned int len);
--- a/cli-authpubkey.c Sun May 17 23:58:31 2020 +0800 +++ b/cli-authpubkey.c Tue May 19 00:31:41 2020 +0800 @@ -184,6 +184,7 @@ /* Returns 1 if a key was tried */ int cli_auth_pubkey() { + enum signature_type sigtype; TRACE(("enter cli_auth_pubkey")) #if DROPBEAR_CLI_AGENTFWD @@ -191,28 +192,77 @@ /* get the list of available keys from the agent */ cli_load_agent_keys(cli_opts.privkeys); cli_opts.agent_keys_loaded = 1; + TRACE(("cli_auth_pubkey: agent keys loaded")) } #endif - /* TODO iterate through privkeys to skip ones not in server-sig-algs */ - - /* TODO: testing */ + /* iterate through privkeys to remove ones not allowed in server-sig-algs */ + while (cli_opts.privkeys->first) { + sign_key * key = (sign_key*)cli_opts.privkeys->first->item; + if (cli_ses.server_sig_algs) { +#ifdef DROPBEAR_RSA + if (key->type == DROPBEAR_SIGNKEY_RSA) { #if DROPBEAR_RSA_SHA256 - cli_ses.preferred_rsa_sigtype = DROPBEAR_SIGNATURE_RSA_SHA256; -#elif DROPBEAR_RSA_SHA1 - cli_ses.preferred_rsa_sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; + if (buf_has_algo(cli_ses.server_sig_algs, SSH_SIGNATURE_RSA_SHA256) + == DROPBEAR_SUCCESS) { + sigtype = DROPBEAR_SIGNATURE_RSA_SHA256; + TRACE(("server-sig-algs allows rsa sha256")) + break; + } +#endif /* DROPBEAR_RSA_SHA256 */ +#if DROPBEAR_RSA_SHA1 + if (buf_has_algo(cli_ses.server_sig_algs, SSH_SIGNKEY_RSA) + == DROPBEAR_SUCCESS) { + sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; + TRACE(("server-sig-algs allows rsa sha1")) + break; + } +#endif /* DROPBEAR_RSA_SHA256 */ + } else +#endif /* DROPBEAR_RSA */ + { + /* Not RSA */ + const char *name = NULL; + sigtype = signature_type_from_signkey(key->type); + name = signature_name_from_type(sigtype, NULL); + if (buf_has_algo(cli_ses.server_sig_algs, name) + == DROPBEAR_SUCCESS) { + TRACE(("server-sig-algs allows %s", name)) + break; + } + } + + /* No match, skip this key */ + TRACE(("server-sig-algs no match keytype %d, skipping", key->type)) + key = list_remove(cli_opts.privkeys->first); + sign_key_free(key); + continue; + } else { + /* Server didn't provide a server-sig-algs list, we'll + assume all except rsa-sha256 are OK. */ +#if DROPBEAR_RSA + if (key->type == DROPBEAR_SIGNKEY_RSA) { +#ifdef DROPBEAR_RSA_SHA1 + sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; + TRACE(("no server-sig-algs, using rsa sha1")) + break; +#else + /* only support rsa-sha256, skip this key */ + TRACE(("no server-sig-algs, skipping rsa sha256")) + key = list_remove(cli_opts.privkeys->first); + sign_key_free(key); + continue; #endif + } /* key->type == DROPBEAR_SIGNKEY_RSA */ +#endif /* DROPBEAR_RSA */ + sigtype = signature_type_from_signkey(key->type); + TRACE(("no server-sig-algs, using key")) + break; + } + } if (cli_opts.privkeys->first) { sign_key * key = (sign_key*)cli_opts.privkeys->first->item; - /* Determine the signature type to use */ - enum signature_type sigtype = (enum signature_type)key->type; -#if DROPBEAR_RSA - if (key->type == DROPBEAR_SIGNKEY_RSA) { - sigtype = cli_ses.preferred_rsa_sigtype; - } -#endif - /* Send a trial request */ send_msg_userauth_pubkey(key, sigtype, 0); cli_ses.lastprivkey = key;
--- a/cli-kex.c Sun May 17 23:58:31 2020 +0800 +++ b/cli-kex.c Tue May 19 00:31:41 2020 +0800 @@ -411,3 +411,28 @@ } m_free(fingerprint); } + +void recv_msg_ext_info(void) { + /* This message is not client-specific in the protocol but Dropbear only handles + a server-sent message at present. */ + unsigned int num_ext; + unsigned int i; + + num_ext = buf_getint(ses.payload); + TRACE(("received SSH_MSG_EXT_INFO with %d items", num_ext)) + + for (i = 0; i < num_ext; i++) { + unsigned int name_len; + char *ext_name = buf_getstring(ses.payload, &name_len); + TRACE(("extension %d name '%s'", i, ext_name)) + if (cli_ses.server_sig_algs == NULL + && name_len == strlen(SSH_SERVER_SIG_ALGS) + && strcmp(ext_name, SSH_SERVER_SIG_ALGS) == 0) { + cli_ses.server_sig_algs = buf_getbuf(ses.payload); + } else { + /* valid extension values could be >MAX_STRING_LEN */ + buf_eatstring(ses.payload); + } + m_free(ext_name); + } +}
--- a/cli-main.c Sun May 17 23:58:31 2020 +0800 +++ b/cli-main.c Tue May 19 00:31:41 2020 +0800 @@ -106,6 +106,7 @@ /* Render the formatted exit message */ vsnprintf(exitmsg, sizeof(exitmsg), format, param); + TRACE(("Exited, cleaning up: %s", exitmsg)) /* Add the prefix depending on session/auth state */ if (!ses.init_done) {
--- a/cli-session.c Sun May 17 23:58:31 2020 +0800 +++ b/cli-session.c Tue May 19 00:31:41 2020 +0800 @@ -81,6 +81,7 @@ {SSH_MSG_REQUEST_SUCCESS, ignore_recv_response}, {SSH_MSG_REQUEST_FAILURE, ignore_recv_response}, #endif + {SSH_MSG_EXT_INFO, recv_msg_ext_info}, {0, NULL} /* End */ }; @@ -352,7 +353,9 @@ (void)fcntl(cli_ses.stderrcopy, F_SETFL, cli_ses.stderrflags); cli_tty_cleanup(); - + if (cli_ses.server_sig_algs) { + buf_free(cli_ses.server_sig_algs); + } } static void cli_finished() {
--- a/common-algo.c Sun May 17 23:58:31 2020 +0800 +++ b/common-algo.c Tue May 19 00:31:41 2020 +0800 @@ -30,6 +30,7 @@ #include "dh_groups.h" #include "ltc_prng.h" #include "ecc.h" +#include "ssh.h" /* This file (algo.c) organises the ciphers which can be used, and is used to * decide which ciphers/hashes/compression/signing to use during key exchange*/ @@ -280,6 +281,7 @@ static const struct dropbear_kex kex_curve25519 = {DROPBEAR_KEX_CURVE25519, NULL, 0, NULL, &sha256_desc }; #endif +/* data == NULL for non-kex algorithm identifiers */ algo_type sshkex[] = { #if DROPBEAR_CURVE25519 {"curve25519-sha256", 0, &kex_curve25519, 1, NULL}, @@ -309,7 +311,11 @@ {"diffie-hellman-group16-sha512", 0, &kex_dh_group16_sha512, 1, NULL}, #endif #if DROPBEAR_KEXGUESS2 - {KEXGUESS2_ALGO_NAME, KEXGUESS2_ALGO_ID, NULL, 1, NULL}, + {KEXGUESS2_ALGO_NAME, 0, NULL, 1, NULL}, +#endif +#if DROPBEAR_CLIENT + /* Set unusable by svr_algos_initialise() */ + {SSH_EXT_INFO_C, 0, NULL, 1, NULL}, #endif {NULL, 0, NULL, 0, NULL} }; @@ -336,15 +342,79 @@ buf_free(algolist); } +/* returns a list of pointers into algolist, of null-terminated names. + ret_list should be passed in with space for *ret_count elements, + on return *ret_count has the number of names filled. + algolist is modified. */ +static void get_algolist(char* algolist, unsigned int algolist_len, + const char* *ret_list, unsigned int *ret_count) { + unsigned int max_count = *ret_count; + unsigned int i; + + if (*ret_count == 0) { + return; + } + if (algolist_len > MAX_PROPOSED_ALGO*(MAX_NAME_LEN+1)) { + *ret_count = 0; + } + + /* ret_list will contain a list of the strings parsed out. + We will have at least one string (even if it's just "") */ + ret_list[0] = algolist; + *ret_count = 1; + for (i = 0; i < algolist_len; i++) { + if (algolist[i] == '\0') { + /* someone is trying something strange */ + *ret_count = 0; + return; + } + + if (algolist[i] == ',') { + if (*ret_count >= max_count) { + /* Too many */ + *ret_count = 0; + return; + } + algolist[i] = '\0'; + ret_list[*ret_count] = &algolist[i+1]; + (*ret_count)++; + } + } +} + +/* Return DROPBEAR_SUCCESS if the namelist contains algo, +DROPBEAR_FAILURE otherwise. buf position is not incremented. */ +int buf_has_algo(buffer *buf, const char *algo) { + unsigned char* algolist = NULL; + unsigned int orig_pos = buf->pos; + unsigned int len, remotecount, i; + const char *remotenames[MAX_PROPOSED_ALGO]; + int ret = DROPBEAR_FAILURE; + + algolist = buf_getstring(buf, &len); + remotecount = MAX_PROPOSED_ALGO; + get_algolist(algolist, len, remotenames, &remotecount); + for (i = 0; i < remotecount; i++) + { + if (strcmp(remotenames[i], algo) == 0) { + ret = DROPBEAR_SUCCESS; + break; + } + } + if (algolist) { + m_free(algolist); + } + buf_setpos(buf, orig_pos); + return ret; +} + /* match the first algorithm in the comma-separated list in buf which is * also in localalgos[], or return NULL on failure. * (*goodguess) is set to 1 if the preferred client/server algos match, * 0 otherwise. This is used for checking if the kexalgo/hostkeyalgos are * guessed correctly */ algo_type * buf_match_algo(buffer* buf, algo_type localalgos[], - enum kexguess2_used *kexguess2, int *goodguess) -{ - + int kexguess2, int *goodguess) { char * algolist = NULL; const char *remotenames[MAX_PROPOSED_ALGO], *localnames[MAX_PROPOSED_ALGO]; unsigned int len; @@ -359,40 +429,8 @@ /* get the comma-separated list from the buffer ie "algo1,algo2,algo3" */ algolist = buf_getstring(buf, &len); TRACE(("buf_match_algo: %s", algolist)) - if (len > MAX_PROPOSED_ALGO*(MAX_NAME_LEN+1)) { - goto out; - } - - /* remotenames will contain a list of the strings parsed out */ - /* We will have at least one string (even if it's just "") */ - remotenames[0] = algolist; - remotecount = 1; - for (i = 0; i < len; i++) { - if (algolist[i] == '\0') { - /* someone is trying something strange */ - goto out; - } - if (algolist[i] == ',') { - algolist[i] = '\0'; - remotenames[remotecount] = &algolist[i+1]; - remotecount++; - } - if (remotecount >= MAX_PROPOSED_ALGO) { - break; - } - } - if (kexguess2 && *kexguess2 == KEXGUESS2_LOOK) { - for (i = 0; i < remotecount; i++) - { - if (strcmp(remotenames[i], KEXGUESS2_ALGO_NAME) == 0) { - *kexguess2 = KEXGUESS2_YES; - break; - } - } - if (*kexguess2 == KEXGUESS2_LOOK) { - *kexguess2 = KEXGUESS2_NO; - } - } + remotecount = MAX_PROPOSED_ALGO; + get_algolist(algolist, len, remotenames, &remotecount); for (i = 0; localalgos[i].name != NULL; i++) { if (localalgos[i].usable) { @@ -424,12 +462,11 @@ } if (strcmp(servnames[j], clinames[i]) == 0) { /* set if it was a good guess */ - if (goodguess && kexguess2) { - if (*kexguess2 == KEXGUESS2_YES) { + if (goodguess != NULL) { + if (kexguess2) { if (i == 0) { *goodguess = 1; } - } else { if (i == 0 && j == 0) { *goodguess = 1;
--- a/common-kex.c Sun May 17 23:58:31 2020 +0800 +++ b/common-kex.c Tue May 19 00:31:41 2020 +0800 @@ -820,21 +820,33 @@ int goodguess = 0; int allgood = 1; /* we AND this with each goodguess and see if its still true after */ - -#if DROPBEAR_KEXGUESS2 - enum kexguess2_used kexguess2 = KEXGUESS2_LOOK; -#else - enum kexguess2_used kexguess2 = KEXGUESS2_NO; -#endif + int kexguess2 = 0; buf_incrpos(ses.payload, 16); /* start after the cookie */ memset(ses.newkeys, 0x0, sizeof(*ses.newkeys)); /* kex_algorithms */ - algo = buf_match_algo(ses.payload, sshkex, &kexguess2, &goodguess); +#if DROPBEAR_KEXGUESS2 + if (buf_has_algo(ses.payload, KEXGUESS2_ALGO_NAME) == DROPBEAR_SUCCESS) { + kexguess2 = 1; + } +#endif + + /* Determine if SSH_MSG_EXT_INFO messages should be sent. + Should be done for the first key exchange. */ + if (!ses.kexstate.donefirstkex) { + if (IS_DROPBEAR_SERVER) { + if (buf_has_algo(ses.payload, SSH_EXT_INFO_C) == DROPBEAR_SUCCESS) { + ses.allow_ext_info = 1; + } + } + } + + algo = buf_match_algo(ses.payload, sshkex, kexguess2, &goodguess); allgood &= goodguess; - if (algo == NULL || algo->val == KEXGUESS2_ALGO_ID) { + if (algo == NULL || algo->data == NULL) { + /* kexguess2, ext-info-c, ext-info-s should not match negotiation */ erralgo = "kex"; goto error; } @@ -843,7 +855,7 @@ ses.newkeys->algo_kex = algo->data; /* server_host_key_algorithms */ - algo = buf_match_algo(ses.payload, sshhostkey, &kexguess2, &goodguess); + algo = buf_match_algo(ses.payload, sshhostkey, kexguess2, &goodguess); allgood &= goodguess; if (algo == NULL) { erralgo = "hostkey";
--- a/fuzzer-verify.c Sun May 17 23:58:31 2020 +0800 +++ b/fuzzer-verify.c Tue May 19 00:31:41 2020 +0800 @@ -29,7 +29,7 @@ sign_key *key = new_sign_key(); enum signkey_type keytype = DROPBEAR_SIGNKEY_ANY; if (buf_get_pub_key(fuzz.input, key, &keytype) == DROPBEAR_SUCCESS) { - enum signature_type sigtype = (enum signature_type)keytype; + enum signature_type sigtype; if (keytype == DROPBEAR_SIGNKEY_RSA) { /* Flip a coin to decide rsa signature type */ int flag = buf_getbyte(fuzz_input); @@ -38,6 +38,8 @@ } else { sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; } + } else { + sigtype = signature_type_from_signkey(keytype); } if (buf_verify(fuzz.input, key, sigtype, verifydata) == DROPBEAR_SUCCESS) { /* The fuzzer is capable of generating keys with a signature to match.
--- a/kex.h Sun May 17 23:58:31 2020 +0800 +++ b/kex.h Tue May 19 00:31:41 2020 +0800 @@ -65,6 +65,8 @@ void send_msg_kexdh_init(void); /* client */ void recv_msg_kexdh_reply(void); /* client */ +void recv_msg_ext_info(void); + struct KEXState { unsigned sentkexinit : 1; /*set when we've sent/recv kexinit packet */
--- a/session.h Sun May 17 23:58:31 2020 +0800 +++ b/session.h Tue May 19 00:31:41 2020 +0800 @@ -185,6 +185,9 @@ /* Enables/disables compression */ algo_type *compress_algos; + + /* Other side allows SSH_MSG_EXT_INFO */ + int allow_ext_info; /* a list of queued replies that should be sent after a KEX has concluded (ie, while dataallowed was unset)*/ @@ -313,13 +316,7 @@ #endif sign_key *lastprivkey; - enum signature_type server_sig_algs[DROPBEAR_SIGNKEY_NUM_NAMED+1]; - int server_sig_algs_count; -#if DROPBEAR_RSA - /* Set to DROPBEAR_SIGNATURE_RSA_SHA256 or DROPBEAR_SIGNATURE_RSA_SHA1 - if depending which the server accepts */ - enum signature_type preferred_rsa_sigtype; -#endif + buffer *server_sig_algs; int retval; /* What the command exit status was - we emulate it */ #if 0
--- a/signkey.c Sun May 17 23:58:31 2020 +0800 +++ b/signkey.c Tue May 19 00:31:41 2020 +0800 @@ -114,13 +114,17 @@ const char* signature_name_from_type(enum signature_type type, unsigned int *namelen) { #if DROPBEAR_RSA_SHA256 if (type == DROPBEAR_SIGNATURE_RSA_SHA256) { - *namelen = strlen(SSH_SIGNATURE_RSA_SHA256); + if (namelen) { + *namelen = strlen(SSH_SIGNATURE_RSA_SHA256); + } return SSH_SIGNATURE_RSA_SHA256; } #endif #if DROPBEAR_RSA_SHA1 if (type == DROPBEAR_SIGNATURE_RSA_SHA1) { - *namelen = strlen(SSH_SIGNKEY_RSA); + if (namelen) { + *namelen = strlen(SSH_SIGNKEY_RSA); + } return SSH_SIGNKEY_RSA; } #endif @@ -144,6 +148,16 @@ return (enum signature_type)signkey_type_from_name(name, namelen); } +/* Returns the signature type from a key type. Must not be called + with RSA keytype */ +enum signature_type signature_type_from_signkey(enum signkey_type keytype) { +#if DROPBEAR_RSA + assert(keytype != DROPBEAR_SIGNKEY_RSA); +#endif + assert(keytype < DROPBEAR_SIGNKEY_NUM_NAMED); + return (enum signature_type)keytype; +} + enum signkey_type signkey_type_from_signature(enum signature_type sigtype) { #if DROPBEAR_RSA_SHA256 if (sigtype == DROPBEAR_SIGNATURE_RSA_SHA256) { @@ -587,8 +601,7 @@ #if DEBUG_TRACE { - int namelen; - const char* signame = signature_name_from_type(sigtype, &namelen); + const char* signame = signature_name_from_type(sigtype, NULL); TRACE(("buf_put_sign type %d %s", sigtype, signame)); } #endif
--- a/signkey.h Sun May 17 23:58:31 2020 +0800 +++ b/signkey.h Tue May 19 00:31:41 2020 +0800 @@ -120,6 +120,8 @@ const char* signature_name_from_type(enum signature_type type, unsigned int *namelen); enum signature_type signature_type_from_name(const char* name, unsigned int namelen); enum signkey_type signkey_type_from_signature(enum signature_type sigtype); +enum signature_type signature_type_from_signkey(enum signkey_type keytype); + int buf_get_pub_key(buffer *buf, sign_key *key, enum signkey_type *type); int buf_get_priv_key(buffer* buf, sign_key *key, enum signkey_type *type); void buf_put_pub_key(buffer* buf, sign_key *key, enum signkey_type type);
--- a/ssh.h Sun May 17 23:58:31 2020 +0800 +++ b/ssh.h Tue May 19 00:31:41 2020 +0800 @@ -32,6 +32,7 @@ #define SSH_MSG_DEBUG 4 #define SSH_MSG_SERVICE_REQUEST 5 #define SSH_MSG_SERVICE_ACCEPT 6 +#define SSH_MSG_EXT_INFO 7 #define SSH_MSG_KEXINIT 20 #define SSH_MSG_NEWKEYS 21 #define SSH_MSG_KEXDH_INIT 30 @@ -94,6 +95,11 @@ #define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 #define SSH_DISCONNECT_ILLEGAL_USER_NAME 15 +/* rfc8308 */ +#define SSH_EXT_INFO_S "ext-info-s" +#define SSH_EXT_INFO_C "ext-info-c" +#define SSH_SERVER_SIG_ALGS "server-sig-algs" + /* service types */ #define SSH_SERVICE_USERAUTH "ssh-userauth" #define SSH_SERVICE_USERAUTH_LEN 12
--- a/svr-session.c Sun May 17 23:58:31 2020 +0800 +++ b/svr-session.c Tue May 19 00:31:41 2020 +0800 @@ -330,13 +330,16 @@ } static void svr_algos_initialise(void) { -#if DROPBEAR_DH_GROUP1 && DROPBEAR_DH_GROUP1_CLIENTONLY algo_type *algo; for (algo = sshkex; algo->name; algo++) { +#if DROPBEAR_DH_GROUP1 && DROPBEAR_DH_GROUP1_CLIENTONLY if (strcmp(algo->name, "diffie-hellman-group1-sha1") == 0) { algo->usable = 0; } +#endif + if (strcmp(algo->name, SSH_EXT_INFO_C) == 0) { + algo->usable = 0; + } } -#endif }