From d1315bf155f5541e769bac58bdbb1cf343a70952 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Mon, 7 Nov 2022 13:08:02 +0100 Subject: [PATCH 1/6] tokens: Add low-level function to exlclude, prepend lists These functions are needed for openssh -,^ features. Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- include/libssh/token.h | 8 +++ src/token.c | 141 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 145 insertions(+), 4 deletions(-) diff --git a/include/libssh/token.h b/include/libssh/token.h index 9896fb06..2d07f8c4 100644 --- a/include/libssh/token.h +++ b/include/libssh/token.h @@ -45,4 +45,12 @@ char *ssh_remove_duplicates(const char *list); char *ssh_append_without_duplicates(const char *list, const char *appended_list); +char *ssh_prefix_without_duplicates(const char *list, + const char *prefixed_list); +char *ssh_remove_all_matching(const char *list, + const char *remove_list); + +#ifdef __cplusplus +} +#endif #endif /* TOKEN_H_ */ diff --git a/src/token.c b/src/token.c index 0924d3bd..58befe1d 100644 --- a/src/token.c +++ b/src/token.c @@ -376,6 +376,7 @@ char *ssh_append_without_duplicates(const char *list, { size_t concat_len = 0; char *ret = NULL, *concat = NULL; + int rc = 0; if (list != NULL) { concat_len = strlen(list); @@ -396,12 +397,144 @@ char *ssh_append_without_duplicates(const char *list, return NULL; } + rc = snprintf(concat, concat_len, "%s%s%s", + list == NULL ? "" : list, + list == NULL ? "" : ",", + appended_list == NULL ? "" : appended_list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; + } + + ret = ssh_remove_duplicates(concat); + + SAFE_FREE(concat); + + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing the elements of the first list without the + * elements of the second list. The order of the elements will be preserved. + * + * @param[in] list The first list + * @param[in] remove_list The list to be removed + * + * @return A newly allocated copy list containing elements of the + * list without the elements of remove_list; NULL in case of error. + */ +char *ssh_remove_all_matching(const char *list, + const char *remove_list) +{ + struct ssh_tokens_st *l_tok = NULL, *r_tok = NULL; + int i, j, cmp; + char *ret = NULL; + size_t len, pos = 0; + bool exclude; + + if ((list == NULL)) { + return NULL; + } + if (remove_list == NULL) { + return strdup (list); + } + + l_tok = ssh_tokenize(list, ','); + if (l_tok == NULL) { + goto out; + } + + r_tok = ssh_tokenize(remove_list, ','); + if (r_tok == NULL) { + goto out; + } + + ret = calloc(1, strlen(list) + 1); + if (ret == NULL) { + goto out; + } + + for (i = 0; l_tok->tokens[i]; i++) { + exclude = false; + for (j = 0; r_tok->tokens[j]; j++) { + cmp = strcmp(l_tok->tokens[i], r_tok->tokens[j]); + if (cmp == 0) { + exclude = true; + break; + } + } + if (exclude == false) { + if (pos != 0) { + ret[pos] = ','; + pos++; + } + + len = strlen(l_tok->tokens[i]); + memcpy(&ret[pos], l_tok->tokens[i], len); + pos += len; + } + } + + if (ret[0] == '\0') { + SAFE_FREE(ret); + } + +out: + ssh_tokens_free(l_tok); + ssh_tokens_free(r_tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing all the elements of the first list prefixed at + * the beginning of the second list, without duplicates. + * + * @param[in] list The first list + * @param[in] prefixed_list The list to use as a prefix + * + * @return A newly allocated list containing all the elements + * of the list prefixed with the elements of the prefixed_list without + * duplicates; NULL in case of error. + */ +char *ssh_prefix_without_duplicates(const char *list, + const char *prefixed_list) +{ + size_t concat_len = 0; + char *ret = NULL, *concat = NULL; + int rc = 0; + if (list != NULL) { - strcpy(concat, list); - strncat(concat, ",", concat_len - strlen(concat) - 1); + concat_len = strlen(list); } - if (appended_list != NULL) { - strncat(concat, appended_list, concat_len - strlen(concat) - 1); + + if (prefixed_list != NULL) { + concat_len += strlen(prefixed_list); + } + + if (concat_len == 0) { + return NULL; + } + + /* Add room for ending '\0' and for middle ',' */ + concat_len += 2; + concat = calloc(concat_len, 1); + if (concat == NULL) { + return NULL; + } + + rc = snprintf(concat, concat_len, "%s%s%s", + prefixed_list == NULL ? "" : prefixed_list, + prefixed_list == NULL ? "" : ",", + list == NULL ? "" : list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; } ret = ssh_remove_duplicates(concat); -- 2.38.1 From f4516b9d43c4730ca5f60d73567596d65a672e16 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Fri, 11 Nov 2022 17:47:22 +0100 Subject: [PATCH 2/6] torture_tokens.c: Add tests for new token functions Functions `ssh_remove_all_matching` and `ssh_prefix_without_duplicates` were added; a little test suite will suite them. Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- tests/unittests/torture_tokens.c | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/unittests/torture_tokens.c b/tests/unittests/torture_tokens.c index 6b52b847..438538de 100644 --- a/tests/unittests/torture_tokens.c +++ b/tests/unittests/torture_tokens.c @@ -265,6 +265,68 @@ static void torture_append_without_duplicate(UNUSED_PARAM(void **state)) } } +static void torture_remove_all_matching (UNUSED_PARAM(void** state)) { + char *p; + + p = ssh_remove_all_matching(NULL, NULL); + assert_null(p); + + p = ssh_remove_all_matching("don't remove", NULL); + assert_non_null(p); + assert_string_equal(p, "don't remove"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "b"); + assert_non_null(p); + assert_string_equal(p, "a,c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "a,b"); + assert_non_null(p); + assert_string_equal(p, "c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "d"); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "a,b,c"); + assert_null(p); +} + +static void torture_prefix_without_duplicates (UNUSED_PARAM(void** state)) { + char *p; + + p = ssh_prefix_without_duplicates(NULL, NULL); + assert_null(p); + + p = ssh_prefix_without_duplicates("a,b,c", NULL); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "a"); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "b"); + assert_non_null(p); + assert_string_equal(p, "b,a,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "x"); + assert_non_null(p); + assert_string_equal(p, "x,a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "c,x"); + assert_non_null(p); + assert_string_equal(p, "c,x,a,b"); + free(p); +} + int torture_run_tests(void) { @@ -275,6 +337,8 @@ int torture_run_tests(void) cmocka_unit_test(torture_find_all_matching), cmocka_unit_test(torture_remove_duplicate), cmocka_unit_test(torture_append_without_duplicate), + cmocka_unit_test(torture_remove_all_matching), + cmocka_unit_test(torture_prefix_without_duplicates), }; ssh_init(); -- 2.38.1 From be50b4296574ba59537415b9903e8e4aa94cce53 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Mon, 7 Nov 2022 08:23:30 +0100 Subject: [PATCH 3/6] kex: Add functions for openssh +,-,^ features The funcions can: - add a list to the default list - remove a list from the default list - prepend a list to the default list Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- include/libssh/kex.h | 4 ++ src/kex.c | 105 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/include/libssh/kex.h b/include/libssh/kex.h index 3a1f4a6f..23b93924 100644 --- a/include/libssh/kex.h +++ b/include/libssh/kex.h @@ -40,6 +40,10 @@ int ssh_kex_select_methods(ssh_session session); int ssh_verify_existing_algo(enum ssh_kex_types_e algo, const char *name); char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list); char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_add_to_default_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_remove_from_default_algos(enum ssh_kex_types_e algo, + const char *list); +char *ssh_prefix_default_algos(enum ssh_kex_types_e algo, const char *list); char **ssh_space_tokenize(const char *chain); int ssh_get_kex1(ssh_session session); char *ssh_find_matching(const char *in_d, const char *what_d); diff --git a/src/kex.c b/src/kex.c index 64083997..1155b9c7 100644 --- a/src/kex.c +++ b/src/kex.c @@ -983,6 +983,111 @@ char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list) return ssh_find_all_matching(fips_methods[algo], list); } +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms plus the algorithms specified in list. If the system + * runs in fips mode, this will add only fips approved algorithms. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be appended + * + * @return A newly allocated list containing the default algorithms and the + * algorithms in list at the end; NULL in case of error. + */ +char *ssh_add_to_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *tmp = NULL, *ret = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_append_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_append_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms excluding the algorithms specified in list. If the system + * runs in fips mode, this will remove from the fips_methods list. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be exclude + * + * @return A newly allocated list containing the default algorithms without the + * algorithms in list; NULL in case of error. + */ +char *ssh_remove_from_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + if (list == NULL || list[0] == '\0') { + if (ssh_fips_mode()) { + return strdup(fips_methods[algo]); + } else { + return strdup(default_methods[algo]); + } + } + + if (ssh_fips_mode()) { + return ssh_remove_all_matching(fips_methods[algo], list); + } else { + return ssh_remove_all_matching(default_methods[algo], list); + } +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms with prioritized algorithms specified in list. If the + * algorithms are present in the default list they get prioritized, if not + * they are added to the front of the default list. If the system + * runs in fips mode, this will work with the fips_methods list. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be pushed to priority + * + * @return A newly allocated list containing the default algorithms prioritized + * with the algorithms in list at the beginning of the list; NULL in case + * of error. + */ +char *ssh_prefix_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *ret = NULL, *tmp = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_prefix_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_prefix_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + int ssh_make_sessionid(ssh_session session) { ssh_string num = NULL; -- 2.38.1 From 0d5d6e750a0c25700a47a760cb066b6027a54b09 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Mon, 7 Nov 2022 13:13:20 +0100 Subject: [PATCH 4/6] options.c: Add support for openssh config +,-,^ These features allow for options Ciphers, HostKeyAlgorithms, KexAlgorithms and MACs to append, remove and prepend to the default list of algorithms respectively Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- include/libssh/options.h | 3 +- src/options.c | 244 +++++++++++++++++++++++++-------------- src/server.c | 3 +- 3 files changed, 161 insertions(+), 89 deletions(-) diff --git a/include/libssh/options.h b/include/libssh/options.h index e8dc6c69..dd04c8af 100644 --- a/include/libssh/options.h +++ b/include/libssh/options.h @@ -25,7 +25,8 @@ int ssh_config_parse_file(ssh_session session, const char *filename); int ssh_config_parse_string(ssh_session session, const char *input); int ssh_options_set_algo(ssh_session session, enum ssh_kex_types_e algo, - const char *list); + const char *list, + char **place); int ssh_options_apply(ssh_session session); #endif /* _OPTIONS_H */ diff --git a/src/options.c b/src/options.c index 49aaefa2..56e09c65 100644 --- a/src/options.c +++ b/src/options.c @@ -221,14 +221,30 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) int ssh_options_set_algo(ssh_session session, enum ssh_kex_types_e algo, - const char *list) + const char *list, + char **place) { - char *p = NULL; + /* When the list start with +,-,^ the filtration of unknown algorithms + * gets handled inside the helper functions, otherwise the list is taken + * as it is. */ + char *p = (char *)list; + + if (algo < SSH_COMP_C_S) { + if (list[0] == '+') { + p = ssh_add_to_default_algos(algo, list+1); + } else if (list[0] == '-') { + p = ssh_remove_from_default_algos(algo, list+1); + } else if (list[0] == '^') { + p = ssh_prefix_default_algos(algo, list+1); + } + } - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(algo, list); - } else { - p = ssh_keep_known_algos(algo, list); + if (p == list) { + if (ssh_fips_mode()) { + p = ssh_keep_fips_algos(algo, list); + } else { + p = ssh_keep_known_algos(algo, list); + } } if (p == NULL) { @@ -238,8 +254,8 @@ int ssh_options_set_algo(ssh_session session, return -1; } - SAFE_FREE(session->opts.wanted_methods[algo]); - session->opts.wanted_methods[algo] = p; + SAFE_FREE(*place); + *place = p; return 0; } @@ -356,34 +372,60 @@ int ssh_options_set_algo(ssh_session session, * * - SSH_OPTIONS_CIPHERS_C_S: * Set the symmetric cipher client to server (const char *, - * comma-separated list). + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. * * - SSH_OPTIONS_CIPHERS_S_C: * Set the symmetric cipher server to client (const char *, - * comma-separated list). + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. * * - SSH_OPTIONS_KEY_EXCHANGE: * Set the key exchange method to be used (const char *, * comma-separated list). ex: * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. * * - SSH_OPTIONS_HMAC_C_S: * Set the Message Authentication Code algorithm client to server - * (const char *, comma-separated list). + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. * * - SSH_OPTIONS_HMAC_S_C: * Set the Message Authentication Code algorithm server to client - * (const char *, comma-separated list). + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. * * - SSH_OPTIONS_HOSTKEYS: * Set the preferred server host key types (const char *, * comma-separated list). ex: - * "ssh-rsa,ssh-dss,ecdh-sha2-nistp256" + * "ssh-rsa,ssh-dss,ecdh-sha2-nistp256". The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. * * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: * Set the preferred public key algorithms to be used for * authentication (const char *, comma-separated list). ex: * "ssh-rsa,rsa-sha2-256,ssh-dss,ecdh-sha2-nistp256" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. * * - SSH_OPTIONS_COMPRESSION_C_S: * Set the compression to use for client to server @@ -496,6 +538,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, long int i; unsigned int u; int rc; + char **wanted_methods = session->opts.wanted_methods; if (session == NULL) { return -1; @@ -779,7 +822,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_CRYPT_C_S, v) < 0) + rc = ssh_options_set_algo(session, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) return -1; } break; @@ -789,7 +836,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_CRYPT_S_C, v) < 0) + rc = ssh_options_set_algo(session, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) return -1; } break; @@ -799,7 +850,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_KEX, v) < 0) + rc = ssh_options_set_algo(session, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); + if (rc < 0) return -1; } break; @@ -809,7 +864,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_HOSTKEYS, v) < 0) + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); + if (rc < 0) return -1; } break; @@ -819,20 +878,12 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(SSH_HOSTKEYS, v); - } else { - p = ssh_keep_known_algos(SSH_HOSTKEYS, v); - } - if (p == NULL) { - ssh_set_error(session, SSH_REQUEST_DENIED, - "Setting method: no known public key algorithm (%s)", - v); + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &session->opts.pubkey_accepted_types); + if (rc < 0) return -1; - } - - SAFE_FREE(session->opts.pubkey_accepted_types); - session->opts.pubkey_accepted_types = p; } break; case SSH_OPTIONS_HMAC_C_S: @@ -841,7 +892,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_MAC_C_S, v) < 0) + rc = ssh_options_set_algo(session, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) return -1; } break; @@ -851,7 +906,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_MAC_S_C, v) < 0) + rc = ssh_options_set_algo(session, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) return -1; } break; @@ -861,16 +920,18 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (strcasecmp(value,"yes")==0){ - if(ssh_options_set_algo(session,SSH_COMP_C_S,"zlib@openssh.com,zlib,none") < 0) - return -1; - } else if (strcasecmp(value,"no")==0){ - if(ssh_options_set_algo(session,SSH_COMP_C_S,"none,zlib@openssh.com,zlib") < 0) - return -1; - } else { - if (ssh_options_set_algo(session, SSH_COMP_C_S, v) < 0) - return -1; + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,zlib,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com,zlib"; } + rc = ssh_options_set_algo(session, + SSH_COMP_C_S, + tmp, + &wanted_methods[SSH_COMP_C_S]); + if (rc < 0) + return -1; } break; case SSH_OPTIONS_COMPRESSION_S_C: @@ -879,16 +940,19 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (strcasecmp(value,"yes")==0){ - if(ssh_options_set_algo(session,SSH_COMP_S_C,"zlib@openssh.com,zlib,none") < 0) - return -1; - } else if (strcasecmp(value,"no")==0){ - if(ssh_options_set_algo(session,SSH_COMP_S_C,"none,zlib@openssh.com,zlib") < 0) - return -1; - } else { - if (ssh_options_set_algo(session, SSH_COMP_S_C, v) < 0) - return -1; + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,zlib,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com,zlib"; } + + rc = ssh_options_set_algo(session, + SSH_COMP_S_C, + tmp, + &wanted_methods[SSH_COMP_S_C]); + if (rc < 0) + return -1; } break; case SSH_OPTIONS_COMPRESSION: @@ -1604,26 +1668,12 @@ ssh_bind_set_key(ssh_bind sshbind, char **key_loc, const void *value) static int ssh_bind_set_algo(ssh_bind sshbind, enum ssh_kex_types_e algo, - const char *list) + const char *list, + char **place) { - char *p = NULL; - - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(algo, list); - } else { - p = ssh_keep_known_algos(algo, list); - } - if (p == NULL) { - ssh_set_error(sshbind, SSH_REQUEST_DENIED, - "Setting method: no algorithm for method \"%s\" (%s)", - ssh_kex_get_description(algo), list); - return -1; - } - - SAFE_FREE(sshbind->wanted_methods[algo]); - sshbind->wanted_methods[algo] = p; - - return 0; + /* sshbind is needed only for ssh_set_error which takes void* + * the typecast is only to satisfy function parameter type */ + return ssh_options_set_algo((ssh_session)sshbind, algo, list, place); } /** @@ -1765,6 +1815,7 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, char *p, *q; const char *v; int i, rc; + char **wanted_methods = sshbind->wanted_methods; if (sshbind == NULL) { return -1; @@ -2014,8 +2065,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_CRYPT_C_S, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_CIPHERS_S_C: @@ -2024,8 +2080,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_CRYPT_S_C, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_KEY_EXCHANGE: @@ -2034,7 +2095,10 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - rc = ssh_bind_set_algo(sshbind, SSH_KEX, v); + rc = ssh_bind_set_algo(sshbind, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); if (rc < 0) { return -1; } @@ -2046,8 +2110,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_MAC_C_S, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_HMAC_S_C: @@ -2056,8 +2125,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_MAC_S_C, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_CONFIG_DIR: @@ -2082,20 +2156,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(SSH_HOSTKEYS, v); - } else { - p = ssh_keep_known_algos(SSH_HOSTKEYS, v); - } - if (p == NULL) { - ssh_set_error(sshbind, SSH_REQUEST_DENIED, - "Setting method: no known public key algorithm (%s)", - v); + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &sshbind->pubkey_accepted_key_types); + if (rc < 0) { return -1; } - - SAFE_FREE(sshbind->pubkey_accepted_key_types); - sshbind->pubkey_accepted_key_types = p; } break; case SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: @@ -2104,7 +2171,10 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - rc = ssh_bind_set_algo(sshbind, SSH_HOSTKEYS, v); + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); if (rc < 0) { return -1; } diff --git a/src/server.c b/src/server.c index 3fc25bd9..1b423fd0 100644 --- a/src/server.c +++ b/src/server.c @@ -160,7 +160,8 @@ int server_set_kex(ssh_session session) rc = ssh_options_set_algo(session, SSH_HOSTKEYS, - kept); + kept, + &session->opts.wanted_methods[SSH_HOSTKEYS]); SAFE_FREE(kept); if (rc < 0) { return -1; -- 2.38.1 From b6cc8f643624231a583bd7972e9503b3fa434caa Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Mon, 7 Nov 2022 08:28:31 +0100 Subject: [PATCH 5/6] torture_options.c: Add test for config +,-,^ feature Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- tests/unittests/torture_options.c | 223 ++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index e1d16f02..dc4df383 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -1087,6 +1087,223 @@ static void torture_options_getopt(void **state) #endif /* _NSC_VER */ } +static void torture_options_plus_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + char *awaited; + size_t alg_len, algs_len; + + if (ssh_fips_mode()) { + alg = ",rsa-sha2-512-cert-v01@openssh.com"; + algs = ",rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = ",ssh-rsa"; + algs = ",ssh-rsa,ssh-rsa-cert-v01@openssh.com"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + /* in fips mode, the default list is the available list, which means + * we can't append anything because everything enabled is already + * included */ + if (ssh_fips_mode()) { + awaited = strdup(def_host_alg); + assert_non_null(awaited); + } else { + awaited = calloc(strlen(def_host_alg) + alg_len + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + memcpy(awaited+strlen(def_host_alg), alg, alg_len); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+ssh-rsa"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + if (!ssh_fips_mode()) { + /* different algorithm list is used here */ + free(awaited); + + awaited = calloc(strlen(def_host_alg) + algs_len + 1, 1); + assert_non_null(awaited); + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + memcpy(awaited+strlen(def_host_alg), algs, algs_len); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "+rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "+ssh-rsa,ssh-rsa-cert-v01@openssh.com"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, NULL); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + free(awaited); +} + +static void torture_options_minus_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + char *awaited, *p; + size_t alg_len, algs_len; + + if (ssh_fips_mode()) { + alg = "rsa-sha2-512-cert-v01@openssh.com,"; + algs = "rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521,"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = "ssh-ed25519,"; + algs = "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + awaited = calloc(strlen(def_host_alg) + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + p = strstr(awaited, alg); + assert_non_null(p); + memmove(p, p+alg_len, strlen(p + alg_len) + 1); + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-ssh-ed25519"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + p = strstr(awaited, algs); + assert_non_null(p); + memmove(p, p+algs_len, strlen(p + algs_len) + 1); + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + free(awaited); +} + +static void torture_options_caret_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + size_t alg_len, algs_len; + char *awaited, *p; + + if (ssh_fips_mode()) { + alg = "rsa-sha2-512-cert-v01@openssh.com,"; + algs = "rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521,"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = "ssh-rsa,"; + algs = "ssh-rsa,ssh-rsa-cert-v01@openssh.com,"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + awaited = calloc(strlen(def_host_alg) + alg_len + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, alg, alg_len); + memcpy(awaited+alg_len, def_host_alg, strlen(def_host_alg)); + if (ssh_fips_mode()) { + p = strstr(awaited, alg); + /* look for second occurrence */ + p = strstr(p+1, algs); + memmove(p, p+alg_len, strlen(p + alg_len) + 1); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^ssh-rsa"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + /* different algorithm list is used here */ + free(awaited); + + awaited = calloc(strlen(def_host_alg) + algs_len + 1, 1); + assert_non_null(awaited); + memcpy(awaited, algs, algs_len); + memcpy(awaited+algs_len, def_host_alg, strlen(def_host_alg)); + if (ssh_fips_mode()) { + p = strstr(awaited, algs); + /* look for second occurrence */ + p = strstr(p+1, algs); + memmove(p, p+algs_len, strlen(p + algs_len) + 1); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "^rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "^ssh-rsa,ssh-rsa-cert-v01@openssh.com"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + free(awaited); +} + #ifdef WITH_SERVER const char template[] = "temp_dir_XXXXXX"; @@ -1881,6 +2098,12 @@ int torture_run_tests(void) { setup, teardown), cmocka_unit_test_setup_teardown(torture_options_getopt, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_plus_sign, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_minus_sign, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_caret_sign, + setup, teardown), }; #ifdef WITH_SERVER -- 2.38.1 From c73996c4e747a9e28f919d660411c804bc748324 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Thu, 10 Nov 2022 10:50:52 +0100 Subject: [PATCH 6/6] torture_config.c: Add test for +,-,^ config feature It should be possible to use features to add,remove,prioritize algorithms in the algorithm list from the config file. Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- tests/unittests/torture_config.c | 393 +++++++++++++++++++++++++++++++ 1 file changed, 393 insertions(+) diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index 31dadae3..354adc2f 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -40,6 +40,9 @@ extern LIBSSH_THREAD int ssh_log_level; #define LIBSSH_TESTCONFIG10 "libssh_testconfig10.tmp" #define LIBSSH_TESTCONFIG11 "libssh_testconfig11.tmp" #define LIBSSH_TESTCONFIG12 "libssh_testconfig12.tmp" +#define LIBSSH_TESTCONFIG14 "libssh_testconfig14.tmp" +#define LIBSSH_TESTCONFIG15 "libssh_testconfig15.tmp" +#define LIBSSH_TESTCONFIG16 "libssh_testconfig16.tmp" #define LIBSSH_TESTCONFIGGLOB "libssh_testc*[36].tmp" #define LIBSSH_TEST_PUBKEYTYPES "libssh_test_PubkeyAcceptedKeyTypes.tmp" #define LIBSSH_TEST_PUBKEYALGORITHMS "libssh_test_PubkeyAcceptedAlgorithms.tmp" @@ -181,6 +184,27 @@ extern LIBSSH_THREAD int ssh_log_level; "IdentityFile id_rsa_one\n" \ "IdentityFile id_ecdsa_two\n" +/* +,-,^ features for all supported list */ +/* kex won't work in fips */ +#define LIBSSH_TESTCONFIG_STRING14 \ + "HostKeyAlgorithms +ssh-rsa\n" \ + "Ciphers +aes128-cbc,aes256-cbc\n" \ + "KexAlgorithms +diffie-hellman-group14-sha1,diffie-hellman-group1-sha1\n" \ + "MACs +hmac-sha1,hmac-sha1-etm@openssh.com\n" + +/* have to be algorithms which are in the default list */ +#define LIBSSH_TESTCONFIG_STRING15 \ + "HostKeyAlgorithms -rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers -aes256-ctr\n" \ + "KexAlgorithms -diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs -hmac-sha2-256-etm@openssh.com\n" + +#define LIBSSH_TESTCONFIG_STRING16 \ + "HostKeyAlgorithms ^rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers ^aes256-cbc\n" \ + "KexAlgorithms ^diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs ^hmac-sha1\n" + #define LIBSSH_TEST_PUBKEYTYPES_STRING \ "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" @@ -238,6 +262,9 @@ static int setup_config_files(void **state) unlink(LIBSSH_TESTCONFIG10); unlink(LIBSSH_TESTCONFIG11); unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); unlink(LIBSSH_TEST_PUBKEYTYPES); unlink(LIBSSH_TEST_PUBKEYALGORITHMS); unlink(LIBSSH_TEST_NONEWLINEEND); @@ -285,6 +312,14 @@ static int setup_config_files(void **state) torture_write_file(LIBSSH_TESTCONFIG12, LIBSSH_TESTCONFIG_STRING12); + /* +,-,^ feature */ + torture_write_file(LIBSSH_TESTCONFIG14, + LIBSSH_TESTCONFIG_STRING14); + torture_write_file(LIBSSH_TESTCONFIG15, + LIBSSH_TESTCONFIG_STRING15); + torture_write_file(LIBSSH_TESTCONFIG16, + LIBSSH_TESTCONFIG_STRING16); + torture_write_file(LIBSSH_TEST_PUBKEYTYPES, LIBSSH_TEST_PUBKEYTYPES_STRING); @@ -316,6 +351,9 @@ static int teardown_config_files(void **state) unlink(LIBSSH_TESTCONFIG10); unlink(LIBSSH_TESTCONFIG11); unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); unlink(LIBSSH_TEST_PUBKEYTYPES); unlink(LIBSSH_TEST_PUBKEYALGORITHMS); @@ -1267,6 +1305,349 @@ static void torture_config_rekey_string(void **state) torture_config_rekey(state, NULL, LIBSSH_TESTCONFIG_STRING12); } +/** + * @brief Remove substring from a string + * + * @param occurrence 0 means "remove the first occurrence" + * 1 means "remove the second occurrence" and so on + */ +static void helper_remove_substring(char *s, const char *subs, int occurrence) { + char *p; + /* remove the substring from the defaults */ + p = strstr(s, subs); + assert_non_null(p); + /* look for second occurrence */ + for (int i = 0; i < occurrence; i++) { + p = strstr(p + 1, subs); + assert_non_null(p); + } + memmove(p, p + strlen(subs), strlen(p + strlen(subs)) + 1); +} + +/** + * @brief test that openssh style '+' feature works + */ +static void torture_config_plus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_added = ",ssh-rsa"; + const char *ciphers_added = "aes128-cbc,aes256-cbc"; + const char *kex_added = ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"; + const char *mac_added = ",hmac-sha1,hmac-sha1-etm@openssh.com"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + /* ssh-rsa is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], fips_hostkeys); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_added) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + strlen(hostkeys_added) + 1, + "%s%s", def_hostkeys, hostkeys_added); + assert_int_equal(rc, strlen(def_hostkeys) + strlen(hostkeys_added)); + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + } + + /* check ciphers */ + if (ssh_fips_mode()) { + /* already all supported is in the list */ + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + } else { + awaited = calloc(strlen(def_ciphers) + strlen(ciphers_added) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + strlen(ciphers_added) + 1, + "%s%s", def_ciphers, ciphers_added); + assert_int_equal(rc, strlen(def_ciphers) + strlen(ciphers_added)); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + } + + /* check kex */ + if (ssh_fips_mode()) { + /* sha1 is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_KEX], fips_kex); + } else { + awaited = calloc(strlen(def_kex) + strlen(kex_added) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + strlen(kex_added) + 1, + "%s%s", def_kex, kex_added); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_added)); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + } + + /* check mac */ + if (ssh_fips_mode()) { + /* the added algos are already in the fips_methods */ + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], fips_mac); + } else { + awaited = calloc(strlen(def_mac) + strlen(mac_added) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + strlen(mac_added) + 1, + "%s%s", def_mac, mac_added); + assert_int_equal(rc, strlen(def_mac) + strlen(mac_added)); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); + } +} + +/** + * @brief test that openssh style '+' feature works from file + */ +static void torture_config_plus_file(void **state) +{ + torture_config_plus(state, LIBSSH_TESTCONFIG14, NULL); +} + +/** + * @brief test that openssh style '+' feature works from string + */ +static void torture_config_plus_string(void **state) +{ + torture_config_plus(state, NULL, LIBSSH_TESTCONFIG_STRING14); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_removed = ",rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_removed = ",aes256-ctr"; + const char *kex_removed = ",diffie-hellman-group18-sha512,diffie-hellman-group16-sha512"; + const char *fips_kex_removed = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_removed = "hmac-sha2-256-etm@openssh.com,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(fips_hostkeys) + 1, "%s", fips_hostkeys); + assert_int_equal(rc, strlen(fips_hostkeys)); + } else { + awaited = calloc(strlen(def_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + 1, "%s", def_hostkeys); + assert_int_equal(rc, strlen(def_hostkeys)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(fips_ciphers) + 1, "%s", fips_ciphers); + assert_int_equal(rc, strlen(fips_ciphers)); + } else { + awaited = calloc(strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + 1, "%s", def_ciphers); + assert_int_equal(rc, strlen(def_ciphers)); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(fips_kex) + 1, "%s", fips_kex); + assert_int_equal(rc, strlen(fips_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, fips_kex_removed, 0); + } else { + awaited = calloc(strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + 1, "%s", def_kex); + assert_int_equal(rc, strlen(def_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_removed, 0); + } + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(fips_mac) + 1, "%s", fips_mac); + assert_int_equal(rc, strlen(fips_mac)); + } else { + awaited = calloc(strlen(def_mac) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + 1, "%s", def_mac); + assert_int_equal(rc, strlen(def_mac)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, mac_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '-' feature works from file + */ +static void torture_config_minus_file(void **state) +{ + torture_config_minus(state, LIBSSH_TESTCONFIG15, NULL); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus_string(void **state) +{ + torture_config_minus(state, NULL, LIBSSH_TESTCONFIG_STRING15); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_prio = "rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_prio = "aes256-cbc,"; + const char *kex_prio = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,"; + const char *fips_kex_prio = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_prio = "hmac-sha1,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + /* +2 for the added comma and the \0 */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, + "%s,%s", hostkeys_prio, fips_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 1); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_prio) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(def_hostkeys) + 2, + "%s,%s", hostkeys_prio, def_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(def_hostkeys) + 1); + } + + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_prio, 1); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(ciphers_prio) + strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(fips_ciphers) + 1, + "%s%s", ciphers_prio, fips_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(fips_ciphers)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_prio, 1); + } else { + /* + 2 because the '\0' and the comma */ + awaited = calloc(strlen(ciphers_prio) + strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(def_ciphers) + 1, + "%s%s", ciphers_prio, def_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(def_ciphers)); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + } + + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(kex_prio) + strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(fips_kex) + 1, + "%s%s", kex_prio, fips_kex); + assert_int_equal(rc, strlen(kex_prio) + strlen(fips_kex)); + /* remove the substring from the defaults */ + /* the default list has different order of these two algos than the fips + * and because here is a braindead string substitution being done, + * change the order and remove the first occurrence of it */ + helper_remove_substring(awaited, fips_kex_prio, 0); + } else { + awaited = calloc(strlen(kex_prio) + strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(def_kex) + 1, + "%s%s", kex_prio, def_kex); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_prio)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_prio, 1); + } + + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(mac_prio) + strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(mac_prio) + strlen(fips_mac) + 1, "%s%s", mac_prio, fips_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(fips_mac)); + /* the fips list contains hmac-sha1 algo */ + helper_remove_substring(awaited, mac_prio, 1); + } else { + awaited = calloc(strlen(mac_prio) + strlen(def_mac) + 1, 1); + /* the mac is not in default; it is added to the list */ + rc = snprintf(awaited, strlen(mac_prio) + strlen(def_mac) + 1, "%s%s", mac_prio, def_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(def_mac)); + } + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '^' feature works from file + */ +static void torture_config_caret_file(void **state) +{ + torture_config_caret(state, LIBSSH_TESTCONFIG16, NULL); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret_string(void **state) +{ + torture_config_caret(state, NULL, LIBSSH_TESTCONFIG_STRING16); +} + /** * @brief test PubkeyAcceptedKeyTypes helper function */ @@ -1848,6 +2229,18 @@ int torture_run_tests(void) setup, teardown), cmocka_unit_test_setup_teardown(torture_config_rekey_string, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_string, + setup, teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_file, setup, teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_string, -- 2.38.1