libssh2: add support for forcing a hostkey type

- Allow forcing the host's key type found in the known_hosts file.

Currently, curl (with libssh2) does not take keys from your known_hosts
file into account when talking to a server. With this patch the
known_hosts file will be searched for an entry matching the hostname
and, if found, libssh2 will be told to claim this key type from the
server.

Closes https://github.com/curl/curl/pull/4747
This commit is contained in:
Santino Keupp 2019-12-20 13:37:20 +01:00 committed by Jay Satiro
parent 8792a59223
commit 272282a054

View File

@ -106,6 +106,7 @@ static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc);
static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc);
static LIBSSH2_FREE_FUNC(my_libssh2_free);
static CURLcode ssh_force_knownhost_key_type(struct connectdata *conn);
static CURLcode ssh_connect(struct connectdata *conn, bool *done);
static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done);
static CURLcode ssh_do(struct connectdata *conn, bool *done);
@ -648,6 +649,129 @@ static CURLcode ssh_check_fingerprint(struct connectdata *conn)
return ssh_knownhost(conn);
}
/*
* ssh_force_knownhost_key_type() will check the known hosts file and try to
* force a specific public key type from the server if an entry is found.
*/
static CURLcode ssh_force_knownhost_key_type(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
static const char * const hostkey_method_ssh_ed25519
= "ssh-ed25519";
#endif
#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_521
static const char * const hostkey_method_ssh_ecdsa_521
= "ecdsa-sha2-nistp521";
#endif
#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_384
static const char * const hostkey_method_ssh_ecdsa_384
= "ecdsa-sha2-nistp384";
#endif
#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
static const char * const hostkey_method_ssh_ecdsa_256
= "ecdsa-sha2-nistp256";
#endif
static const char * const hostkey_method_ssh_rsa
= "ssh-rsa";
static const char * const hostkey_method_ssh_dss
= "ssh-dss";
const char *hostkey_method = NULL;
struct ssh_conn *sshc = &conn->proto.sshc;
struct Curl_easy *data = conn->data;
struct libssh2_knownhost* store = NULL;
const char *kh_name_end = NULL;
long unsigned int kh_name_size = 0;
int port = 0;
bool found = false;
if(sshc->kh && !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) {
/* lets try to find our host in the known hosts file */
while(!libssh2_knownhost_get(sshc->kh, &store, store)) {
/* For non-standard ports, the name will be enclosed in */
/* square brackets, followed by a colon and the port */
if(store->name[0] == '[') {
kh_name_end = strstr(store->name, "]:");
if(!kh_name_end) {
infof(data, "Invalid host pattern %s in %s\n",
store->name, data->set.str[STRING_SSH_KNOWNHOSTS]);
continue;
}
port = atoi(kh_name_end + 2);
if(kh_name_end && (port == conn->remote_port)) {
kh_name_size = strlen(store->name) - 1 - strlen(kh_name_end);
if(strncmp(store->name + 1, conn->host.name, kh_name_size) == 0) {
found = true;
break;
}
}
}
else if(strcmp(store->name, conn->host.name) == 0) {
found = true;
break;
}
}
if(found) {
infof(data, "Found host %s in %s\n",
store->name, data->set.str[STRING_SSH_KNOWNHOSTS]);
switch(store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) {
#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
case LIBSSH2_KNOWNHOST_KEY_ED25519:
hostkey_method = hostkey_method_ssh_ed25519;
break;
#endif
#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_521
case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
hostkey_method = hostkey_method_ssh_ecdsa_521;
break;
#endif
#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_384
case LIBSSH2_KNOWNHOST_KEY_ECDSA_384:
hostkey_method = hostkey_method_ssh_ecdsa_384;
break;
#endif
#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
case LIBSSH2_KNOWNHOST_KEY_ECDSA_256:
hostkey_method = hostkey_method_ssh_ecdsa_256;
break;
#endif
case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
hostkey_method = hostkey_method_ssh_rsa;
break;
case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
hostkey_method = hostkey_method_ssh_dss;
break;
case LIBSSH2_KNOWNHOST_KEY_RSA1:
failf(data, "Found host key type RSA1 which is not supported\n");
return CURLE_SSH;
default:
failf(data, "Unknown host key type: %i\n",
(store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK));
return CURLE_SSH;
}
infof(data, "Set \"%s\" as SSH hostkey type\n", hostkey_method);
result = libssh2_session_error_to_CURLE(
libssh2_session_method_pref(
sshc->ssh_session, LIBSSH2_METHOD_HOSTKEY, hostkey_method));
}
else {
infof(data, "Did not find host %s in %s\n",
conn->host.name, data->set.str[STRING_SSH_KNOWNHOSTS]);
}
}
#endif /* HAVE_LIBSSH2_KNOWNHOST_API */
return result;
}
/*
* ssh_statemach_act() runs the SSH state machine as far as it can without
* blocking and without reaching the end. The data the pointer 'block' points
@ -680,6 +804,12 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
non-blocking */
libssh2_session_set_blocking(sshc->ssh_session, 0);
result = ssh_force_knownhost_key_type(conn);
if(result) {
state(conn, SSH_SESSION_FREE);
break;
}
state(conn, SSH_S_STARTUP);
/* FALLTHROUGH */