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:
committed by
Jakub Jelen
parent
bccb8513fa
commit
26895498fb
@ -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 */
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
104
src/auth.c
104
src/auth.c
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
18
src/pki.c
18
src/pki.c
@ -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++) {
|
||||
|
@ -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 |
|
||||
|
@ -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),
|
||||
|
Reference in New Issue
Block a user