1
0
mirror of https://git.libssh.org/projects/libssh.git synced 2025-07-31 00:03:07 +03:00

Implement IdentitiesOnly

Signed-off-by: Linus Kardell <linus.kardell@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
This commit is contained in:
Linus Kardell
2022-07-09 21:54:47 +02:00
committed by Jakub Jelen
parent bccb8513fa
commit 26895498fb
9 changed files with 262 additions and 7 deletions

View File

@ -62,6 +62,7 @@ enum ssh_config_opcode_e {
SOC_PUBKEYACCEPTEDKEYTYPES,
SOC_REKEYLIMIT,
SOC_IDENTITYAGENT,
SOC_IDENTITIESONLY,
SOC_MAX /* Keep this one last in the list */
};

View File

@ -408,6 +408,7 @@ enum ssh_options_e {
SSH_OPTIONS_REKEY_TIME,
SSH_OPTIONS_RSA_MIN_SIZE,
SSH_OPTIONS_IDENTITY_AGENT,
SSH_OPTIONS_IDENTITIES_ONLY,
};
enum {

View File

@ -237,6 +237,7 @@ struct ssh_session_struct {
uint64_t rekey_data;
uint32_t rekey_time;
int rsa_min_size;
bool identities_only;
} opts;
/* counters */
ssh_counter socket_counter;

View File

@ -906,6 +906,9 @@ int ssh_userauth_agent(ssh_session session,
{
int rc = SSH_AUTH_ERROR;
struct ssh_agent_state_struct *state;
ssh_key *configKeys = NULL;
size_t configKeysCount = 0;
size_t i;
if (session == NULL) {
return SSH_AUTH_ERROR;
@ -934,10 +937,92 @@ int ssh_userauth_agent(ssh_session session,
return SSH_AUTH_DENIED;
}
if (session->opts.identities_only) {
/*
* Read keys mentioned in the config, so we can check if key from agent
* is in there.
*/
size_t identityLen = ssh_list_count(session->opts.identity);
struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity);
configKeys = malloc(identityLen * sizeof(configKeys[0]));
if (!configKeys) {
ssh_set_error_oom(session);
return SSH_AUTH_ERROR;
}
while (it != NULL && configKeysCount < identityLen) {
const char *privkeyFile = it->data;
/*
* Read the private key file listed in the config, but we're only
* interested in the public key. Don't try to decrypt private key.
*/
ssh_key pubkey = NULL;
rc = ssh_pki_import_pubkey_file(privkeyFile, &pubkey);
if (rc == SSH_OK) {
configKeys[configKeysCount++] = pubkey;
} else {
char *pubkeyFile = NULL;
size_t pubkeyPathLen = strlen(privkeyFile) + sizeof(".pub");
if (pubkey) {
ssh_key_free(pubkey);
}
/*
* If we couldn't get the public key from the private key file,
* try a .pub file instead.
*/
pubkeyFile = malloc(pubkeyPathLen);
if (!pubkeyFile) {
ssh_set_error_oom(session);
rc = SSH_AUTH_ERROR;
goto done;
}
snprintf(pubkeyFile, pubkeyPathLen, "%s.pub", privkeyFile);
rc = ssh_pki_import_pubkey_file(pubkeyFile, &pubkey);
if (rc == SSH_OK) {
configKeys[configKeysCount++] = pubkey;
} else if (pubkey) {
ssh_key_free(pubkey);
}
free(pubkeyFile);
}
it = it->next;
}
}
while (state->pubkey != NULL) {
if (state->state == SSH_AGENT_STATE_NONE) {
SSH_LOG(SSH_LOG_DEBUG,
"Trying identity %s", state->comment);
if (session->opts.identities_only) {
/* Check if this key is one of the keys listed in the config */
bool found_key = false;
for (i = 0; i < configKeysCount; i++) {
if (ssh_key_cmp(state->pubkey, configKeys[i],
SSH_KEY_CMP_PUBLIC) == 0) {
found_key = true;
break;
}
}
if (!found_key) {
SSH_LOG(SSH_LOG_DEBUG,
"Identities only is enabled and identity %s was "
"not listed in config, skipping", state->comment);
SSH_STRING_FREE_CHAR(state->comment);
state->comment = NULL;
state->pubkey = ssh_agent_get_next_ident(
session, &state->comment);
if (state->pubkey == NULL) {
rc = SSH_AUTH_DENIED;
}
continue;
}
}
}
if (state->state == SSH_AGENT_STATE_NONE ||
state->state == SSH_AGENT_STATE_PUBKEY) {
@ -945,10 +1030,10 @@ int ssh_userauth_agent(ssh_session session,
if (rc == SSH_AUTH_ERROR) {
ssh_agent_state_free (state);
session->agent_state = NULL;
return rc;
goto done;
} else if (rc == SSH_AUTH_AGAIN) {
state->state = SSH_AGENT_STATE_PUBKEY;
return rc;
goto done;
} else if (rc != SSH_AUTH_SUCCESS) {
SSH_LOG(SSH_LOG_DEBUG,
"Public key of %s refused by server", state->comment);
@ -966,14 +1051,15 @@ int ssh_userauth_agent(ssh_session session,
}
if (state->state == SSH_AGENT_STATE_AUTH) {
rc = ssh_userauth_agent_publickey(session, username, state->pubkey);
if (rc == SSH_AUTH_AGAIN)
return rc;
if (rc == SSH_AUTH_AGAIN) {
goto done;
}
SSH_STRING_FREE_CHAR(state->comment);
state->comment = NULL;
if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_PARTIAL) {
ssh_agent_state_free (session->agent_state);
session->agent_state = NULL;
return rc;
goto done;
} else if (rc != SSH_AUTH_SUCCESS) {
SSH_LOG(SSH_LOG_INFO,
"Server accepted public key but refused the signature");
@ -984,12 +1070,18 @@ int ssh_userauth_agent(ssh_session session,
}
ssh_agent_state_free (session->agent_state);
session->agent_state = NULL;
return SSH_AUTH_SUCCESS;
rc = SSH_AUTH_SUCCESS;
goto done;
}
}
ssh_agent_state_free (session->agent_state);
session->agent_state = NULL;
done:
for (i = 0; i < configKeysCount; i++) {
ssh_key_free(configKeys[i]);
}
free(configKeys);
return rc;
}

View File

@ -103,7 +103,7 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
{ "hostbasedauthentication", SOC_UNSUPPORTED},
{ "hostbasedacceptedalgorithms", SOC_UNSUPPORTED},
{ "hostkeyalias", SOC_UNSUPPORTED},
{ "identitiesonly", SOC_UNSUPPORTED},
{ "identitiesonly", SOC_IDENTITIESONLY},
{ "identityagent", SOC_IDENTITYAGENT},
{ "ipqos", SOC_UNSUPPORTED},
{ "kbdinteractivedevices", SOC_UNSUPPORTED},
@ -1179,6 +1179,13 @@ ssh_config_parse_line(ssh_session session,
ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p);
}
break;
case SOC_IDENTITIESONLY:
i = ssh_config_get_yesno(&s, -1);
if (i >= 0 && *parsing) {
bool b = i;
ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b);
}
break;
default:
ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d",
opcode);

View File

@ -481,6 +481,11 @@ int ssh_options_set_algo(ssh_session session,
* Set the path to the SSH agent socket. If unset, the
* SSH_AUTH_SOCK environment is consulted.
* (const char *)
* - SSH_OPTIONS_IDENTITIES_ONLY
* Use only keys specified in the SSH config, even if agent
* offers more.
* (bool)
*
* @param value The value to set. This is a generic pointer and the
* datatype which is used should be set according to the
@ -1077,6 +1082,15 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
}
}
break;
case SSH_OPTIONS_IDENTITIES_ONLY:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
bool *x = (bool *)value;
session->opts.identities_only = *x;
}
break;
default:
ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type);
return -1;

View File

@ -1783,6 +1783,7 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey)
off_t size;
int rc, cmp;
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
ssh_key priv_key = NULL;
if (pkey == NULL || filename == NULL || *filename == '\0') {
return SSH_ERROR;
@ -1852,6 +1853,23 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey)
return SSH_OK;
}
/*
* Try to parse key as PEM. Set empty passphrase, so user won't be prompted
* for passphrase. Don't try to decrypt encrypted private key.
*/
priv_key = pki_private_key_from_base64(key_buf, "", NULL, NULL);
if (priv_key) {
rc = ssh_pki_export_privkey_to_pubkey(priv_key, pkey);
ssh_key_free(priv_key);
SAFE_FREE(key_buf);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARN, "Failed to import public key from PEM"
" private key file");
return SSH_ERROR;
}
return SSH_OK;
}
/* This the old one-line public key format */
q = p = key_buf;
for (i = 0; i < buflen; i++) {

View File

@ -108,6 +108,7 @@ ssh_session ssh_new(void)
session->opts.fd = -1;
session->opts.compressionlevel = 7;
session->opts.nodelay = 0;
session->opts.identities_only = false;
session->opts.flags = SSH_OPT_FLAG_PASSWORD_AUTH |
SSH_OPT_FLAG_PUBKEY_AUTH |

View File

@ -686,6 +686,120 @@ static void torture_auth_agent_nonblocking(void **state) {
assert_ssh_return_code(session, rc);
}
static void torture_auth_agent_identities_only(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
char bob_ssh_key[1024];
struct passwd *pwd;
int rc;
int identities_only = 1;
char *id;
pwd = getpwnam("bob");
assert_non_null(pwd);
snprintf(bob_ssh_key,
sizeof(bob_ssh_key),
"%s/.ssh/id_rsa",
pwd->pw_dir);
if (!ssh_agent_is_running(session)){
print_message("*** Agent not running. Test ignored\n");
return;
}
rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE);
assert_int_equal(rc, SSH_OK);
rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only);
assert_int_equal(rc, SSH_OK);
/* Remove the default identities */
while ((id = ssh_list_pop_head(char *, session->opts.identity)) != NULL) {
SAFE_FREE(id);
}
rc = ssh_connect(session);
assert_int_equal(rc, SSH_OK);
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);
/* Should fail as key is not in config */
rc = ssh_userauth_agent(session, NULL);
assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED);
/* Re-add a key */
rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key));
assert_int_equal(rc, SSH_OK);
/* Should succeed as key now in config */
rc = ssh_userauth_agent(session, NULL);
assert_ssh_return_code(session, rc);
}
static void torture_auth_agent_identities_only_protected(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
char bob_ssh_key[1024];
struct passwd *pwd;
int rc;
int identities_only = 1;
char *id;
pwd = getpwnam("bob");
assert_non_null(pwd);
snprintf(bob_ssh_key,
sizeof(bob_ssh_key),
"%s/.ssh/id_rsa_protected",
pwd->pw_dir);
if (!ssh_agent_is_running(session)){
print_message("*** Agent not running. Test ignored\n");
return;
}
rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE);
assert_int_equal(rc, SSH_OK);
rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only);
assert_int_equal(rc, SSH_OK);
/* Remove the default identities */
while ((id = ssh_list_pop_head(char *, session->opts.identity)) != NULL) {
SAFE_FREE(id);
}
rc = ssh_connect(session);
assert_int_equal(rc, SSH_OK);
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);
/* Should fail as key is not in config */
rc = ssh_userauth_agent(session, NULL);
assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED);
/* Re-add a key */
rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key));
assert_int_equal(rc, SSH_OK);
/* Should succeed as key now in config */
rc = ssh_userauth_agent(session, NULL);
assert_ssh_return_code(session, rc);
}
static void torture_auth_cert(void **state) {
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
@ -1242,6 +1356,12 @@ int torture_run_tests(void) {
cmocka_unit_test_setup_teardown(torture_auth_agent_nonblocking,
agent_setup,
agent_teardown),
cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only,
agent_setup,
agent_teardown),
cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only_protected,
agent_setup,
agent_teardown),
cmocka_unit_test_setup_teardown(torture_auth_cert,
pubkey_setup,
session_teardown),