diff options
Diffstat (limited to 'src/auth.c')
-rw-r--r-- | src/auth.c | 676 |
1 files changed, 530 insertions, 146 deletions
@@ -47,7 +47,7 @@ #include "libssh/legacy.h" /** - * @defgroup libssh_auth The SSH authentication functions. + * @defgroup libssh_auth The SSH authentication functions * @ingroup libssh * * Functions to authenticate with a server. @@ -58,7 +58,7 @@ /** * @internal * - * @brief Ask access to the ssh-userauth service. + * @brief Ask for access to the ssh-userauth service. * * @param[in] session The SSH session handle. * @@ -66,19 +66,21 @@ * @returns SSH_AGAIN on nonblocking mode, if calling that function * again is necessary */ -static int ssh_userauth_request_service(ssh_session session) { +static int ssh_userauth_request_service(ssh_session session) +{ int rc; rc = ssh_service_request(session, "ssh-userauth"); if ((rc != SSH_OK) && (rc != SSH_AGAIN)) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Failed to request \"ssh-userauth\" service"); } return rc; } -static int ssh_auth_response_termination(void *user) { +static int ssh_auth_response_termination(void *user) +{ ssh_session session = (ssh_session)user; switch (session->auth.state) { case SSH_AUTH_STATE_NONE: @@ -139,7 +141,8 @@ static const char *ssh_auth_get_current_method(ssh_session session) * SSH_AUTH_AGAIN In nonblocking mode, call has to be made again * SSH_AUTH_ERROR Error during the process. */ -static int ssh_userauth_get_response(ssh_session session) { +static int ssh_userauth_get_response(ssh_session session) +{ int rc = SSH_AUTH_ERROR; rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, @@ -199,7 +202,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_banner) { banner = ssh_buffer_get_ssh_string(packet); if (banner == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Invalid SSH_USERAUTH_BANNER packet"); } else { SSH_LOG(SSH_LOG_DEBUG, @@ -237,7 +240,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { if (partial) { session->auth.state = SSH_AUTH_STATE_PARTIAL; - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "Partial success for '%s'. Authentication that can continue: %s", current_method, auth_methods); @@ -247,7 +250,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { "Access denied for '%s'. Authentication that can continue: %s", current_method, auth_methods); - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "%s", ssh_get_error(session)); @@ -364,7 +367,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok) { * * @param[in] username Deprecated, set to NULL. * - * @returns A bitfield of the fllowing values: + * @returns A bitfield of the following values: * - SSH_AUTH_METHOD_PASSWORD * - SSH_AUTH_METHOD_PUBLICKEY * - SSH_AUTH_METHOD_HOSTBASED @@ -403,10 +406,11 @@ int ssh_userauth_list(ssh_session session, const char *username) * authentication. The username should only be set with ssh_options_set() only * before you connect to the server. */ -int ssh_userauth_none(ssh_session session, const char *username) { +int ssh_userauth_none(ssh_session session, const char *username) +{ int rc; - switch(session->pending_call_state) { + switch (session->pending_call_state) { case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_AUTH_NONE: @@ -492,6 +496,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) { @@ -513,7 +518,7 @@ int ssh_userauth_try_publickey(ssh_session session, SSH_FATAL, "Wrong state (%d) during pending SSH call", session->pending_call_state); - return SSH_ERROR; + return SSH_AUTH_ERROR; } /* Check if the given public key algorithm is allowed */ @@ -523,13 +528,21 @@ int ssh_userauth_try_publickey(ssh_session session, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", 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) { @@ -544,6 +557,7 @@ int ssh_userauth_try_publickey(ssh_session session, goto fail; } + SSH_LOG(SSH_LOG_TRACE, "Trying signature type %s", sig_type_c); /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", SSH2_MSG_USERAUTH_REQUEST, @@ -611,6 +625,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; @@ -647,13 +662,21 @@ int ssh_userauth_publickey(ssh_session session, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", 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) { @@ -668,6 +691,7 @@ int ssh_userauth_publickey(ssh_session session, goto fail; } + SSH_LOG(SSH_LOG_TRACE, "Sending signature type %s", sig_type_c); /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", SSH2_MSG_USERAUTH_REQUEST, @@ -722,7 +746,6 @@ fail: return SSH_AUTH_ERROR; } -#ifndef _WIN32 static int ssh_userauth_agent_publickey(ssh_session session, const char *username, ssh_key pubkey) @@ -730,18 +753,20 @@ 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) { - case SSH_PENDING_CALL_NONE: - break; - case SSH_PENDING_CALL_AUTH_AGENT: - goto pending; - default: - ssh_set_error(session, - SSH_FATAL, - "Bad call during pending SSH call in ssh_userauth_try_publickey"); - return SSH_ERROR; + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_AGENT: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Bad call during pending SSH call in %s", + __func__); + return SSH_ERROR; } rc = ssh_userauth_request_service(session); @@ -765,7 +790,8 @@ static int ssh_userauth_agent_publickey(ssh_session session, SSH_STRING_FREE(pubkey_s); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", @@ -773,17 +799,25 @@ 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", - SSH2_MSG_USERAUTH_REQUEST, - username ? username : session->opts.username, - "ssh-connection", - "publickey", - 1, /* private key */ - sig_type_c, /* algo */ - pubkey_s /* public key */ - ); + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "publickey", + 1, /* private key */ + sig_type_c, /* algo */ + pubkey_s /* public key */ + ); SSH_STRING_FREE(pubkey_s); if (rc < 0) { goto fail; @@ -827,6 +861,7 @@ fail: enum ssh_agent_state_e { SSH_AGENT_STATE_NONE = 0, SSH_AGENT_STATE_PUBKEY, + SSH_AGENT_STATE_CERT, SSH_AGENT_STATE_AUTH }; @@ -837,7 +872,8 @@ struct ssh_agent_state_struct { }; /* Internal function */ -void ssh_agent_state_free(void *data) { +void ssh_agent_state_free(void *data) +{ struct ssh_agent_state_struct *state = data; if (state) { @@ -870,9 +906,15 @@ void ssh_agent_state_free(void *data) { * before you connect to the server. */ int ssh_userauth_agent(ssh_session session, - const char *username) { + const char *username) +{ int rc = SSH_AUTH_ERROR; - struct ssh_agent_state_struct *state; + struct ssh_agent_state_struct *state = NULL; + ssh_key *configKeys = NULL; + ssh_key *configCerts = NULL; + size_t configKeysCount = 0; + size_t configCertsCount = 0; + size_t i; if (session == NULL) { return SSH_AUTH_ERROR; @@ -889,7 +931,7 @@ int ssh_userauth_agent(ssh_session session, return SSH_AUTH_ERROR; } ZERO_STRUCTP(session->agent_state); - session->agent_state->state=SSH_AGENT_STATE_NONE; + session->agent_state->state = SSH_AGENT_STATE_NONE; } state = session->agent_state; @@ -901,70 +943,239 @@ 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); + size_t certsLen = ssh_list_count(session->opts.certificate); + struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); + + configKeys = malloc(identityLen * sizeof(ssh_key)); + configCerts = malloc((certsLen + identityLen) * sizeof(ssh_key)); + if (configKeys == NULL || configCerts == NULL) { + free(configKeys); + free(configCerts); + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + while (it != NULL && configKeysCount < identityLen) { + const char *privkeyFile = it->data; + size_t certPathLen; + char *certFile = NULL; + ssh_key pubkey = NULL; + ssh_key cert = NULL; + + /* + * 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. + */ + 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"); + + 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); + free(pubkeyFile); + if (rc == SSH_OK) { + configKeys[configKeysCount++] = pubkey; + } else if (pubkey) { + SSH_KEY_FREE(pubkey); + } + } + /* Now try to see if there is a certificate with default name + * do not merge it yet with the key as we need to try first the + * non-certified key */ + certPathLen = strlen(privkeyFile) + sizeof("-cert.pub"); + certFile = malloc(certPathLen); + if (!certFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(certFile, certPathLen, "%s-cert.pub", privkeyFile); + rc = ssh_pki_import_cert_file(certFile, &cert); + free(certFile); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + /* And now load separately-listed certificates. */ + it = ssh_list_get_iterator(session->opts.certificate); + while (it != NULL && configCertsCount < certsLen + identityLen) { + const char *certFile = it->data; + ssh_key cert = NULL; + + rc = ssh_pki_import_cert_file(certFile, &cert); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + } + while (state->pubkey != NULL) { if (state->state == SSH_AGENT_STATE_NONE) { SSH_LOG(SSH_LOG_DEBUG, - "Trying identity %s", state->comment); + "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++) { + int cmp = ssh_key_cmp(state->pubkey, + configKeys[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } + /* or in separate certificates */ + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 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; + SSH_KEY_FREE(state->pubkey); + 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) { + state->state == SSH_AGENT_STATE_PUBKEY || + state->state == SSH_AGENT_STATE_CERT) { rc = ssh_userauth_try_publickey(session, username, state->pubkey); if (rc == SSH_AUTH_ERROR) { - ssh_agent_state_free (state); + 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; + state->state = (state->state == SSH_AGENT_STATE_NONE ? + SSH_AGENT_STATE_PUBKEY : state->state); + goto done; } else if (rc != SSH_AUTH_SUCCESS) { SSH_LOG(SSH_LOG_DEBUG, - "Public key of %s refused by server", state->comment); + "Public key of %s refused by server", + state->comment); + if (state->state == SSH_AGENT_STATE_PUBKEY) { + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Retry with matching certificate"); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_key_dup(configCerts[i]); + state->state = SSH_AGENT_STATE_CERT; + continue; + } + } + } SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; - ssh_key_free(state->pubkey); - state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); state->state = SSH_AGENT_STATE_NONE; continue; } SSH_LOG(SSH_LOG_DEBUG, - "Public key of %s accepted by server", state->comment); + "Public key of %s accepted by server", + state->comment); state->state = SSH_AGENT_STATE_AUTH; } 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); + 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, + SSH_LOG(SSH_LOG_DEBUG, "Server accepted public key but refused the signature"); - ssh_key_free(state->pubkey); - state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); state->state = SSH_AGENT_STATE_NONE; continue; } 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); + for (i = 0; i < configCertsCount; i++) { + ssh_key_free(configCerts[i]); + } + free(configCerts); return rc; } -#endif enum ssh_auth_auto_state_e { SSH_AUTH_AUTO_STATE_NONE = 0, SSH_AUTH_AUTO_STATE_PUBKEY, SSH_AUTH_AUTO_STATE_KEY_IMPORTED, + SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE, + SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION, SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED }; @@ -973,9 +1184,61 @@ struct ssh_auth_auto_state_struct { struct ssh_iterator *it; ssh_key privkey; ssh_key pubkey; + ssh_key cert; + struct ssh_iterator *cert_it; }; /** + * @brief Get the identity that is currently being processed by + * ssh_userauth_publickey_auto() + * + * This is meant to be used by a callback that happens as part of the + * execution of ssh_userauth_publickey_auto(). The auth_function + * callback might want to know which key a passphrase is needed for, + * for example. + * + * @param[in] session The SSH session. + * + * @param[out] value The value to get into. As a char**, space will be + * allocated by the function for the value, it is + * your responsibility to free the memory using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, + char** value) +{ + const char *id = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (value == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + if (session->auth.auto_state != NULL && + session->auth.auto_state->it != NULL) { + id = session->auth.auto_state->it->data; + } + + if (id == NULL) { + return SSH_ERROR; + } + + *value = strdup(id); + if (*value == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** * @brief Tries to automatically authenticate with public key and "none" * * It may fail, for instance it doesn't ask for a password and uses a default @@ -995,14 +1258,16 @@ struct ssh_auth_auto_state_struct { * method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n - * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use - * ssh_userauth_publickey().\n + * SSH_AUTH_SUCCESS: Authentication success\n * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. * * @note Most server implementations do not permit changing the username during * authentication. The username should only be set with ssh_options_set() only * before you connect to the server. + * + * The OpenSSH iterates over the identities and first try the plain public key + * and then the certificate if it is in place. */ int ssh_userauth_publickey_auto(ssh_session session, const char *username, @@ -1010,7 +1275,7 @@ int ssh_userauth_publickey_auto(ssh_session session, { ssh_auth_callback auth_fn = NULL; void *auth_data = NULL; - struct ssh_auth_auto_state_struct *state; + struct ssh_auth_auto_state_struct *state = NULL; int rc; if (session == NULL) { @@ -1037,15 +1302,13 @@ int ssh_userauth_publickey_auto(ssh_session session, } state = session->auth.auto_state; if (state->state == SSH_AUTH_AUTO_STATE_NONE) { -#ifndef _WIN32 /* Try authentication with ssh-agent first */ rc = ssh_userauth_agent(session, username); if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL || - rc == SSH_AUTH_AGAIN ) { + rc == SSH_AUTH_AGAIN) { return rc; } -#endif state->state = SSH_AUTH_AUTO_STATE_PUBKEY; } if (state->it == NULL) { @@ -1054,14 +1317,17 @@ int ssh_userauth_publickey_auto(ssh_session session, while (state->it != NULL) { const char *privkey_file = state->it->data; - char pubkey_file[1024] = {0}; + char pubkey_file[PATH_MAX] = {0}; if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY) { SSH_LOG(SSH_LOG_DEBUG, - "Trying to authenticate with %s", privkey_file); + "Trying to authenticate with %s", + privkey_file); + state->cert = NULL; state->privkey = NULL; state->pubkey = NULL; +#ifdef WITH_PKCS11_URI if (ssh_pki_is_uri(privkey_file)) { char *pub_uri_from_priv = NULL; SSH_LOG(SSH_LOG_INFO, @@ -1070,84 +1336,165 @@ int ssh_userauth_publickey_auto(ssh_session session, if (pub_uri_from_priv == NULL) { return SSH_ERROR; } else { - snprintf(pubkey_file, sizeof(pubkey_file), "%s", + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s", pub_uri_from_priv); SAFE_FREE(pub_uri_from_priv); } - } else { - snprintf(pubkey_file, sizeof(pubkey_file), "%s.pub", privkey_file); + } else +#endif /* WITH_PKCS11_URI */ + { + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s.pub", + privkey_file); } rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey); if (rc == SSH_ERROR) { ssh_set_error(session, - SSH_FATAL, - "Failed to import public key: %s", - pubkey_file); + SSH_FATAL, + "Failed to import public key: %s", + pubkey_file); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_ERROR; } else if (rc == SSH_EOF) { /* Read the private key and save the public key to file */ rc = ssh_pki_import_privkey_file(privkey_file, - passphrase, - auth_fn, - auth_data, - &state->privkey); + passphrase, + auth_fn, + auth_data, + &state->privkey); if (rc == SSH_ERROR) { ssh_set_error(session, - SSH_FATAL, - "Failed to read private key: %s", - privkey_file); - state->it=state->it->next; + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; continue; } else if (rc == SSH_EOF) { /* If the file doesn't exist, continue */ SSH_LOG(SSH_LOG_DEBUG, "Private key %s doesn't exist.", privkey_file); - state->it=state->it->next; + state->it = state->it->next; continue; } - rc = ssh_pki_export_privkey_to_pubkey(state->privkey, &state->pubkey); + rc = ssh_pki_export_privkey_to_pubkey(state->privkey, + &state->pubkey); if (rc == SSH_ERROR) { - ssh_key_free(state->privkey); + SSH_KEY_FREE(state->privkey); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_ERROR; } rc = ssh_pki_export_pubkey_file(state->pubkey, pubkey_file); if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Could not write public key to file: %s", pubkey_file); } } state->state = SSH_AUTH_AUTO_STATE_KEY_IMPORTED; } - if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { - rc = ssh_userauth_try_publickey(session, username, state->pubkey); + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + ssh_key k = state->pubkey; + if (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + k = state->cert; + } + rc = ssh_userauth_try_publickey(session, username, k); if (rc == SSH_AUTH_ERROR) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Public key authentication error for %s", privkey_file); - ssh_key_free(state->privkey); - state->privkey = NULL; - ssh_key_free(state->pubkey); - state->pubkey = NULL; + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); SAFE_FREE(session->auth.auto_state); return rc; } else if (rc == SSH_AUTH_AGAIN) { return rc; } else if (rc != SSH_AUTH_SUCCESS) { + int r; /* do not reuse `rc` as it is used to return from here */ + SSH_KEY_FREE(state->cert); SSH_LOG(SSH_LOG_DEBUG, - "Public key for %s refused by server", - privkey_file); - ssh_key_free(state->privkey); - state->privkey = NULL; - ssh_key_free(state->pubkey); - state->pubkey = NULL; - state->it=state->it->next; + "Public key for %s%s refused by server", + privkey_file, + (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED + ? " (with certificate)" : "")); + /* Try certificate file by appending -cert.pub (if present) */ + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + char cert_file[PATH_MAX] = {0}; + ssh_key cert = NULL; + + snprintf(cert_file, + sizeof(cert_file), + "%s-cert.pub", + privkey_file); + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (default path)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + /* TODO check the pubkey and certs match */ + SSH_LOG(SSH_LOG_TRACE, + "Certificate loaded %s. Retry the authentication.", + cert_file); + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE; + SSH_KEY_FREE(state->cert); + state->cert = cert; + /* try to authenticate with this certificate */ + continue; + } + /* if the file does not exists, try configuration options */ + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + } + /* Try certificate files loaded through options */ + if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + SSH_KEY_FREE(state->cert); + if (state->cert_it == NULL) { + state->cert_it = ssh_list_get_iterator(session->opts.certificate); + } + while (state->cert_it != NULL) { + const char *cert_file = state->cert_it->data; + ssh_key cert = NULL; + + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (options)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + int cmp = ssh_key_cmp(cert, + state->pubkey, + SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + state->cert_it = state->cert_it->next; + SSH_KEY_FREE(cert); + continue; /* with next cert */ + } + SSH_LOG(SSH_LOG_TRACE, + "Found matching certificate %s in options. Retry the authentication.", + cert_file); + state->cert = cert; + cert = NULL; + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + /* try to authenticate with this identity */ + break; /* try this cert */ + } + /* continue with next identity */ + } + if (state->cert != NULL) { + continue; /* retry with the certificate */ + } + } + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; continue; } @@ -1157,25 +1504,25 @@ int ssh_userauth_publickey_auto(ssh_session session, /* Public key has been accepted by the server */ if (state->privkey == NULL) { rc = ssh_pki_import_privkey_file(privkey_file, - passphrase, - auth_fn, - auth_data, - &state->privkey); + passphrase, + auth_fn, + auth_data, + &state->privkey); if (rc == SSH_ERROR) { - ssh_key_free(state->pubkey); - state->pubkey=NULL; + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); ssh_set_error(session, - SSH_FATAL, - "Failed to read private key: %s", - privkey_file); - state->it=state->it->next; + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; continue; } else if (rc == SSH_EOF) { /* If the file doesn't exist, continue */ - ssh_key_free(state->pubkey); - state->pubkey = NULL; - SSH_LOG(SSH_LOG_INFO, + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); + SSH_LOG(SSH_LOG_DEBUG, "Private key %s doesn't exist.", privkey_file); state->it = state->it->next; @@ -1183,16 +1530,33 @@ int ssh_userauth_publickey_auto(ssh_session session, continue; } } + if (state->cert != NULL && !is_cert_type(state->privkey->cert_type)) { + rc = ssh_pki_copy_cert_to_privkey(state->cert, state->privkey); + if (rc != SSH_OK) { + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + ssh_set_error(session, + SSH_FATAL, + "Failed to copy cert to private key"); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + } rc = ssh_userauth_publickey(session, username, state->privkey); if (rc != SSH_AUTH_AGAIN && rc != SSH_AUTH_DENIED) { - ssh_key_free(state->privkey); - ssh_key_free(state->pubkey); + bool cert_used = (state->cert != NULL); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); SAFE_FREE(session->auth.auto_state); if (rc == SSH_AUTH_SUCCESS) { - SSH_LOG(SSH_LOG_INFO, - "Successfully authenticated using %s", - privkey_file); + SSH_LOG(SSH_LOG_DEBUG, + "Successfully authenticated using %s%s", + privkey_file, + (cert_used ? " and certificate" : "")); } return rc; } @@ -1200,18 +1564,19 @@ int ssh_userauth_publickey_auto(ssh_session session, return rc; } - ssh_key_free(state->privkey); - ssh_key_free(state->pubkey); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_DEBUG, "The server accepted the public key but refused the signature"); state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; /* continue */ } } - SSH_LOG(SSH_LOG_INFO, - "Tried every public key, none matched"); + SSH_LOG(SSH_LOG_WARN, + "Access denied: Tried every public key, none matched"); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_DENIED; } @@ -1250,10 +1615,11 @@ int ssh_userauth_publickey_auto(ssh_session session, */ int ssh_userauth_password(ssh_session session, const char *username, - const char *password) { + const char *password) +{ int rc; - switch(session->pending_call_state) { + switch (session->pending_call_state) { case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_AUTH_PASSWORD: @@ -1311,7 +1677,6 @@ fail: return SSH_AUTH_ERROR; } -#ifndef _WIN32 /* LEGACY */ int ssh_userauth_agent_pubkey(ssh_session session, const char *username, @@ -1328,20 +1693,26 @@ int ssh_userauth_agent_pubkey(ssh_session session, key->type = publickey->type; key->type_c = ssh_key_type_to_char(key->type); key->flags = SSH_KEY_FLAG_PUBLIC; - key->dsa = publickey->dsa_pub; +#ifndef HAVE_LIBCRYPTO key->rsa = publickey->rsa_pub; +#else + key->key = publickey->key_pub; +#endif /* HAVE_LIBCRYPTO */ rc = ssh_userauth_agent_publickey(session, username, key); - key->dsa = NULL; +#ifndef HAVE_LIBCRYPTO key->rsa = NULL; +#else + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); return rc; } -#endif /* _WIN32 */ -ssh_kbdint ssh_kbdint_new(void) { +ssh_kbdint ssh_kbdint_new(void) +{ ssh_kbdint kbd; kbd = calloc(1, sizeof(struct ssh_kbdint_struct)); @@ -1353,7 +1724,8 @@ ssh_kbdint ssh_kbdint_new(void) { } -void ssh_kbdint_free(ssh_kbdint kbd) { +void ssh_kbdint_free(ssh_kbdint kbd) +{ size_t i, n; if (kbd == NULL) { @@ -1389,7 +1761,8 @@ void ssh_kbdint_free(ssh_kbdint kbd) { SAFE_FREE(kbd); } -void ssh_kbdint_clean(ssh_kbdint kbd) { +void ssh_kbdint_clean(ssh_kbdint kbd) +{ size_t i, n; if (kbd == NULL) { @@ -1589,10 +1962,10 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { } SSH_LOG(SSH_LOG_DEBUG, - "%d keyboard-interactive prompts", nprompts); + "%" PRIu32 " keyboard-interactive prompts", nprompts); if (nprompts > KBDINT_MAX_PROMPT) { ssh_set_error(session, SSH_FATAL, - "Too much prompts requested by the server: %u (0x%.4x)", + "Too much prompts requested by the server: %" PRIu32 " (0x%.4" PRIx32 ")", nprompts, nprompts); ssh_kbdint_free(session->kbdint); session->kbdint = NULL; @@ -1667,7 +2040,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { * @see ssh_userauth_kbdint_setanswer() */ int ssh_userauth_kbdint(ssh_session session, const char *user, - const char *submethods) { + const char *submethods) +{ int rc = SSH_AUTH_ERROR; if (session == NULL) { @@ -1709,7 +2083,8 @@ int ssh_userauth_kbdint(ssh_session session, const char *user, * * @returns The number of prompts. */ -int ssh_userauth_kbdint_getnprompts(ssh_session session) { +int ssh_userauth_kbdint_getnprompts(ssh_session session) +{ if (session == NULL) { return SSH_ERROR; } @@ -1731,7 +2106,8 @@ int ssh_userauth_kbdint_getnprompts(ssh_session session) { * * @returns The name of the message block. Do not free it. */ -const char *ssh_userauth_kbdint_getname(ssh_session session) { +const char *ssh_userauth_kbdint_getname(ssh_session session) +{ if (session == NULL) { return NULL; } @@ -1754,7 +2130,8 @@ const char *ssh_userauth_kbdint_getname(ssh_session session) { * @returns The instruction of the message block. */ -const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { +const char *ssh_userauth_kbdint_getinstruction(ssh_session session) +{ if (session == NULL) return NULL; if (session->kbdint == NULL) { @@ -1789,8 +2166,9 @@ const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { * if (echo) ... * @endcode */ -const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, - char *echo) { +const char * +ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo) +{ if (session == NULL) return NULL; if (session->kbdint == NULL) { @@ -1817,7 +2195,8 @@ const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, * * @returns The number of answers. */ -int ssh_userauth_kbdint_getnanswers(ssh_session session) { +int ssh_userauth_kbdint_getnanswers(ssh_session session) +{ if (session == NULL || session->kbdint == NULL) { return SSH_ERROR; } @@ -1825,15 +2204,17 @@ int ssh_userauth_kbdint_getnanswers(ssh_session session) { } /** - * @brief Get the answer for a question from a message block. + * @brief Get the answer to a question from a message block. * * @param[in] session The ssh session to use. * * @param[in] i index The number of the ith answer. * - * @return 0 on success, < 0 on error. + * @return The answer string, or NULL if the answer is not + * available. Do not free the string. */ -const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { +const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) +{ if (session == NULL || session->kbdint == NULL || session->kbdint->answers == NULL) { return NULL; @@ -1864,8 +2245,10 @@ const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { * * @return 0 on success, < 0 on error. */ -int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, - const char *answer) { +int +ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer) +{ if (session == NULL) { return -1; } @@ -1911,7 +2294,8 @@ int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. */ -int ssh_userauth_gssapi(ssh_session session) { +int ssh_userauth_gssapi(ssh_session session) +{ int rc = SSH_AUTH_DENIED; #ifdef WITH_GSSAPI switch(session->pending_call_state) { @@ -1933,7 +2317,7 @@ int ssh_userauth_gssapi(ssh_session session) { } else if (rc == SSH_ERROR) { return SSH_AUTH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "Authenticating with gssapi-with-mic"); + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-with-mic"); session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_MIC; session->auth.state = SSH_AUTH_STATE_NONE; |