vssh: keyfile use cleanups

- make the libssh backend do the same fallback for private and public key
  filename as libssh2 already did and is documented behavior. This now uses a
  common function.

- set the file names already in setup_connection if public key auth is
  requested, so that the connection reuse functions can use the
  information when checking for existing connections to reuse

- rename the oddly named struct fields 'rsa' to 'priv_key' and 'rsa_pub'
  to 'pub_key' to better reflect their purposes

Fixes #22243
Closes #22244
This commit is contained in:
Daniel Stenberg 2026-07-02 09:07:05 +02:00
parent d3a7e57157
commit c7f67be010
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
6 changed files with 112 additions and 107 deletions

View file

@ -681,8 +681,8 @@ static bool ssh_config_matches(struct connectdata *one,
sshc1 = Curl_conn_meta_get(one, CURL_META_SSH_CONN);
sshc2 = Curl_conn_meta_get(two, CURL_META_SSH_CONN);
return sshc1 && sshc2 && Curl_safecmp(sshc1->rsa, sshc2->rsa) &&
Curl_safecmp(sshc1->rsa_pub, sshc2->rsa_pub);
return sshc1 && sshc2 && Curl_safecmp(sshc1->priv_key, sshc2->priv_key) &&
Curl_safecmp(sshc1->pub_key, sshc2->pub_key);
}
#endif

View file

