* 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.
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
#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>
}
/* 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.
*/
{
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;
if(rc != SSH_OK)
return rc;
+ 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_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]) {
- int i;
- char md5buffer[33];
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, &hlen);
- if(rc != SSH_OK || hlen != 16) {
+ 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);
/* !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);