diff --git a/CHANGES b/CHANGES index 1fddc88f0..bcfe39805 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,13 @@ Changelog +Daniel S (3 October 2007) +- Based on a patch brought by Johnny Luong, libcurl now offers + CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 and the curl tool --hostpubmd5. They both + make the SCP or SFTP connection verify the remote host's md5 checksum of the + public key before doing a connect, to reduce the risk of a man-in-the-middle + attack. + Daniel S (2 October 2007) - libcurl now handles chunked-encoded CONNECT responses diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 5d792cca1..9c4e06381 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -2,8 +2,8 @@ Curl and libcurl 7.17.1 Public curl release number: 102 Releases counted from the very beginning: 128 - Available command line options: 120 - Available curl_easy_setopt() options: 144 + Available command line options: 121 + Available curl_easy_setopt() options: 145 Number of public functions in libcurl: 55 Amount of public web site mirrors: 42 Number of known libcurl bindings: 36 @@ -16,6 +16,7 @@ This release includes the following changes: o added --proxy-negotiate o added --post301 and CURLOPT_POST301 o builds with c-ares 1.5.0 + o added CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 and --hostpubmd5 This release includes the following bugfixes: @@ -47,6 +48,6 @@ This release would not have looked like this without help, code, reports and advice from friends like these: Dan Fandrich, Michal Marek, Günter Knauf, Rob Crittenden, Immanuel Gregoire, - Mark Davies, Max Katsev, Philip Langdale, Alex Fishman + Mark Davies, Max Katsev, Philip Langdale, Alex Fishman, Johnny Luong Thanks! (and sorry if I forgot to mention someone) diff --git a/docs/curl.1 b/docs/curl.1 index f415d6f30..26d70b8d2 100644 --- a/docs/curl.1 +++ b/docs/curl.1 @@ -544,6 +544,11 @@ for you. See also the \fI-A/--user-agent\fP and \fI-e/--referer\fP options. This option can be used multiple times to add/replace/remove multiple headers. +.IP "--hostpubmd5" +Pass a string containing 32 hexadecimal digits. The string should be the 128 +bit MD5 cheksum of the remote host's public key, curl will refuse the +connection with the host unless the md5sums match. This option is only for SCP +and SFTP transfers. (Added in 7.17.1) .IP "--ignore-content-length" (HTTP) Ignore the Content-Length header. This is particularly useful for servers diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index edf7473ff..ac460ae09 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -1411,6 +1411,11 @@ Pass a long set to a bitmask consisting of one or more of CURLSSH_AUTH_PUBLICKEY, CURLSSH_AUTH_PASSWORD, CURLSSH_AUTH_HOST, CURLSSH_AUTH_KEYBOARD. Set CURLSSH_AUTH_ANY to let libcurl pick one. (Added in 7.16.1) +.IP CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 +Pass a char * pointing to a string containing 32 hexadecimal digits. The +string should be the 128 bit MD5 cheksum of the remote host's public key, and +libcurl will reject the connection to the host unless the md5sums match. This +option is only for SCP and SFTP transfers. (Added in 7.17.1) .IP CURLOPT_SSH_PUBLIC_KEYFILE Pass a char * pointing to a file name for your public key. If not used, libcurl defaults to using \fB~/.ssh/id_dsa.pub\fP. diff --git a/include/curl/curl.h b/include/curl/curl.h index 0df943344..88d495fc9 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -419,7 +419,7 @@ typedef enum { /* These are scheduled to disappear by 2009 */ /* The following were added in 7.17.0 */ -#define CURLE_OBSOLETE CURLE_OBSOLETE50 /* noone should be using this! */ +#define CURLE_OBSOLETE CURLE_OBSOLETE50 /* noone should be using this! */ #define CURLE_BAD_PASSWORD_ENTERED CURLE_OBSOLETE46 #define CURLE_BAD_CALLING_ORDER CURLE_OBSOLETE44 #define CURLE_FTP_USER_PASSWORD_INCORRECT CURLE_OBSOLETE10 @@ -438,7 +438,7 @@ typedef enum { #define CURLE_FTP_QUOTE_ERROR CURLE_QUOTE_ERROR #define CURLE_TFTP_DISKFULL CURLE_REMOTE_DISK_FULL #define CURLE_TFTP_EXISTS CURLE_REMOTE_FILE_EXISTS -#define CURLE_HTTP_RANGE_ERROR CURLE_RANGE_ERROR +#define CURLE_HTTP_RANGE_ERROR CURLE_RANGE_ERROR #define CURLE_FTP_SSL_FAILED CURLE_USE_SSL_FAILED /* The following were added earlier */ @@ -1127,6 +1127,9 @@ typedef enum { /* Obey RFC 2616/10.3.2 and keep POSTs as POSTs after a 301 */ CINIT(POST301, LONG, 161), + /* used by scp/sftp to verify the host's public key */ + CINIT(SSH_HOST_PUBLIC_KEY_MD5, OBJECTPOINT, 162), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; @@ -1137,7 +1140,7 @@ typedef enum { /* These are scheduled to disappear by 2009 */ /* The following were added in 7.17.0 */ -#define CURLOPT_SSLKEYPASSWD CURLOPT_KEYPASSWD +#define CURLOPT_SSLKEYPASSWD CURLOPT_KEYPASSWD #define CURLOPT_FTPAPPEND CURLOPT_APPEND #define CURLOPT_FTPLISTONLY CURLOPT_DIRLISTONLY #define CURLOPT_FTP_SSL CURLOPT_USE_SSL diff --git a/lib/ssh.c b/lib/ssh.c index d56eb855c..4a9d03732 100644 --- a/lib/ssh.c +++ b/lib/ssh.c @@ -310,7 +310,8 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) #ifdef CURL_LIBSSH2_DEBUG const char *fingerprint; #endif /* CURL_LIBSSH2_DEBUG */ - int rc; + const char *host_public_key_md5; + int rc,i; long err; switch(sshc->state) { @@ -351,6 +352,30 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) infof(data, "\n"); #endif /* CURL_LIBSSH2_DEBUG */ + /* Before we authenticate we check the hostkey's MD5 fingerprint + * against a known fingerprint, if available. This implementation pulls + * it from the curl option. + */ + if (data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] && + strlen(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) == 32) + { + char buf[33]; + host_public_key_md5 = libssh2_hostkey_hash(sftp_scp->ssh_session, + LIBSSH2_HOSTKEY_HASH_MD5); + for (i = 0; i < 16; i++) + snprintf(&buf[i*2], 3, "%02x", + (unsigned char) host_public_key_md5[i]); + if(!strequal(buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5])) { + failf(data, + "Denied establishing ssh session: mismatch md5 fingerprint. " + "Remote %s is not equal to %s", + buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]); + state(conn, SSH_SESSION_FREE); + sshc->actualCode = CURLE_FAILED_INIT; + break; + } + } + state(conn, SSH_AUTHLIST); break; diff --git a/lib/url.c b/lib/url.c index afb0b3837..c91d062fe 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1836,7 +1836,14 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, result = Curl_setstropt(&data->set.str[STRING_SSH_PRIVATE_KEY], va_arg(param, char *)); break; - + case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: + /* + * Option to allow for the MD5 of the host public key to be checked + * for validation purposes. + */ + result = Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], + va_arg(param, char *)); + break; case CURLOPT_HTTP_TRANSFER_DECODING: /* * disable libcurl transfer encoding is used diff --git a/lib/urldata.h b/lib/urldata.h index 4ed161a5f..72bd0eb83 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1289,6 +1289,7 @@ enum dupstring { STRING_SSL_RANDOM_FILE, /* path to file containing "random" data */ STRING_USERAGENT, /* User-Agent string */ STRING_USERPWD, /* , if used */ + STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ /* -- end of strings -- */ STRING_LAST /* not used, just an end-of-list marker */ diff --git a/src/main.c b/src/main.c index cf68e845b..4f9b3e4ec 100644 --- a/src/main.c +++ b/src/main.c @@ -407,6 +407,7 @@ struct Configurable { char *key_type; char *key_passwd; char *pubkey; + char *hostpubmd5; char *engine; bool list_engines; bool crlf; @@ -639,6 +640,7 @@ static void help(void) " --cacert CA certificate to verify peer against (SSL)", " --capath CA directory (made using c_rehash) to verify", " peer against (SSL)", + " --hostpubmd5 Hex encoded MD5 string of the host public key. (SSH)", " --ciphers SSL ciphers to use (SSL)", " --compressed Request compressed response (using deflate or gzip)", " --connect-timeout Maximum time allowed for connection", @@ -1541,6 +1543,7 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */ {"Ef","engine", TRUE}, {"Eg","capath ", TRUE}, {"Eh","pubkey", TRUE}, + {"Ei", "hostpubmd5", TRUE}, {"f", "fail", FALSE}, {"F", "form", TRUE}, {"Fs","form-string", TRUE}, @@ -2159,6 +2162,11 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */ case 'h': /* --pubkey public key file */ GetStr(&config->pubkey, nextarg); break; + case 'i': /* --hostpubmd5 md5 of the host public key */ + GetStr(&config->hostpubmd5, nextarg); + if (!config->hostpubmd5 || strlen(config->hostpubmd5) != 32) + return PARAM_BAD_USE; + break; default: /* certificate file */ { char *ptr = strchr(nextarg, ':'); @@ -4206,6 +4214,12 @@ operate(struct Configurable *config, int argc, argv_item_t argv[]) my_setopt(curl, CURLOPT_SSH_PRIVATE_KEYFILE, config->key); my_setopt(curl, CURLOPT_SSH_PUBLIC_KEYFILE, config->pubkey); + /* SSH host key md5 checking allows us to fail if we are + * not talking to who we think we should + */ + my_setopt(curl, CURLOPT_SSH_HOST_PUBLIC_KEY_MD5, config->hostpubmd5); + + /* default to strict verifyhost */ my_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2); if(config->cacert || config->capath) {