mirror of
https://github.com/curl/curl.git
synced 2026-05-19 11:26:29 +03:00
libssh: add support for SHA256 host public keys
Reported-by: Joshua Rogers Fixes #21605 Closes #21607
This commit is contained in:
parent
9135294115
commit
eb9b253d66
11 changed files with 85 additions and 48 deletions
4
.github/workflows/windows.yml
vendored
4
.github/workflows/windows.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -73,10 +73,6 @@ int main(void)
|
|||
}
|
||||
~~~
|
||||
|
||||
# NOTES
|
||||
|
||||
Requires the libssh2 backend.
|
||||
|
||||
# %AVAILABILITY%
|
||||
|
||||
# RETURN VALUE
|
||||
|
|
|
|||
12
lib/setopt.c
12
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
|
||||
|
|
|
|||
|
|
@ -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 <unistd.h>
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ test
|
|||
|
||||
# Client-side
|
||||
<client>
|
||||
# so far only the libssh2 backend supports SHA256
|
||||
<features>
|
||||
libssh2
|
||||
</features>
|
||||
<server>
|
||||
sftp
|
||||
</server>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ test
|
|||
|
||||
# Client-side
|
||||
<client>
|
||||
# so far only the libssh2 backend supports SHA256
|
||||
<features>
|
||||
libssh2
|
||||
</features>
|
||||
<server>
|
||||
scp
|
||||
</server>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue