]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
libssh2: add SHA256 fingerprint support
authorMats Lindestam <matslm@axis.com>
Sun, 26 Sep 2021 21:20:53 +0000 (23:20 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 26 Sep 2021 21:20:53 +0000 (23:20 +0200)
Added support for SHA256 fingerprint in command line curl and in
libcurl.

Closes #7646

27 files changed:
docs/TODO
docs/cmdline-opts/Makefile.inc
docs/cmdline-opts/hostpubsha256.d [new file with mode: 0644]
docs/libcurl/curl_easy_setopt.3
docs/libcurl/opts/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.3 [new file with mode: 0644]
docs/libcurl/opts/Makefile.inc
docs/libcurl/symbols-in-versions
docs/options-in-versions
include/curl/curl.h
include/curl/typecheck-gcc.h
lib/easyoptions.c
lib/setopt.c
lib/urldata.h
lib/vssh/libssh2.c
src/tool_cfgable.c
src/tool_cfgable.h
src/tool_getparam.c
src/tool_help.c
src/tool_operate.c
tests/.gitignore
tests/FILEFORMAT.md
tests/data/Makefile.inc
tests/data/test3021 [new file with mode: 0644]
tests/data/test3022 [new file with mode: 0644]
tests/runtests.pl
tests/sshhelp.pm
tests/sshserver.pl

index 2fca647970a5fb149a9c5845cccbedfb72f69423..4a9d998051369063f488d9aa8276a63b7b3b6035 100644 (file)
--- a/docs/TODO
+++ b/docs/TODO
  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
 
 
  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
index 6e04552e96c05d4c38c42d7095ccc39c6d2b41ab..506025a753373fe27ecb9e6aa8fcfa338c5a8b36 100644 (file)
@@ -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 (file)
index 0000000..81e6f98
--- /dev/null
@@ -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.
index b83f5b6356aee117ceb444e8bbb40f828f67a128..592692b94936ad439e0e0dcc4c940ab85fb8c850 100644 (file)
@@ -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 (file)
index 0000000..30be32e
--- /dev/null
@@ -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), "
index 1181331b980710e59f75e8c1cf2b48b79923af67..4ef5ddf4323337bf0b7ae35e014b3db8ed4c742e 100644 (file)
@@ -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                      \
index 55f25b6b0f24c2ff18a4cb1158e5f6b8128f9773..8be22d504787d68d5b7d6555ec8a0d23a429e1f4 100644 (file)
@@ -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
index 6fada9381c75415cf127769e45f77ac2b236a7c9..ac087a1ef313e355131f03055f3e2bad4a6f4ab5 100644 (file)
@@ -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
index 835c3d871bcbaf260313770fe0256a4778358902..6eb0fcb826ed8232463291b235205878c8dc76b5 100644 (file)
@@ -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;
 
index 34d0267ed95ad568630691f1eebdd824f3038b83..77ce461bccf6a6c841cf4f48fff442c8a8c21d12 100644 (file)
@@ -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 ||                                  \
index 4e65e3525bf585430110af79412df1b28e1e24a1..b1c0704d5eb37bd92a5ba74c05743dd069a4fcd6 100644 (file)
@@ -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
index 08827d1ef9e0c3594e8a6d93c59528b78e61641b..c62a62fb5ecf560fbc17ce2bf4e97843319c270f 100644 (file)
@@ -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.
index 6ffd97621f8af19e03dcac42266313a2346befc2..2d1e873a582cd3ac9a4ffe48f6eb47901788d310 100644 (file)
@@ -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 */
index 8ccfe68a871b2f27721a03878d5b43c8a25325f8..7466840ffa89192f49a1cb78d68cbddc78e99364 100644 (file)
 #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);
 }
 
 /*
index c3f7cecb14b0d6614131ef29268889a1aa471b64..34e17ce55b043fa419bdce715e65178ed6bc45cc 100644 (file)
@@ -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);
index b00aacb76d5f27756b316224fa4883f6cd2f4262..eff55f95dbae696a669ba36948b71f42ce081515 100644 (file)
@@ -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;
index 73ba8f5374f166959c4554ece43f4d282b7538a7..1de79634c14a16720bb32f605e503e771ab7fc9a 100644 (file)
@@ -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;
index cc23b12e23b4d6f26cf33c3bd970ba75f087e8b4..46969e77f73f93879a8995043a511d6be884a886 100644 (file)
@@ -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},
index ca53d29f7d42975b578523a5baff946640840570..5d24ccb7d0eff7b0f9ec8645de212976cf491a7b 100644 (file)
@@ -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);
index 00f787cc89dc8b6b9fa64c48e1d4e31d199c5c5a..a8882b27594585a49d82b034c7bf0699afa0032d 100644 (file)
@@ -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
index 7a9c4820428193f092564fb30fd58014f4905921..df61c412fa58b817af9d9d47c2b1d1c91217110a 100644 (file)
@@ -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
index 1f774ce4e8b45d894d0b01307df00b6a095a10af..d906ca3382ed7a733d8cdc36cc1b466d8cde4b0a 100644 (file)
@@ -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 (file)
index 0000000..0a02e18
--- /dev/null
@@ -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 (file)
index 0000000..f347790
--- /dev/null
@@ -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>
index 38b76e878c16357fd108fdeb3b099b9551e6afbf..9a1c169b6af610a1fd72a12d66eac773c75de386 100755 (executable)
@@ -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
index 0f71b30799854f1a7b583547b250e9ad633683a7..41047e9c6ab010e2654fc79a8e94f0ba8327d966 100644 (file)
@@ -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
 
index 412cab33e89b4df6c7ab7baa9f176c40ee8e8980..526ed099f374fb00ea71d65533991b8f89f38d81 100644 (file)
@@ -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);