From fdc660f313a1e8d692c1131d8ac35e83288c0d85 Mon Sep 17 00:00:00 2001 From: Aris Adamantiadis Date: Tue, 4 Feb 2014 22:28:30 +0100 Subject: [PATCH] knownhosts: detect variations of ecdsa --- include/libssh/knownhosts.h | 27 ++++++++++++ include/libssh/libssh.h | 1 - src/kex.c | 72 +++++++++++++++++++++---------- src/known_hosts.c | 40 +++++++++++++---- tests/client/torture_knownhosts.c | 20 ++++----- 5 files changed, 117 insertions(+), 43 deletions(-) create mode 100644 include/libssh/knownhosts.h diff --git a/include/libssh/knownhosts.h b/include/libssh/knownhosts.h new file mode 100644 index 00000000..723c7ebc --- /dev/null +++ b/include/libssh/knownhosts.h @@ -0,0 +1,27 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 20014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef KNOWNHOSTS_H_ +#define KNOWNHOSTS_H_ + +char **ssh_knownhosts_algorithms(ssh_session session); + +#endif /* KNOWNHOSTS_H_ */ diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 9c71d3b9..48050756 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -517,7 +517,6 @@ LIBSSH_API int ssh_key_cmp(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what); -LIBSSH_API int ssh_knownhosts_algorithms(ssh_session session); LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey); LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key, diff --git a/src/kex.c b/src/kex.c index d620549f..6433ee6f 100644 --- a/src/kex.c +++ b/src/kex.c @@ -35,6 +35,7 @@ #include "libssh/ssh2.h" #include "libssh/string.h" #include "libssh/curve25519.h" +#include "libssh/knownhosts.h" #ifdef HAVE_LIBGCRYPT # define BLOWFISH "blowfish-cbc," @@ -373,6 +374,51 @@ void ssh_list_kex(struct ssh_kex_struct *kex) { } } +/** + * @internal + * @brief selects the hostkey mechanisms to be chosen for the key exchange, + * as some hostkey mechanisms may be present in known_hosts file and preferred + * @returns a cstring containing a comma-separated list of hostkey methods. + * NULL if no method matches + */ +static char *ssh_client_select_hostkeys(ssh_session session){ + char methods_buffer[128]={0}; + static const char *preferred_hostkeys[]={"ecdsa-sha2-nistp521","ecdsa-sha2-nistp384", + "ecdsa-sha2-nistp256", "ssh-rsa", "ssh-dss", "ssh-rsa1", NULL}; + char **methods; + int i,j; + int needcoma=0; + + methods = ssh_knownhosts_algorithms(session); + if (methods == NULL || methods[0] == NULL) + return NULL; + + for (i=0;preferred_hostkeys[i] != NULL; ++i){ + for (j=0; methods[j] != NULL; ++j){ + if(strcmp(preferred_hostkeys[i], methods[j]) == 0){ + if (verify_existing_algo(SSH_HOSTKEYS, methods[j])){ + if(needcoma) + strncat(methods_buffer,",",sizeof(methods_buffer)-strlen(methods_buffer)-1); + strncat(methods_buffer, methods[j], sizeof(methods_buffer)-strlen(methods_buffer)-1); + needcoma = 1; + } + } + } + } + for(i=0;methods[i]!= NULL; ++i){ + SAFE_FREE(methods[i]); + } + SAFE_FREE(methods); + + if(strlen(methods_buffer) > 0){ + SSH_LOG(SSH_LOG_DEBUG, "Changing host key method to \"%s\"", methods_buffer); + return strdup(methods_buffer); + } else { + SSH_LOG(SSH_LOG_DEBUG, "No supported kex method for existing key in known_hosts file"); + return NULL; + } + +} /** * @brief sets the key exchange parameters to be sent to the server, * in function of the options and available methods. @@ -380,10 +426,7 @@ void ssh_list_kex(struct ssh_kex_struct *kex) { int set_client_kex(ssh_session session){ struct ssh_kex_struct *client= &session->next_crypto->client_kex; const char *wanted; - char methods_buffer[128]={0}; - int prefered_hostkeys[]={SSH_KEYTYPE_ECDSA, SSH_KEYTYPE_RSA, - SSH_KEYTYPE_DSS, SSH_KEYTYPE_RSA1, SSH_KEYTYPE_UNKNOWN}; - int i, methods, needcoma=0; + int i; ssh_get_random(client->cookie, 16, 0); @@ -391,25 +434,8 @@ int set_client_kex(ssh_session session){ /* first check if we have specific host key methods */ if(session->opts.wanted_methods[SSH_HOSTKEYS] == NULL){ /* Only if no override */ - methods = ssh_knownhosts_algorithms(session); - if (methods != SSH_ERROR && methods != 0){ - for(i=0; prefered_hostkeys[i] != SSH_KEYTYPE_UNKNOWN;++i){ - if (methods & (1 << prefered_hostkeys[i])){ - if (verify_existing_algo(SSH_HOSTKEYS, ssh_key_type_to_char(prefered_hostkeys[i]))){ - if(needcoma) - strncat(methods_buffer,",",sizeof(methods_buffer)-strlen(methods_buffer)-1); - strncat(methods_buffer, ssh_key_type_to_char(prefered_hostkeys[i]), sizeof(methods_buffer)-strlen(methods_buffer)-1); - needcoma = 1; - } - } - } - if(strlen(methods_buffer) > 0){ - SSH_LOG(SSH_LOG_DEBUG, "Changing host key method to \"%s\"", methods_buffer); - session->opts.wanted_methods[SSH_HOSTKEYS] = strdup(methods_buffer); - } else { - SSH_LOG(SSH_LOG_DEBUG, "No supported kex method for existing key in known_hosts file"); - } - } + session->opts.wanted_methods[SSH_HOSTKEYS] = + ssh_client_select_hostkeys(session); } for (i = 0; i < KEX_METHODS_SIZE; i++) { diff --git a/src/known_hosts.c b/src/known_hosts.c index 2d90a41d..57cd0e75 100644 --- a/src/known_hosts.c +++ b/src/known_hosts.c @@ -34,7 +34,7 @@ #include "libssh/misc.h" #include "libssh/pki.h" #include "libssh/options.h" - +#include "libssh/knownhosts.h" /*todo: remove this include */ #include "libssh/string.h" @@ -647,29 +647,33 @@ int ssh_write_knownhost(ssh_session session) { return 0; } +#define KNOWNHOSTS_MAXTYPES 10 + /** + * @internal * @brief Check which kind of host keys should be preferred for connection * by reading the known_hosts file. * * @param[in] session The SSH session to use. * - * @returns Bitfield of supported SSH hostkey algorithms - * SSH_ERROR on error + * @returns array of supported key types + * NULL on error */ -int ssh_knownhosts_algorithms(ssh_session session) { +char **ssh_knownhosts_algorithms(ssh_session session) { FILE *file = NULL; char **tokens; char *host; char *hostport; const char *type; int match; - int ret = 0; + char **array; + int i=0, j; if (session->opts.knownhosts == NULL) { if (ssh_options_apply(session) < 0) { ssh_set_error(session, SSH_REQUEST_DENIED, "Can't find a known_hosts file"); - return SSH_ERROR; + return NULL; } } @@ -683,8 +687,13 @@ int ssh_knownhosts_algorithms(ssh_session session) { ssh_set_error_oom(session); SAFE_FREE(host); SAFE_FREE(hostport); + return NULL; + } - return SSH_ERROR; + array = malloc(sizeof(char *) * KNOWNHOSTS_MAXTYPES); + if (array==NULL){ + ssh_set_error_oom(session); + return NULL; } do { @@ -709,11 +718,24 @@ int ssh_knownhosts_algorithms(ssh_session session) { /* We got a match. Now check the key type */ SSH_LOG(SSH_LOG_DEBUG, "server %s:%d has %s in known_hosts", host, session->opts.port, type); - ret |= 1 << ssh_key_type_from_name(type); + /* don't copy more than once */ + for(j=0;j= KNOWNHOSTS_MAXTYPES-1){ + tokens_free(tokens); + break; + } + } } tokens_free(tokens); } while (1); + array[i]=NULL; SAFE_FREE(host); SAFE_FREE(hostport); if (file != NULL) { @@ -721,7 +743,7 @@ int ssh_knownhosts_algorithms(ssh_session session) { } /* Return the current state at end of file */ - return ret; + return array; } /** @} */ diff --git a/tests/client/torture_knownhosts.c b/tests/client/torture_knownhosts.c index e8100719..df99ae90 100644 --- a/tests/client/torture_knownhosts.c +++ b/tests/client/torture_knownhosts.c @@ -23,6 +23,7 @@ #include "torture.h" #include "session.c" +#include "known_hosts.c" #define KNOWNHOSTFILES "libssh_torture_knownhosts" #define BADRSA "AAAAB3NzaC1yc2EAAAADAQABAAABAQChm5" \ @@ -255,8 +256,7 @@ static void torture_knownhosts_precheck(void **state) { ssh_session session = *state; FILE *file; int rc; - int dsa; - int rsa; + char **kex; rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); assert_true(rc == SSH_OK); @@ -270,14 +270,14 @@ static void torture_knownhosts_precheck(void **state) { fprintf(file, "localhost ssh-dss %s\n", BADDSA); fclose(file); - rc = ssh_knownhosts_algorithms(session); - assert_true(rc != SSH_ERROR); - dsa = 1 << SSH_KEYTYPE_DSS; - rsa = 1 << SSH_KEYTYPE_RSA; - assert_true(rc & dsa); - assert_true(rc & rsa); - /* nothing else than dsa and rsa */ - assert_true((rc & (dsa | rsa)) == rc); + kex = ssh_knownhosts_algorithms(session); + assert_true(kex != NULL); + assert_string_equal(kex[0],"ssh-rsa"); + assert_string_equal(kex[1],"ssh-dss"); + assert_true(kex[2]==NULL); + free(kex[1]); + free(kex[0]); + free(kex); } int torture_run_tests(void) {