Blame SOURCES/libssh-0.9.0-do-not-ignore-known-hosts-keys.patch

b01d5e
From b040856ccfde1a5d4c21791f46ca6ee00c21a47b Mon Sep 17 00:00:00 2001
b01d5e
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Date: Tue, 2 Jul 2019 10:21:15 +0200
b01d5e
Subject: [PATCH 1/6] knownhosts: Fix possible memory leak
b01d5e
b01d5e
The memory allocated for host_port can leak if the global knownhosts
b01d5e
file is unaccessible.
b01d5e
b01d5e
Found by address sanitizer build in CI.
b01d5e
b01d5e
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
b01d5e
(cherry picked from commit fe248414fec1e654e4ee1259927d68777dd870ae)
b01d5e
---
b01d5e
 src/knownhosts.c | 4 +++-
b01d5e
 1 file changed, 3 insertions(+), 1 deletion(-)
b01d5e
b01d5e
diff --git a/src/knownhosts.c b/src/knownhosts.c
b01d5e
index 8a4a8ba7..9383cc97 100644
b01d5e
--- a/src/knownhosts.c
b01d5e
+++ b/src/knownhosts.c
b01d5e
@@ -706,13 +706,15 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
b01d5e
         rc = ssh_known_hosts_read_entries(host_port,
b01d5e
                                           session->opts.global_knownhosts,
b01d5e
                                           &entry_list);
b01d5e
-        SAFE_FREE(host_port);
b01d5e
         if (rc != 0) {
b01d5e
+            SAFE_FREE(host_port);
b01d5e
             ssh_list_free(entry_list);
b01d5e
             return SSH_KNOWN_HOSTS_ERROR;
b01d5e
         }
b01d5e
     }
b01d5e
 
b01d5e
+    SAFE_FREE(host_port);
b01d5e
+
b01d5e
     if (ssh_list_count(entry_list) == 0) {
b01d5e
         ssh_list_free(entry_list);
b01d5e
         return SSH_KNOWN_HOSTS_UNKNOWN;
b01d5e
-- 
b01d5e
2.21.0
b01d5e
b01d5e
b01d5e
From 7ff0af75436ee6e549907dd563e968b92f7f8db2 Mon Sep 17 00:00:00 2001
b01d5e
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Date: Fri, 28 Jun 2019 13:19:51 +0200
b01d5e
Subject: [PATCH 2/6] tests: Check if known_hosts works with single
b01d5e
 unaccessible file
b01d5e
b01d5e
Make sure known hosts check works when local known_hosts file is
b01d5e
unaccessible, but the host is present in global known_hosts file.
b01d5e
b01d5e
Remove double return value check in previous existing test.
b01d5e
b01d5e
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
b01d5e
(cherry picked from commit ad68de7271e6ccda261df4d9fc827321e7d90fd0)
b01d5e
---
b01d5e
 tests/unittests/torture_knownhosts_parsing.c | 19 +++++++++++--------
b01d5e
 1 file changed, 11 insertions(+), 8 deletions(-)
b01d5e
b01d5e
diff --git a/tests/unittests/torture_knownhosts_parsing.c b/tests/unittests/torture_knownhosts_parsing.c
b01d5e
index ac8d7f31..a087caef 100644
b01d5e
--- a/tests/unittests/torture_knownhosts_parsing.c
b01d5e
+++ b/tests/unittests/torture_knownhosts_parsing.c
b01d5e
@@ -384,22 +384,19 @@ static void torture_knownhosts_host_exists(void **state)
b01d5e
 
b01d5e
     /* This makes sure the system's known_hosts are not used */
b01d5e
     ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "/dev/null");
b01d5e
-
b01d5e
     found = ssh_session_has_known_hosts_entry(session);
b01d5e
     assert_int_equal(found, SSH_KNOWN_HOSTS_OK);
b01d5e
-    assert_true(found == SSH_KNOWN_HOSTS_OK);
b01d5e
 
b01d5e
     /* This makes sure the check will not fail when the system's known_hosts is
b01d5e
      * not accessible*/
b01d5e
     ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "./unaccessible");
b01d5e
-
b01d5e
     found = ssh_session_has_known_hosts_entry(session);
b01d5e
     assert_int_equal(found, SSH_KNOWN_HOSTS_OK);
b01d5e
-    assert_true(found == SSH_KNOWN_HOSTS_OK);
b01d5e
 
b01d5e
+    /* This makes sure the check will fail for an unknown host */
b01d5e
     ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot");
b01d5e
     found = ssh_session_has_known_hosts_entry(session);
b01d5e
-    assert_true(found == SSH_KNOWN_HOSTS_UNKNOWN);
b01d5e
+    assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN);
b01d5e
 
b01d5e
     ssh_free(session);
b01d5e
 }
b01d5e
@@ -414,17 +411,23 @@ static void torture_knownhosts_host_exists_global(void **state)
b01d5e
     assert_non_null(session);
b01d5e
 
b01d5e
     ssh_options_set(session, SSH_OPTIONS_HOST, "localhost");
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file);
b01d5e
+
b01d5e
     /* This makes sure the user's known_hosts are not used */
b01d5e
     ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null");
b01d5e
-    ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file);
b01d5e
+    found = ssh_session_has_known_hosts_entry(session);
b01d5e
+    assert_int_equal(found, SSH_KNOWN_HOSTS_OK);
b01d5e
 
b01d5e
+    /* This makes sure the check will not fail when the user's known_hosts is
b01d5e
+     * not accessible*/
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "./unaccessible");
b01d5e
     found = ssh_session_has_known_hosts_entry(session);
b01d5e
     assert_int_equal(found, SSH_KNOWN_HOSTS_OK);
b01d5e
-    assert_true(found == SSH_KNOWN_HOSTS_OK);
b01d5e
 
b01d5e
+    /* This makes sure the check will fail for an unknown host */
b01d5e
     ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot");
b01d5e
     found = ssh_session_has_known_hosts_entry(session);
b01d5e
-    assert_true(found == SSH_KNOWN_HOSTS_UNKNOWN);
b01d5e
+    assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN);
b01d5e
 
b01d5e
     ssh_free(session);
b01d5e
 }
b01d5e
-- 
b01d5e
2.21.0
b01d5e
b01d5e
b01d5e
From b9530cedbeb169762307096dfeb485ab94e09740 Mon Sep 17 00:00:00 2001
b01d5e
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Date: Fri, 28 Jun 2019 13:27:34 +0200
b01d5e
Subject: [PATCH 3/6] knownhosts: Read knownhosts file only if found
b01d5e
b01d5e
Avoid trying to open the files if they are not accessible.  This was
b01d5e
already treated as a non-error, but with this we save one function call.
b01d5e
b01d5e
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
b01d5e
(cherry picked from commit e5a64a3d6b1b601cbaf207468a6658d1a4fa0031)
b01d5e
---
b01d5e
 src/knownhosts.c | 4 ++--
b01d5e
 1 file changed, 2 insertions(+), 2 deletions(-)
b01d5e
b01d5e
diff --git a/src/knownhosts.c b/src/knownhosts.c
b01d5e
index 9383cc97..8040a0c0 100644
b01d5e
--- a/src/knownhosts.c
b01d5e
+++ b/src/knownhosts.c
b01d5e
@@ -691,7 +691,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
b01d5e
         return SSH_KNOWN_HOSTS_ERROR;
b01d5e
     }
b01d5e
 
