diff options
-rw-r--r-- | docs/TODO | 10 | ||||
-rw-r--r-- | docs/cmdline-opts/Makefile.inc | 1 | ||||
-rw-r--r-- | docs/cmdline-opts/hostpubsha256.d | 11 | ||||
-rw-r--r-- | docs/libcurl/curl_easy_setopt.3 | 2 | ||||
-rw-r--r-- | docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.3 | 60 | ||||
-rw-r--r-- | docs/libcurl/opts/Makefile.inc | 1 | ||||
-rw-r--r-- | docs/libcurl/symbols-in-versions | 1 | ||||
-rw-r--r-- | docs/options-in-versions | 1 | ||||
-rw-r--r-- | include/curl/curl.h | 3 | ||||
-rw-r--r-- | include/curl/typecheck-gcc.h | 1 | ||||
-rw-r--r-- | lib/easyoptions.c | 4 | ||||
-rw-r--r-- | lib/setopt.c | 9 | ||||
-rw-r--r-- | lib/urldata.h | 1 | ||||
-rw-r--r-- | lib/vssh/libssh2.c | 153 | ||||
-rw-r--r-- | src/tool_cfgable.c | 1 | ||||
-rw-r--r-- | src/tool_cfgable.h | 1 | ||||
-rw-r--r-- | src/tool_getparam.c | 4 | ||||
-rw-r--r-- | src/tool_help.c | 3 | ||||
-rw-r--r-- | src/tool_operate.c | 5 | ||||
-rw-r--r-- | tests/.gitignore | 1 | ||||
-rw-r--r-- | tests/FILEFORMAT.md | 1 | ||||
-rw-r--r-- | tests/data/Makefile.inc | 2 | ||||
-rw-r--r-- | tests/data/test3021 | 44 | ||||
-rw-r--r-- | tests/data/test3022 | 44 | ||||
-rwxr-xr-x | tests/runtests.pl | 13 | ||||
-rw-r--r-- | tests/sshhelp.pm | 3 | ||||
-rw-r--r-- | tests/sshserver.pl | 18 |
27 files changed, 360 insertions, 38 deletions
@@ -138,7 +138,6 @@ 17. SSH protocols 17.1 Multiplexing 17.2 Handle growing SFTP files - 17.3 Support better than MD5 hostkey hash 17.4 Support CURLOPT_PREQUOTE 17.5 SSH over HTTPS proxy with more backends @@ -930,15 +929,6 @@ https://github.com/curl/curl/issues/4344 -17.3 Support better than MD5 hostkey hash - - libcurl offers the CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 option for verifying the - server's key. MD5 is generally being deprecated so we should implement - support for stronger hashing algorithms. libssh2 itself is what provides this - underlying functionality and it supports at least SHA-1 as an alternative. - SHA-1 is also being deprecated these days so we should consider working with - libssh2 to instead offer support for SHA-256 or similar. - 17.4 Support CURLOPT_PREQUOTE The two other QUOTE options are supported for SFTP, but this was left out for diff --git a/docs/cmdline-opts/Makefile.inc b/docs/cmdline-opts/Makefile.inc index 6e04552e9..506025a75 100644 --- a/docs/cmdline-opts/Makefile.inc +++ b/docs/cmdline-opts/Makefile.inc @@ -96,6 +96,7 @@ DPAGES = \ header.d \ help.d \ hostpubmd5.d \ + hostpubsha256.d \ hsts.d \ http0.9.d \ http1.0.d \ diff --git a/docs/cmdline-opts/hostpubsha256.d b/docs/cmdline-opts/hostpubsha256.d new file mode 100644 index 000000000..81e6f9851 --- /dev/null +++ b/docs/cmdline-opts/hostpubsha256.d @@ -0,0 +1,11 @@ +Long: hostpubsha256 +Arg: <sha256> +Help: Acceptable SHA256 hash of the host public key +Protocols: SFTP SCP +Added: 7.80.0 +Category: sftp scp +Example: --hostpubsha256 NDVkMTQxMGQ1ODdmMjQ3MjczYjAyOTY5MmRkMjVmNDQ= sftp://example.com/ +--- +Pass a string containing a Base64-encoded SHA256 hash of the remote +host's public key. Curl will refuse the connection with the host +unless the hashes match. diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index b83f5b635..592692b94 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -642,6 +642,8 @@ SSH authentication types. See \fICURLOPT_SSH_AUTH_TYPES(3)\fP Enable SSH compression. See \fICURLOPT_SSH_COMPRESSION(3)\fP .IP CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 MD5 of host's public key. See \fICURLOPT_SSH_HOST_PUBLIC_KEY_MD5(3)\fP +.IP CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 +SHA256 of host's public key. See \fICURLOPT_SSH_HOST_PUBLIC_KEY_SHA256(3)\fP .IP CURLOPT_SSH_PUBLIC_KEYFILE File name of public key. See \fICURLOPT_SSH_PUBLIC_KEYFILE(3)\fP .IP CURLOPT_SSH_PRIVATE_KEYFILE diff --git a/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.3 b/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.3 new file mode 100644 index 000000000..30be32ef2 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.3 @@ -0,0 +1,60 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at https://curl.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 3 "27 Aug 2021" "libcurl 7.80.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 \- SHA256 hash of SSH server public key +.SH SYNOPSIS +.nf +#include <curl/curl.h> + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, + char *sha256); +.SH DESCRIPTION +Pass a char * pointing to a string containing a Base64-encoded SHA256 +hash of the remote host's public key. +The transfer will fail if the given hash doesn't match the hash the +remote host provides. + +.SH DEFAULT +NULL +.SH PROTOCOLS +SCP and SFTP +.SH EXAMPLE +.nf +CURL *curl = curl_easy_init(); +if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "sftp://example.com/file"); + curl_easy_setopt(curl, CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, + "NDVkMTQxMGQ1ODdmMjQ3MjczYjAyOTY5MmRkMjVmNDQ="); + ret = curl_easy_perform(curl); + curl_easy_cleanup(curl); +} +.fi +.SH AVAILABILITY +Added in 7.80.0 +Requires the libssh2 back-end. +.SH RETURN VALUE +Returns CURLE_OK if the option is supported, CURLE_UNKNOWN_OPTION if not, or +CURLE_OUT_OF_MEMORY if there was insufficient heap space. +.SH "SEE ALSO" +.BR CURLOPT_SSH_PUBLIC_KEYFILE "(3), " CURLOPT_SSH_AUTH_TYPES "(3), " diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index 1181331b9..4ef5ddf43 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -326,6 +326,7 @@ man_MANS = \ CURLOPT_SSH_AUTH_TYPES.3 \ CURLOPT_SSH_COMPRESSION.3 \ CURLOPT_SSH_HOST_PUBLIC_KEY_MD5.3 \ + CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.3 \ CURLOPT_SSH_KEYDATA.3 \ CURLOPT_SSH_KEYFUNCTION.3 \ CURLOPT_SSH_KNOWNHOSTS.3 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 55f25b6b0..8be22d504 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -613,6 +613,7 @@ CURLOPT_SOURCE_USERPWD 7.12.1 - 7.15.5 CURLOPT_SSH_AUTH_TYPES 7.16.1 CURLOPT_SSH_COMPRESSION 7.56.0 CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 7.17.1 +CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 7.80.0 CURLOPT_SSH_KEYDATA 7.19.6 CURLOPT_SSH_KEYFUNCTION 7.19.6 CURLOPT_SSH_KNOWNHOSTS 7.19.6 diff --git a/docs/options-in-versions b/docs/options-in-versions index 6fada9381..ac087a1ef 100644 --- a/docs/options-in-versions +++ b/docs/options-in-versions @@ -84,6 +84,7 @@ --header (-H) 5.0 --help (-h) 4.0 --hostpubmd5 7.17.1 +--hostpubsha256 7.80.0 --hsts 7.74.0 --http0.9 7.64.0 --http1.0 (-0) 7.9.1 diff --git a/include/curl/curl.h b/include/curl/curl.h index 835c3d871..6eb0fcb82 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -2102,6 +2102,9 @@ typedef enum { this option is used only if PROXY_SSL_VERIFYPEER is true */ CURLOPT(CURLOPT_PROXY_CAINFO_BLOB, CURLOPTTYPE_BLOB, 310), + /* used by scp/sftp to verify the host's public key */ + CURLOPT(CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, CURLOPTTYPE_STRINGPOINT, 311), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h index 34d0267ed..77ce461bc 100644 --- a/include/curl/typecheck-gcc.h +++ b/include/curl/typecheck-gcc.h @@ -317,6 +317,7 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t, (option) == CURLOPT_SERVICE_NAME || \ (option) == CURLOPT_SOCKS5_GSSAPI_SERVICE || \ (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 || \ + (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 || \ (option) == CURLOPT_SSH_KNOWNHOSTS || \ (option) == CURLOPT_SSH_PRIVATE_KEYFILE || \ (option) == CURLOPT_SSH_PUBLIC_KEYFILE || \ diff --git a/lib/easyoptions.c b/lib/easyoptions.c index 4e65e3525..b1c0704d5 100644 --- a/lib/easyoptions.c +++ b/lib/easyoptions.c @@ -271,6 +271,8 @@ struct curl_easyoption Curl_easyopts[] = { {"SSH_COMPRESSION", CURLOPT_SSH_COMPRESSION, CURLOT_LONG, 0}, {"SSH_HOST_PUBLIC_KEY_MD5", CURLOPT_SSH_HOST_PUBLIC_KEY_MD5, CURLOT_STRING, 0}, + {"SSH_HOST_PUBLIC_KEY_SHA256", CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, + CURLOT_STRING, 0}, {"SSH_KEYDATA", CURLOPT_SSH_KEYDATA, CURLOT_CBPTR, 0}, {"SSH_KEYFUNCTION", CURLOPT_SSH_KEYFUNCTION, CURLOT_FUNCTION, 0}, {"SSH_KNOWNHOSTS", CURLOPT_SSH_KNOWNHOSTS, CURLOT_STRING, 0}, @@ -354,6 +356,6 @@ struct curl_easyoption Curl_easyopts[] = { */ int Curl_easyopts_check(void) { - return ((CURLOPT_LASTENTRY%10000) != (310 + 1)); + return ((CURLOPT_LASTENTRY%10000) != (311 + 1)); } #endif diff --git a/lib/setopt.c b/lib/setopt.c index 08827d1ef..c62a62fb5 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -2477,6 +2477,15 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) va_arg(param, char *)); break; + case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: + /* + * Option to allow for the SHA256 of the host public key to be checked + * for validation purposes. + */ + result = Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], + va_arg(param, char *)); + break; + case CURLOPT_SSH_KNOWNHOSTS: /* * Store the file name to read known hosts from. diff --git a/lib/urldata.h b/lib/urldata.h index 6ffd97621..2d1e873a5 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1554,6 +1554,7 @@ enum dupstring { 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_HOST_PUBLIC_KEY_SHA256, /* sha256 of host public key in base64 */ STRING_SSH_KNOWNHOSTS, /* file name of knownhosts file */ STRING_PROXY_SERVICE_NAME, /* Proxy service name */ STRING_SERVICE_NAME, /* Service name */ diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 8ccfe68a8..7466840ff 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -81,6 +81,11 @@ #include "select.h" #include "warnless.h" #include "curl_path.h" +#include "strcase.h" + +#include <curl_base64.h> /* for base64 encoding/decoding */ +#include <curl_sha256.h> + /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -615,40 +620,142 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) struct connectdata *conn = data->conn; struct ssh_conn *sshc = &conn->proto.sshc; const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; - char md5buffer[33]; + const char *pubkey_sha256 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]; + + infof(data, "SSH MD5 public key: %s", + pubkey_md5 != NULL ? pubkey_md5 : "NULL"); + infof(data, "SSH SHA256 public key: %s", + pubkey_sha256 != NULL ? pubkey_sha256 : "NULL"); - const char *fingerprint = libssh2_hostkey_hash(sshc->ssh_session, - LIBSSH2_HOSTKEY_HASH_MD5); + if(pubkey_sha256) { + const char *fingerprint = NULL; + char *fingerprint_b64 = NULL; + size_t fingerprint_b64_len; + size_t pub_pos = 0; + size_t b64_pos = 0; - if(fingerprint) { +#ifdef LIBSSH2_HOSTKEY_HASH_SHA256 /* The fingerprint points to static storage (!), don't free() it. */ - int i; - for(i = 0; i < 16; i++) - msnprintf(&md5buffer[i*2], 3, "%02x", (unsigned char) fingerprint[i]); - infof(data, "SSH MD5 fingerprint: %s", md5buffer); - } + fingerprint = libssh2_hostkey_hash(sshc->ssh_session, + LIBSSH2_HOSTKEY_HASH_SHA256); +#else + const char *hostkey; + size_t len = 0; + unsigned char hash[32]; + + hostkey = libssh2_session_hostkey(sshc->ssh_session, &len, NULL); + if(hostkey) { + Curl_sha256it(hash, (const unsigned char *) hostkey, len); + fingerprint = (char *) hash; + } +#endif - /* Before we authenticate we check the hostkey's MD5 fingerprint - * against a known fingerprint, if available. - */ - if(pubkey_md5 && strlen(pubkey_md5) == 32) { - if(!fingerprint || !strcasecompare(md5buffer, pubkey_md5)) { - if(fingerprint) - failf(data, - "Denied establishing ssh session: mismatch md5 fingerprint. " - "Remote %s is not equal to %s", md5buffer, pubkey_md5); - else - failf(data, - "Denied establishing ssh session: md5 fingerprint not available"); + if(!fingerprint) { + failf(data, + "Denied establishing ssh session: sha256 fingerprint " + "not available"); + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + + /* The length of fingerprint is 32 bytes for SHA256. + * See libssh2_hostkey_hash documentation. */ + if(Curl_base64_encode (data, fingerprint, 32, &fingerprint_b64, + &fingerprint_b64_len) != CURLE_OK) { + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + + if(!fingerprint_b64) { + failf(data, + "sha256 fingerprint could not be encoded"); + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + + infof(data, "SSH SHA256 fingerprint: %s", fingerprint_b64); + + /* Find the position of any = padding characters in the public key */ + while((pubkey_sha256[pub_pos] != '=') && pubkey_sha256[pub_pos]) { + pub_pos++; + } + + /* Find the position of any = padding characters in the base64 coded + * hostkey fingerprint */ + while((fingerprint_b64[b64_pos] != '=') && fingerprint_b64[b64_pos]) { + b64_pos++; + } + + /* Before we authenticate we check the hostkey's sha256 fingerprint + * against a known fingerprint, if available. + */ + if((pub_pos != b64_pos) || + Curl_strncasecompare(fingerprint_b64, pubkey_sha256, pub_pos) != 1) { + free(fingerprint_b64); + + failf(data, + "Denied establishing ssh session: mismatch sha256 fingerprint. " + "Remote %s is not equal to %s", fingerprint, pubkey_sha256); state(data, SSH_SESSION_FREE); sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; return sshc->actualcode; } - infof(data, "MD5 checksum match!"); + + free(fingerprint_b64); + + infof(data, "SHA256 checksum match!"); + } + + if(pubkey_md5) { + char md5buffer[33]; + const char *fingerprint = NULL; + + fingerprint = libssh2_hostkey_hash(sshc->ssh_session, + LIBSSH2_HOSTKEY_HASH_MD5); + + if(fingerprint) { + /* The fingerprint points to static storage (!), don't free() it. */ + int i; + for(i = 0; i < 16; i++) { + msnprintf(&md5buffer[i*2], 3, "%02x", (unsigned char) fingerprint[i]); + } + + infof(data, "SSH MD5 fingerprint: %s", md5buffer); + } + + /* Before we authenticate we check the hostkey's MD5 fingerprint + * against a known fingerprint, if available. + */ + if(pubkey_md5 && strlen(pubkey_md5) == 32) { + if(!fingerprint || !strcasecompare(md5buffer, pubkey_md5)) { + if(fingerprint) { + failf(data, + "Denied establishing ssh session: mismatch md5 fingerprint. " + "Remote %s is not equal to %s", md5buffer, pubkey_md5); + } + else { + failf(data, + "Denied establishing ssh session: md5 fingerprint " + "not available"); + } + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + infof(data, "MD5 checksum match!"); + } + } + + if(!pubkey_md5 && !pubkey_sha256) { + return ssh_knownhost(data); + } + else { /* as we already matched, we skip the check for known hosts */ return CURLE_OK; } - return ssh_knownhost(data); } /* diff --git a/src/tool_cfgable.c b/src/tool_cfgable.c index c3f7cecb1..34e17ce55 100644 --- a/src/tool_cfgable.c +++ b/src/tool_cfgable.c @@ -131,6 +131,7 @@ static void free_config_fields(struct OperationConfig *config) Curl_safefree(config->proxy_key_passwd); Curl_safefree(config->pubkey); Curl_safefree(config->hostpubmd5); + Curl_safefree(config->hostpubsha256); Curl_safefree(config->engine); Curl_safefree(config->etag_save_file); Curl_safefree(config->etag_compare_file); diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index b00aacb76..eff55f95d 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -158,6 +158,7 @@ struct OperationConfig { char *proxy_key_passwd; char *pubkey; char *hostpubmd5; + char *hostpubsha256; char *engine; char *etag_save_file; char *etag_compare_file; diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 73ba8f537..1de79634c 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -241,6 +241,7 @@ static const struct LongShort aliases[]= { {"Eg", "capath", ARG_FILENAME}, {"Eh", "pubkey", ARG_STRING}, {"Ei", "hostpubmd5", ARG_STRING}, + {"EF", "hostpubsha256", ARG_STRING}, {"Ej", "crlfile", ARG_FILENAME}, {"Ek", "tlsuser", ARG_STRING}, {"El", "tlspassword", ARG_STRING}, @@ -1602,6 +1603,9 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ if(!config->hostpubmd5 || strlen(config->hostpubmd5) != 32) return PARAM_BAD_USE; break; + case 'F': /* --hostpubsha256 sha256 of the host public key */ + GetStr(&config->hostpubsha256, nextarg); + break; case 'j': /* CRL file */ GetStr(&config->crlfile, nextarg); break; diff --git a/src/tool_help.c b/src/tool_help.c index cc23b12e2..46969e77f 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -346,6 +346,9 @@ static const struct helptxt helptext[] = { {" --hostpubmd5 <md5>", "Acceptable MD5 hash of the host public key", CURLHELP_SFTP | CURLHELP_SCP}, + {" --hostpubsha256 <sha256>", + "Acceptable SHA256 hash of the host public key", + CURLHELP_SFTP | CURLHELP_SCP}, {" --hsts <file name>", "Enable HSTS with this cache file", CURLHELP_HTTP}, diff --git a/src/tool_operate.c b/src/tool_operate.c index ca53d29f7..5d24ccb7d 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1408,6 +1408,11 @@ static CURLcode single_transfer(struct GlobalConfig *global, my_setopt_str(curl, CURLOPT_SSH_HOST_PUBLIC_KEY_MD5, config->hostpubmd5); + /* new in libcurl 7.80.0: SSH host key sha256 checking allows us + to fail if we are not talking to who we think we should */ + my_setopt_str(curl, CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, + config->hostpubsha256); + /* new in libcurl 7.56.0 */ if(config->ssh_compression) my_setopt(curl, CURLOPT_SSH_COMPRESSION, 1L); diff --git a/tests/.gitignore b/tests/.gitignore index 00f787cc8..a8882b275 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -7,6 +7,7 @@ curl_client_knownhosts curl_host_rsa_key curl_host_rsa_key.pub curl_host_rsa_key.pub_md5 +curl_host_rsa_key.pub_sha256 curl_sftp_cmds curl_sftp_config curl_ssh_config diff --git a/tests/FILEFORMAT.md b/tests/FILEFORMAT.md index 7a9c48204..df61c412f 100644 --- a/tests/FILEFORMAT.md +++ b/tests/FILEFORMAT.md @@ -148,6 +148,7 @@ Available substitute variables include: - `%SRCDIR` - Full path to the source dir - `%SSHPORT` - Port number of the SCP/SFTP server - `%SSHSRVMD5` - MD5 of SSH server's public key +- `%SSHSRVSHA256` - SHA256 of SSH server's public key - `%SSH_PWD` - Current directory friendly for the SSH server - `%TESTNUMBER` - Number of the test case - `%TFTP6PORT` - IPv6 port number of the TFTP server diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 1f774ce4e..d906ca338 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -237,4 +237,4 @@ test2200 test2201 test2202 test2203 test2204 test2205 \ \ test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \ test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \ -test3016 test3017 test3018 test3019 test3020 +test3016 test3017 test3018 test3019 test3020 test3021 test3022 diff --git a/tests/data/test3021 b/tests/data/test3021 new file mode 100644 index 000000000..0a02e1844 --- /dev/null +++ b/tests/data/test3021 @@ -0,0 +1,44 @@ +<testcase> +<info> +<keywords> +SFTP +server sha256 key check +</keywords> +</info> + +# +# Server-side +<reply> +<data> +test +</data> +</reply> + +# +# Client-side +<client> +<server> +sftp +</server> + <name> +SFTP correct sha256 host key + </name> + <command> +--hostpubsha256 %SSHSRVSHA256 --key curl_client_key --pubkey curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SSH_PWD/log/file%TESTNUMBER.txt +</command> +<file name="log/file%TESTNUMBER.txt"> +test +</file> +</client> + +# +# Verify data after the test has been "shot" +<verify> +<errorcode> +0 +</errorcode> +<valgrind> +disable +</valgrind> +</verify> +</testcase> diff --git a/tests/data/test3022 b/tests/data/test3022 new file mode 100644 index 000000000..f3477909d --- /dev/null +++ b/tests/data/test3022 @@ -0,0 +1,44 @@ +<testcase> +<info> +<keywords> +SCP +server sha256 key check +</keywords> +</info> + +# +# Server-side +<reply> +<data> +test +</data> +</reply> + +# +# Client-side +<client> +<server> +scp +</server> + <name> +SCP correct sha256 host key + </name> + <command> +--hostpubsha256 %SSHSRVSHA256 --key curl_client_key --pubkey curl_client_key.pub -u %USER: scp://%HOSTIP:%SSHPORT%SSH_PWD/log/file%TESTNUMBER.txt +</command> +<file name="log/file%TESTNUMBER.txt"> +test +</file> +</client> + +# +# Verify data after the test has been "shot" +<verify> +<errorcode> +0 +</errorcode> +<valgrind> +disable +</valgrind> +</verify> +</testcase> diff --git a/tests/runtests.pl b/tests/runtests.pl index 38b76e878..9a1c169b6 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -168,6 +168,7 @@ my $proxy_address; my %custom_skip_reasons; my $SSHSRVMD5 = "[uninitialized]"; # MD5 of ssh server public key +my $SSHSRVSHA256 = "[uninitialized]"; # SHA256 of ssh server public key my $VERSION=""; # curl's reported version number my $srcdir = $ENV{'srcdir'} || '.'; @@ -2287,6 +2288,17 @@ sub runsshserver { die $msg; } + my $hstpubsha256f = "curl_host_rsa_key.pub_sha256"; + if(!open(PUBSHA256FILE, "<", $hstpubsha256f) || + (read(PUBSHA256FILE, $SSHSRVSHA256, 48) == 0) || + !close(PUBSHA256FILE)) + { + my $msg = "Fatal: $srvrname pubkey sha256 missing : \"$hstpubsha256f\" : $!"; + logmsg "$msg\n"; + stopservers($verbose); + die $msg; + } + logmsg "RUN: $srvrname on PID $pid2 port $wport\n" if($verbose); return ($pid2, $sshpid, $wport); @@ -3374,6 +3386,7 @@ sub subVariables { $$thing =~ s/${prefix}USER/$USER/g; $$thing =~ s/${prefix}SSHSRVMD5/$SSHSRVMD5/g; + $$thing =~ s/${prefix}SSHSRVSHA256/$SSHSRVSHA256/g; # The purpose of FTPTIME2 and FTPTIME3 is to provide times that can be # used for time-out tests and that would work on most hosts as these diff --git a/tests/sshhelp.pm b/tests/sshhelp.pm index 0f71b3079..41047e9c6 100644 --- a/tests/sshhelp.pm +++ b/tests/sshhelp.pm @@ -51,6 +51,7 @@ use vars qw( $hstprvkeyf $hstpubkeyf $hstpubmd5f + $hstpubsha256f $cliprvkeyf $clipubkeyf @sftppath @@ -84,6 +85,7 @@ use vars qw( $hstprvkeyf $hstpubkeyf $hstpubmd5f + $hstpubsha256f $cliprvkeyf $clipubkeyf display_sshdconfig @@ -125,6 +127,7 @@ $knownhosts = 'curl_client_knownhosts'; # ssh knownhosts file $hstprvkeyf = 'curl_host_rsa_key'; # host private key file $hstpubkeyf = 'curl_host_rsa_key.pub'; # host public key file $hstpubmd5f = 'curl_host_rsa_key.pub_md5'; # md5 hash of host public key +$hstpubsha256f = 'curl_host_rsa_key.pub_sha256'; # sha256 hash of host public key $cliprvkeyf = 'curl_client_key'; # client private key file $clipubkeyf = 'curl_client_key.pub'; # client public key file diff --git a/tests/sshserver.pl b/tests/sshserver.pl index 412cab33e..526ed099f 100644 --- a/tests/sshserver.pl +++ b/tests/sshserver.pl @@ -30,6 +30,8 @@ use Cwd; use Cwd 'abs_path'; use Digest::MD5; use Digest::MD5 'md5_hex'; +use Digest::SHA; +use Digest::SHA 'sha256_base64'; use MIME::Base64; #*************************************************************************** @@ -52,6 +54,7 @@ use sshhelp qw( $hstprvkeyf $hstpubkeyf $hstpubmd5f + $hstpubsha256f $cliprvkeyf $clipubkeyf display_sshdconfig @@ -362,10 +365,12 @@ if((($sshid =~ /OpenSSH/) && ($sshvernum < 299)) || if((! -e $hstprvkeyf) || (! -s $hstprvkeyf) || (! -e $hstpubkeyf) || (! -s $hstpubkeyf) || (! -e $hstpubmd5f) || (! -s $hstpubmd5f) || + (! -e $hstpubsha256f) || (! -s $hstpubsha256f) || (! -e $cliprvkeyf) || (! -s $cliprvkeyf) || (! -e $clipubkeyf) || (! -s $clipubkeyf)) { # Make sure all files are gone so ssh-keygen doesn't complain - unlink($hstprvkeyf, $hstpubkeyf, $hstpubmd5f, $cliprvkeyf, $clipubkeyf); + unlink($hstprvkeyf, $hstpubkeyf, $hstpubmd5f, $hstpubsha256f, + $cliprvkeyf, $clipubkeyf); logmsg 'generating host keys...' if($verbose); if(system "\"$sshkeygen\" -q -t rsa -f $hstprvkeyf -C 'curl test server' -N ''") { logmsg 'Could not generate host key'; @@ -379,7 +384,7 @@ if((! -e $hstprvkeyf) || (! -s $hstprvkeyf) || # Make sure that permissions are restricted so openssh doesn't complain system "chmod 600 $hstprvkeyf"; system "chmod 600 $cliprvkeyf"; - # Save md5 hash of public host key + # Save md5 and sha256 hashes of public host key open(RSAKEYFILE, "<$hstpubkeyf"); my @rsahostkey = do { local $/ = ' '; <RSAKEYFILE> }; close(RSAKEYFILE); @@ -394,6 +399,13 @@ if((! -e $hstprvkeyf) || (! -s $hstprvkeyf) || logmsg 'Failed writing md5 hash of RSA host key'; exit 1; } + open(PUBSHA256FILE, ">$hstpubsha256f"); + print PUBSHA256FILE sha256_base64(decode_base64($rsahostkey[1])); + close(PUBSHA256FILE); + if((! -e $hstpubsha256f) || (! -s $hstpubsha256f)) { + logmsg 'Failed writing sha256 hash of RSA host key'; + exit 1; + } } @@ -1141,7 +1153,7 @@ elsif($verbose && ($rc >> 8)) { #*************************************************************************** # Clean up once the server has stopped # -unlink($hstprvkeyf, $hstpubkeyf, $hstpubmd5f, +unlink($hstprvkeyf, $hstpubkeyf, $hstpubmd5f, $hstpubsha256f, $cliprvkeyf, $clipubkeyf, $knownhosts, $sshdconfig, $sshconfig, $sftpconfig); |