diff --git a/include/libssh/server.h b/include/libssh/server.h index 0a893595..29a5565e 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -54,6 +54,7 @@ enum ssh_bind_options_e { SSH_BIND_OPTIONS_HMAC_S_C, SSH_BIND_OPTIONS_CONFIG_DIR, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, }; typedef struct ssh_bind_struct* ssh_bind; diff --git a/src/options.c b/src/options.c index 244f3edf..3b58fe2c 100644 --- a/src/options.c +++ b/src/options.c @@ -1612,6 +1612,15 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * Set the public key algorithm accepted by the server * (const char *, comma-separated list). * + * - SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: + * Set the list of allowed hostkey signatures algorithms + * to offer to the client, ordered by preference. This + * list is used as a filter when creating the list of + * algorithms to offer to the client: first the list of + * possible algorithms is created from the list of keys + * set and then filtered against this list. + * (const char *, comma-separated list). + * * @param value The value to set. This is a generic pointer and the * datatype which should be used is described at the * corresponding value of type above. @@ -1934,6 +1943,18 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, sshbind->pubkey_accepted_key_types = p; } break; + case SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, SSH_HOSTKEYS, v); + if (rc < 0) { + return -1; + } + } + break; default: ssh_set_error(sshbind, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return -1; diff --git a/src/server.c b/src/server.c index 2937d0f5..adb6ff7c 100644 --- a/src/server.c +++ b/src/server.c @@ -59,6 +59,7 @@ #include "libssh/messages.h" #include "libssh/options.h" #include "libssh/curve25519.h" +#include "libssh/token.h" #define set_status(session, status) do {\ if (session->common.callbacks && session->common.callbacks->connect_status_function) \ @@ -82,82 +83,99 @@ int server_set_kex(ssh_session session) { - struct ssh_kex_struct *server = &session->next_crypto->server_kex; - int i, j, rc; - const char *wanted; - char hostkeys[128] = {0}; - enum ssh_keytypes_e keytype; - size_t len; - int ok; + struct ssh_kex_struct *server = &session->next_crypto->server_kex; + int i, j, rc; + const char *wanted, *allowed; + char *kept; + char hostkeys[128] = {0}; + enum ssh_keytypes_e keytype; + size_t len; + int ok; - ZERO_STRUCTP(server); + ZERO_STRUCTP(server); - ok = ssh_get_random(server->cookie, 16, 0); - if (!ok) { - ssh_set_error(session, SSH_FATAL, "PRNG error"); - return -1; - } + ok = ssh_get_random(server->cookie, 16, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return -1; + } - if (session->srv.ed25519_key != NULL) { - snprintf(hostkeys, - sizeof(hostkeys), - "%s", - ssh_key_type_to_char(ssh_key_type(session->srv.ed25519_key))); - } + if (session->srv.ed25519_key != NULL) { + snprintf(hostkeys, + sizeof(hostkeys), + "%s", + ssh_key_type_to_char(ssh_key_type(session->srv.ed25519_key))); + } #ifdef HAVE_ECC - if (session->srv.ecdsa_key != NULL) { - len = strlen(hostkeys); - snprintf(hostkeys + len, sizeof(hostkeys) - len, - ",%s", session->srv.ecdsa_key->type_c); - } + if (session->srv.ecdsa_key != NULL) { + len = strlen(hostkeys); + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", session->srv.ecdsa_key->type_c); + } #endif #ifdef HAVE_DSA - if (session->srv.dsa_key != NULL) { - len = strlen(hostkeys); - keytype = ssh_key_type(session->srv.dsa_key); + if (session->srv.dsa_key != NULL) { + len = strlen(hostkeys); + keytype = ssh_key_type(session->srv.dsa_key); - snprintf(hostkeys + len, sizeof(hostkeys) - len, - ",%s", ssh_key_type_to_char(keytype)); - } + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", ssh_key_type_to_char(keytype)); + } #endif - if (session->srv.rsa_key != NULL) { - /* We support also the SHA2 variants */ - len = strlen(hostkeys); - snprintf(hostkeys + len, sizeof(hostkeys) - len, - ",rsa-sha2-512,rsa-sha2-256"); + if (session->srv.rsa_key != NULL) { + /* We support also the SHA2 variants */ + len = strlen(hostkeys); + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",rsa-sha2-512,rsa-sha2-256"); - len = strlen(hostkeys); - keytype = ssh_key_type(session->srv.rsa_key); + len = strlen(hostkeys); + keytype = ssh_key_type(session->srv.rsa_key); - snprintf(hostkeys + len, sizeof(hostkeys) - len, - ",%s", ssh_key_type_to_char(keytype)); - } - - if (strlen(hostkeys) == 0) { - return -1; - } - - rc = ssh_options_set_algo(session, - SSH_HOSTKEYS, - hostkeys[0] == ',' ? hostkeys + 1 : hostkeys); - if (rc < 0) { - return -1; - } - - for (i = 0; i < 10; i++) { - if ((wanted = session->opts.wanted_methods[i]) == NULL) { - wanted = ssh_kex_get_supported_method(i); + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", ssh_key_type_to_char(keytype)); } - server->methods[i] = strdup(wanted); - if (server->methods[i] == NULL) { - for (j = 0; j < i; j++) { - SAFE_FREE(server->methods[j]); - } - return -1; - } - } - return 0; + if (strlen(hostkeys) == 0) { + return -1; + } + + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + allowed = session->opts.wanted_methods[SSH_HOSTKEYS]; + } else { + allowed = ssh_kex_get_supported_method(SSH_HOSTKEYS); + } + + /* It is expected for the list of allowed hostkeys to be ordered by + * preference */ + kept = ssh_find_all_matching(hostkeys[0] == ',' ? hostkeys + 1 : hostkeys, + allowed); + if (kept == NULL) { + /* Nothing was allowed */ + return -1; + } + + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + kept); + SAFE_FREE(kept); + if (rc < 0) { + return -1; + } + + for (i = 0; i < 10; i++) { + if ((wanted = session->opts.wanted_methods[i]) == NULL) { + wanted = ssh_kex_get_supported_method(i); + } + server->methods[i] = strdup(wanted); + if (server->methods[i] == NULL) { + for (j = 0; j < i; j++) { + SAFE_FREE(server->methods[j]); + } + return -1; + } + } + + return 0; } int ssh_server_init_kex(ssh_session session) { diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index 6b6fff95..6a26768f 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -1413,6 +1413,61 @@ static void torture_bind_options_set_pubkey_accepted_key_types(void **state) "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); } +static void torture_bind_options_set_hostkey_algorithms(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test known Pubkey Types */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + + SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with some unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ssh-ed25519,ecdsa-sha2-nistp384,unknown-type,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + + SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with only unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); + assert_int_equal(rc, -1); + assert_null(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with something set and then try unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); + assert_int_equal(rc, -1); + + /* Check that nothing changed */ + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); +} + #endif /* WITH_SERVER */ @@ -1484,6 +1539,8 @@ int torture_run_tests(void) { sshbind_setup, sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_set_pubkey_accepted_key_types, sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_hostkey_algorithms, + sshbind_setup, sshbind_teardown), }; #endif /* WITH_SERVER */