diff --git a/.gitignore b/.gitignore index cedfe7a..9130fdb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -SOURCES/libssh-0.8.5.tar.xz +SOURCES/libssh-0.9.0.tar.xz SOURCES/libssh.keyring diff --git a/.libssh.metadata b/.libssh.metadata index 19fd4e7..2c4bf88 100644 --- a/.libssh.metadata +++ b/.libssh.metadata @@ -1,2 +1,2 @@ -b5564774f986e396a7288a593595455bf10d9ce8 SOURCES/libssh-0.8.5.tar.xz +570bffef68af6c1211673bc9a8036c9265935b2b SOURCES/libssh-0.9.0.tar.xz 3f2ab0bca02893402ba0ad172a6bd44456a65f86 SOURCES/libssh.keyring diff --git a/SOURCES/libssh-0.8.5-allow-kex-init.patch b/SOURCES/libssh-0.8.5-allow-kex-init.patch deleted file mode 100644 index 511283e..0000000 --- a/SOURCES/libssh-0.8.5-allow-kex-init.patch +++ /dev/null @@ -1,39 +0,0 @@ -From e6e8335847d4870c99c07f511b4a24ae9e053326 Mon Sep 17 00:00:00 2001 -From: Jakub Jelen -Date: Thu, 15 Nov 2018 11:03:56 +0100 -Subject: [PATCH] packet: Adjust the packet filter to allow client-initialized - rekey - -If the rekey is initialized by client, it sends the first KEXINIT -message, changes to the INIT_SENT state and waits for the KEXINIT -message from the server. This was not covered in the current filter. - -Signed-off-by: Jakub Jelen -Reviewed-by: Daiki Ueno ---- - src/packet.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/src/packet.c b/src/packet.c -index 9b7b9b8f..86314961 100644 ---- a/src/packet.c -+++ b/src/packet.c -@@ -292,6 +292,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se - * - session_state == SSH_SESSION_STATE_AUTHENTICATED - * or session_state == SSH_SESSION_STATE_INITIAL_KEX - * - dh_handshake_state == DH_STATE_INIT -+ * or dh_handshake_state == DH_STATE_INIT_SENT (re-exchange) - * or dh_handshake_state == DH_STATE_FINISHED (re-exchange) - * - * Transitions: -@@ -310,6 +311,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se - } - - if ((session->dh_handshake_state != DH_STATE_INIT) && -+ (session->dh_handshake_state != DH_STATE_INIT_SENT) && - (session->dh_handshake_state != DH_STATE_FINISHED)) - { - rc = SSH_PACKET_DENIED; --- -2.19.1 - diff --git a/SOURCES/libssh-0.8.5-allow-msg-ext-info.patch b/SOURCES/libssh-0.8.5-allow-msg-ext-info.patch deleted file mode 100644 index 37f3d19..0000000 --- a/SOURCES/libssh-0.8.5-allow-msg-ext-info.patch +++ /dev/null @@ -1,92 +0,0 @@ -From a6e055c42b34ec50f55606312b09ec2e14990416 Mon Sep 17 00:00:00 2001 -From: Anderson Toshiyuki Sasaki -Date: Fri, 7 Dec 2018 18:19:33 +0100 -Subject: [PATCH] packet: Allow SSH2_MSG_EXT_INFO when authenticated - -When the server requests rekey, it can send the SSH2_MSG_EXT_INFO. This -message was being filtered out by the packet filtering. This includes a -test to enforce the filtering rules for this packet type. - -Signed-off-by: Anderson Toshiyuki Sasaki -Reviewed-by: Andreas Schneider -(cherry picked from commit fe309ba43fb904da4385fc40a338ecc7482f8388) ---- - src/packet.c | 6 ++++- - tests/unittests/torture_packet_filter.c | 31 +++++++++++++++++++++++++ - 2 files changed, 36 insertions(+), 1 deletion(-) - -diff --git a/src/packet.c b/src/packet.c -index 72e3c096..61a44237 100644 ---- a/src/packet.c -+++ b/src/packet.c -@@ -263,13 +263,17 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se - /* - * States required: - * - session_state == SSH_SESSION_STATE_AUTHENTICATING -+ * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED -+ * (re-exchange) - * - dh_handshake_state == DH_STATE_FINISHED - * - * Transitions: - * - None - * */ - -- if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { -+ if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && -+ (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) -+ { - rc = SSH_PACKET_DENIED; - break; - } -diff --git a/tests/unittests/torture_packet_filter.c b/tests/unittests/torture_packet_filter.c -index 72cbc4cd..44ee3598 100644 ---- a/tests/unittests/torture_packet_filter.c -+++ b/tests/unittests/torture_packet_filter.c -@@ -462,6 +462,36 @@ static void torture_packet_filter_check_auth_success(void **state) - assert_int_equal(rc, 0); - } - -+static void torture_packet_filter_check_msg_ext_info(void **state) -+{ -+ int rc; -+ -+ global_state accepted[] = { -+ { -+ .flags = (COMPARE_SESSION_STATE | -+ COMPARE_DH_STATE), -+ .session = SSH_SESSION_STATE_AUTHENTICATING, -+ .dh = DH_STATE_FINISHED, -+ }, -+ { -+ .flags = (COMPARE_SESSION_STATE | -+ COMPARE_DH_STATE), -+ .session = SSH_SESSION_STATE_AUTHENTICATED, -+ .dh = DH_STATE_FINISHED, -+ }, -+ }; -+ -+ int accepted_count = 2; -+ -+ /* Unused */ -+ (void) state; -+ -+ rc = check_message_in_all_states(accepted, accepted_count, -+ SSH2_MSG_EXT_INFO); -+ -+ assert_int_equal(rc, 0); -+} -+ - static void torture_packet_filter_check_channel_open(void **state) - { - int rc; -@@ -492,6 +522,7 @@ int torture_run_tests(void) - cmocka_unit_test(torture_packet_filter_check_auth_success), - cmocka_unit_test(torture_packet_filter_check_channel_open), - cmocka_unit_test(torture_packet_filter_check_unfiltered), -+ cmocka_unit_test(torture_packet_filter_check_msg_ext_info) - }; - - ssh_init(); --- -2.19.1 - diff --git a/SOURCES/libssh-0.8.5-ensure-ssh-session-fd-writable.patch b/SOURCES/libssh-0.8.5-ensure-ssh-session-fd-writable.patch deleted file mode 100644 index f0e2d82..0000000 --- a/SOURCES/libssh-0.8.5-ensure-ssh-session-fd-writable.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 7f1d30de47d67c03cf895f0d4d4e68daf9d396c5 Mon Sep 17 00:00:00 2001 -From: Sanne Raymaekers -Date: Fri, 26 Oct 2018 14:58:34 +0200 -Subject: [PATCH] tests: Ensure the ssh session fd is read-/writeable in - torture_proxycommand - -Signed-off-by: Sanne Raymaekers -Reviewed-by: Andreas Schneider -Reviewed-by: Andreas Schneider -(cherry picked from commit 03c30e9c8ad34b3fa659a70e474a9b8cb248f85b) -(cherry picked from commit 3de34944ad11bf4e22fc981562f32cb4b3b90ba9) ---- - tests/client/torture_proxycommand.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/tests/client/torture_proxycommand.c b/tests/client/torture_proxycommand.c -index ea1e1838..9608e663 100644 ---- a/tests/client/torture_proxycommand.c -+++ b/tests/client/torture_proxycommand.c -@@ -9,6 +9,7 @@ - #include - #include - #include -+#include - - static int sshd_setup(void **state) - { -@@ -61,11 +62,16 @@ static void torture_options_set_proxycommand(void **state) { - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - int rc; -+ socket_t fd; - - rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "nc 127.0.0.10 22"); - assert_int_equal(rc, 0); - rc = ssh_connect(session); - assert_ssh_return_code(session, rc); -+ fd = ssh_get_fd(session); -+ assert_true(fd != SSH_INVALID_SOCKET); -+ rc = fcntl(fd, F_GETFL); -+ assert_int_equal(rc & O_RDWR, O_RDWR); - } - - static void torture_options_set_proxycommand_notexist(void **state) { --- -2.19.1 - diff --git a/SOURCES/libssh-0.8.5-make-sure-both-known-hosts-ready.patch b/SOURCES/libssh-0.8.5-make-sure-both-known-hosts-ready.patch deleted file mode 100644 index 8206dc0..0000000 --- a/SOURCES/libssh-0.8.5-make-sure-both-known-hosts-ready.patch +++ /dev/null @@ -1,37 +0,0 @@ -From bed645ed5fca3ff776fd1997ec9d5c6b9065a7eb Mon Sep 17 00:00:00 2001 -From: Jakub Jelen -Date: Tue, 30 Oct 2018 13:55:30 +0100 -Subject: [PATCH] knownhosts: Make sure we have both knownhosts files ready - -If either one is missing at this point, fill it with default vaules in -ssh_options_apply(). - -Previously, when setting up only knownhosts, global_knownhosts file -was left pointing to NULL and the ssh_known_hosts_read_entries() -was trying to open NULL file which is invalid. - -Signed-off-by: Jakub Jelen -Reviewed-by: Andreas Schneider -(cherry picked from commit 5159cd96e8b61c9f8f96786f70cf23167980b621) -(cherry picked from commit a4b99eedf2f0f993f10c31d9bea0f1ad9fa7737e) ---- - src/knownhosts.c | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/src/knownhosts.c b/src/knownhosts.c -index 23902a5f..546619aa 100644 ---- a/src/knownhosts.c -+++ b/src/knownhosts.c -@@ -306,7 +306,8 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) - int list_error = 0; - int rc; - -- if (session->opts.knownhosts == NULL) { -+ if (session->opts.knownhosts == NULL || -+ session->opts.global_knownhosts == NULL) { - if (ssh_options_apply(session) < 0) { - ssh_set_error(session, - SSH_REQUEST_DENIED, --- -2.19.1 - diff --git a/SOURCES/libssh-0.8.5.tar.xz.asc b/SOURCES/libssh-0.8.5.tar.xz.asc deleted file mode 100644 index 446b5b9..0000000 --- a/SOURCES/libssh-0.8.5.tar.xz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCAAdFiEEjf9T4Y8qvI2PPJIjfuD8TcwBTj0FAlvW3ZEACgkQfuD8TcwB -Tj0mPw/9HZty2xOzKekX8+cedyZbW2lAoG9YNVxgTvQ2+98+TBH6M8qr5EryuH/q -mAFq/TJOrM3yccr1kJXFOm6QBw0xMTpWcosXjKmZxvJwW6iFEP33shVDkZGJWwmM -bzr165NTdRMJiaBfk47j0e1H2U9MHyV7wKmb79+bFtMDhiJYmXR1Oxa1SjjeG42c -XmrcH559kZ1mKmai6jKwTRUKGu3RLdWrXQIobCrdQM8FYrbSx5luZtDjdfXTRRtv -K96alBYxuey1nsZVei1y2hJlLLLVqao997Q7iMI63+/IJpYTmEPciDnTDDu767X6 -rCXSuWbxcwk77zrt/dh7WJBOyLwh4aCFsSixBsONj4otwmFHNm/FAxV991ewcvQB -NMHPh3DcYLfoY/aUzwf160SfZu56mdVnFPLOnPUw3ARcPdgJG4OzqP9Jmz4Us+dS -ImZTduM62/+Af+LrODtRBRYJRSn38eBVVBYT01WsDSvvR2LaTn3gVAtY2m3Dr6wA -6sdMmYbn2zM+MNFE1+qlsaKmF+WmyCrBKBojBB+wh5G+dStJ7KGzlXjxNQudvWeA -VMlERLZYNBjSH1/XBH6VTnOl2xhExPv4eCUTVCXhlYUFEWjRunoxEed+b7r45iiu -+nRT+OfQZ8RFOLkFYxhB8iQNZAZ6T46ALSxo3V8PVEPDHZWdPTU= -=7iiz ------END PGP SIGNATURE----- diff --git a/SOURCES/libssh-0.9.0-do-not-ignore-known-hosts-keys.patch b/SOURCES/libssh-0.9.0-do-not-ignore-known-hosts-keys.patch new file mode 100644 index 0000000..c007677 --- /dev/null +++ b/SOURCES/libssh-0.9.0-do-not-ignore-known-hosts-keys.patch @@ -0,0 +1,1240 @@ +From b040856ccfde1a5d4c21791f46ca6ee00c21a47b Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Tue, 2 Jul 2019 10:21:15 +0200 +Subject: [PATCH 1/6] knownhosts: Fix possible memory leak + +The memory allocated for host_port can leak if the global knownhosts +file is unaccessible. + +Found by address sanitizer build in CI. + +Signed-off-by: Anderson Toshiyuki Sasaki +Reviewed-by: Jakub Jelen +(cherry picked from commit fe248414fec1e654e4ee1259927d68777dd870ae) +--- + src/knownhosts.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/knownhosts.c b/src/knownhosts.c +index 8a4a8ba7..9383cc97 100644 +--- a/src/knownhosts.c ++++ b/src/knownhosts.c +@@ -706,13 +706,15 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); +- SAFE_FREE(host_port); + if (rc != 0) { ++ SAFE_FREE(host_port); + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_ERROR; + } + } + ++ SAFE_FREE(host_port); ++ + if (ssh_list_count(entry_list) == 0) { + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; +-- +2.21.0 + + +From 7ff0af75436ee6e549907dd563e968b92f7f8db2 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Fri, 28 Jun 2019 13:19:51 +0200 +Subject: [PATCH 2/6] tests: Check if known_hosts works with single + unaccessible file + +Make sure known hosts check works when local known_hosts file is +unaccessible, but the host is present in global known_hosts file. + +Remove double return value check in previous existing test. + +Signed-off-by: Anderson Toshiyuki Sasaki +Reviewed-by: Jakub Jelen +(cherry picked from commit ad68de7271e6ccda261df4d9fc827321e7d90fd0) +--- + tests/unittests/torture_knownhosts_parsing.c | 19 +++++++++++-------- + 1 file changed, 11 insertions(+), 8 deletions(-) + +diff --git a/tests/unittests/torture_knownhosts_parsing.c b/tests/unittests/torture_knownhosts_parsing.c +index ac8d7f31..a087caef 100644 +--- a/tests/unittests/torture_knownhosts_parsing.c ++++ b/tests/unittests/torture_knownhosts_parsing.c +@@ -384,22 +384,19 @@ static void torture_knownhosts_host_exists(void **state) + + /* This makes sure the system's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "/dev/null"); +- + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +- assert_true(found == SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will not fail when the system's known_hosts is + * not accessible*/ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "./unaccessible"); +- + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +- assert_true(found == SSH_KNOWN_HOSTS_OK); + ++ /* This makes sure the check will fail for an unknown host */ + ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot"); + found = ssh_session_has_known_hosts_entry(session); +- assert_true(found == SSH_KNOWN_HOSTS_UNKNOWN); ++ assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + ssh_free(session); + } +@@ -414,17 +411,23 @@ static void torture_knownhosts_host_exists_global(void **state) + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); ++ ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); ++ + /* This makes sure the user's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); +- ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); ++ found = ssh_session_has_known_hosts_entry(session); ++ assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + ++ /* This makes sure the check will not fail when the user's known_hosts is ++ * not accessible*/ ++ ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "./unaccessible"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +- assert_true(found == SSH_KNOWN_HOSTS_OK); + ++ /* This makes sure the check will fail for an unknown host */ + ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot"); + found = ssh_session_has_known_hosts_entry(session); +- assert_true(found == SSH_KNOWN_HOSTS_UNKNOWN); ++ assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + ssh_free(session); + } +-- +2.21.0 + + +From b9530cedbeb169762307096dfeb485ab94e09740 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Fri, 28 Jun 2019 13:27:34 +0200 +Subject: [PATCH 3/6] knownhosts: Read knownhosts file only if found + +Avoid trying to open the files if they are not accessible. This was +already treated as a non-error, but with this we save one function call. + +Signed-off-by: Anderson Toshiyuki Sasaki +Reviewed-by: Jakub Jelen +(cherry picked from commit e5a64a3d6b1b601cbaf207468a6658d1a4fa0031) +--- + src/knownhosts.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/knownhosts.c b/src/knownhosts.c +index 9383cc97..8040a0c0 100644 +--- a/src/knownhosts.c ++++ b/src/knownhosts.c +@@ -691,7 +691,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) + return SSH_KNOWN_HOSTS_ERROR; + } + +- if (session->opts.knownhosts != NULL) { ++ if (known_hosts_found) { + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); +@@ -702,7 +702,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) + } + } + +- if (session->opts.global_knownhosts != NULL) { ++ if (global_known_hosts_found) { + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); +-- +2.21.0 + + +From aaa978ad06ebdeff88a39b9a894696254263162c Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Fri, 28 Jun 2019 22:35:38 +0200 +Subject: [PATCH 4/6] token: Added function to remove duplicates + +Added a function to remove duplicates from lists. This function is used +in a new provided function to append lists removing duplicates. + +Signed-off-by: Anderson Toshiyuki Sasaki +Reviewed-by: Jakub Jelen +(cherry picked from commit 548753b3389518ebce98a7ddbf0640db3ad72de8) +--- + include/libssh/token.h | 6 +- + src/token.c | 152 ++++++++++++++++++++++++++++++- + tests/unittests/torture_tokens.c | 122 +++++++++++++++++++++++++ + 3 files changed, 278 insertions(+), 2 deletions(-) + +diff --git a/include/libssh/token.h b/include/libssh/token.h +index 7b244189..9896fb06 100644 +--- a/include/libssh/token.h ++++ b/include/libssh/token.h +@@ -38,7 +38,11 @@ void ssh_tokens_free(struct ssh_tokens_st *tokens); + char *ssh_find_matching(const char *available_d, + const char *preferred_d); + +- + char *ssh_find_all_matching(const char *available_d, + const char *preferred_d); ++ ++char *ssh_remove_duplicates(const char *list); ++ ++char *ssh_append_without_duplicates(const char *list, ++ const char *appended_list); + #endif /* TOKEN_H_ */ +diff --git a/src/token.c b/src/token.c +index aee235ac..0924d3bd 100644 +--- a/src/token.c ++++ b/src/token.c +@@ -26,6 +26,7 @@ + + #include + #include ++#include + + #include "libssh/priv.h" + #include "libssh/token.h" +@@ -175,7 +176,7 @@ char *ssh_find_matching(const char *available_list, + + for (i = 0; p_tok->tokens[i]; i++) { + for (j = 0; a_tok->tokens[j]; j++) { +- if (strcmp(a_tok->tokens[j], p_tok->tokens[i]) == 0){ ++ if (strcmp(a_tok->tokens[j], p_tok->tokens[i]) == 0) { + ret = strdup(a_tok->tokens[j]); + goto out; + } +@@ -260,3 +261,152 @@ out: + ssh_tokens_free(p_tok); + return ret; + } ++ ++/** ++ * @internal ++ * ++ * @brief Given a string containing a list of elements, remove all duplicates ++ * and return in a newly allocated string. ++ * ++ * @param[in] list The list to be freed of duplicates ++ * ++ * @return A newly allocated copy of the string free of duplicates; NULL in ++ * case of error. ++ */ ++char *ssh_remove_duplicates(const char *list) ++{ ++ struct ssh_tokens_st *tok = NULL; ++ ++ size_t i, j, num_tokens, max_len; ++ char *ret = NULL; ++ bool *should_copy = NULL, need_comma = false; ++ ++ if (list == NULL) { ++ return NULL; ++ } ++ ++ /* The maximum number of tokens is the size of the list */ ++ max_len = strlen(list); ++ if (max_len == 0) { ++ return NULL; ++ } ++ ++ /* Add space for ending '\0' */ ++ max_len++; ++ ++ tok = ssh_tokenize(list, ','); ++ if ((tok == NULL) || (tok->tokens == NULL) || (tok->tokens[0] == NULL)) { ++ goto out; ++ } ++ ++ should_copy = calloc(1, max_len); ++ if (should_copy == NULL) { ++ goto out; ++ } ++ ++ if (strlen(tok->tokens[0]) > 0) { ++ should_copy[0] = true; ++ } ++ ++ for (i = 1; tok->tokens[i]; i++) { ++ for (j = 0; j < i; j++) { ++ if (strcmp(tok->tokens[i], tok->tokens[j]) == 0) { ++ /* Found a duplicate; do not copy */ ++ should_copy[i] = false; ++ break; ++ } ++ } ++ ++ /* No matching token before */ ++ if (j == i) { ++ /* Only copy if it is not an empty string */ ++ if (strlen(tok->tokens[i]) > 0) { ++ should_copy[i] = true; ++ } else { ++ should_copy[i] = false; ++ } ++ } ++ } ++ ++ num_tokens = i; ++ ++ ret = calloc(1, max_len); ++ if (ret == NULL) { ++ goto out; ++ } ++ ++ for (i = 0; i < num_tokens; i++) { ++ if (should_copy[i]) { ++ if (need_comma) { ++ strncat(ret, ",", (max_len - strlen(ret) - 1)); ++ } ++ strncat(ret, tok->tokens[i], (max_len - strlen(ret) - 1)); ++ need_comma = true; ++ } ++ } ++ ++ /* If no comma is needed, nothing was copied */ ++ if (!need_comma) { ++ SAFE_FREE(ret); ++ } ++ ++out: ++ SAFE_FREE(should_copy); ++ ssh_tokens_free(tok); ++ return ret; ++} ++ ++/** ++ * @internal ++ * ++ * @brief Given two strings containing lists of tokens, return a newly ++ * allocated string containing all the elements of the first list appended with ++ * all the elements of the second list, without duplicates. The order of the ++ * elements will be preserved. ++ * ++ * @param[in] list The first list ++ * @param[in] appended_list The list to be appended ++ * ++ * @return A newly allocated copy list containing all the elements of the ++ * kept_list appended with the elements of the appended_list without duplicates; ++ * NULL in case of error. ++ */ ++char *ssh_append_without_duplicates(const char *list, ++ const char *appended_list) ++{ ++ size_t concat_len = 0; ++ char *ret = NULL, *concat = NULL; ++ ++ if (list != NULL) { ++ concat_len = strlen(list); ++ } ++ ++ if (appended_list != NULL) { ++ concat_len += strlen(appended_list); ++ } ++ ++ if (concat_len == 0) { ++ return NULL; ++ } ++ ++ /* Add room for ending '\0' and for middle ',' */ ++ concat_len += 2; ++ concat = calloc(1, concat_len); ++ if (concat == NULL) { ++ return NULL; ++ } ++ ++ if (list != NULL) { ++ strcpy(concat, list); ++ strncat(concat, ",", concat_len - strlen(concat) - 1); ++ } ++ if (appended_list != NULL) { ++ strncat(concat, appended_list, concat_len - strlen(concat) - 1); ++ } ++ ++ ret = ssh_remove_duplicates(concat); ++ ++ SAFE_FREE(concat); ++ ++ return ret; ++} +diff --git a/tests/unittests/torture_tokens.c b/tests/unittests/torture_tokens.c +index e192842f..6b52b847 100644 +--- a/tests/unittests/torture_tokens.c ++++ b/tests/unittests/torture_tokens.c +@@ -146,6 +146,126 @@ static void torture_tokens_sanity(UNUSED_PARAM(void **state)) + tokenize_compare_expected(",", single_colon, 1); + } + ++static void torture_remove_duplicate(UNUSED_PARAM(void **state)) ++{ ++ ++ const char *simple[] = {"a,a,b,b,c,c", ++ "a,b,c,a,b,c", ++ "a,b,c,c,b,a", ++ "a,a,,b,b,,c,c", ++ ",a,a,b,b,c,c", ++ "a,a,b,b,c,c,"}; ++ const char *empty[] = {"", ++ ",,,,,,,,,", ++ NULL}; ++ char *ret = NULL; ++ int i; ++ ++ for (i = 0; i < 6; i++) { ++ ret = ssh_remove_duplicates(simple[i]); ++ assert_non_null(ret); ++ assert_string_equal("a,b,c", ret); ++ printf("simple[%d] resulted in '%s'\n", i, ret); ++ SAFE_FREE(ret); ++ } ++ ++ for (i = 0; i < 3; i++) { ++ ret = ssh_remove_duplicates(empty[i]); ++ if (ret != NULL) { ++ printf("empty[%d] resulted in '%s'\n", i, ret); ++ } ++ assert_null(ret); ++ } ++ ++ ret = ssh_remove_duplicates("a"); ++ assert_non_null(ret); ++ assert_string_equal("a", ret); ++ SAFE_FREE(ret); ++} ++ ++static void torture_append_without_duplicate(UNUSED_PARAM(void **state)) ++{ ++ const char *s1[] = {"a,a,b,b,c,c", ++ "a,b,c,a,b,c", ++ "a,b,c,c,b,a", ++ "a,a,,b,b,,c,c", ++ ",a,a,b,b,c,c", ++ "a,a,b,b,c,c,"}; ++ const char *s2[] = {"a,a,b,b,c,c,d,d", ++ "a,b,c,d,a,b,c,d", ++ "a,b,c,d,d,c,b,a", ++ "a,a,,b,b,,c,c,,d,d", ++ ",a,a,b,b,c,c,d,d", ++ "a,a,b,b,c,c,d,d,", ++ "d"}; ++ const char *empty[] = {"", ++ ",,,,,,,,,", ++ NULL, ++ NULL}; ++ char *ret = NULL; ++ int i, j; ++ ++ ret = ssh_append_without_duplicates("a", "a"); ++ assert_non_null(ret); ++ assert_string_equal("a", ret); ++ SAFE_FREE(ret); ++ ++ ret = ssh_append_without_duplicates("a", "b"); ++ assert_non_null(ret); ++ assert_string_equal("a,b", ret); ++ SAFE_FREE(ret); ++ ++ ret = ssh_append_without_duplicates("a", NULL); ++ assert_non_null(ret); ++ assert_string_equal("a", ret); ++ SAFE_FREE(ret); ++ ++ ret = ssh_append_without_duplicates(NULL, "b"); ++ assert_non_null(ret); ++ assert_string_equal("b", ret); ++ SAFE_FREE(ret); ++ ++ for (i = 0; i < 6; i++) { ++ for (j = 0; j < 7; j++) { ++ ret = ssh_append_without_duplicates(s1[i], s2[j]); ++ assert_non_null(ret); ++ printf("s1[%d] + s2[%d] resulted in '%s'\n", i, j, ret); ++ assert_string_equal("a,b,c,d", ret); ++ SAFE_FREE(ret); ++ } ++ } ++ ++ for (i = 0; i < 6; i++) { ++ for (j = 0; j < 3; j++) { ++ ret = ssh_append_without_duplicates(s1[i], empty[j]); ++ assert_non_null(ret); ++ printf("s1[%d] + empty[%d] resulted in '%s'\n", i, j, ret); ++ assert_string_equal("a,b,c", ret); ++ SAFE_FREE(ret); ++ } ++ } ++ ++ for (i = 0; i < 3; i++) { ++ for (j = 0; j < 6; j++) { ++ ret = ssh_append_without_duplicates(empty[i], s1[j]); ++ assert_non_null(ret); ++ printf("empty[%d] + s1[%d] resulted in '%s'\n", i, j, ret); ++ assert_string_equal("a,b,c", ret); ++ SAFE_FREE(ret); ++ } ++ } ++ for (i = 0; i < 4; i++) { ++ for (j = 0; j < 4; j++) { ++ ret = ssh_append_without_duplicates(empty[i], empty[j]); ++ if (ret != NULL) { ++ printf("empty[%d] + empty[%d] resulted in '%s'\n", i, j, ret); ++ } ++ assert_null(ret); ++ } ++ } ++} ++ ++ + int torture_run_tests(void) + { + int rc; +@@ -153,6 +273,8 @@ int torture_run_tests(void) + cmocka_unit_test(torture_tokens_sanity), + cmocka_unit_test(torture_find_matching), + cmocka_unit_test(torture_find_all_matching), ++ cmocka_unit_test(torture_remove_duplicate), ++ cmocka_unit_test(torture_append_without_duplicate), + }; + + ssh_init(); +-- +2.21.0 + + +From fa3caa61fdb39269cd78c4919df515b71991d231 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Tue, 2 Jul 2019 13:48:17 +0200 +Subject: [PATCH 5/6] knownhosts: Introduced + ssh_known_hosts_get_algorithms_names() + +The added internal function obtain a newly allocated string containing a +list of the signature types that can be generated by the keys present in +the known_hosts files, separated by commas. + +Signed-off-by: Anderson Toshiyuki Sasaki +Reviewed-by: Jakub Jelen +(cherry picked from commit 65a38759ca872e8bec0158ab3676e74b6afd336f) +--- + include/libssh/knownhosts.h | 1 + + src/knownhosts.c | 141 +++++++++++++++++++ + tests/unittests/torture_knownhosts_parsing.c | 28 ++++ + 3 files changed, 170 insertions(+) + +diff --git a/include/libssh/knownhosts.h b/include/libssh/knownhosts.h +index dcaa6c24..44e434c0 100644 +--- a/include/libssh/knownhosts.h ++++ b/include/libssh/knownhosts.h +@@ -23,6 +23,7 @@ + #define SSH_KNOWNHOSTS_H_ + + struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session); ++char *ssh_known_hosts_get_algorithms_names(ssh_session session); + enum ssh_known_hosts_e + ssh_session_get_known_hosts_entry_file(ssh_session session, + const char *filename, +diff --git a/src/knownhosts.c b/src/knownhosts.c +index 8040a0c0..cf9d8a6b 100644 +--- a/src/knownhosts.c ++++ b/src/knownhosts.c +@@ -42,6 +42,7 @@ + #include "libssh/pki.h" + #include "libssh/dh.h" + #include "libssh/knownhosts.h" ++#include "libssh/token.h" + + /** + * @addtogroup libssh_session +@@ -451,6 +452,146 @@ error: + return NULL; + } + ++/** ++ * @internal ++ * ++ * @brief Returns a static string containing a list of the signature types the ++ * given key type can generate. ++ * ++ * @returns A static cstring containing the signature types the key is able to ++ * generate separated by commas; NULL in case of error ++ */ ++static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e type) ++{ ++ switch (type) { ++ case SSH_KEYTYPE_RSA: ++ return "rsa-sha2-512,rsa-sha2-256,ssh-rsa"; ++ case SSH_KEYTYPE_ED25519: ++ return "ssh-ed25519"; ++#ifdef HAVE_DSA ++ case SSH_KEYTYPE_DSS: ++ return "ssh-dss"; ++#endif ++#ifdef HAVE_ECDH ++ case SSH_KEYTYPE_ECDSA_P256: ++ return "ecdsa-sha2-nistp256"; ++ case SSH_KEYTYPE_ECDSA_P384: ++ return "ecdsa-sha2-nistp384"; ++ case SSH_KEYTYPE_ECDSA_P521: ++ return "ecdsa-sha2-nistp521"; ++#endif ++ case SSH_KEYTYPE_UNKNOWN: ++ default: ++ SSH_LOG(SSH_LOG_WARN, "The given type %d is not a base private key type " ++ "or is unsupported", type); ++ return NULL; ++ } ++} ++ ++/** ++ * @internal ++ * @brief Get the host keys algorithms identifiers from the known_hosts files ++ * ++ * This expands the signatures types that can be generated from the keys types ++ * present in the known_hosts files ++ * ++ * @param[in] session The ssh session to use. ++ * ++ * @return A newly allocated cstring containing a list of signature algorithms ++ * that can be generated by the host using the keys listed in the known_hosts ++ * files, NULL on error. ++ */ ++char *ssh_known_hosts_get_algorithms_names(ssh_session session) ++{ ++ char methods_buffer[256 + 1] = {0}; ++ struct ssh_list *entry_list = NULL; ++ struct ssh_iterator *it = NULL; ++ char *host_port = NULL; ++ size_t count; ++ bool needcomma = false; ++ char *names; ++ ++ int rc; ++ ++ if (session->opts.knownhosts == NULL || ++ session->opts.global_knownhosts == NULL) { ++ if (ssh_options_apply(session) < 0) { ++ ssh_set_error(session, ++ SSH_REQUEST_DENIED, ++ "Can't find a known_hosts file"); ++ ++ return NULL; ++ } ++ } ++ ++ host_port = ssh_session_get_host_port(session); ++ if (host_port == NULL) { ++ return NULL; ++ } ++ ++ rc = ssh_known_hosts_read_entries(host_port, ++ session->opts.knownhosts, ++ &entry_list); ++ if (rc != 0) { ++ SAFE_FREE(host_port); ++ ssh_list_free(entry_list); ++ return NULL; ++ } ++ ++ rc = ssh_known_hosts_read_entries(host_port, ++ session->opts.global_knownhosts, ++ &entry_list); ++ SAFE_FREE(host_port); ++ if (rc != 0) { ++ ssh_list_free(entry_list); ++ return NULL; ++ } ++ ++ if (entry_list == NULL) { ++ return NULL; ++ } ++ ++ count = ssh_list_count(entry_list); ++ if (count == 0) { ++ ssh_list_free(entry_list); ++ return NULL; ++ } ++ ++ for (it = ssh_list_get_iterator(entry_list); ++ it != NULL; ++ it = ssh_list_get_iterator(entry_list)) ++ { ++ struct ssh_knownhosts_entry *entry = NULL; ++ const char *algo = NULL; ++ ++ entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); ++ algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type); ++ if (algo == NULL) { ++ continue; ++ } ++ ++ if (needcomma) { ++ strncat(methods_buffer, ++ ",", ++ sizeof(methods_buffer) - strlen(methods_buffer) - 1); ++ } ++ ++ strncat(methods_buffer, ++ algo, ++ sizeof(methods_buffer) - strlen(methods_buffer) - 1); ++ needcomma = true; ++ ++ ssh_knownhosts_entry_free(entry); ++ ssh_list_remove(entry_list, it); ++ } ++ ++ ssh_list_free(entry_list); ++ ++ names = ssh_remove_duplicates(methods_buffer); ++ ++ return names; ++} ++ + /** + * @brief Parse a line from a known_hosts entry into a structure + * +diff --git a/tests/unittests/torture_knownhosts_parsing.c b/tests/unittests/torture_knownhosts_parsing.c +index a087caef..6952e858 100644 +--- a/tests/unittests/torture_knownhosts_parsing.c ++++ b/tests/unittests/torture_knownhosts_parsing.c +@@ -369,6 +369,31 @@ static void torture_knownhosts_read_file(void **state) + ssh_list_free(entry_list); + } + ++static void torture_knownhosts_get_algorithms_names(void **state) ++{ ++ const char *knownhosts_file = *state; ++ ssh_session session; ++ const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa"; ++ char *names = NULL; ++ bool process_config = false; ++ ++ session = ssh_new(); ++ assert_non_null(session); ++ ++ /* This makes sure the global configuration file is not processed */ ++ ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); ++ ++ ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); ++ ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); ++ ++ names = ssh_known_hosts_get_algorithms_names(session); ++ assert_non_null(names); ++ assert_string_equal(names, expect); ++ ++ SAFE_FREE(names); ++ ssh_free(session); ++} ++ + #ifndef _WIN32 /* There is no /dev/null on Windows */ + static void torture_knownhosts_host_exists(void **state) + { +@@ -510,6 +535,9 @@ int torture_run_tests(void) { + cmocka_unit_test_setup_teardown(torture_knownhosts_read_file, + setup_knownhosts_file_duplicate, + teardown_knownhosts_file), ++ cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names, ++ setup_knownhosts_file, ++ teardown_knownhosts_file), + #ifndef _WIN32 + cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists, + setup_knownhosts_file, +-- +2.21.0 + + +From 1fd68ec732214a12ba3f59ca23f80463411e22dd Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Mon, 1 Jul 2019 19:39:07 +0200 +Subject: [PATCH 6/6] kex: Do not ignore keys in known_hosts files + +Previously, if the SSH_OPTIONS_HOSTKEYS option was set by any mean, +including the client configuration file, the keys in known_hosts files +wouldn't be considered before advertising the list of wanted host keys. + +This could result in the client requesting the server to provide a +signature using a key not present in the known_hosts files (e.g. when +the first wanted algorithm in SSH_OPTIONS_HOSTKEYS is not present in the +known_hosts files), causing a host key mismatch and possible key +rejection. + +Now, the keys present in the known_hosts files are prioritized over the +other wanted keys. This do not change the fact that only keys of types +present in the list set in SSH_OPTIONS_HOSTKEYS will be accepted and +prioritized following the order defined by such list. + +The new wanted list of hostkeys is given by: + - The keys present in known_hosts files, ordered by preference defined + in SSH_OPTIONS_HOSTKEYS. If the option is not set, a default order + of preference is used. + - The other keys present in the same option are appended without adding + duplicates. If the option is not set, the default list of keys is + used. + +Fixes: T156 + +Signed-off-by: Anderson Toshiyuki Sasaki +Reviewed-by: Jakub Jelen +(cherry picked from commit f18a7cc17e399ae7bc92f707da3a676c52fd948e) +--- + src/kex.c | 165 +++++++++---------- + tests/unittests/torture_knownhosts_parsing.c | 148 ++++++++++++++++- + 2 files changed, 223 insertions(+), 90 deletions(-) + +diff --git a/src/kex.c b/src/kex.c +index 6ea5e8ba..0d4cad6d 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -561,103 +561,94 @@ 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 ++ * as some hostkey mechanisms may be present in known_hosts files. ++ * + * @returns a cstring containing a comma-separated list of hostkey methods. + * NULL if no method matches + */ + char *ssh_client_select_hostkeys(ssh_session session) + { +- char methods_buffer[128]={0}; +- char tail_buffer[128]={0}; ++ const char *wanted = NULL; ++ char *wanted_without_certs = NULL; ++ char *known_hosts_algorithms = NULL; ++ char *known_hosts_ordered = NULL; + char *new_hostkeys = NULL; +- static const char *preferred_hostkeys[] = { +- "ssh-ed25519", +- "ecdsa-sha2-nistp521", +- "ecdsa-sha2-nistp384", +- "ecdsa-sha2-nistp256", +- "rsa-sha2-512", +- "rsa-sha2-256", +- "ssh-rsa", +-#ifdef HAVE_DSA +- "ssh-dss", +-#endif +- NULL +- }; +- struct ssh_list *algo_list = NULL; +- struct ssh_iterator *it = NULL; +- size_t algo_count; +- int needcomma = 0; +- size_t i, len; +- +- algo_list = ssh_known_hosts_get_algorithms(session); +- if (algo_list == NULL) { +- return NULL; ++ char *fips_hostkeys = NULL; ++ ++ wanted = session->opts.wanted_methods[SSH_HOSTKEYS]; ++ if (wanted == NULL) { ++ if (ssh_fips_mode()) { ++ wanted = ssh_kex_get_fips_methods(SSH_HOSTKEYS); ++ } else { ++ wanted = ssh_kex_get_default_methods(SSH_HOSTKEYS); ++ } + } + +- algo_count = ssh_list_count(algo_list); +- if (algo_count == 0) { +- ssh_list_free(algo_list); ++ /* This removes the certificate types, unsupported for now */ ++ wanted_without_certs = ssh_find_all_matching(HOSTKEYS, wanted); ++ if (wanted_without_certs == NULL) { ++ SSH_LOG(SSH_LOG_WARNING, ++ "List of allowed host key algorithms is empty or contains only " ++ "unsupported algorithms"); + return NULL; + } + +- for (i = 0; preferred_hostkeys[i] != NULL; ++i) { +- bool found = false; +- /* This is a signature type: We list also the SHA2 extensions */ +- enum ssh_keytypes_e base_preferred = +- ssh_key_type_from_signature_name(preferred_hostkeys[i]); +- +- for (it = ssh_list_get_iterator(algo_list); +- it != NULL; +- it = it->next) { +- const char *algo = ssh_iterator_value(const char *, it); +- /* This is always key type so we do not have to care for the +- * SHA2 extension */ +- enum ssh_keytypes_e base_algo = ssh_key_type_from_name(algo); +- +- if (base_preferred == base_algo) { +- /* Matching the keys already verified it is a known type */ +- if (needcomma) { +- strncat(methods_buffer, +- ",", +- sizeof(methods_buffer) - strlen(methods_buffer) - 1); +- } +- strncat(methods_buffer, +- preferred_hostkeys[i], +- sizeof(methods_buffer) - strlen(methods_buffer) - 1); +- needcomma = 1; +- found = true; +- } +- } +- /* Collect the rest of the algorithms in other buffer, that will +- * follow the preferred buffer. This will signalize all the algorithms +- * we are willing to accept. +- */ +- if (!found) { +- snprintf(tail_buffer + strlen(tail_buffer), +- sizeof(tail_buffer) - strlen(tail_buffer), +- ",%s", preferred_hostkeys[i]); +- } ++ SSH_LOG(SSH_LOG_DEBUG, ++ "Order of wanted host keys: \"%s\"", ++ wanted_without_certs); ++ ++ known_hosts_algorithms = ssh_known_hosts_get_algorithms_names(session); ++ if (known_hosts_algorithms == NULL) { ++ SSH_LOG(SSH_LOG_DEBUG, ++ "No key found in known_hosts; " ++ "changing host key method to \"%s\"", ++ wanted_without_certs); ++ ++ return wanted_without_certs; + } +- ssh_list_free(algo_list); + +- if (strlen(methods_buffer) == 0) { ++ SSH_LOG(SSH_LOG_DEBUG, ++ "Algorithms found in known_hosts files: \"%s\"", ++ known_hosts_algorithms); ++ ++ /* Filter and order the keys from known_hosts according to wanted list */ ++ known_hosts_ordered = ssh_find_all_matching(known_hosts_algorithms, ++ wanted_without_certs); ++ SAFE_FREE(known_hosts_algorithms); ++ if (known_hosts_ordered == NULL) { + SSH_LOG(SSH_LOG_DEBUG, +- "No supported kex method for existing key in known_hosts file"); +- return NULL; ++ "No key found in known_hosts is allowed; " ++ "changing host key method to \"%s\"", ++ wanted_without_certs); ++ ++ return wanted_without_certs; + } + +- /* Append the supported list to the preferred. +- * The length is maximum 128 + 128 + 1, which will not overflow +- */ +- len = strlen(methods_buffer) + strlen(tail_buffer) + 1; +- new_hostkeys = malloc(len); ++ /* Append the other supported keys after the preferred ones ++ * This function tolerates NULL pointers in parameters */ ++ new_hostkeys = ssh_append_without_duplicates(known_hosts_ordered, ++ wanted_without_certs); ++ SAFE_FREE(known_hosts_ordered); ++ SAFE_FREE(wanted_without_certs); + if (new_hostkeys == NULL) { + ssh_set_error_oom(session); + return NULL; + } +- snprintf(new_hostkeys, len, +- "%s%s", methods_buffer, tail_buffer); ++ ++ if (ssh_fips_mode()) { ++ /* Filter out algorithms not allowed in FIPS mode */ ++ fips_hostkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, new_hostkeys); ++ SAFE_FREE(new_hostkeys); ++ if (fips_hostkeys == NULL) { ++ SSH_LOG(SSH_LOG_WARNING, ++ "None of the wanted host keys or keys in known_hosts files " ++ "is allowed in FIPS mode."); ++ return NULL; ++ } ++ new_hostkeys = fips_hostkeys; ++ } + + SSH_LOG(SSH_LOG_DEBUG, + "Changing host key method to \"%s\"", +@@ -672,7 +663,7 @@ char *ssh_client_select_hostkeys(ssh_session session) + */ + int ssh_set_client_kex(ssh_session session) + { +- struct ssh_kex_struct *client= &session->next_crypto->client_kex; ++ struct ssh_kex_struct *client = &session->next_crypto->client_kex; + const char *wanted; + char *kex = NULL; + char *kex_tmp = NULL; +@@ -687,14 +678,22 @@ int ssh_set_client_kex(ssh_session session) + } + + memset(client->methods, 0, KEX_METHODS_SIZE * sizeof(char **)); +- /* first check if we have specific host key methods */ +- if (session->opts.wanted_methods[SSH_HOSTKEYS] == NULL) { +- /* Only if no override */ +- session->opts.wanted_methods[SSH_HOSTKEYS] = +- ssh_client_select_hostkeys(session); +- } + ++ /* Set the list of allowed algorithms in order of preference, if it hadn't ++ * been set yet. */ + for (i = 0; i < KEX_METHODS_SIZE; i++) { ++ if (i == SSH_HOSTKEYS) { ++ /* Set the hostkeys in the following order: ++ * - First: keys present in known_hosts files ordered by preference ++ * - Next: other wanted algorithms ordered by preference */ ++ client->methods[i] = ssh_client_select_hostkeys(session); ++ if (client->methods[i] == NULL) { ++ ssh_set_error_oom(session); ++ return SSH_ERROR; ++ } ++ continue; ++ } ++ + wanted = session->opts.wanted_methods[i]; + if (wanted == NULL) { + if (ssh_fips_mode()) { +diff --git a/tests/unittests/torture_knownhosts_parsing.c b/tests/unittests/torture_knownhosts_parsing.c +index 6952e858..1c2ccc10 100644 +--- a/tests/unittests/torture_knownhosts_parsing.c ++++ b/tests/unittests/torture_knownhosts_parsing.c +@@ -28,6 +28,8 @@ + + #define TMP_FILE_NAME "/tmp/known_hosts_XXXXXX" + ++const char template[] = "temp_dir_XXXXXX"; ++ + static int setup_knownhosts_file(void **state) + { + char *tmp_file = NULL; +@@ -394,6 +396,115 @@ static void torture_knownhosts_get_algorithms_names(void **state) + ssh_free(session); + } + ++static void torture_knownhosts_algorithms_wanted(void **state) ++{ ++ const char *knownhosts_file = *state; ++ char *algo_list = NULL; ++ ssh_session session; ++ bool process_config = false; ++ const char *wanted = "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256," ++ "rsa-sha2-256,ecdsa-sha2-nistp521"; ++ const char *expect = "rsa-sha2-256,ecdsa-sha2-nistp384," ++ "ecdsa-sha2-nistp256,ecdsa-sha2-nistp521"; ++ int verbose = 4; ++ ++ session = ssh_new(); ++ assert_non_null(session); ++ ++ ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); ++ ++ /* This makes sure the global configuration file is not processed */ ++ ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); ++ ++ /* Set the wanted list of hostkeys, ordered by preference */ ++ ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); ++ ++ ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); ++ ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); ++ ++ algo_list = ssh_client_select_hostkeys(session); ++ assert_non_null(algo_list); ++ assert_string_equal(algo_list, expect); ++ free(algo_list); ++ ++ ssh_free(session); ++} ++ ++static void torture_knownhosts_algorithms_negative(UNUSED_PARAM(void **state)) ++{ ++ const char *wanted = NULL; ++ const char *expect = NULL; ++ ++ char *algo_list = NULL; ++ ++ char *cwd = NULL; ++ char *tmp_dir = NULL; ++ ++ bool process_config = false; ++ int verbose = 4; ++ int rc = 0; ++ ++ ssh_session session; ++ /* Create temporary directory */ ++ cwd = torture_get_current_working_dir(); ++ assert_non_null(cwd); ++ ++ tmp_dir = torture_make_temp_dir(template); ++ assert_non_null(tmp_dir); ++ ++ rc = torture_change_dir(tmp_dir); ++ assert_int_equal(rc, 0); ++ ++ session = ssh_new(); ++ assert_non_null(session); ++ ++ ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); ++ ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); ++ ++ /* Test with unknown key type in known_hosts */ ++ wanted = "rsa-sha2-256"; ++ ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); ++ torture_write_file("unknown_key_type", "localhost unknown AAAABBBBCCCC"); ++ ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "unknown_key_type"); ++ algo_list = ssh_client_select_hostkeys(session); ++ assert_non_null(algo_list); ++ assert_string_equal(algo_list, wanted); ++ SAFE_FREE(algo_list); ++ ++ /* Test with unsupported, but existing types */ ++ wanted = "rsa-sha2-256-cert-v01@openssh.com," ++ "rsa-sha2-512-cert-v01@openssh.com"; ++ ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); ++ algo_list = ssh_client_select_hostkeys(session); ++ assert_null(algo_list); ++ ++ /* In FIPS mode, test filtering keys not allowed */ ++ if (ssh_fips_mode()) { ++ wanted = "ssh-ed25519,rsa-sha2-256,ssh-rsa"; ++ expect = "rsa-sha2-256"; ++ ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); ++ torture_write_file("no_fips", LOCALHOST_DEFAULT_ED25519); ++ ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "no_fips"); ++ algo_list = ssh_client_select_hostkeys(session); ++ assert_non_null(algo_list); ++ assert_string_equal(algo_list, expect); ++ SAFE_FREE(algo_list); ++ } ++ ++ ssh_free(session); ++ ++ /* Teardown */ ++ rc = torture_change_dir(cwd); ++ assert_int_equal(rc, 0); ++ ++ rc = torture_rmdirs(tmp_dir); ++ assert_int_equal(rc, 0); ++ ++ SAFE_FREE(tmp_dir); ++ SAFE_FREE(cwd); ++} ++ + #ifndef _WIN32 /* There is no /dev/null on Windows */ + static void torture_knownhosts_host_exists(void **state) + { +@@ -457,12 +568,12 @@ static void torture_knownhosts_host_exists_global(void **state) + ssh_free(session); + } + +-static void +-torture_knownhosts_algorithms(void **state) ++static void torture_knownhosts_algorithms(void **state) + { + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; ++ bool process_config = false; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256" +@@ -470,10 +581,15 @@ torture_knownhosts_algorithms(void **state) + ",ssh-dss" + #endif + ; ++ const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," ++ "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; + + session = ssh_new(); + assert_non_null(session); + ++ /* This makes sure the global configuration file is not processed */ ++ ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); ++ + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + /* This makes sure the system's known_hosts are not used */ +@@ -481,18 +597,22 @@ torture_knownhosts_algorithms(void **state) + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); +- assert_string_equal(algo_list, expect); ++ if (ssh_fips_mode()) { ++ assert_string_equal(algo_list, expect_fips); ++ } else { ++ assert_string_equal(algo_list, expect); ++ } + free(algo_list); + + ssh_free(session); + } + +-static void +-torture_knownhosts_algorithms_global(void **state) ++static void torture_knownhosts_algorithms_global(void **state) + { + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; ++ bool process_config = false; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256" +@@ -500,10 +620,15 @@ torture_knownhosts_algorithms_global(void **state) + ",ssh-dss" + #endif + ; ++ const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," ++ "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; + + session = ssh_new(); + assert_non_null(session); + ++ /* This makes sure the global configuration file is not processed */ ++ ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); ++ + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + /* This makes sure the current-user's known hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); +@@ -511,12 +636,17 @@ torture_knownhosts_algorithms_global(void **state) + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); +- assert_string_equal(algo_list, expect); ++ if (ssh_fips_mode()) { ++ assert_string_equal(algo_list, expect_fips); ++ } else { ++ assert_string_equal(algo_list, expect); ++ } + free(algo_list); + + ssh_free(session); + } +-#endif ++ ++#endif /* _WIN32 There is no /dev/null on Windows */ + + int torture_run_tests(void) { + int rc; +@@ -538,6 +668,10 @@ int torture_run_tests(void) { + cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names, + setup_knownhosts_file, + teardown_knownhosts_file), ++ cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_wanted, ++ setup_knownhosts_file, ++ teardown_knownhosts_file), ++ cmocka_unit_test(torture_knownhosts_algorithms_negative), + #ifndef _WIN32 + cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists, + setup_knownhosts_file, +-- +2.21.0 + diff --git a/SOURCES/libssh-0.9.0-run-sshd-confined.patch b/SOURCES/libssh-0.9.0-run-sshd-confined.patch new file mode 100644 index 0000000..4bc62bb --- /dev/null +++ b/SOURCES/libssh-0.9.0-run-sshd-confined.patch @@ -0,0 +1,67 @@ +--- a/tests/torture.c 2019-06-28 14:01:52.936462964 +0200 ++++ b/tests/torture.c 2019-06-28 14:03:52.324325029 +0200 +@@ -854,7 +854,7 @@ + s = *state; + + snprintf(sshd_start_cmd, sizeof(sshd_start_cmd), +- "/usr/sbin/sshd -r -f %s -E %s/sshd/daemon.log 2> %s/sshd/cwrap.log", ++ "runcon -t sshd_t -u system_u -r system_r /usr/sbin/sshd -r -f %s -E %s/sshd/daemon.log 2> %s/sshd/cwrap.log", + s->srv_config, s->socket_dir, s->socket_dir); + + rc = system(sshd_start_cmd); +@@ -864,7 +864,7 @@ + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ +- rc = torture_wait_for_daemon(5); ++ rc = torture_wait_for_daemon(10); + assert_int_equal(rc, 0); + } + +@@ -904,27 +904,32 @@ + torture_reload_sshd_server(void **state) + { + struct torture_state *s = *state; +- pid_t pid; ++ char sshd_start_cmd[1024]; + int rc; + +- /* read the pidfile */ +- pid = torture_read_pidfile(s->srv_pidfile); +- assert_int_not_equal(pid, -1); ++ rc = torture_terminate_process(s->srv_pidfile); ++ if (rc != 0) { ++ fprintf(stderr, "XXXXXX Failed to terminate sshd\n"); ++ } + +- kill(pid, SIGHUP); ++ usleep(100 * 1000); + +- /* 10 ms */ +- usleep(10 * 1000); ++ /* Set the default interface for the server */ ++ setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); ++ setenv("PAM_WRAPPER", "1", 1); + +- rc = kill(pid, 0); +- if (rc != 0) { +- fprintf(stderr, +- "ERROR: SSHD process %u died during reload!\n", pid); +- return SSH_ERROR; +- } ++ snprintf(sshd_start_cmd, sizeof(sshd_start_cmd), ++ "runcon -t sshd_t -u system_u -r system_r /usr/sbin/sshd -r -f %s -E %s/sshd/daemon.log 2> %s/sshd/cwrap.log", ++ s->srv_config, s->socket_dir, s->socket_dir); ++ ++ rc = system(sshd_start_cmd); ++ assert_return_code(rc, errno); ++ ++ setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); ++ unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ +- rc = torture_wait_for_daemon(5); ++ rc = torture_wait_for_daemon(10); + assert_int_equal(rc, 0); + return SSH_OK; + } diff --git a/SOURCES/libssh-0.9.0-skip-1k-rsa-key-generation-test.patch b/SOURCES/libssh-0.9.0-skip-1k-rsa-key-generation-test.patch new file mode 100644 index 0000000..f45a27d --- /dev/null +++ b/SOURCES/libssh-0.9.0-skip-1k-rsa-key-generation-test.patch @@ -0,0 +1,59 @@ +From bf2ed2ca929e5e12279f85c930f8fbb452ada888 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Tue, 30 Jul 2019 18:22:30 +0200 +Subject: [PATCH] tests: Skip testing 1024 bits key generation in FIPS mode + +In torture_threads_pki_rsa, skip the test which generates 1024 bits RSA +key pair when in FIPS mode. + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + tests/unittests/torture_threads_pki_rsa.c | 28 ++++++++++++----------- + 1 file changed, 15 insertions(+), 13 deletions(-) + +diff --git a/tests/unittests/torture_threads_pki_rsa.c b/tests/unittests/torture_threads_pki_rsa.c +index 5a841ee9..03d526cd 100644 +--- a/tests/unittests/torture_threads_pki_rsa.c ++++ b/tests/unittests/torture_threads_pki_rsa.c +@@ -571,23 +571,25 @@ static void *thread_pki_rsa_generate_key(void *threadid) + session = ssh_new(); + assert_non_null(session); + +- rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); +- assert_ssh_return_code(session, rc); +- assert_non_null(key); ++ if (!ssh_fips_mode()) { ++ rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); ++ assert_ssh_return_code(session, rc); ++ assert_non_null(key); + +- rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); +- assert_int_equal(rc, SSH_OK); +- assert_non_null(pubkey); ++ rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); ++ assert_int_equal(rc, SSH_OK); ++ assert_non_null(pubkey); + +- sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); +- assert_non_null(sign); ++ sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); ++ assert_non_null(sign); + +- rc = pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); +- assert_ssh_return_code(session, rc); ++ rc = pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); ++ assert_ssh_return_code(session, rc); + +- ssh_signature_free(sign); +- SSH_KEY_FREE(key); +- SSH_KEY_FREE(pubkey); ++ ssh_signature_free(sign); ++ SSH_KEY_FREE(key); ++ SSH_KEY_FREE(pubkey); ++ } + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_ssh_return_code(session, rc); +-- +2.21.0 + diff --git a/SOURCES/libssh-0.9.0.tar.xz.asc b/SOURCES/libssh-0.9.0.tar.xz.asc new file mode 100644 index 0000000..880c31b --- /dev/null +++ b/SOURCES/libssh-0.9.0.tar.xz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCAAdFiEEjf9T4Y8qvI2PPJIjfuD8TcwBTj0FAl0VtfEACgkQfuD8TcwB +Tj0cthAApYglGlE2+f06uKvPUc8LvzVkRFJA5ycm7uMddrMbSJ+ElScEu1a6q/qV +xPK3w49WdDpbYcz3s/GtdhGkSTye49Zav2PJzDFWvac+PoXVOwt7C5JzlPSq7kfa +0i6k1/7YSiPcFCo7467v2fpg0t80OHgS+Tbc1mwI0KtRzPA2pjqHsnu+RVNc6EA1 +paUf1QMhjPFIQwcqJcQGAHZBqpx4JBspzBpC7wDxQKFh5FfMbVOxPG3qvgeOYDqd +cbfsJkdOJQ0rAxyNBB6xbz8XktJyK4Bjg5eT9GEz+zpsjh8jtfJMhNEemaBLXkug ++4wb4DU2uhEuOoOFh0YOgLcRLm2INNhVSTcizasmA8X1YcvAUmKB7fUMuYwOQOPZ +bsSkJ8kg7v76Y8w/pUger99pVYg0iiLi9KI6a2t7qTSibZewv38IV6eSJqORnZja +SLeswZUAAtHK/nTf7ohZ8Blnhx3UE5M6vyuli+KMmHAxTOzHhyWJvYDYJ2oJ7+tR +N49U1O77VE0WYY7HoyEXYkvSwWLb+MYK6ueaJTVBEbq7ZdpzQBQtPLoyCXUdQwZ2 +DyZaaZnhkn9FYvsJp/twHra3XlQ359EXdMwflISaKgFUpfaluLIu1xRGrYH4XPNm +FHZRPCj34PY1uDOKHXiRl/xUdaR4CSIKPgofhxzy/mLZepJR9vU= +=xGD9 +-----END PGP SIGNATURE----- diff --git a/SOURCES/libssh_client.config b/SOURCES/libssh_client.config new file mode 100644 index 0000000..1d293bd --- /dev/null +++ b/SOURCES/libssh_client.config @@ -0,0 +1,4 @@ +# Parse system-wide crypto configuration file +Include /etc/crypto-policies/back-ends/libssh.config +# Parse OpenSSH configuration file for consistency +Include /etc/ssh/ssh_config diff --git a/SOURCES/libssh_server.config b/SOURCES/libssh_server.config new file mode 100644 index 0000000..279b5a3 --- /dev/null +++ b/SOURCES/libssh_server.config @@ -0,0 +1,4 @@ +# Parse system-wide crypto configuration file +Include /etc/crypto-policies/back-ends/libssh.config +# Parse OpenSSH configuration file for consistency +Include /etc/ssh/sshd_config diff --git a/SPECS/libssh.spec b/SPECS/libssh.spec index 968f6a2..925aa8f 100644 --- a/SPECS/libssh.spec +++ b/SPECS/libssh.spec @@ -1,18 +1,19 @@ Name: libssh -Version: 0.8.5 -Release: 2%{?dist} +Version: 0.9.0 +Release: 4%{?dist} Summary: A library implementing the SSH protocol License: LGPLv2+ URL: http://www.libssh.org -Source0: https://www.libssh.org/files/0.8/%{name}-%{version}.tar.xz -Source1: https://www.libssh.org/files/0.8/%{name}-%{version}.tar.xz.asc +Source0: https://www.libssh.org/files/0.9/%{name}-%{version}.tar.xz +Source1: https://www.libssh.org/files/0.9/%{name}-%{version}.tar.xz.asc Source2: https://cryptomilk.org/gpgkey-8DFF53E18F2ABC8D8F3C92237EE0FC4DCC014E3D.gpg#/%{name}.keyring +Source3: libssh_client.config +Source4: libssh_server.config -Patch1: libssh-0.8.5-make-sure-both-known-hosts-ready.patch -Patch2: libssh-0.8.5-ensure-ssh-session-fd-writable.patch -Patch3: libssh-0.8.5-allow-msg-ext-info.patch -Patch4: libssh-0.8.5-allow-kex-init.patch +Patch0: libssh-0.9.0-run-sshd-confined.patch +Patch1: libssh-0.9.0-do-not-ignore-known-hosts-keys.patch +Patch2: libssh-0.9.0-skip-1k-rsa-key-generation-test.patch BuildRequires: cmake BuildRequires: doxygen @@ -24,6 +25,9 @@ BuildRequires: zlib-devel BuildRequires: krb5-devel BuildRequires: libcmocka-devel +Requires: crypto-policies +Requires: %{name}-config = %{version}-%{release} + %ifarch aarch64 ppc64 ppc64le s390x x86_64 Provides: libssh_threads.so()(64bit) Provides: libssh_threads.so.4()(64bit) @@ -48,6 +52,14 @@ Requires: %{name}%{?_isa} = %{version}-%{release} The %{name}-devel package contains libraries and header files for developing applications that use %{name}. +%package config +Summary: Configuration files for %{name} +BuildArch: noarch +Obsoletes: %{name} < 0.9.0-1 + +%description config +The %{name}-config package provides the default configuration files for %{name}. + %prep gpgv2 --quiet --keyring %{SOURCE2} %{SOURCE1} %{SOURCE0} %autosetup -p1 @@ -59,7 +71,10 @@ fi pushd obj %cmake .. \ - -DUNIT_TESTING=ON + -DUNIT_TESTING=ON \ + -DGLOBAL_CLIENT_CONFIG="%{_sysconfdir}/libssh/libssh_client.config" \ + -DGLOBAL_BIND_CONFIG="%{_sysconfdir}/libssh/libssh_server.config" + %make_build VERBOSE=1 make docs @@ -68,6 +83,9 @@ popd %install make DESTDIR=%{buildroot} install/fast -C obj +install -d -m755 %{buildroot}%{_sysconfdir}/libssh +install -m644 %{SOURCE3} %{buildroot}%{_sysconfdir}/libssh/libssh_client.config +install -m644 %{SOURCE4} %{buildroot}%{_sysconfdir}/libssh/libssh_server.config # # Workaround for the removal of libssh_threads.so @@ -109,7 +127,35 @@ popd %{_libdir}/pkgconfig/libssh.pc %{_libdir}/libssh.so +%files config +%attr(0755,root,root) %dir %{_sysconfdir}/libssh +%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/libssh/libssh_client.config +%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/libssh/libssh_server.config + %changelog +* Mon Aug 05 2019 Anderson Sasaki - 0.9.0-4 +- Skip 1024 bits RSA key generation test in FIPS mode (#1734485) + +* Thu Jul 11 2019 Anderson Sasaki - 0.9.0-3 +- Add Obsoletes in libssh-config to avoid conflict with old libssh which + installed the configuration files. + +* Wed Jul 10 2019 Anderson Sasaki - 0.9.0-2 +- Eliminate circular dependency with libssh-config subpackage + +* Wed Jul 10 2019 Anderson Sasaki - 0.9.0-1 +- Update to version 0.9.0 + https://www.libssh.org/2019/06/28/libssh-0-9-0/ +- Added explicit Requires for crypto-policies +- Do not ignore known_hosts keys when SSH_OPTIONS_HOSTKEYS is set +- Provide the configuration files in a separate libssh-config subpackage + +* Mon Jun 17 2019 Anderson Sasaki - 0.8.91-0.1 +- Update to 0.9.0 pre release version (0.8.91) +- Added default configuration files for client and server +- Removed unused patch files left behind +- Fixed issues found to run upstream test suite with SELinux + * Fri Dec 14 2018 Anderson Sasaki - 0.8.5-2 - Fix more regressions introduced by the fixes for CVE-2018-10933