@ -832,8 +832,7 @@ static int myssh_in_AUTHLIST(struct Curl_easy *data,
/* For public key auth we need either the private key or
CURLSSH_AUTH_AGENT. */
if((sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) &&
(data->set.str[STRING_SSH_PRIVATE_KEY] ||
(data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) {
(sshc->priv_key || (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) {
myssh_to(data, sshc, SSH_AUTH_PKEY_INIT);
infof(data, "Authentication using SSH public key file");
}
@ -863,7 +862,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data,
/* Two choices, (1) private key was given on CMD,
* (2) use the "default" keys. */
if(data->set.str[STRING_SSH_PRIVATE_KEY]) {
if(sshc->priv_key) {
if(sshc->pubkey && !data->set.ssl.primary.key_passwd) {
rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, sshc->pubkey);
if(rc == SSH_AUTH_AGAIN)
@ -875,13 +874,11 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data,
}
}
rc = ssh_pki_import_privkey_file(data->
set.str[STRING_SSH_PRIVATE_KEY],
rc = ssh_pki_import_privkey_file(sshc->priv_key,
data->set.ssl.primary.key_passwd, NULL,
NULL, &sshc->privkey);
if(rc != SSH_OK) {
failf(data, "Could not load private key file %s",
data->set.str[STRING_SSH_PRIVATE_KEY]);
failf(data, "Could not load private key file %s", sshc->priv_key);
rc = myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED);
return rc;
}
@ -1916,8 +1913,8 @@ static void sshc_cleanup(struct ssh_conn *sshc)
sshc->pubkey = NULL;
}
curlx_safefree(sshc->rsa_pub);
curlx_safefree(sshc->rsa);
curlx_safefree(sshc->pub_key);
curlx_safefree(sshc->priv_key);
curlx_safefree(sshc->quote_path1);
curlx_safefree(sshc->quote_path2);
curlx_dyn_free(&sshc->readdir_buf);
@ -2547,7 +2544,7 @@ static CURLcode myssh_setup_connection(struct Curl_easy *data,
Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor))
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
return Curl_ssh_setup_pkey(data, sshc);
}
static Curl_recv scp_recv, sftp_recv;
@ -2655,9 +2652,8 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done)
sshc->privkey = NULL;
sshc->pubkey = NULL;
if(data->set.str[STRING_SSH_PUBLIC_KEY]) {
rc = ssh_pki_import_pubkey_file(data->set.str[STRING_SSH_PUBLIC_KEY],
&sshc->pubkey);
if(sshc->pub_key) {
rc = ssh_pki_import_pubkey_file(sshc->pub_key, &sshc->pubkey);
if(rc != SSH_OK) {
failf(data, "Could not load public key file");
return CURLE_FAILED_INIT;

View file

@ -1094,8 +1094,7 @@ static CURLcode sftp_upload_init(struct Curl_easy *data,
return CURLE_OK;
}
static CURLcode ssh_state_pkey_init(struct Curl_easy *data,
struct ssh_conn *sshc)
static void ssh_state_pkey_init(struct Curl_easy *data, struct ssh_conn *sshc)
{
/*
* Check the supported auth types in the order I feel is most secure
@ -1105,88 +1104,13 @@ static CURLcode ssh_state_pkey_init(struct Curl_easy *data,
if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) &&
strstr(sshc->authlist, "publickey")) {
bool out_of_memory = FALSE;
sshc->rsa_pub = sshc->rsa = NULL;
if(data->set.str[STRING_SSH_PRIVATE_KEY]) {
sshc->rsa = curlx_strdup(data->set.str[STRING_SSH_PRIVATE_KEY]);
if(!sshc->rsa)
out_of_memory = TRUE;
}
else {
/* To ponder about: should really the lib be messing about with the
HOME environment variable etc? */
char *home = curl_getenv("HOME");
curlx_struct_stat sbuf;
/* If no private key file is specified, try some common paths. */
if(home) {
/* Try ~/.ssh first. */
sshc->rsa = curl_maprintf("%s/.ssh/id_rsa", home);
if(!sshc->rsa)
out_of_memory = TRUE;
else if(curlx_stat(sshc->rsa, &sbuf)) {
curlx_free(sshc->rsa);
sshc->rsa = curl_maprintf("%s/.ssh/id_dsa", home);
if(!sshc->rsa)
out_of_memory = TRUE;
else if(curlx_stat(sshc->rsa, &sbuf)) {
curlx_safefree(sshc->rsa);
}
}
curlx_free(home);
}
if(!out_of_memory && !sshc->rsa) {
/* Nothing found; try the current dir. */
sshc->rsa = curlx_strdup("id_rsa");
if(sshc->rsa && curlx_stat(sshc->rsa, &sbuf)) {
curlx_free(sshc->rsa);
sshc->rsa = curlx_strdup("id_dsa");
if(sshc->rsa && curlx_stat(sshc->rsa, &sbuf)) {
curlx_free(sshc->rsa);
/* Out of guesses. Set to the empty string to avoid
* surprising info messages. */
sshc->rsa = curlx_strdup("");
}
}
}
}
/*
* Unless the user explicitly specifies a public key file, let
* libssh2 extract the public key from the private key file.
* This is done by passing sshc->rsa_pub = NULL.
*/
if(!out_of_memory && data->set.str[STRING_SSH_PUBLIC_KEY] &&
/* treat empty string the same way as NULL */
data->set.str[STRING_SSH_PUBLIC_KEY][0]) {
sshc->rsa_pub = curlx_strdup(data->set.str[STRING_SSH_PUBLIC_KEY]);
if(!sshc->rsa_pub)
out_of_memory = TRUE;
}
if(out_of_memory || !sshc->rsa) {
curlx_safefree(sshc->rsa);
curlx_safefree(sshc->rsa_pub);
myssh_to(data, sshc, SSH_SESSION_FREE);
return CURLE_OUT_OF_MEMORY;
}
sshc->passphrase = data->set.ssl.primary.key_passwd;
if(!sshc->passphrase)
sshc->passphrase = "";
if(sshc->rsa_pub)
infof(data, "SSH: trying public key file '%s'", sshc->rsa_pub);
infof(data, "SSH: trying private key file '%s'", sshc->rsa);
if(sshc->pub_key)
infof(data, "SSH: trying public key file '%s'", sshc->pub_key);
infof(data, "SSH: trying private key file '%s'", sshc->priv_key);
myssh_to(data, sshc, SSH_AUTH_PKEY);
}
else {
else
myssh_to(data, sshc, SSH_AUTH_PASS_INIT);
}
return CURLE_OK;
}
static CURLcode sftp_quote_stat(struct Curl_easy *data,
@ -1559,14 +1483,11 @@ static CURLcode ssh_state_auth_pkey(struct Curl_easy *data,
libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session,
user,
curlx_uztoui(strlen(user)),
sshc->rsa_pub,
sshc->rsa, sshc->passphrase);
sshc->pub_key,
sshc->priv_key, sshc->passphrase);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
curlx_safefree(sshc->rsa_pub);
curlx_safefree(sshc->rsa);
if(rc == 0) {
sshc->authed = TRUE;
infof(data, "SSH: authenticated via publickey");
@ -2626,8 +2547,8 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data,
DEBUGASSERT(!sshc->kh);
DEBUGASSERT(!sshc->ssh_agent);
curlx_safefree(sshc->rsa_pub);
curlx_safefree(sshc->rsa);
curlx_safefree(sshc->pub_key);
curlx_safefree(sshc->priv_key);
curlx_safefree(sshc->quote_path1);
curlx_safefree(sshc->quote_path2);
curlx_safefree(sshc->homedir);
@ -2968,7 +2889,7 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
break;
case SSH_AUTH_PKEY_INIT:
result = ssh_state_pkey_init(data, sshc);
ssh_state_pkey_init(data, sshc);
break;
case SSH_AUTH_PKEY:
@ -3343,7 +3264,7 @@ static CURLcode ssh_setup_connection(struct Curl_easy *data,
if(Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor))
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
return Curl_ssh_setup_pkey(data, sshc);
}
static Curl_recv scp_recv, sftp_recv;

View file

@ -144,8 +144,8 @@ struct ssh_conn {
/* common */
const char *passphrase; /* pass-phrase to use */
char *rsa_pub; /* strdup'ed public key file */
char *rsa; /* strdup'ed private key file */
char *pub_key; /* strdup'ed public key file */
char *priv_key; /* strdup'ed private key file */
sshstate state; /* always use ssh.c:state() to change state! */
sshstate nextstate; /* the state to goto after stopping */
struct curl_slist *quote_item; /* for the quote option */

View file

@ -32,6 +32,7 @@
#include "escape.h"
#include "select.h" /* for Curl_pollset_change() */
#include "url.h" /* for Curl_conn_meta_get() */
#include "curlx/fopen.h"
#ifdef CURLVERBOSE
const char *Curl_ssh_statename(sshstate state)
@ -361,4 +362,87 @@ CURLcode Curl_ssh_pollset(struct Curl_easy *data, struct easy_pollset *ps)
return CURLE_OK;
}
CURLcode Curl_ssh_setup_pkey(struct Curl_easy *data, struct ssh_conn *sshc)
{
char *home = NULL;
if(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) {
sshc->pub_key = sshc->priv_key = NULL;
if(data->set.str[STRING_SSH_PRIVATE_KEY]) {
sshc->priv_key = curlx_strdup(data->set.str[STRING_SSH_PRIVATE_KEY]);
if(!sshc->priv_key)
goto fail;
}
else {
/* To ponder about: should really the lib be messing about with the HOME
environment variable etc? */
curlx_struct_stat sbuf;
home = curl_getenv("HOME");
/* If no private key file is specified, try some common paths. */
if(home) {
/* Try ~/.ssh first. */
sshc->priv_key = curl_maprintf("%s/.ssh/id_rsa", home);
if(!sshc->priv_key)
goto fail;
else if(curlx_stat(sshc->priv_key, &sbuf)) {
curlx_free(sshc->priv_key);
sshc->priv_key = curl_maprintf("%s/.ssh/id_dsa", home);
if(!sshc->priv_key)
goto fail;
else if(curlx_stat(sshc->priv_key, &sbuf)) {
curlx_safefree(sshc->priv_key);
}
}
curlx_safefree(home);
}
if(!sshc->priv_key) {
/* Nothing found; try the current dir. */
sshc->priv_key = curlx_strdup("id_rsa");
if(sshc->priv_key && curlx_stat(sshc->priv_key, &sbuf)) {
curlx_free(sshc->priv_key);
sshc->priv_key = curlx_strdup("id_dsa");
if(sshc->priv_key && curlx_stat(sshc->priv_key, &sbuf)) {
curlx_free(sshc->priv_key);
/* Out of guesses. Set to the empty string to avoid
* surprising info messages. */
sshc->priv_key = curlx_strdup("");
}
}
}
}
/*
* Unless the user explicitly specifies a public key file, let the SSH
* library extract the public key from the private key file. This is done
* by passing sshc->pub_key = NULL.
*/
if(data->set.str[STRING_SSH_PUBLIC_KEY] &&
/* treat empty string the same way as NULL */
data->set.str[STRING_SSH_PUBLIC_KEY][0]) {
sshc->pub_key = curlx_strdup(data->set.str[STRING_SSH_PUBLIC_KEY]);
if(!sshc->pub_key)
goto fail;
}
sshc->passphrase = data->set.ssl.primary.key_passwd;
if(!sshc->passphrase)
sshc->passphrase = "";
if(sshc->pub_key)
infof(data, "SSH: public key file '%s'", sshc->pub_key);
if(sshc->priv_key)
infof(data, "SSH: private key file '%s'", sshc->priv_key);
else
infof(data, "SSH: public key auth without private key set!");
}
return CURLE_OK;
fail:
curlx_safefree(home);
curlx_safefree(sshc->priv_key);
curlx_safefree(sshc->pub_key);
return CURLE_OUT_OF_MEMORY;
}
#endif /* USE_SSH */

View file

@ -29,6 +29,8 @@
#include "urldata.h"
struct ssh_conn;
CURLcode Curl_getworkingpath(struct Curl_easy *data,
const char *homedir,
char **path);
@ -40,5 +42,7 @@ CURLcode Curl_ssh_range(struct Curl_easy *data,
curl_off_t *startp, curl_off_t *sizep);
CURLcode Curl_ssh_pollset(struct Curl_easy *data, struct easy_pollset *ps);
CURLcode Curl_ssh_setup_pkey(struct Curl_easy *data, struct ssh_conn *sshc);
#endif /* USE_SSH */
#endif /* HEADER_CURL_VSSH_VSSH_H */