diff --git a/include/libssh/bind.h b/include/libssh/bind.h index b22a8c7b..4ca23760 100644 --- a/include/libssh/bind.h +++ b/include/libssh/bind.h @@ -48,6 +48,7 @@ struct ssh_bind_struct { int toaccept; bool config_processed; char *config_dir; + char *pubkey_accepted_key_types; }; struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct diff --git a/include/libssh/server.h b/include/libssh/server.h index 64e7ca87..0a893595 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -53,6 +53,7 @@ enum ssh_bind_options_e { SSH_BIND_OPTIONS_HMAC_C_S, SSH_BIND_OPTIONS_HMAC_S_C, SSH_BIND_OPTIONS_CONFIG_DIR, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, }; typedef struct ssh_bind_struct* ssh_bind; diff --git a/src/bind.c b/src/bind.c index a1f25de2..d5193f77 100644 --- a/src/bind.c +++ b/src/bind.c @@ -38,6 +38,7 @@ #include "libssh/buffer.h" #include "libssh/socket.h" #include "libssh/session.h" +#include "libssh/token.h" /** * @addtogroup libssh_server @@ -402,6 +403,7 @@ void ssh_bind_free(ssh_bind sshbind){ SAFE_FREE(sshbind->banner); SAFE_FREE(sshbind->bindaddr); SAFE_FREE(sshbind->config_dir); + SAFE_FREE(sshbind->pubkey_accepted_key_types); SAFE_FREE(sshbind->dsakey); SAFE_FREE(sshbind->rsakey); @@ -456,6 +458,29 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ } } + if (sshbind->pubkey_accepted_key_types != NULL) { + if (session->opts.pubkey_accepted_types == NULL) { + session->opts.pubkey_accepted_types = strdup(sshbind->pubkey_accepted_key_types); + if (session->opts.pubkey_accepted_types == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } else { + char *p; + /* If something was set to the session prior to calling this + * function, keep only what is allowed by the options set in + * sshbind */ + p = ssh_find_all_matching(sshbind->pubkey_accepted_key_types, + session->opts.pubkey_accepted_types); + if (p == NULL) { + return SSH_ERROR; + } + + SAFE_FREE(session->opts.pubkey_accepted_types); + session->opts.pubkey_accepted_types = p; + } + } + session->common.log_verbosity = sshbind->common.log_verbosity; if(sshbind->banner != NULL) session->opts.custombanner = strdup(sshbind->banner); diff --git a/src/messages.c b/src/messages.c index f65a484c..f0c6d781 100644 --- a/src/messages.c +++ b/src/messages.c @@ -868,11 +868,28 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ msg->auth_request.pubkey, &sig); if (rc == SSH_OK) { - rc = ssh_pki_signature_verify(session, - sig, - msg->auth_request.pubkey, - ssh_buffer_get(digest), - ssh_buffer_get_len(digest)); + /* Check if the signature from client matches server preferences */ + if (session->opts.pubkey_accepted_types) { + if (!ssh_match_group(session->opts.pubkey_accepted_types, + sig->type_c)) + { + ssh_set_error(session, + SSH_FATAL, + "Public key from client (%s) doesn't match server " + "preference (%s)", + sig->type_c, + session->opts.pubkey_accepted_types); + rc = SSH_ERROR; + } + } + + if (rc == SSH_OK) { + rc = ssh_pki_signature_verify(session, + sig, + msg->auth_request.pubkey, + ssh_buffer_get(digest), + ssh_buffer_get_len(digest)); + } } ssh_string_free(sig_blob); ssh_buffer_free(digest); diff --git a/src/options.c b/src/options.c index e9d8a721..244f3edf 100644 --- a/src/options.c +++ b/src/options.c @@ -1608,6 +1608,10 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * paths of configuration files to * ssh_bind_options_parse_config(). * + * - SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES: + * Set the public key algorithm accepted by the server + * (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. @@ -1912,6 +1916,24 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, } } break; + case SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } 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); + return -1; + } + + SAFE_FREE(sshbind->pubkey_accepted_key_types); + sshbind->pubkey_accepted_key_types = p; + } + 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 b2c81b60..2937d0f5 100644 --- a/src/server.c +++ b/src/server.c @@ -180,11 +180,14 @@ static int ssh_server_send_extensions(ssh_session session) { const char *hostkey_algorithms; SSH_LOG(SSH_LOG_PACKET, "Sending SSH_MSG_EXT_INFO"); - /* - * We can list here all the default hostkey methods, since - * they already contain the SHA2 extension algorithms - */ - hostkey_algorithms = ssh_kex_get_default_methods(SSH_HOSTKEYS); + + if (session->opts.pubkey_accepted_types) { + hostkey_algorithms = session->opts.pubkey_accepted_types; + } else { + /* There are no restrictions to the accepted public keys */ + hostkey_algorithms = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + rc = ssh_buffer_pack(session->out_buffer, "bdss", SSH2_MSG_EXT_INFO, diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index 48120013..6b6fff95 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -1358,6 +1358,60 @@ static void torture_bind_options_config_dir(void **state) assert_string_equal(bind->config_dir, replacement_dir); } +static void torture_bind_options_set_pubkey_accepted_key_types(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_PUBKEY_ACCEPTED_KEY_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + + SAFE_FREE(bind->pubkey_accepted_key_types); + + /* Test with some unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,unknown-type,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + + SAFE_FREE(bind->pubkey_accepted_key_types); + + /* Test with only unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); + assert_int_equal(rc, -1); + assert_null(bind->pubkey_accepted_key_types); + + /* Test with something set and then try unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); + assert_int_equal(rc, -1); + + /* Check that nothing changed */ + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); +} #endif /* WITH_SERVER */ @@ -1387,7 +1441,7 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_options_copy, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_config_host, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_config_match, - setup, teardown) + setup, teardown), }; #ifdef WITH_SERVER @@ -1428,6 +1482,8 @@ int torture_run_tests(void) { sshbind_setup, sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_config_dir, sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_pubkey_accepted_key_types, + sshbind_setup, sshbind_teardown), }; #endif /* WITH_SERVER */