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

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