b01d5e
-    if (session->opts.knownhosts != NULL) {
b01d5e
+    if (known_hosts_found) {
b01d5e
         rc = ssh_known_hosts_read_entries(host_port,
b01d5e
                                           session->opts.knownhosts,
b01d5e
                                           &entry_list);
b01d5e
@@ -702,7 +702,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
b01d5e
         }
b01d5e
     }
b01d5e
 
b01d5e
-    if (session->opts.global_knownhosts != NULL) {
b01d5e
+    if (global_known_hosts_found) {
b01d5e
         rc = ssh_known_hosts_read_entries(host_port,
b01d5e
                                           session->opts.global_knownhosts,
b01d5e
                                           &entry_list);
b01d5e
-- 
b01d5e
2.21.0
b01d5e
b01d5e
b01d5e
From aaa978ad06ebdeff88a39b9a894696254263162c Mon Sep 17 00:00:00 2001
b01d5e
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Date: Fri, 28 Jun 2019 22:35:38 +0200
b01d5e
Subject: [PATCH 4/6] token: Added function to remove duplicates
b01d5e
b01d5e
Added a function to remove duplicates from lists.  This function is used
b01d5e
in a new provided function to append lists removing duplicates.
b01d5e
b01d5e
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
b01d5e
(cherry picked from commit 548753b3389518ebce98a7ddbf0640db3ad72de8)
b01d5e
---
b01d5e
 include/libssh/token.h           |   6 +-
b01d5e
 src/token.c                      | 152 ++++++++++++++++++++++++++++++-
b01d5e
 tests/unittests/torture_tokens.c | 122 +++++++++++++++++++++++++
b01d5e
 3 files changed, 278 insertions(+), 2 deletions(-)
b01d5e
b01d5e
diff --git a/include/libssh/token.h b/include/libssh/token.h
b01d5e
index 7b244189..9896fb06 100644
b01d5e
--- a/include/libssh/token.h
b01d5e
+++ b/include/libssh/token.h
b01d5e
@@ -38,7 +38,11 @@ void ssh_tokens_free(struct ssh_tokens_st *tokens);
b01d5e
 char *ssh_find_matching(const char *available_d,
b01d5e
                         const char *preferred_d);
b01d5e
 
b01d5e
-
b01d5e
 char *ssh_find_all_matching(const char *available_d,
b01d5e
                             const char *preferred_d);
b01d5e
+
b01d5e
+char *ssh_remove_duplicates(const char *list);
b01d5e
+
b01d5e
+char *ssh_append_without_duplicates(const char *list,
b01d5e
+                                    const char *appended_list);
b01d5e
 #endif /* TOKEN_H_ */
b01d5e
diff --git a/src/token.c b/src/token.c
b01d5e
index aee235ac..0924d3bd 100644
b01d5e
--- a/src/token.c
b01d5e
+++ b/src/token.c
b01d5e
@@ -26,6 +26,7 @@
b01d5e
 
b01d5e
 #include <stdio.h>
b01d5e
 #include <string.h>
b01d5e
+#include <stdbool.h>
b01d5e
 
b01d5e
 #include "libssh/priv.h"
b01d5e
 #include "libssh/token.h"
b01d5e
@@ -175,7 +176,7 @@ char *ssh_find_matching(const char *available_list,
b01d5e
 
b01d5e
     for (i = 0; p_tok->tokens[i]; i++) {
b01d5e
         for (j = 0; a_tok->tokens[j]; j++) {
b01d5e
-            if (strcmp(a_tok->tokens[j], p_tok->tokens[i]) == 0){
b01d5e
+            if (strcmp(a_tok->tokens[j], p_tok->tokens[i]) == 0) {
b01d5e
                 ret = strdup(a_tok->tokens[j]);
b01d5e
                 goto out;
b01d5e
             }
b01d5e
@@ -260,3 +261,152 @@ out:
b01d5e
     ssh_tokens_free(p_tok);
b01d5e
     return ret;
b01d5e
 }
b01d5e
+
b01d5e
+/**
b01d5e
+ * @internal
b01d5e
+ *
b01d5e
+ * @brief Given a string containing a list of elements, remove all duplicates
b01d5e
+ * and return in a newly allocated string.
b01d5e
+ *
b01d5e
+ * @param[in] list  The list to be freed of duplicates
b01d5e
+ *
b01d5e
+ * @return  A newly allocated copy of the string free of duplicates; NULL in
b01d5e
+ * case of error.
b01d5e
+ */
b01d5e
+char *ssh_remove_duplicates(const char *list)
b01d5e
+{
b01d5e
+    struct ssh_tokens_st *tok = NULL;
b01d5e
+
b01d5e
+    size_t i, j, num_tokens, max_len;
b01d5e
+    char *ret = NULL;
b01d5e
+    bool *should_copy = NULL, need_comma = false;
b01d5e
+
b01d5e
+    if (list == NULL) {
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+
b01d5e
+    /* The maximum number of tokens is the size of the list */
b01d5e
+    max_len = strlen(list);
b01d5e
+    if (max_len == 0) {
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+
b01d5e
+    /* Add space for ending '\0' */
b01d5e
+    max_len++;
b01d5e
+
b01d5e
+    tok = ssh_tokenize(list, ',');
b01d5e
+    if ((tok == NULL) || (tok->tokens == NULL) || (tok->tokens[0] == NULL)) {
b01d5e
+        goto out;
b01d5e
+    }
b01d5e
+
b01d5e
+    should_copy = calloc(1, max_len);
b01d5e
+    if (should_copy == NULL) {
b01d5e
+        goto out;
b01d5e
+    }
b01d5e
+
b01d5e
+    if (strlen(tok->tokens[0]) > 0) {
b01d5e
+        should_copy[0] = true;
b01d5e
+    }
b01d5e
+
b01d5e
+    for (i = 1; tok->tokens[i]; i++) {
b01d5e
+        for (j = 0; j < i; j++) {
b01d5e
+            if (strcmp(tok->tokens[i], tok->tokens[j]) == 0) {
b01d5e
+                /* Found a duplicate; do not copy */
b01d5e
+                should_copy[i] = false;
b01d5e
+                break;
b01d5e
+            }
b01d5e
+        }
b01d5e
+
b01d5e
+        /* No matching token before */
b01d5e
+        if (j == i) {
b01d5e
+            /* Only copy if it is not an empty string */
b01d5e
+            if (strlen(tok->tokens[i]) > 0) {
b01d5e
+                should_copy[i] = true;
b01d5e
+            } else {
b01d5e
+                should_copy[i] = false;
b01d5e
+            }
b01d5e
+        }
b01d5e
+    }
b01d5e
+
b01d5e
+    num_tokens = i;
b01d5e
+
b01d5e
+    ret = calloc(1, max_len);
b01d5e
+    if (ret == NULL) {
b01d5e
+        goto out;
b01d5e
+    }
b01d5e
+
b01d5e
+    for (i = 0; i < num_tokens; i++) {
b01d5e
+        if (should_copy[i]) {
b01d5e
+            if (need_comma) {
b01d5e
+                strncat(ret, ",", (max_len - strlen(ret) - 1));
b01d5e
+            }
b01d5e
+            strncat(ret, tok->tokens[i], (max_len - strlen(ret) - 1));
b01d5e
+            need_comma = true;
b01d5e
+        }
b01d5e
+    }
b01d5e
+
b01d5e
+    /* If no comma is needed, nothing was copied */
b01d5e
+    if (!need_comma) {
b01d5e
+        SAFE_FREE(ret);
b01d5e
+    }
b01d5e
+
b01d5e
+out:
b01d5e
+    SAFE_FREE(should_copy);
b01d5e
+    ssh_tokens_free(tok);
b01d5e
+    return ret;
b01d5e
+}
b01d5e
+
b01d5e
+/**
b01d5e
+ * @internal
b01d5e
+ *
b01d5e
+ * @brief Given two strings containing lists of tokens, return a newly
b01d5e
+ * allocated string containing all the elements of the first list appended with
b01d5e
+ * all the elements of the second list, without duplicates. The order of the
b01d5e
+ * elements will be preserved.
b01d5e
+ *
b01d5e
+ * @param[in] list             The first list
b01d5e
+ * @param[in] appended_list    The list to be appended
b01d5e
+ *
b01d5e
+ * @return  A newly allocated copy list containing all the elements of the
b01d5e
+ * kept_list appended with the elements of the appended_list without duplicates;
b01d5e
+ * NULL in case of error.
b01d5e
+ */
b01d5e
+char *ssh_append_without_duplicates(const char *list,
b01d5e
+                                    const char *appended_list)
b01d5e
+{
b01d5e
+    size_t concat_len = 0;
b01d5e
+    char *ret = NULL, *concat = NULL;
b01d5e
+
b01d5e
+    if (list != NULL) {
b01d5e
+        concat_len = strlen(list);
b01d5e
+    }
b01d5e
+
b01d5e
+    if (appended_list != NULL) {
b01d5e
+        concat_len += strlen(appended_list);
b01d5e
+    }
b01d5e
+
b01d5e
+    if (concat_len == 0) {
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+
b01d5e
+    /* Add room for ending '\0' and for middle ',' */
b01d5e
+    concat_len += 2;
b01d5e
+    concat = calloc(1, concat_len);
b01d5e
+    if (concat == NULL) {
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+
b01d5e
+    if (list != NULL) {
b01d5e
+        strcpy(concat, list);
b01d5e
+        strncat(concat, ",", concat_len - strlen(concat) - 1);
b01d5e
+    }
b01d5e
+    if (appended_list != NULL) {
b01d5e
+        strncat(concat, appended_list, concat_len - strlen(concat) - 1);
b01d5e
+    }
b01d5e
+
b01d5e
+    ret = ssh_remove_duplicates(concat);
b01d5e
+
b01d5e
+    SAFE_FREE(concat);
b01d5e
+
b01d5e
+    return ret;
b01d5e
+}
b01d5e
diff --git a/tests/unittests/torture_tokens.c b/tests/unittests/torture_tokens.c
b01d5e
index e192842f..6b52b847 100644
b01d5e
--- a/tests/unittests/torture_tokens.c
b01d5e
+++ b/tests/unittests/torture_tokens.c
b01d5e
@@ -146,6 +146,126 @@ static void torture_tokens_sanity(UNUSED_PARAM(void **state))
b01d5e
     tokenize_compare_expected(",", single_colon, 1);
b01d5e
 }
b01d5e
 
b01d5e
+static void torture_remove_duplicate(UNUSED_PARAM(void **state))
b01d5e
+{
b01d5e
+
b01d5e
+    const char *simple[] = {"a,a,b,b,c,c",
b01d5e
+                            "a,b,c,a,b,c",
b01d5e
+                            "a,b,c,c,b,a",
b01d5e
+                            "a,a,,b,b,,c,c",
b01d5e
+                            ",a,a,b,b,c,c",
b01d5e
+                            "a,a,b,b,c,c,"};
b01d5e
+    const char *empty[] = {"",
b01d5e
+                           ",,,,,,,,,",
b01d5e
+                           NULL};
b01d5e
+    char *ret = NULL;
b01d5e
+    int i;
b01d5e
+
b01d5e
+    for (i = 0; i < 6; i++) {
b01d5e
+        ret = ssh_remove_duplicates(simple[i]);
b01d5e
+        assert_non_null(ret);
b01d5e
+        assert_string_equal("a,b,c", ret);
b01d5e
+        printf("simple[%d] resulted in '%s'\n", i, ret);
b01d5e
+        SAFE_FREE(ret);
b01d5e
+    }
b01d5e
+
b01d5e
+    for (i = 0; i < 3; i++) {
b01d5e
+        ret = ssh_remove_duplicates(empty[i]);
b01d5e
+        if (ret != NULL) {
b01d5e
+            printf("empty[%d] resulted in '%s'\n", i, ret);
b01d5e
+        }
b01d5e
+        assert_null(ret);
b01d5e
+    }
b01d5e
+
b01d5e
+    ret = ssh_remove_duplicates("a");
b01d5e
+    assert_non_null(ret);
b01d5e
+    assert_string_equal("a", ret);
b01d5e
+    SAFE_FREE(ret);
b01d5e
+}
b01d5e
+
b01d5e
+static void torture_append_without_duplicate(UNUSED_PARAM(void **state))
b01d5e
+{
b01d5e
+    const char *s1[] = {"a,a,b,b,c,c",
b01d5e
+                        "a,b,c,a,b,c",
b01d5e
+                        "a,b,c,c,b,a",
b01d5e
+                        "a,a,,b,b,,c,c",
b01d5e
+                        ",a,a,b,b,c,c",
b01d5e
+                        "a,a,b,b,c,c,"};
b01d5e
+    const char *s2[] = {"a,a,b,b,c,c,d,d",
b01d5e
+                        "a,b,c,d,a,b,c,d",
b01d5e
+                        "a,b,c,d,d,c,b,a",
b01d5e
+                        "a,a,,b,b,,c,c,,d,d",
b01d5e
+                        ",a,a,b,b,c,c,d,d",
b01d5e
+                        "a,a,b,b,c,c,d,d,",
b01d5e
+                        "d"};
b01d5e
+    const char *empty[] = {"",
b01d5e
+                           ",,,,,,,,,",
b01d5e
+                           NULL,
b01d5e
+                           NULL};
b01d5e
+    char *ret = NULL;
b01d5e
+    int i, j;
b01d5e
+
b01d5e
+    ret = ssh_append_without_duplicates("a", "a");
b01d5e
+    assert_non_null(ret);
b01d5e
+    assert_string_equal("a", ret);
b01d5e
+    SAFE_FREE(ret);
b01d5e
+
b01d5e
+    ret = ssh_append_without_duplicates("a", "b");
b01d5e
+    assert_non_null(ret);
b01d5e
+    assert_string_equal("a,b", ret);
b01d5e
+    SAFE_FREE(ret);
b01d5e
+
b01d5e
+    ret = ssh_append_without_duplicates("a", NULL);
b01d5e
+    assert_non_null(ret);
b01d5e
+    assert_string_equal("a", ret);
b01d5e
+    SAFE_FREE(ret);
b01d5e
+
b01d5e
+    ret = ssh_append_without_duplicates(NULL, "b");
b01d5e
+    assert_non_null(ret);
b01d5e
+    assert_string_equal("b", ret);
b01d5e
+    SAFE_FREE(ret);
b01d5e
+
b01d5e
+    for (i = 0; i < 6; i++) {
b01d5e
+        for (j = 0; j < 7; j++) {
b01d5e
+            ret = ssh_append_without_duplicates(s1[i], s2[j]);
b01d5e
+            assert_non_null(ret);
b01d5e
+            printf("s1[%d] + s2[%d] resulted in '%s'\n", i, j, ret);
b01d5e
+            assert_string_equal("a,b,c,d", ret);
b01d5e
+            SAFE_FREE(ret);
b01d5e
+        }
b01d5e
+    }
b01d5e
+
b01d5e
+    for (i = 0; i < 6; i++) {
b01d5e
+        for (j = 0; j < 3; j++) {
b01d5e
+            ret = ssh_append_without_duplicates(s1[i], empty[j]);
b01d5e
+            assert_non_null(ret);
b01d5e
+            printf("s1[%d] + empty[%d] resulted in '%s'\n", i, j, ret);
b01d5e
+            assert_string_equal("a,b,c", ret);
b01d5e
+            SAFE_FREE(ret);
b01d5e
+        }
b01d5e
+    }
b01d5e
+
b01d5e
+    for (i = 0; i < 3; i++) {
b01d5e
+        for (j = 0; j < 6; j++) {
b01d5e
+            ret = ssh_append_without_duplicates(empty[i], s1[j]);
b01d5e
+            assert_non_null(ret);
b01d5e
+            printf("empty[%d] + s1[%d] resulted in '%s'\n", i, j, ret);
b01d5e
+            assert_string_equal("a,b,c", ret);
b01d5e
+            SAFE_FREE(ret);
b01d5e
+        }
b01d5e
+    }
b01d5e
+    for (i = 0; i < 4; i++) {
b01d5e
+        for (j = 0; j < 4; j++) {
b01d5e
+            ret = ssh_append_without_duplicates(empty[i], empty[j]);
b01d5e
+            if (ret != NULL) {
b01d5e
+                printf("empty[%d] + empty[%d] resulted in '%s'\n", i, j, ret);
b01d5e
+            }
b01d5e
+            assert_null(ret);
b01d5e
+        }
b01d5e
+    }
b01d5e
+}
b01d5e
+
b01d5e
+
b01d5e
 int torture_run_tests(void)
b01d5e
 {
b01d5e
     int rc;
b01d5e
@@ -153,6 +273,8 @@ int torture_run_tests(void)
b01d5e
         cmocka_unit_test(torture_tokens_sanity),
b01d5e
         cmocka_unit_test(torture_find_matching),
b01d5e
         cmocka_unit_test(torture_find_all_matching),
b01d5e
+        cmocka_unit_test(torture_remove_duplicate),
b01d5e
+        cmocka_unit_test(torture_append_without_duplicate),
b01d5e
     };
b01d5e
 
b01d5e
     ssh_init();
b01d5e
-- 
b01d5e
2.21.0
b01d5e
b01d5e
b01d5e
From fa3caa61fdb39269cd78c4919df515b71991d231 Mon Sep 17 00:00:00 2001
b01d5e
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Date: Tue, 2 Jul 2019 13:48:17 +0200
b01d5e
Subject: [PATCH 5/6] knownhosts: Introduced
b01d5e
 ssh_known_hosts_get_algorithms_names()
b01d5e
b01d5e
The added internal function obtain a newly allocated string containing a
b01d5e
list of the signature types that can be generated by the keys present in
b01d5e
the known_hosts files, separated by commas.
b01d5e
b01d5e
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
b01d5e
(cherry picked from commit 65a38759ca872e8bec0158ab3676e74b6afd336f)
b01d5e
---
b01d5e
 include/libssh/knownhosts.h                  |   1 +
b01d5e
 src/knownhosts.c                             | 141 +++++++++++++++++++
b01d5e
 tests/unittests/torture_knownhosts_parsing.c |  28 ++++
b01d5e
 3 files changed, 170 insertions(+)
b01d5e
b01d5e
diff --git a/include/libssh/knownhosts.h b/include/libssh/knownhosts.h
b01d5e
index dcaa6c24..44e434c0 100644
b01d5e
--- a/include/libssh/knownhosts.h
b01d5e
+++ b/include/libssh/knownhosts.h
b01d5e
@@ -23,6 +23,7 @@
b01d5e
 #define SSH_KNOWNHOSTS_H_
b01d5e
 
b01d5e
 struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session);
b01d5e
+char *ssh_known_hosts_get_algorithms_names(ssh_session session);
b01d5e
 enum ssh_known_hosts_e
b01d5e
 ssh_session_get_known_hosts_entry_file(ssh_session session,
b01d5e
                                        const char *filename,
b01d5e
diff --git a/src/knownhosts.c b/src/knownhosts.c
b01d5e
index 8040a0c0..cf9d8a6b 100644
b01d5e
--- a/src/knownhosts.c
b01d5e
+++ b/src/knownhosts.c
b01d5e
@@ -42,6 +42,7 @@
b01d5e
 #include "libssh/pki.h"
b01d5e
 #include "libssh/dh.h"
b01d5e
 #include "libssh/knownhosts.h"
b01d5e
+#include "libssh/token.h"
b01d5e
 
b01d5e
 /**
b01d5e
  * @addtogroup libssh_session
b01d5e
@@ -451,6 +452,146 @@ error:
b01d5e
     return NULL;
b01d5e
 }
b01d5e
 
b01d5e
+/**
b01d5e
+ * @internal
b01d5e
+ *
b01d5e
+ * @brief   Returns a static string containing a list of the signature types the
b01d5e
+ * given key type can generate.
b01d5e
+ *
b01d5e
+ * @returns A static cstring containing the signature types the key is able to
b01d5e
+ * generate separated by commas; NULL in case of error
b01d5e
+ */
b01d5e
+static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e type)
b01d5e
+{
b01d5e
+    switch (type) {
b01d5e
+    case SSH_KEYTYPE_RSA:
b01d5e
+        return "rsa-sha2-512,rsa-sha2-256,ssh-rsa";
b01d5e
+    case SSH_KEYTYPE_ED25519:
b01d5e
+        return "ssh-ed25519";
b01d5e
+#ifdef HAVE_DSA
b01d5e
+    case SSH_KEYTYPE_DSS:
b01d5e
+        return "ssh-dss";
b01d5e
+#endif
b01d5e
+#ifdef HAVE_ECDH
b01d5e
+    case SSH_KEYTYPE_ECDSA_P256:
b01d5e
+        return "ecdsa-sha2-nistp256";
b01d5e
+    case SSH_KEYTYPE_ECDSA_P384:
b01d5e
+        return "ecdsa-sha2-nistp384";
b01d5e
+    case SSH_KEYTYPE_ECDSA_P521:
b01d5e
+        return "ecdsa-sha2-nistp521";
b01d5e
+#endif
b01d5e
+    case SSH_KEYTYPE_UNKNOWN:
b01d5e
+    default:
b01d5e
+        SSH_LOG(SSH_LOG_WARN, "The given type %d is not a base private key type "
b01d5e
+                "or is unsupported", type);
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+}
b01d5e
+
b01d5e
+/**
b01d5e
+ * @internal
b01d5e
+ * @brief Get the host keys algorithms identifiers from the known_hosts files
b01d5e
+ *
b01d5e
+ * This expands the signatures types that can be generated from the keys types
b01d5e
+ * present in the known_hosts files
b01d5e
+ *
b01d5e
+ * @param[in]  session  The ssh session to use.
b01d5e
+ *
b01d5e
+ * @return A newly allocated cstring containing a list of signature algorithms
b01d5e
+ * that can be generated by the host using the keys listed in the known_hosts
b01d5e
+ * files, NULL on error.
b01d5e
+ */
b01d5e
+char *ssh_known_hosts_get_algorithms_names(ssh_session session)
b01d5e
+{
b01d5e
+    char methods_buffer[256 + 1] = {0};
b01d5e
+    struct ssh_list *entry_list = NULL;
b01d5e
+    struct ssh_iterator *it = NULL;
b01d5e
+    char *host_port = NULL;
b01d5e
+    size_t count;
b01d5e
+    bool needcomma = false;
b01d5e
+    char *names;
b01d5e
+
b01d5e
+    int rc;
b01d5e
+
b01d5e
+    if (session->opts.knownhosts == NULL ||
b01d5e
+        session->opts.global_knownhosts == NULL) {
b01d5e
+        if (ssh_options_apply(session) < 0) {
b01d5e
+            ssh_set_error(session,
b01d5e
+                          SSH_REQUEST_DENIED,
b01d5e
+                          "Can't find a known_hosts file");
b01d5e
+
b01d5e
+            return NULL;
b01d5e
+        }
b01d5e
+    }
b01d5e
+
b01d5e
+    host_port = ssh_session_get_host_port(session);
b01d5e
+    if (host_port == NULL) {
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+
b01d5e
+    rc = ssh_known_hosts_read_entries(host_port,
b01d5e
+                                      session->opts.knownhosts,
b01d5e
+                                      &entry_list);
b01d5e
+    if (rc != 0) {
b01d5e
+        SAFE_FREE(host_port);
b01d5e
+        ssh_list_free(entry_list);
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+
b01d5e
+    rc = ssh_known_hosts_read_entries(host_port,
b01d5e
+                                      session->opts.global_knownhosts,
b01d5e
+                                      &entry_list);
b01d5e
+    SAFE_FREE(host_port);
b01d5e
+    if (rc != 0) {
b01d5e
+        ssh_list_free(entry_list);
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+
b01d5e
+    if (entry_list == NULL) {
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+
b01d5e
+    count = ssh_list_count(entry_list);
b01d5e
+    if (count == 0) {
b01d5e
+        ssh_list_free(entry_list);
b01d5e
+        return NULL;
b01d5e
+    }
b01d5e
+
b01d5e
+    for (it = ssh_list_get_iterator(entry_list);
b01d5e
+         it != NULL;
b01d5e
+         it = ssh_list_get_iterator(entry_list))
b01d5e
+    {
b01d5e
+        struct ssh_knownhosts_entry *entry = NULL;
b01d5e
+        const char *algo = NULL;
b01d5e
+
b01d5e
+        entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
b01d5e
+        algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type);
b01d5e
+        if (algo == NULL) {
b01d5e
+            continue;
b01d5e
+        }
b01d5e
+
b01d5e
+        if (needcomma) {
b01d5e
+            strncat(methods_buffer,
b01d5e
+                    ",",
b01d5e
+                    sizeof(methods_buffer) - strlen(methods_buffer) - 1);
b01d5e
+        }
b01d5e
+
b01d5e
+        strncat(methods_buffer,
b01d5e
+                algo,
b01d5e
+                sizeof(methods_buffer) - strlen(methods_buffer) - 1);
b01d5e
+        needcomma = true;
b01d5e
+
b01d5e
+        ssh_knownhosts_entry_free(entry);
b01d5e
+        ssh_list_remove(entry_list, it);
b01d5e
+    }
b01d5e
+
b01d5e
+    ssh_list_free(entry_list);
b01d5e
+
b01d5e
+    names = ssh_remove_duplicates(methods_buffer);
b01d5e
+
b01d5e
+    return names;
b01d5e
+}
b01d5e
+
b01d5e
 /**
b01d5e
  * @brief Parse a line from a known_hosts entry into a structure
b01d5e
  *
b01d5e
diff --git a/tests/unittests/torture_knownhosts_parsing.c b/tests/unittests/torture_knownhosts_parsing.c
b01d5e
index a087caef..6952e858 100644
b01d5e
--- a/tests/unittests/torture_knownhosts_parsing.c
b01d5e
+++ b/tests/unittests/torture_knownhosts_parsing.c
b01d5e
@@ -369,6 +369,31 @@ static void torture_knownhosts_read_file(void **state)
b01d5e
     ssh_list_free(entry_list);
b01d5e
 }
b01d5e
 
b01d5e
+static void torture_knownhosts_get_algorithms_names(void **state)
b01d5e
+{
b01d5e
+    const char *knownhosts_file = *state;
b01d5e
+    ssh_session session;
b01d5e
+    const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa";
b01d5e
+    char *names = NULL;
b01d5e
+    bool process_config = false;
b01d5e
+
b01d5e
+    session = ssh_new();
b01d5e
+    assert_non_null(session);
b01d5e
+
b01d5e
+    /* This makes sure the global configuration file is not processed */
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config);
b01d5e
+
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_HOST, "localhost");
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file);
b01d5e
+
b01d5e
+    names = ssh_known_hosts_get_algorithms_names(session);
b01d5e
+    assert_non_null(names);
b01d5e
+    assert_string_equal(names, expect);
b01d5e
+
b01d5e
+    SAFE_FREE(names);
b01d5e
+    ssh_free(session);
b01d5e
+}
b01d5e
+
b01d5e
 #ifndef _WIN32 /* There is no /dev/null on Windows */
b01d5e
 static void torture_knownhosts_host_exists(void **state)
b01d5e
 {
b01d5e
@@ -510,6 +535,9 @@ int torture_run_tests(void) {
b01d5e
         cmocka_unit_test_setup_teardown(torture_knownhosts_read_file,
b01d5e
                                         setup_knownhosts_file_duplicate,
b01d5e
                                         teardown_knownhosts_file),
b01d5e
+        cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names,
b01d5e
+                                        setup_knownhosts_file,
b01d5e
+                                        teardown_knownhosts_file),
b01d5e
 #ifndef _WIN32
b01d5e
         cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists,
b01d5e
                                         setup_knownhosts_file,
b01d5e
-- 
b01d5e
2.21.0
b01d5e
b01d5e
b01d5e
From 1fd68ec732214a12ba3f59ca23f80463411e22dd Mon Sep 17 00:00:00 2001
b01d5e
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Date: Mon, 1 Jul 2019 19:39:07 +0200
b01d5e
Subject: [PATCH 6/6] kex: Do not ignore keys in known_hosts files
b01d5e
b01d5e
Previously, if the SSH_OPTIONS_HOSTKEYS option was set by any mean,
b01d5e
including the client configuration file, the keys in known_hosts files
b01d5e
wouldn't be considered before advertising the list of wanted host keys.
b01d5e
b01d5e
This could result in the client requesting the server to provide a
b01d5e
signature using a key not present in the known_hosts files (e.g. when
b01d5e
the first wanted algorithm in SSH_OPTIONS_HOSTKEYS is not present in the
b01d5e
known_hosts files), causing a host key mismatch and possible key
b01d5e
rejection.
b01d5e
b01d5e
Now, the keys present in the known_hosts files are prioritized over the
b01d5e
other wanted keys.  This do not change the fact that only keys of types
b01d5e
present in the list set in SSH_OPTIONS_HOSTKEYS will be accepted and
b01d5e
prioritized following the order defined by such list.
b01d5e
b01d5e
The new wanted list of hostkeys is given by:
b01d5e
 - The keys present in known_hosts files, ordered by preference defined
b01d5e
   in SSH_OPTIONS_HOSTKEYS.  If the option is not set, a default order
b01d5e
   of preference is used.
b01d5e
 - The other keys present in the same option are appended without adding
b01d5e
   duplicates.  If the option is not set, the default list of keys is
b01d5e
   used.
b01d5e
b01d5e
Fixes: T156
b01d5e
b01d5e
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
b01d5e
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
b01d5e
(cherry picked from commit f18a7cc17e399ae7bc92f707da3a676c52fd948e)
b01d5e
---
b01d5e
 src/kex.c                                    | 165 +++++++++----------
b01d5e
 tests/unittests/torture_knownhosts_parsing.c | 148 ++++++++++++++++-
b01d5e
 2 files changed, 223 insertions(+), 90 deletions(-)
b01d5e
b01d5e
diff --git a/src/kex.c b/src/kex.c
b01d5e
index 6ea5e8ba..0d4cad6d 100644
b01d5e
--- a/src/kex.c
b01d5e
+++ b/src/kex.c
b01d5e
@@ -561,103 +561,94 @@ void ssh_list_kex(struct ssh_kex_struct *kex) {
b01d5e
 
b01d5e
 /**
b01d5e
  * @internal
b01d5e
+ *
b01d5e
  * @brief selects the hostkey mechanisms to be chosen for the key exchange,
b01d5e
- * as some hostkey mechanisms may be present in known_hosts file and preferred
b01d5e
+ * as some hostkey mechanisms may be present in known_hosts files.
b01d5e
+ *
b01d5e
  * @returns a cstring containing a comma-separated list of hostkey methods.
b01d5e
  *          NULL if no method matches
b01d5e
  */
b01d5e
 char *ssh_client_select_hostkeys(ssh_session session)
b01d5e
 {
b01d5e
-    char methods_buffer[128]={0};
b01d5e
-    char tail_buffer[128]={0};
b01d5e
+    const char *wanted = NULL;
b01d5e
+    char *wanted_without_certs = NULL;
b01d5e
+    char *known_hosts_algorithms = NULL;
b01d5e
+    char *known_hosts_ordered = NULL;
b01d5e
     char *new_hostkeys = NULL;
b01d5e
-    static const char *preferred_hostkeys[] = {
b01d5e
-        "ssh-ed25519",
b01d5e
-        "ecdsa-sha2-nistp521",
b01d5e
-        "ecdsa-sha2-nistp384",
b01d5e
-        "ecdsa-sha2-nistp256",
b01d5e
-        "rsa-sha2-512",
b01d5e
-        "rsa-sha2-256",
b01d5e
-        "ssh-rsa",
b01d5e
-#ifdef HAVE_DSA
b01d5e
-        "ssh-dss",
b01d5e
-#endif
b01d5e
-        NULL
b01d5e
-    };
b01d5e
-    struct ssh_list *algo_list = NULL;
b01d5e
-    struct ssh_iterator *it = NULL;
b01d5e
-    size_t algo_count;
b01d5e
-    int needcomma = 0;
b01d5e
-    size_t i, len;
b01d5e
-
b01d5e
-    algo_list = ssh_known_hosts_get_algorithms(session);
b01d5e
-    if (algo_list == NULL) {
b01d5e
-        return NULL;
b01d5e
+    char *fips_hostkeys = NULL;
b01d5e
+
b01d5e
+    wanted = session->opts.wanted_methods[SSH_HOSTKEYS];
b01d5e
+    if (wanted == NULL) {
b01d5e
+        if (ssh_fips_mode()) {
b01d5e
+            wanted = ssh_kex_get_fips_methods(SSH_HOSTKEYS);
b01d5e
+        } else {
b01d5e
+            wanted = ssh_kex_get_default_methods(SSH_HOSTKEYS);
b01d5e
+        }
b01d5e
     }
b01d5e
 
b01d5e
-    algo_count = ssh_list_count(algo_list);
b01d5e
-    if (algo_count == 0) {
b01d5e
-        ssh_list_free(algo_list);
b01d5e
+    /* This removes the certificate types, unsupported for now */
b01d5e
+    wanted_without_certs = ssh_find_all_matching(HOSTKEYS, wanted);
b01d5e
+    if (wanted_without_certs == NULL) {
b01d5e
+        SSH_LOG(SSH_LOG_WARNING,
b01d5e
+                "List of allowed host key algorithms is empty or contains only "
b01d5e
+                "unsupported algorithms");
b01d5e
         return NULL;
b01d5e
     }
b01d5e
 
b01d5e
-    for (i = 0; preferred_hostkeys[i] != NULL; ++i) {
b01d5e
-        bool found = false;
b01d5e
-        /* This is a signature type: We list also the SHA2 extensions */
b01d5e
-        enum ssh_keytypes_e base_preferred =
b01d5e
-            ssh_key_type_from_signature_name(preferred_hostkeys[i]);
b01d5e
-
b01d5e
-        for (it = ssh_list_get_iterator(algo_list);
b01d5e
-             it != NULL;
b01d5e
-             it = it->next) {
b01d5e
-            const char *algo = ssh_iterator_value(const char *, it);
b01d5e
-            /* This is always key type so we do not have to care for the
b01d5e
-             * SHA2 extension */
b01d5e
-            enum ssh_keytypes_e base_algo = ssh_key_type_from_name(algo);
b01d5e
-
b01d5e
-            if (base_preferred == base_algo) {
b01d5e
-                /* Matching the keys already verified it is a known type */
b01d5e
-                if (needcomma) {
b01d5e
-                    strncat(methods_buffer,
b01d5e
-                            ",",
b01d5e
-                            sizeof(methods_buffer) - strlen(methods_buffer) - 1);
b01d5e
-                }
b01d5e
-                strncat(methods_buffer,
b01d5e
-                        preferred_hostkeys[i],
b01d5e
-                        sizeof(methods_buffer) - strlen(methods_buffer) - 1);
b01d5e
-                needcomma = 1;
b01d5e
-                found = true;
b01d5e
-            }
b01d5e
-        }
b01d5e
-        /* Collect the rest of the algorithms in other buffer, that will
b01d5e
-         * follow the preferred buffer. This will signalize all the algorithms
b01d5e
-         * we are willing to accept.
b01d5e
-         */
b01d5e
-        if (!found) {
b01d5e
-            snprintf(tail_buffer + strlen(tail_buffer),
b01d5e
-                     sizeof(tail_buffer) - strlen(tail_buffer),
b01d5e
-                     ",%s", preferred_hostkeys[i]);
b01d5e
-        }
b01d5e
+    SSH_LOG(SSH_LOG_DEBUG,
b01d5e
+            "Order of wanted host keys: \"%s\"",
b01d5e
+            wanted_without_certs);
b01d5e
+
b01d5e
+    known_hosts_algorithms = ssh_known_hosts_get_algorithms_names(session);
b01d5e
+    if (known_hosts_algorithms == NULL) {
b01d5e
+        SSH_LOG(SSH_LOG_DEBUG,
b01d5e
+                "No key found in known_hosts; "
b01d5e
+                "changing host key method to \"%s\"",
b01d5e
+                wanted_without_certs);
b01d5e
+
b01d5e
+        return wanted_without_certs;
b01d5e
     }
b01d5e
-    ssh_list_free(algo_list);
b01d5e
 
b01d5e
-    if (strlen(methods_buffer) == 0) {
b01d5e
+    SSH_LOG(SSH_LOG_DEBUG,
b01d5e
+            "Algorithms found in known_hosts files: \"%s\"",
b01d5e
+            known_hosts_algorithms);
b01d5e
+
b01d5e
+    /* Filter and order the keys from known_hosts according to wanted list */
b01d5e
+    known_hosts_ordered = ssh_find_all_matching(known_hosts_algorithms,
b01d5e
+                                                wanted_without_certs);
b01d5e
+    SAFE_FREE(known_hosts_algorithms);
b01d5e
+    if (known_hosts_ordered == NULL) {
b01d5e
         SSH_LOG(SSH_LOG_DEBUG,
b01d5e
-                "No supported kex method for existing key in known_hosts file");
b01d5e
-        return NULL;
b01d5e
+                "No key found in known_hosts is allowed; "
b01d5e
+                "changing host key method to \"%s\"",
b01d5e
+                wanted_without_certs);
b01d5e
+
b01d5e
+        return wanted_without_certs;
b01d5e
     }
b01d5e
 
b01d5e
-    /* Append the supported list to the preferred.
b01d5e
-     * The length is maximum 128 + 128 + 1, which will not overflow
b01d5e
-     */
b01d5e
-    len = strlen(methods_buffer) + strlen(tail_buffer) + 1;
b01d5e
-    new_hostkeys = malloc(len);
b01d5e
+    /* Append the other supported keys after the preferred ones
b01d5e
+     * This function tolerates NULL pointers in parameters */
b01d5e
+    new_hostkeys = ssh_append_without_duplicates(known_hosts_ordered,
b01d5e
+                                                 wanted_without_certs);
b01d5e
+    SAFE_FREE(known_hosts_ordered);
b01d5e
+    SAFE_FREE(wanted_without_certs);
b01d5e
     if (new_hostkeys == NULL) {
b01d5e
         ssh_set_error_oom(session);
b01d5e
         return NULL;
b01d5e
     }
b01d5e
-    snprintf(new_hostkeys, len,
b01d5e
-             "%s%s", methods_buffer, tail_buffer);
b01d5e
+
b01d5e
+    if (ssh_fips_mode()) {
b01d5e
+        /* Filter out algorithms not allowed in FIPS mode */
b01d5e
+        fips_hostkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, new_hostkeys);
b01d5e
+        SAFE_FREE(new_hostkeys);
b01d5e
+        if (fips_hostkeys == NULL) {
b01d5e
+            SSH_LOG(SSH_LOG_WARNING,
b01d5e
+                    "None of the wanted host keys or keys in known_hosts files "
b01d5e
+                    "is allowed in FIPS mode.");
b01d5e
+            return NULL;
b01d5e
+        }
b01d5e
+        new_hostkeys = fips_hostkeys;
b01d5e
+    }
b01d5e
 
b01d5e
     SSH_LOG(SSH_LOG_DEBUG,
b01d5e
             "Changing host key method to \"%s\"",
b01d5e
@@ -672,7 +663,7 @@ char *ssh_client_select_hostkeys(ssh_session session)
b01d5e
  */
b01d5e
 int ssh_set_client_kex(ssh_session session)
b01d5e
 {
b01d5e
-    struct ssh_kex_struct *client= &session->next_crypto->client_kex;
b01d5e
+    struct ssh_kex_struct *client = &session->next_crypto->client_kex;
b01d5e
     const char *wanted;
b01d5e
     char *kex = NULL;
b01d5e
     char *kex_tmp = NULL;
b01d5e
@@ -687,14 +678,22 @@ int ssh_set_client_kex(ssh_session session)
b01d5e
     }
b01d5e
 
b01d5e
     memset(client->methods, 0, KEX_METHODS_SIZE * sizeof(char **));
b01d5e
-    /* first check if we have specific host key methods */
b01d5e
-    if (session->opts.wanted_methods[SSH_HOSTKEYS] == NULL) {
b01d5e
-    	/* Only if no override */
b01d5e
-    	session->opts.wanted_methods[SSH_HOSTKEYS] =
b01d5e
-            ssh_client_select_hostkeys(session);
b01d5e
-    }
b01d5e
 
b01d5e
+    /* Set the list of allowed algorithms in order of preference, if it hadn't
b01d5e
+     * been set yet. */
b01d5e
     for (i = 0; i < KEX_METHODS_SIZE; i++) {
b01d5e
+        if (i == SSH_HOSTKEYS) {
b01d5e
+            /* Set the hostkeys in the following order:
b01d5e
+             * - First: keys present in known_hosts files ordered by preference
b01d5e
+             * - Next: other wanted algorithms ordered by preference */
b01d5e
+            client->methods[i] = ssh_client_select_hostkeys(session);
b01d5e
+            if (client->methods[i] == NULL) {
b01d5e
+                ssh_set_error_oom(session);
b01d5e
+                return SSH_ERROR;
b01d5e
+            }
b01d5e
+            continue;
b01d5e
+        }
b01d5e
+
b01d5e
         wanted = session->opts.wanted_methods[i];
b01d5e
         if (wanted == NULL) {
b01d5e
             if (ssh_fips_mode()) {
b01d5e
diff --git a/tests/unittests/torture_knownhosts_parsing.c b/tests/unittests/torture_knownhosts_parsing.c
b01d5e
index 6952e858..1c2ccc10 100644
b01d5e
--- a/tests/unittests/torture_knownhosts_parsing.c
b01d5e
+++ b/tests/unittests/torture_knownhosts_parsing.c
b01d5e
@@ -28,6 +28,8 @@
b01d5e
 
b01d5e
 #define TMP_FILE_NAME "/tmp/known_hosts_XXXXXX"
b01d5e
 
b01d5e
+const char template[] = "temp_dir_XXXXXX";
b01d5e
+
b01d5e
 static int setup_knownhosts_file(void **state)
b01d5e
 {
b01d5e
     char *tmp_file = NULL;
b01d5e
@@ -394,6 +396,115 @@ static void torture_knownhosts_get_algorithms_names(void **state)
b01d5e
     ssh_free(session);
b01d5e
 }
b01d5e
 
b01d5e
+static void torture_knownhosts_algorithms_wanted(void **state)
b01d5e
+{
b01d5e
+    const char *knownhosts_file = *state;
b01d5e
+    char *algo_list = NULL;
b01d5e
+    ssh_session session;
b01d5e
+    bool process_config = false;
b01d5e
+    const char *wanted = "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256,"
b01d5e
+                         "rsa-sha2-256,ecdsa-sha2-nistp521";
b01d5e
+    const char *expect = "rsa-sha2-256,ecdsa-sha2-nistp384,"
b01d5e
+                         "ecdsa-sha2-nistp256,ecdsa-sha2-nistp521";
b01d5e
+    int verbose = 4;
b01d5e
+
b01d5e
+    session = ssh_new();
b01d5e
+    assert_non_null(session);
b01d5e
+
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose);
b01d5e
+
b01d5e
+    /* This makes sure the global configuration file is not processed */
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config);
b01d5e
+
b01d5e
+    /* Set the wanted list of hostkeys, ordered by preference */
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted);
b01d5e
+
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_HOST, "localhost");
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file);
b01d5e
+
b01d5e
+    algo_list = ssh_client_select_hostkeys(session);
b01d5e
+    assert_non_null(algo_list);
b01d5e
+    assert_string_equal(algo_list, expect);
b01d5e
+    free(algo_list);
b01d5e
+
b01d5e
+    ssh_free(session);
b01d5e
+}
b01d5e
+
b01d5e
+static void torture_knownhosts_algorithms_negative(UNUSED_PARAM(void **state))
b01d5e
+{
b01d5e
+    const char *wanted = NULL;
b01d5e
+    const char *expect = NULL;
b01d5e
+
b01d5e
+    char *algo_list = NULL;
b01d5e
+
b01d5e
+    char *cwd = NULL;
b01d5e
+    char *tmp_dir = NULL;
b01d5e
+
b01d5e
+    bool process_config = false;
b01d5e
+    int verbose = 4;
b01d5e
+    int rc = 0;
b01d5e
+
b01d5e
+    ssh_session session;
b01d5e
+    /* Create temporary directory */
b01d5e
+    cwd = torture_get_current_working_dir();
b01d5e
+    assert_non_null(cwd);
b01d5e
+
b01d5e
+    tmp_dir = torture_make_temp_dir(template);
b01d5e
+    assert_non_null(tmp_dir);
b01d5e
+
b01d5e
+    rc = torture_change_dir(tmp_dir);
b01d5e
+    assert_int_equal(rc, 0);
b01d5e
+
b01d5e
+    session = ssh_new();
b01d5e
+    assert_non_null(session);
b01d5e
+
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose);
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config);
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_HOST, "localhost");
b01d5e
+
b01d5e
+    /* Test with unknown key type in known_hosts */
b01d5e
+    wanted = "rsa-sha2-256";
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted);
b01d5e
+    torture_write_file("unknown_key_type", "localhost unknown AAAABBBBCCCC");
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "unknown_key_type");
b01d5e
+    algo_list = ssh_client_select_hostkeys(session);
b01d5e
+    assert_non_null(algo_list);
b01d5e
+    assert_string_equal(algo_list, wanted);
b01d5e
+    SAFE_FREE(algo_list);
b01d5e
+
b01d5e
+    /* Test with unsupported, but existing types */
b01d5e
+    wanted = "rsa-sha2-256-cert-v01@openssh.com,"
b01d5e
+             "rsa-sha2-512-cert-v01@openssh.com";
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted);
b01d5e
+    algo_list = ssh_client_select_hostkeys(session);
b01d5e
+    assert_null(algo_list);
b01d5e
+
b01d5e
+    /* In FIPS mode, test filtering keys not allowed */
b01d5e
+    if (ssh_fips_mode()) {
b01d5e
+        wanted = "ssh-ed25519,rsa-sha2-256,ssh-rsa";
b01d5e
+        expect = "rsa-sha2-256";
b01d5e
+        ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted);
b01d5e
+        torture_write_file("no_fips", LOCALHOST_DEFAULT_ED25519);
b01d5e
+        ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "no_fips");
b01d5e
+        algo_list = ssh_client_select_hostkeys(session);
b01d5e
+        assert_non_null(algo_list);
b01d5e
+        assert_string_equal(algo_list, expect);
b01d5e
+        SAFE_FREE(algo_list);
b01d5e
+    }
b01d5e
+
b01d5e
+    ssh_free(session);
b01d5e
+
b01d5e
+    /* Teardown */
b01d5e
+    rc = torture_change_dir(cwd);
b01d5e
+    assert_int_equal(rc, 0);
b01d5e
+
b01d5e
+    rc = torture_rmdirs(tmp_dir);
b01d5e
+    assert_int_equal(rc, 0);
b01d5e
+
b01d5e
+    SAFE_FREE(tmp_dir);
b01d5e
+    SAFE_FREE(cwd);
b01d5e
+}
b01d5e
+
b01d5e
 #ifndef _WIN32 /* There is no /dev/null on Windows */
b01d5e
 static void torture_knownhosts_host_exists(void **state)
b01d5e
 {
b01d5e
@@ -457,12 +568,12 @@ static void torture_knownhosts_host_exists_global(void **state)
b01d5e
     ssh_free(session);
b01d5e
 }
b01d5e
 
b01d5e
-static void
b01d5e
-torture_knownhosts_algorithms(void **state)
b01d5e
+static void torture_knownhosts_algorithms(void **state)
b01d5e
 {
b01d5e
     const char *knownhosts_file = *state;
b01d5e
     char *algo_list = NULL;
b01d5e
     ssh_session session;
b01d5e
+    bool process_config = false;
b01d5e
     const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa,"
b01d5e
                          "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"
b01d5e
                          "ecdsa-sha2-nistp256"
b01d5e
@@ -470,10 +581,15 @@ torture_knownhosts_algorithms(void **state)
b01d5e
                          ",ssh-dss"
b01d5e
 #endif
b01d5e
     ;
b01d5e
+    const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521,"
b01d5e
+                              "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256";
b01d5e
 
b01d5e
     session = ssh_new();
b01d5e
     assert_non_null(session);
b01d5e
 
b01d5e
+    /* This makes sure the global configuration file is not processed */
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config);
b01d5e
+
b01d5e
     ssh_options_set(session, SSH_OPTIONS_HOST, "localhost");
b01d5e
     ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file);
b01d5e
     /* This makes sure the system's known_hosts are not used */
b01d5e
@@ -481,18 +597,22 @@ torture_knownhosts_algorithms(void **state)
b01d5e
 
b01d5e
     algo_list = ssh_client_select_hostkeys(session);
b01d5e
     assert_non_null(algo_list);
b01d5e
-    assert_string_equal(algo_list, expect);
b01d5e
+    if (ssh_fips_mode()) {
b01d5e
+        assert_string_equal(algo_list, expect_fips);
b01d5e
+    } else {
b01d5e
+        assert_string_equal(algo_list, expect);
b01d5e
+    }
b01d5e
     free(algo_list);
b01d5e
 
b01d5e
     ssh_free(session);
b01d5e
 }
b01d5e
 
b01d5e
-static void
b01d5e
-torture_knownhosts_algorithms_global(void **state)
b01d5e
+static void torture_knownhosts_algorithms_global(void **state)
b01d5e
 {
b01d5e
     const char *knownhosts_file = *state;
b01d5e
     char *algo_list = NULL;
b01d5e
     ssh_session session;
b01d5e
+    bool process_config = false;
b01d5e
     const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa,"
b01d5e
                          "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"
b01d5e
                          "ecdsa-sha2-nistp256"
b01d5e
@@ -500,10 +620,15 @@ torture_knownhosts_algorithms_global(void **state)
b01d5e
                          ",ssh-dss"
b01d5e
 #endif
b01d5e
     ;
b01d5e
+    const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521,"
b01d5e
+                              "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256";
b01d5e
 
b01d5e
     session = ssh_new();
b01d5e
     assert_non_null(session);
b01d5e
 
b01d5e
+    /* This makes sure the global configuration file is not processed */
b01d5e
+    ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config);
b01d5e
+
b01d5e
     ssh_options_set(session, SSH_OPTIONS_HOST, "localhost");
b01d5e
     /* This makes sure the current-user's known hosts are not used */
b01d5e
     ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null");
b01d5e
@@ -511,12 +636,17 @@ torture_knownhosts_algorithms_global(void **state)
b01d5e
 
b01d5e
     algo_list = ssh_client_select_hostkeys(session);
b01d5e
     assert_non_null(algo_list);
b01d5e
-    assert_string_equal(algo_list, expect);
b01d5e
+    if (ssh_fips_mode()) {
b01d5e
+        assert_string_equal(algo_list, expect_fips);
b01d5e
+    } else {
b01d5e
+        assert_string_equal(algo_list, expect);
b01d5e
+    }
b01d5e
     free(algo_list);
b01d5e
 
b01d5e
     ssh_free(session);
b01d5e
 }
b01d5e
-#endif
b01d5e
+
b01d5e
+#endif /* _WIN32 There is no /dev/null on Windows */
b01d5e
 
b01d5e
 int torture_run_tests(void) {
b01d5e
     int rc;
b01d5e
@@ -538,6 +668,10 @@ int torture_run_tests(void) {
b01d5e
         cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names,
b01d5e
                                         setup_knownhosts_file,
b01d5e
                                         teardown_knownhosts_file),
b01d5e
+        cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_wanted,
b01d5e
+                                        setup_knownhosts_file,
b01d5e
+                                        teardown_knownhosts_file),
b01d5e
+        cmocka_unit_test(torture_knownhosts_algorithms_negative),
b01d5e
 #ifndef _WIN32
b01d5e
         cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists,
b01d5e
                                         setup_knownhosts_file,
b01d5e
-- 
b01d5e
2.21.0
b01d5e