diff options
-rw-r--r-- | include/libssh/libssh.h | 6 | ||||
-rw-r--r-- | src/knownhosts.c | 156 | ||||
-rw-r--r-- | tests/client/torture_knownhosts_verify.c | 20 |
3 files changed, 181 insertions, 1 deletions
diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 336e31da..03241493 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -540,9 +540,13 @@ LIBSSH_API enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session LIBSSH_API int ssh_session_export_known_hosts_entry(ssh_session session, char **pentry_string); - LIBSSH_API int ssh_session_update_known_hosts(ssh_session session); +LIBSSH_API enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session); + /* LOGGING */ LIBSSH_API int ssh_set_log_level(int level); LIBSSH_API int ssh_get_log_level(void); diff --git a/src/knownhosts.c b/src/knownhosts.c index bc789afc..6cb14989 100644 --- a/src/knownhosts.c +++ b/src/knownhosts.c @@ -32,6 +32,7 @@ #include <netinet/in.h> #include "libssh/priv.h" +#include "libssh/dh.h" #include "libssh/session.h" #include "libssh/options.h" #include "libssh/misc.h" @@ -634,3 +635,158 @@ int ssh_session_update_known_hosts(ssh_session session) return SSH_OK; } + +static enum ssh_known_hosts_e +ssh_known_hosts_check_server_key(const char *hosts_entry, + const char *filename, + ssh_key server_key, + struct ssh_knownhosts_entry **pentry) +{ + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; + int rc; + + rc = ssh_known_hosts_read_entries(hosts_entry, + filename, + &entry_list); + if (rc != 0) { + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + + it = ssh_list_get_iterator(entry_list); + if (it == NULL) { + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + for (;it != NULL; it = it->next) { + struct ssh_knownhosts_entry *entry = NULL; + int cmp; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + + cmp = ssh_key_cmp(server_key, entry->publickey, SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found = SSH_KNOWN_HOSTS_OK; + if (pentry != NULL) { + *pentry = entry; + ssh_list_remove(entry_list, it); + } + break; + } + + if (ssh_key_type(server_key) == ssh_key_type(entry->publickey)) { + found = SSH_KNOWN_HOSTS_CHANGED; + } else { + found = SSH_KNOWN_HOSTS_OTHER; + } + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); + + return found; +} + +/** + * @brief Get the known_hosts entry for the current connected session. + * + * @param[in] session The session to validate. + * + * @param[in] pentry A pointer to store the allocated known hosts entry. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct.\n + * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The + * host is thus unknown. File will be + * created if host key is accepted.\n + * SSH_KNOWN_HOSTS_ERROR: There had been an eror checking the host. + * + * @see ssh_knownhosts_entry_free() + */ +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry) +{ + ssh_key server_pubkey = NULL; + char *host_port = NULL; + enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; + + 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_KNOWN_HOSTS_NOT_FOUND; + } + } + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL) { + ssh_set_error(session, + SSH_FATAL, + "ssh_session_is_known_host called without a " + "server_key!"); + + return SSH_KNOWN_HOSTS_ERROR; + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return SSH_KNOWN_HOSTS_ERROR; + } + + found = ssh_known_hosts_check_server_key(host_port, + session->opts.knownhosts, + server_pubkey, + pentry); + + return found; +} + +/** + * @brief Check if the servers public key for the connected session is known. + * + * This checks if we already know the public key of the server we want to + * connect to. This allows to detect if there is a MITM attach going on + * of if there have been changes on the server we don't know about. + * + * @param[in] session The SSH to validate. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct.\n + * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The + * host is thus unknown. File will be + * created if host key is accepted.\n + * SSH_KNOWN_HOSTS_ERROR: There had been an eror checking the host. + */ +enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session) +{ + return ssh_session_get_known_hosts_entry(session, NULL); +} diff --git a/tests/client/torture_knownhosts_verify.c b/tests/client/torture_knownhosts_verify.c index a266e92a..323b8e2b 100644 --- a/tests/client/torture_knownhosts_verify.c +++ b/tests/client/torture_knownhosts_verify.c @@ -103,12 +103,32 @@ static void torture_knownhosts_export(void **state) SAFE_FREE(entry); } +static void torture_knownhosts_write_and_verify(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e found; + int rc; + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_session_update_known_hosts(session); + assert_int_equal(rc, SSH_OK); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(torture_knownhosts_export, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_write_and_verify, + session_setup, + session_teardown), }; ssh_init(); |