mirror of
https://git.libssh.org/projects/libssh.git
synced 2025-07-31 00:03:07 +03:00
Allow limiting RSA key size used for authentication
Thanks to Harry Sintonen from WithSecure for pointing this out. Signed-off-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
This commit is contained in:
@ -406,6 +406,7 @@ enum ssh_options_e {
|
|||||||
SSH_OPTIONS_PROCESS_CONFIG,
|
SSH_OPTIONS_PROCESS_CONFIG,
|
||||||
SSH_OPTIONS_REKEY_DATA,
|
SSH_OPTIONS_REKEY_DATA,
|
||||||
SSH_OPTIONS_REKEY_TIME,
|
SSH_OPTIONS_REKEY_TIME,
|
||||||
|
SSH_OPTIONS_RSA_MIN_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -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);
|
ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key);
|
||||||
|
|
||||||
int ssh_key_algorithm_allowed(ssh_session session, const char *type);
|
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 */
|
/* Return the key size in bits */
|
||||||
int ssh_key_size(ssh_key key);
|
int ssh_key_size(ssh_key key);
|
||||||
|
@ -165,4 +165,5 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey,
|
|||||||
/* URI Function */
|
/* URI Function */
|
||||||
int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type);
|
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_ */
|
#endif /* PKI_PRIV_H_ */
|
||||||
|
@ -233,6 +233,7 @@ struct ssh_session_struct {
|
|||||||
uint8_t options_seen[SOC_MAX];
|
uint8_t options_seen[SOC_MAX];
|
||||||
uint64_t rekey_data;
|
uint64_t rekey_data;
|
||||||
uint32_t rekey_time;
|
uint32_t rekey_time;
|
||||||
|
unsigned int rsa_min_size;
|
||||||
} opts;
|
} opts;
|
||||||
/* counters */
|
/* counters */
|
||||||
ssh_counter socket_counter;
|
ssh_counter socket_counter;
|
||||||
|
25
src/auth.c
25
src/auth.c
@ -492,6 +492,7 @@ int ssh_userauth_try_publickey(ssh_session session,
|
|||||||
{
|
{
|
||||||
ssh_string pubkey_s = NULL;
|
ssh_string pubkey_s = NULL;
|
||||||
const char *sig_type_c = NULL;
|
const char *sig_type_c = NULL;
|
||||||
|
bool allowed;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
if (session == NULL) {
|
if (session == NULL) {
|
||||||
@ -531,6 +532,13 @@ int ssh_userauth_try_publickey(ssh_session session,
|
|||||||
sig_type_c);
|
sig_type_c);
|
||||||
return SSH_AUTH_DENIED;
|
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);
|
rc = ssh_userauth_request_service(session);
|
||||||
if (rc == SSH_AGAIN) {
|
if (rc == SSH_AGAIN) {
|
||||||
@ -612,6 +620,7 @@ int ssh_userauth_publickey(ssh_session session,
|
|||||||
const ssh_key privkey)
|
const ssh_key privkey)
|
||||||
{
|
{
|
||||||
ssh_string str = NULL;
|
ssh_string str = NULL;
|
||||||
|
bool allowed;
|
||||||
int rc;
|
int rc;
|
||||||
const char *sig_type_c = NULL;
|
const char *sig_type_c = NULL;
|
||||||
enum ssh_keytypes_e key_type;
|
enum ssh_keytypes_e key_type;
|
||||||
@ -656,6 +665,13 @@ int ssh_userauth_publickey(ssh_session session,
|
|||||||
sig_type_c);
|
sig_type_c);
|
||||||
return SSH_AUTH_DENIED;
|
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);
|
rc = ssh_userauth_request_service(session);
|
||||||
if (rc == SSH_AGAIN) {
|
if (rc == SSH_AGAIN) {
|
||||||
@ -732,6 +748,7 @@ static int ssh_userauth_agent_publickey(ssh_session session,
|
|||||||
ssh_string pubkey_s = NULL;
|
ssh_string pubkey_s = NULL;
|
||||||
ssh_string sig_blob = NULL;
|
ssh_string sig_blob = NULL;
|
||||||
const char *sig_type_c = NULL;
|
const char *sig_type_c = NULL;
|
||||||
|
bool allowed;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
switch(session->pending_call_state) {
|
switch(session->pending_call_state) {
|
||||||
@ -776,6 +793,14 @@ static int ssh_userauth_agent_publickey(ssh_session session,
|
|||||||
SSH_STRING_FREE(pubkey_s);
|
SSH_STRING_FREE(pubkey_s);
|
||||||
return SSH_AUTH_DENIED;
|
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 */
|
/* request */
|
||||||
rc = ssh_buffer_pack(session->out_buffer, "bsssbsS",
|
rc = ssh_buffer_pack(session->out_buffer, "bsssbsS",
|
||||||
|
@ -465,6 +465,15 @@ int ssh_options_set_algo(ssh_session session,
|
|||||||
* in seconds. RFC 4253 Section 9 recommends one hour.
|
* in seconds. RFC 4253 Section 9 recommends one hour.
|
||||||
* (uint32_t, 0=off)
|
* (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
|
* @param value The value to set. This is a generic pointer and the
|
||||||
* datatype which is used should be set according to the
|
* datatype which is used should be set according to the
|
||||||
* type set.
|
* type set.
|
||||||
@ -1028,6 +1037,21 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
|
|||||||
session->opts.rekey_time = (*x) * 1000;
|
session->opts.rekey_time = (*x) * 1000;
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type);
|
ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type);
|
||||||
return -1;
|
return -1;
|
||||||
|
33
src/pki.c
33
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);
|
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
|
* @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
|
* for all the key types, unless the SHA2 extension (RFC 8332) is
|
||||||
|
@ -986,6 +986,124 @@ static void torture_auth_pubkey_types_ed25519_nonblocking(void **state)
|
|||||||
} while (rc == SSH_AUTH_AGAIN);
|
} while (rc == SSH_AUTH_AGAIN);
|
||||||
assert_int_equal(rc, SSH_AUTH_SUCCESS);
|
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) {
|
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,
|
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking,
|
||||||
pubkey_setup,
|
pubkey_setup,
|
||||||
session_teardown),
|
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();
|
ssh_init();
|
||||||
|
Reference in New Issue
Block a user