From b408f5724a7fcccc7962e3d2ff57a78051cbdc7d Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 9 May 2022 22:16:12 +0200 Subject: [PATCH] Allow limiting RSA key size used for authentication Thanks to Harry Sintonen from WithSecure for pointing this out. Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- include/libssh/libssh.h | 1 + include/libssh/pki.h | 1 + include/libssh/pki_priv.h | 1 + include/libssh/session.h | 1 + src/auth.c | 25 ++++++++ src/options.c | 24 +++++++ src/pki.c | 33 ++++++++++ tests/client/torture_auth.c | 124 ++++++++++++++++++++++++++++++++++++ 8 files changed, 210 insertions(+) diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 803d5e2a..f333db24 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -406,6 +406,7 @@ enum ssh_options_e { SSH_OPTIONS_PROCESS_CONFIG, SSH_OPTIONS_REKEY_DATA, SSH_OPTIONS_REKEY_TIME, + SSH_OPTIONS_RSA_MIN_SIZE, }; enum { diff --git a/include/libssh/pki.h b/include/libssh/pki.h index 3dc2d172..2901ba8b 100644 --- a/include/libssh/pki.h +++ b/include/libssh/pki.h @@ -176,6 +176,7 @@ ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key); ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key); int ssh_key_algorithm_allowed(ssh_session session, const char *type); +bool ssh_key_size_allowed(ssh_session session, ssh_key key); /* Return the key size in bits */ int ssh_key_size(ssh_key key); diff --git a/include/libssh/pki_priv.h b/include/libssh/pki_priv.h index 71418fdc..e0cf6ed7 100644 --- a/include/libssh/pki_priv.h +++ b/include/libssh/pki_priv.h @@ -165,4 +165,5 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, /* URI Function */ int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type); +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key); #endif /* PKI_PRIV_H_ */ diff --git a/include/libssh/session.h b/include/libssh/session.h index ddd52fd6..ba3baefc 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -233,6 +233,7 @@ struct ssh_session_struct { uint8_t options_seen[SOC_MAX]; uint64_t rekey_data; uint32_t rekey_time; + unsigned int rsa_min_size; } opts; /* counters */ ssh_counter socket_counter; diff --git a/src/auth.c b/src/auth.c index 24e109ee..ea0aab0c 100644 --- a/src/auth.c +++ b/src/auth.c @@ -492,6 +492,7 @@ int ssh_userauth_try_publickey(ssh_session session, { ssh_string pubkey_s = NULL; const char *sig_type_c = NULL; + bool allowed; int rc; if (session == NULL) { @@ -531,6 +532,13 @@ int ssh_userauth_try_publickey(ssh_session session, sig_type_c); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(pubkey)); + return SSH_AUTH_DENIED; + } rc = ssh_userauth_request_service(session); if (rc == SSH_AGAIN) { @@ -612,6 +620,7 @@ int ssh_userauth_publickey(ssh_session session, const ssh_key privkey) { ssh_string str = NULL; + bool allowed; int rc; const char *sig_type_c = NULL; enum ssh_keytypes_e key_type; @@ -656,6 +665,13 @@ int ssh_userauth_publickey(ssh_session session, sig_type_c); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(privkey)); + return SSH_AUTH_DENIED; + } rc = ssh_userauth_request_service(session); if (rc == SSH_AGAIN) { @@ -732,6 +748,7 @@ static int ssh_userauth_agent_publickey(ssh_session session, ssh_string pubkey_s = NULL; ssh_string sig_blob = NULL; const char *sig_type_c = NULL; + bool allowed; int rc; switch(session->pending_call_state) { @@ -776,6 +793,14 @@ static int ssh_userauth_agent_publickey(ssh_session session, SSH_STRING_FREE(pubkey_s); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(pubkey)); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", diff --git a/src/options.c b/src/options.c index a16c70f7..395ff39f 100644 --- a/src/options.c +++ b/src/options.c @@ -465,6 +465,15 @@ int ssh_options_set_algo(ssh_session session, * in seconds. RFC 4253 Section 9 recommends one hour. * (uint32_t, 0=off) * + * - SSH_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by the + * client for both authentication and hostkey verification. + * The values under 768 bits are not accepted even with this + * configuration option as they are considered completely broken. + * Setting 0 will revert the value to defaults. + * Default is 1024 bits or 2048 bits in FIPS mode. + * (unsigned int *) + * * @param value The value to set. This is a generic pointer and the * datatype which is used should be set according to the * type set. @@ -1028,6 +1037,21 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.rekey_time = (*x) * 1000; } break; + case SSH_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + unsigned int *x = (unsigned int *)value; + if (*x > 0 && *x < 768) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The provided value (%u) for minimal RSA key " + "size is too small. Use at least 768 bits.", *x); + return -1; + } + session->opts.rsa_min_size = *x; + } + break; default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return -1; diff --git a/src/pki.c b/src/pki.c index d3f91a8b..773a427f 100644 --- a/src/pki.c +++ b/src/pki.c @@ -404,6 +404,39 @@ int ssh_key_algorithm_allowed(ssh_session session, const char *type) return ssh_match_group(allowed_list, type); } +/** + * @brief Check the given key is acceptable in regards to the key size policy + * specified by the configuration + * + * @param[in] session The SSH session + * @param[in] key The SSH key + * @returns true if the key is allowed, false otherwise + */ +bool ssh_key_size_allowed(ssh_session session, ssh_key key) +{ + int key_size = ssh_key_size(key); + int min_size = 0; + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = session->opts.rsa_min_size; + if (min_size < 768) { + if (ssh_fips_mode()) { + min_size = 2048; + } else { + min_size = 1024; + } + } + if (key_size < min_size) { + return false; + } + /* fall through */ + default: + return true; + } +} + /** * @brief Convert a key type to a hash type. This is usually unambiguous * for all the key types, unless the SHA2 extension (RFC 8332) is diff --git a/tests/client/torture_auth.c b/tests/client/torture_auth.c index 191de103..072f61ef 100644 --- a/tests/client/torture_auth.c +++ b/tests/client/torture_auth.c @@ -986,6 +986,124 @@ static void torture_auth_pubkey_types_ed25519_nonblocking(void **state) } while (rc == SSH_AUTH_AGAIN); assert_int_equal(rc, SSH_AUTH_SUCCESS); + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_rsa_key_size(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_rsa_key_size_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); } int torture_run_tests(void) { @@ -1051,6 +1169,12 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking, pubkey_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size_nonblocking, + pubkey_setup, + session_teardown), }; ssh_init();