1
0
mirror of https://github.com/moparisthebest/curl synced 2024-08-13 17:03:50 -04:00

- Added CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA.

They introduce known_host support for SSH keys to libcurl. See docs for
  details.
This commit is contained in:
Daniel Stenberg 2009-07-22 22:49:01 +00:00
parent 9cff716925
commit 47c392e135
9 changed files with 311 additions and 14 deletions

19
CHANGES
View File

@ -6,19 +6,26 @@
Changelog
Daniel Stenberg (23 Jul 2009)
- Added CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA.
They introduce known_host support for SSH keys to libcurl. See docs for
details. Note that this feature depends on a new enough libssh2 version, to
be supported in libssh2 1.2 and later (or current git repo at this time).
Michal Marek (22 Jul 2009)
- David Binderman found a memory and fd leak in lib/gtls.c:load_file()
(https://bugzilla.novell.com/523919). When looking at the code, I found
that also the ptr pointer can leak.
(https://bugzilla.novell.com/523919). When looking at the code, I found that
also the ptr pointer can leak.
Kamil Dudka (20 Jul 2009)
- Claes Jakobsson improved the support for client certificates handling
in NSS-powered libcurl. Now the client certificates can be selected
- Claes Jakobsson improved the support for client certificates handling in
NSS-powered libcurl. Now the client certificates can be selected
automatically by a NSS built-in hook. Additionally pre-login to all PKCS11
slots is no more performed. It used to cause problems with HW tokens.
- Fixed reference counting for NSS client certificates. Now the PEM reader
module should be always properly unloaded on Curl_nss_cleanup(). If the unload
fails though, libcurl will try to reuse the already loaded instance.
module should be always properly unloaded on Curl_nss_cleanup(). If the
unload fails though, libcurl will try to reuse the already loaded instance.
Daniel Fandrich (15 Jul 2009)
- Added nonblock.c to the non-automake makefiles (note that the dependencies

View File

@ -10,6 +10,7 @@ Curl and libcurl 7.19.6
This release includes the following changes:
o CURLOPT_FTPPORT (and curl's -P/--ftpport) support port ranges
o Added CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA
This release includes the following bugfixes:
@ -32,6 +33,7 @@ This release includes the following bugfixes:
o curl -o - sends data to stdout using binary mode on windows
o fixed the separators for "array" style string that CURLINFO_CERTINFO returns
o auth problem over several hosts with re-used connection
o improved the support for client certificates in libcurl+NSS
o fix leak in gtls code
This release includes the following known bugs:

View File

@ -1743,6 +1743,44 @@ Pass a char * pointing to a file name for your private key. If not used,
libcurl defaults to using \fB~/.ssh/id_dsa\fP.
If the file is password-protected, set the password with \fICURLOPT_KEYPASSWD\fP.
(Added in 7.16.1)
.IP CURLOPT_SSH_KNOWNHOSTS
Pass a pointer to a zero terminated string holding the file name of the
known_host file to use. The known_hosts file should use the OpenSSH file
format as supported by libssh2. If this file is specified, libcurl will only
accept connections with hosts that are known and present in that file, with a
matching public key. Use \fICURLOPT_SSH_KEYFUNCTION\fP to alter the default
behavior on host and key (mis)matching. (Added in 7.19.6)
.IP CURLOPT_SSH_KEYFUNCTION
Pass a pointer to a curl_sshkeycallback function. It gets called when the
known_host matching has been done, to allow the application to act and decide
for libcurl how to proceed. It gets passed the CURL handle, the key from the
known_hosts file, the key from the remote site, info from libcurl on the
matching status and a custom pointer (set with \fICURLOPT_SSH_KEYDATA\fP). It
MUST return one of the following return codes to tell libcurl how to act:
.RS
.IP CURLKHSTAT_FINE_ADD_TO_FILE
The host+key is accepted and libcurl will append it to the known_hosts file
before continuing with the connection. This will also add the host+key combo
to the known_host pool kept in memory if it wasn't already present there. Note
that the adding of data to the file is done by completely replacing the file
with a new copy, so the permissions of the file must allow this.
.IP CURLKHSTAT_FINE
The host+key is accepted libcurl will continue with the connection. This will
also add the host+key combo to the known_host pool kept in memory if it wasn't
already present there.
.IP CURLKHSTAT_REJECT
The host+key is rejected. libcurl will deny the connection to continue and it
will be closed.
.IP CURLKHSTAT_DEFER
The host+key is rejected, but the SSH connection is asked to be kept alive.
This feature could be used when the app wants to somehow return back and act
on the host+key situation and then retry without needing the overhead of
setting it up from scratch again.
.RE
(Added in 7.19.6)
.IP CURLOPT_SSH_KEYDATA
Pass a void * as parameter. This pointer will be passed along verbatim to the
callback set with \fICURLOPT_SSH_KEYFUNCTION\fP. (Added in 7.19.6)
.SH OTHER OPTIONS
.IP CURLOPT_PRIVATE
Pass a void * as parameter, pointing to data that should be associated with

View File

@ -493,6 +493,45 @@ typedef enum {
#define CURL_ERROR_SIZE 256
struct curl_khkey {
const char *key; /* points to a zero-terminated string encoded with base64
if len is zero, otherwise to the "raw" data */
size_t len;
enum type {
CURLKHTYPE_UNKNOWN,
CURLKHTYPE_RSA1,
CURLKHTYPE_RSA,
CURLKHTYPE_DSS
} keytype;
};
/* this is the set of return values expected from the curl_sshkeycallback
callback */
enum curl_khstat {
CURLKHSTAT_FINE_ADD_TO_FILE,
CURLKHSTAT_FINE,
CURLKHSTAT_REJECT, /* reject the connection, return an error */
CURLKHSTAT_DEFER, /* do not accept it, but we can't answer right now so
this causes a CURLE_DEFER error but otherwise the
connection will be left intact etc */
CURLKHSTAT_LAST /* not for use, only a marker for last-in-list */
};
/* this is the set of status codes pass in to the callback */
enum curl_khmatch {
CURLKHMATCH_OK, /* match */
CURLKHMATCH_MISMATCH, /* host found, key mismatch! */
CURLKHMATCH_MISSING, /* no matching host/key found */
CURLKHMATCH_LAST /* not for use, only a marker for last-in-list */
};
typedef int
(*curl_sshkeycallback) (CURL *easy, /* easy handle */
const struct curl_khkey *knownkey, /* known */
const struct curl_khkey *foundkey, /* found */
enum curl_khmatch, /* libcurl's view on the keys */
void *clientp); /* custom pointer passed from app */
/* parameter for the CURLOPT_USE_SSL option */
typedef enum {
CURLUSESSL_NONE, /* do not attempt to use SSL */
@ -1214,6 +1253,16 @@ typedef enum {
to all protocols except FILE and SCP. */
CINIT(REDIR_PROTOCOLS, LONG, 182),
/* set the SSH knownhost file name to use */
CINIT(SSH_KNOWNHOSTS, OBJECTPOINT, 183),
/* set the SSH host key callback, must point to a curl_sshkeycallback
function */
CINIT(SSH_KEYFUNCTION, FUNCTIONPOINT, 184),
/* set the SSH host key callback custom pointer */
CINIT(SSH_KEYDATA, OBJECTPOINT, 185),
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;

157
lib/ssh.c
View File

@ -306,6 +306,7 @@ static void state(struct connectdata *conn, sshstate nowstate)
static const char * const names[] = {
"SSH_STOP",
"SSH_S_STARTUP",
"SSH_HOSTKEY",
"SSH_AUTHLIST",
"SSH_AUTH_PKEY_INIT",
"SSH_AUTH_PKEY",
@ -433,6 +434,21 @@ static CURLcode ssh_getworkingpath(struct connectdata *conn,
return CURLE_OK;
}
static int sshkeycallback(CURL *easy,
const struct curl_khkey *knownkey, /* known */
const struct curl_khkey *foundkey, /* found */
enum curl_khmatch match,
void *clientp)
{
(void)easy;
(void)knownkey;
(void)foundkey;
(void)clientp;
/* we only allow perfect matches, and we reject everything else */
return (match != CURLKHMATCH_OK)?CURLKHSTAT_REJECT:CURLKHSTAT_FINE;
}
/*
* Earlier libssh2 versions didn't have the ability to seek to 64bit positions
* with 32bit size_t.
@ -483,9 +499,15 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
break;
}
/* Set libssh2 to non-blocking, since cURL is all non-blocking */
/* Set libssh2 to non-blocking, since everything internally is
non-blocking */
libssh2_session_set_blocking(sshc->ssh_session, 0);
state(conn, SSH_HOSTKEY);
/* fall-through */
case SSH_HOSTKEY:
#ifdef CURL_LIBSSH2_DEBUG
/*
* Before we authenticate we should check the hostkey's fingerprint
@ -527,12 +549,121 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
}
}
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
/* we're asked to verify the host against a file */
int keytype;
size_t keylen;
const char *remotekey = libssh2_session_hostkey(sshc->ssh_session,
&keylen, &keytype);
int keycheck;
int keybit;
if(remotekey) {
/*
* A subject to figure out is what host name we need to pass in here.
* What host name does OpenSSH store in its file if an IDN name is
* used?
*/
struct libssh2_knownhost *host;
enum curl_khmatch keymatch;
curl_sshkeycallback func =
data->set.ssh_keyfunc?data->set.ssh_keyfunc:sshkeycallback;
struct curl_khkey knownkey;
struct curl_khkey *knownkeyp = NULL;
struct curl_khkey foundkey;
keybit = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
LIBSSH2_KNOWNHOST_KEY_SSHRSA:LIBSSH2_KNOWNHOST_KEY_SSHDSS;
keycheck = libssh2_knownhost_check(sshc->kh,
conn->host.name,
remotekey, keylen,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW|
keybit,
&host);
infof(data, "SSH host check: %d, key: %s\n", keycheck,
(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)?
host->key:"<none>");
/* setup 'knownkey' */
if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) {
knownkey.key = host->key;
knownkey.len = 0;
knownkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
CURLKHTYPE_RSA : CURLKHTYPE_DSS;
knownkeyp = &knownkey;
}
/* setup 'foundkey' */
foundkey.key = remotekey;
foundkey.len = keylen;
foundkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
CURLKHTYPE_RSA : CURLKHTYPE_DSS;
/*
* if any of the LIBSSH2_KNOWNHOST_CHECK_* defines and the
* curl_khmatch enum are ever modified, we need to introduce a
* translation table here!
*/
keymatch = (enum curl_khmatch)keycheck;
/* Ask the callback how to behave */
rc = func(data, knownkeyp, /* from the knownhosts file */
&foundkey, /* from the remote host */
keymatch, data->set.ssh_keyfunc_userp);
}
else
/* no remotekey means failure! */
rc = CURLKHSTAT_REJECT;
switch(rc) {
default: /* unknown return codes will equal reject */
case CURLKHSTAT_REJECT:
state(conn, SSH_SESSION_FREE);
case CURLKHSTAT_DEFER:
/* DEFER means bail out but keep the SSH_HOSTKEY state */
result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION;
break;
case CURLKHSTAT_FINE:
case CURLKHSTAT_FINE_ADD_TO_FILE:
/* proceed */
if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) {
/* the found host+key didn't match but has been told to be fine
anyway so we add it in memory */
int addrc = libssh2_knownhost_add(sshc->kh,
conn->host.name, NULL,
remotekey, keylen,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW|
keybit, NULL);
if(addrc)
infof(data, "Warning adding the known host %s failed!\n",
conn->host.name);
else if(rc == CURLKHSTAT_FINE_ADD_TO_FILE) {
/* now we write the entire in-memory list of known hosts to the
known_hosts file */
int wrc =
libssh2_knownhost_writefile(sshc->kh,
data->set.str[STRING_SSH_KNOWNHOSTS],
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
if(wrc) {
infof(data, "Warning, writing %s failed!\n",
data->set.str[STRING_SSH_KNOWNHOSTS]);
}
}
}
break;
}
}
#endif /* HAVE_LIBSSH2_KNOWNHOST_API */
state(conn, SSH_AUTHLIST);
break;
case SSH_AUTHLIST:
/* TBD - methods to check the host keys need to be done */
/*
* Figure out authentication methods
* NB: As soon as we have provided a username to an openssh server we
@ -2278,6 +2409,26 @@ static CURLcode ssh_connect(struct connectdata *conn, bool *done)
return CURLE_FAILED_INIT;
}
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
int rc;
ssh->kh = libssh2_knownhost_init(ssh->ssh_session);
if(!ssh->kh) {
/* eeek. TODO: free the ssh_session! */
return CURLE_FAILED_INIT;
}
/* read all known hosts from there */
rc = libssh2_knownhost_readfile(ssh->kh,
data->set.str[STRING_SSH_KNOWNHOSTS],
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
if(rc) {
infof(data, "Failed to read known hosts from %s\n",
data->set.str[STRING_SSH_KNOWNHOSTS]);
}
}
#endif /* HAVE_LIBSSH2_KNOWNHOST_API */
#ifdef CURL_LIBSSH2_DEBUG
libssh2_trace(ssh->ssh_session, ~0);
infof(data, "SSH socket: %d\n", sock);

View File

@ -172,7 +172,7 @@ curl_easy_strerror(CURLcode error)
return "Malformed telnet option";
case CURLE_PEER_FAILED_VERIFICATION:
return "SSL peer certificate or SSH md5 fingerprint was not OK";
return "SSL peer certificate or SSH remote key was not OK";
case CURLE_GOT_NOTHING:
return "Server returned nothing (no headers, no data)";

View File

@ -2169,6 +2169,8 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
data->set.ssl.sessionid = (bool)(0 != va_arg(param, long));
break;
#ifdef USE_LIBSSH2
/* we only include SSH options if explicitly built to support SSH */
case CURLOPT_SSH_AUTH_TYPES:
data->set.ssh_auth_types = va_arg(param, long);
break;
@ -2196,6 +2198,31 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
result = setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5],
va_arg(param, char *));
break;
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
case CURLOPT_SSH_KNOWNHOSTS:
/*
* Store the file name to read known hosts from.
*/
result = setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS],
va_arg(param, char *));
break;
case CURLOPT_SSH_KEYFUNCTION:
/* setting to NULL is fine since the ssh.c functions themselves will
then rever to use the internal default */
data->set.ssh_keyfunc = va_arg(param, curl_sshkeycallback);
break;
case CURLOPT_SSH_KEYDATA:
/*
* Custom client data to pass to the SSH keyfunc callback
*/
data->set.ssh_keyfunc_userp = va_arg(param, void *);
break;
#endif /* HAVE_LIBSSH2_KNOWNHOST_API */
#endif /* USE_LIBSSH2 */
case CURLOPT_HTTP_TRANSFER_DECODING:
/*
* disable libcurl transfer encoding is used

View File

@ -468,6 +468,7 @@ typedef enum {
SSH_STOP = 0, /* do nothing state, stops the state machine */
SSH_S_STARTUP, /* Session startup, First state in SSH-CONNECT */
SSH_HOSTKEY, /* verify hostkey */
SSH_AUTHLIST,
SSH_AUTH_PKEY_INIT,
SSH_AUTH_PKEY,
@ -525,7 +526,7 @@ typedef enum {
Everything that is strictly related to a connection is banned from this
struct. */
struct SSHPROTO {
char *path; /* the path we operate on */
char *path; /* the path we operate on */
};
/* ssh_conn is used for struct connection-oriented data in the connectdata
@ -566,6 +567,12 @@ struct ssh_conn {
LIBSSH2_SFTP_HANDLE *sftp_handle;
int waitfor; /* current READ/WRITE bits to wait for */
int orig_waitfor; /* default READ/WRITE bits wait for */
/* note that HAVE_LIBSSH2_KNOWNHOST_API is a define set in the libssh2.h
header */
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
LIBSSH2_KNOWNHOSTS *kh;
#endif
#endif /* USE_LIBSSH2 */
};
@ -1366,15 +1373,12 @@ enum dupstring {
STRING_SET_RANGE, /* range, if used */
STRING_SET_REFERER, /* custom string for the HTTP referer field */
STRING_SET_URL, /* what original URL to work on */
STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */
STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */
STRING_SSL_CAPATH, /* CA directory name (doesn't work on windows) */
STRING_SSL_CAFILE, /* certificate file to verify peer against */
STRING_SSL_CIPHER_LIST, /* list of ciphers to use */
STRING_SSL_EGDSOCKET, /* path to file containing the EGD daemon socket */
STRING_SSL_RANDOM_FILE, /* path to file containing "random" data */
STRING_USERAGENT, /* User-Agent string */
STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */
STRING_SSL_CRLFILE, /* crl file to check certificate */
STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */
STRING_USERNAME, /* <username>, if used */
@ -1383,6 +1387,12 @@ enum dupstring {
STRING_PROXYPASSWORD, /* Proxy <password>, if used */
STRING_NOPROXY, /* List of hosts which should not use the proxy, if
used */
#ifdef USE_LIBSSH2
STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */
STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */
STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */
STRING_SSH_KNOWNHOSTS, /* file name of knownhosts file */
#endif
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
STRING_SOCKS5_GSSAPI_SERVICE, /* GSSAPI service name */
#endif
@ -1496,6 +1506,9 @@ struct UserDefined {
2 - the same but also allow MKD to fail once
*/
curl_sshkeycallback ssh_keyfunc; /* key matching callback */
void *ssh_keyfunc_userp; /* custom pointer to callback */
/* Here follows boolean settings that define how to behave during
this session. They are STATIC, set by libcurl users or at least initially
and they don't change during operations. */

View File

@ -4694,6 +4694,16 @@ operate(struct Configurable *config, int argc, argv_item_t argv[])
my_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
my_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1);
}
else {
char *home = homedir();
char *file = aprintf("%s/%sssh/known_hosts", home, DOT_CHAR);
if(home && file) {
free(home);
my_setopt_str(curl, CURLOPT_SSH_KNOWNHOSTS, file);
}
else
return CURLE_OUT_OF_MEMORY;
}
if(config->no_body || config->remote_time) {
/* no body or use remote time */