diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 696fd09bab..67db3798fe 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -522,11 +522,9 @@ jobs: fi fi if [ -n "${MATRIX_OPENSSH}" ]; then # OpenSSH-Windows - TFLAGS+=' ~601 ~603 ~617 ~619 ~621 ~641 ~665 ~2004' # SCP + TFLAGS+=' ~601 ~603 ~617 ~619 ~621 ~641 ~665 ~2004 ~3022' # SCP if [[ "${MATRIX_INSTALL} " = *'libssh '* ]]; then TFLAGS+=' ~614' # 'SFTP pre-quote chmod' SFTP, pre-quote, directory - else - TFLAGS+=' ~3022' # 'SCP correct sha256 host key' SCP, server sha256 key check fi fi if [ "${MATRIX_OPENSSH}" = 'OpenSSH-Windows' ]; then diff --git a/docs/cmdline-opts/hostpubsha256.md b/docs/cmdline-opts/hostpubsha256.md index e695a10cb5..a92dbe5d7c 100644 --- a/docs/cmdline-opts/hostpubsha256.md +++ b/docs/cmdline-opts/hostpubsha256.md @@ -18,6 +18,3 @@ Example: Pass a string containing a Base64-encoded SHA256 hash of the remote host's public key. curl refuses the connection with the host unless the hashes match. - -This feature requires libcurl to be built with libssh2 and does not work with -other SSH backends. diff --git a/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.md b/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.md index 43a6d9e708..fce7e58f04 100644 --- a/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.md +++ b/docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.md @@ -73,10 +73,6 @@ int main(void) } ~~~ -# NOTES - -Requires the libssh2 backend. - # %AVAILABILITY% # RETURN VALUE diff --git a/lib/setopt.c b/lib/setopt.c index 61d87be06f..0fc5ec7e87 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -2342,6 +2342,12 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * for validation purposes. */ return Curl_setstropt(&s->str[STRING_SSH_HOST_PUBLIC_KEY_MD5], ptr); + case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: + /* + * Option to allow for the SHA256 of the host public key to be checked + * for validation purposes. + */ + return Curl_setstropt(&s->str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], ptr); case CURLOPT_SSH_KNOWNHOSTS: /* * Store the filename to read known hosts from. @@ -2349,12 +2355,6 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, return Curl_setstropt(&s->str[STRING_SSH_KNOWNHOSTS], ptr); #endif #ifdef USE_LIBSSH2 - case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: - /* - * Option to allow for the SHA256 of the host public key to be checked - * for validation purposes. - */ - return Curl_setstropt(&s->str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], ptr); case CURLOPT_SSH_HOSTKEYDATA: /* * Custom client data to pass to the SSH keyfunc callback diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 49f9d3f93d..d37dcd712f 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -57,6 +57,7 @@ #include "multiif.h" #include "select.h" #include "vssh/vssh.h" +#include "curlx/base64.h" /* for curlx_base64_encode() */ #ifdef HAVE_UNISTD_H #include @@ -109,12 +110,14 @@ static CURLcode sftp_error_to_CURLE(int err) } /* Multiple options: - * 1. data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] is set with an MD5 + * 1. data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256] is set with a SHA256 + * hash. + * 2. data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] is set with an MD5 * hash (90s style auth, not sure we should have it here) - * 2. data->set.ssh_keyfunc callback is set. Then we do trust on first + * 3. data->set.ssh_keyfunc callback is set. Then we do trust on first * use. We even save on knownhosts if CURLKHSTAT_FINE_ADD_TO_FILE * is returned by it. - * 3. none of the above. We only accept if it is present on known hosts. + * 4. none of the above. We only accept if it is present on known hosts. * * Returns SSH_OK or SSH_ERROR. */ @@ -122,8 +125,10 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) { int rc; ssh_key pubkey; - size_t hlen; - unsigned char *hash = NULL; + unsigned char *hash_sha256 = NULL; + size_t hlen_sha256; + unsigned char *hash_md5 = NULL; + size_t hlen_md5; char *found_base64 = NULL; char *known_base64 = NULL; int vstate; @@ -139,20 +144,75 @@ static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) if(rc != SSH_OK) return rc; - if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { - int i; - char md5buffer[33]; - const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; + if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]) { + const char *pubkey_sha256 = + data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]; + char *fingerprint_b64 = NULL; + size_t fingerprint_b64_len; + size_t pub_pos = 0; + size_t b64_pos = 0; - rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, &hash, &hlen); - if(rc != SSH_OK || hlen != 16) { + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256, + &hash_sha256, &hlen_sha256); + if(rc != SSH_OK || hlen_sha256 != 32) { + failf(data, "Denied establishing ssh session: " + "SHA256 fingerprint not available"); + goto cleanup; + } + + if(curlx_base64_encode((const uint8_t *)hash_sha256, 32, &fingerprint_b64, + &fingerprint_b64_len) != CURLE_OK) { + rc = SSH_ERROR; + goto cleanup; + } + + 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) || + strncmp(fingerprint_b64, pubkey_sha256, pub_pos)) { + failf(data, + "Denied establishing ssh session: mismatch SHA256 fingerprint. " + "Remote %s is not equal to %s", fingerprint_b64, pubkey_sha256); + curlx_free(fingerprint_b64); + rc = SSH_ERROR; + goto cleanup; + } + + curlx_free(fingerprint_b64); + + rc = SSH_OK; + goto cleanup; + } + + if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { + const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; + char md5buffer[33]; + int i; + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, + &hash_md5, &hlen_md5); + if(rc != SSH_OK || hlen_md5 != 16) { failf(data, "Denied establishing ssh session: MD5 fingerprint not available"); goto cleanup; } for(i = 0; i < 16; i++) - curl_msnprintf(&md5buffer[i * 2], 3, "%02x", hash[i]); + curl_msnprintf(&md5buffer[i * 2], 3, "%02x", hash_md5[i]); infof(data, "SSH MD5 fingerprint: %s", md5buffer); @@ -297,8 +357,10 @@ cleanup: /* !checksrc! disable BANNEDFUNC 1 */ free(known_base64); /* allocated by libssh, deallocate with system free */ } - if(hash) - ssh_clean_pubkey_hash(&hash); + if(hash_sha256) + ssh_clean_pubkey_hash(&hash_sha256); + if(hash_md5) + ssh_clean_pubkey_hash(&hash_md5); ssh_key_free(pubkey); if(knownhostsentry) { ssh_knownhosts_entry_free(knownhostsentry); diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 118bc594f6..0226ebfd27 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -57,7 +57,7 @@ #include "curlx/fopen.h" #include "vssh/vssh.h" #include "curlx/strparse.h" -#include "curlx/base64.h" /* for base64 encoding/decoding */ +#include "curlx/base64.h" /* for curlx_base64_encode() */ static const char *sftp_libssh2_strerror(unsigned long err) { diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 176d3ebc38..6c69acd95b 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -2768,10 +2768,7 @@ static ParameterError opt_string(struct OperationConfig *config, } break; case C_HOSTPUBSHA256: /* --hostpubsha256 */ - if(!feature_libssh2) - err = PARAM_LIBCURL_DOESNT_SUPPORT; - else - err = getstr(&config->hostpubsha256, nextarg, DENY_BLANK); + err = getstr(&config->hostpubsha256, nextarg, DENY_BLANK); break; case C_TLSUSER: /* --tlsuser */ if(!feature_tls_srp) diff --git a/src/tool_libinfo.c b/src/tool_libinfo.c index 5a5382c007..9aee234280 100644 --- a/src/tool_libinfo.c +++ b/src/tool_libinfo.c @@ -71,7 +71,6 @@ bool feature_http2 = FALSE; bool feature_http3 = FALSE; bool feature_httpsproxy = FALSE; bool feature_libz = FALSE; -bool feature_libssh2 = FALSE; bool feature_ntlm = FALSE; bool feature_ntlm_wb = FALSE; bool feature_spnego = FALSE; @@ -183,9 +182,6 @@ CURLcode get_libcurl_info(void) ++feature_count; } - feature_libssh2 = curlinfo->age >= CURLVERSION_FOURTH && - curlinfo->libssh_version && - !strncmp("libssh2", curlinfo->libssh_version, 7); return CURLE_OK; } diff --git a/src/tool_libinfo.h b/src/tool_libinfo.h index ddc41a1338..e8c3517a9b 100644 --- a/src/tool_libinfo.h +++ b/src/tool_libinfo.h @@ -54,7 +54,6 @@ extern bool feature_http2; extern bool feature_http3; extern bool feature_httpsproxy; extern bool feature_libz; -extern bool feature_libssh2; extern bool feature_ntlm; extern bool feature_ntlm_wb; extern bool feature_spnego; diff --git a/tests/data/test3021 b/tests/data/test3021 index b7fe479cb2..1aa973a688 100644 --- a/tests/data/test3021 +++ b/tests/data/test3021 @@ -17,10 +17,6 @@ test # Client-side -# so far only the libssh2 backend supports SHA256 - -libssh2 - sftp diff --git a/tests/data/test3022 b/tests/data/test3022 index 057242b0f5..6d692a817e 100644 --- a/tests/data/test3022 +++ b/tests/data/test3022 @@ -17,10 +17,6 @@ test # Client-side -# so far only the libssh2 backend supports SHA256 - -libssh2 - scp