Commit 47c392e1 authored by Daniel Stenberg's avatar Daniel Stenberg
Browse files

- Added CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA.

  They introduce known_host support for SSH keys to libcurl. See docs for
  details.
parent 9cff7169
Loading
Loading
Loading
Loading
+13 −6
Original line number Diff line number Diff line
@@ -6,19 +6,26 @@

                                  Changelog

Daniel Stenberg (23 Jul 2009)
- Added CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA.
  They introduce known_host support for SSH keys to libcurl. See docs for
  details. Note that this feature depends on a new enough libssh2 version, to
  be supported in libssh2 1.2 and later (or current git repo at this time).

Michal Marek (22 Jul 2009)
- David Binderman found a memory and fd leak in lib/gtls.c:load_file()
  (https://bugzilla.novell.com/523919). When looking at the code, I found
  that also the ptr pointer can leak.
  (https://bugzilla.novell.com/523919). When looking at the code, I found that
  also the ptr pointer can leak.

Kamil Dudka (20 Jul 2009)
- Claes Jakobsson improved the support for client certificates handling
  in NSS-powered libcurl. Now the client certificates can be selected
- Claes Jakobsson improved the support for client certificates handling in
  NSS-powered libcurl. Now the client certificates can be selected
  automatically by a NSS built-in hook. Additionally pre-login to all PKCS11
  slots is no more performed. It used to cause problems with HW tokens.

- Fixed reference counting for NSS client certificates. Now the PEM reader
  module should be always properly unloaded on Curl_nss_cleanup(). If the unload
  fails though, libcurl will try to reuse the already loaded instance.
  module should be always properly unloaded on Curl_nss_cleanup(). If the
  unload fails though, libcurl will try to reuse the already loaded instance.

Daniel Fandrich (15 Jul 2009)
- Added nonblock.c to the non-automake makefiles (note that the dependencies
+2 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ Curl and libcurl 7.19.6
This release includes the following changes:

 o CURLOPT_FTPPORT (and curl's -P/--ftpport) support port ranges
 o Added CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA

This release includes the following bugfixes:

@@ -32,6 +33,7 @@ This release includes the following bugfixes:
 o curl -o - sends data to stdout using binary mode on windows
 o fixed the separators for "array" style string that CURLINFO_CERTINFO returns
 o auth problem over several hosts with re-used connection
 o improved the support for client certificates in libcurl+NSS
 o fix leak in gtls code

This release includes the following known bugs:
+38 −0
Original line number Diff line number Diff line
@@ -1743,6 +1743,44 @@ Pass a char * pointing to a file name for your private key. If not used,
libcurl defaults to using \fB~/.ssh/id_dsa\fP.
If the file is password-protected, set the password with \fICURLOPT_KEYPASSWD\fP.
(Added in 7.16.1)
.IP CURLOPT_SSH_KNOWNHOSTS
Pass a pointer to a zero terminated string holding the file name of the
known_host file to use.  The known_hosts file should use the OpenSSH file
format as supported by libssh2. If this file is specified, libcurl will only
accept connections with hosts that are known and present in that file, with a
matching public key. Use \fICURLOPT_SSH_KEYFUNCTION\fP to alter the default
behavior on host and key (mis)matching. (Added in 7.19.6)
.IP CURLOPT_SSH_KEYFUNCTION
Pass a pointer to a curl_sshkeycallback function. It gets called when the
known_host matching has been done, to allow the application to act and decide
for libcurl how to proceed. It gets passed the CURL handle, the key from the
known_hosts file, the key from the remote site, info from libcurl on the
matching status and a custom pointer (set with \fICURLOPT_SSH_KEYDATA\fP). It
MUST return one of the following return codes to tell libcurl how to act:
.RS
.IP CURLKHSTAT_FINE_ADD_TO_FILE
The host+key is accepted and libcurl will append it to the known_hosts file
before continuing with the connection. This will also add the host+key combo
to the known_host pool kept in memory if it wasn't already present there. Note
that the adding of data to the file is done by completely replacing the file
with a new copy, so the permissions of the file must allow this.
.IP CURLKHSTAT_FINE
The host+key is accepted libcurl will continue with the connection. This will
also add the host+key combo to the known_host pool kept in memory if it wasn't
already present there.
.IP CURLKHSTAT_REJECT
The host+key is rejected. libcurl will deny the connection to continue and it
will be closed.
.IP CURLKHSTAT_DEFER
The host+key is rejected, but the SSH connection is asked to be kept alive.
This feature could be used when the app wants to somehow return back and act
on the host+key situation and then retry without needing the overhead of
setting it up from scratch again.
.RE
 (Added in 7.19.6)
.IP CURLOPT_SSH_KEYDATA
Pass a void * as parameter. This pointer will be passed along verbatim to the
callback set with \fICURLOPT_SSH_KEYFUNCTION\fP. (Added in 7.19.6)
.SH OTHER OPTIONS
.IP CURLOPT_PRIVATE
Pass a void * as parameter, pointing to data that should be associated with
+49 −0
Original line number Diff line number Diff line
@@ -493,6 +493,45 @@ typedef enum {

#define CURL_ERROR_SIZE 256

struct curl_khkey {
  const char *key; /* points to a zero-terminated string encoded with base64
                      if len is zero, otherwise to the "raw" data */
  size_t len;
  enum type {
    CURLKHTYPE_UNKNOWN,
    CURLKHTYPE_RSA1,
    CURLKHTYPE_RSA,
    CURLKHTYPE_DSS
  } keytype;
};

/* this is the set of return values expected from the curl_sshkeycallback
   callback */
enum curl_khstat {
  CURLKHSTAT_FINE_ADD_TO_FILE,
  CURLKHSTAT_FINE,
  CURLKHSTAT_REJECT, /* reject the connection, return an error */
  CURLKHSTAT_DEFER,  /* do not accept it, but we can't answer right now so
                        this causes a CURLE_DEFER error but otherwise the
                        connection will be left intact etc */
  CURLKHSTAT_LAST    /* not for use, only a marker for last-in-list */
};

/* this is the set of status codes pass in to the callback */
enum curl_khmatch {
  CURLKHMATCH_OK,       /* match */
  CURLKHMATCH_MISMATCH, /* host found, key mismatch! */
  CURLKHMATCH_MISSING,  /* no matching host/key found */
  CURLKHMATCH_LAST      /* not for use, only a marker for last-in-list */
};

typedef int
  (*curl_sshkeycallback) (CURL *easy,     /* easy handle */
                          const struct curl_khkey *knownkey, /* known */
                          const struct curl_khkey *foundkey, /* found */
                          enum curl_khmatch, /* libcurl's view on the keys */
                          void *clientp); /* custom pointer passed from app */

/* parameter for the CURLOPT_USE_SSL option */
typedef enum {
  CURLUSESSL_NONE,    /* do not attempt to use SSL */
@@ -1214,6 +1253,16 @@ typedef enum {
     to all protocols except FILE and SCP. */
  CINIT(REDIR_PROTOCOLS, LONG, 182),

  /* set the SSH knownhost file name to use */
  CINIT(SSH_KNOWNHOSTS, OBJECTPOINT, 183),

  /* set the SSH host key callback, must point to a curl_sshkeycallback
     function */
  CINIT(SSH_KEYFUNCTION, FUNCTIONPOINT, 184),

  /* set the SSH host key callback custom pointer */
  CINIT(SSH_KEYDATA, OBJECTPOINT, 185),

  CURLOPT_LASTENTRY /* the last unused */
} CURLoption;

+154 −3
Original line number Diff line number Diff line
@@ -306,6 +306,7 @@ static void state(struct connectdata *conn, sshstate nowstate)
  static const char * const names[] = {
    "SSH_STOP",
    "SSH_S_STARTUP",
    "SSH_HOSTKEY",
    "SSH_AUTHLIST",
    "SSH_AUTH_PKEY_INIT",
    "SSH_AUTH_PKEY",
@@ -433,6 +434,21 @@ static CURLcode ssh_getworkingpath(struct connectdata *conn,
  return CURLE_OK;
}

static int sshkeycallback(CURL *easy,
                          const struct curl_khkey *knownkey, /* known */
                          const struct curl_khkey *foundkey, /* found */
                          enum curl_khmatch match,
                          void *clientp)
{
  (void)easy;
  (void)knownkey;
  (void)foundkey;
  (void)clientp;

  /* we only allow perfect matches, and we reject everything else */
  return (match != CURLKHMATCH_OK)?CURLKHSTAT_REJECT:CURLKHSTAT_FINE;
}

/*
 * Earlier libssh2 versions didn't have the ability to seek to 64bit positions
 * with 32bit size_t.
@@ -483,9 +499,15 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
      break;
    }

    /* Set libssh2 to non-blocking, since cURL is all non-blocking */
    /* Set libssh2 to non-blocking, since everything internally is
       non-blocking */
    libssh2_session_set_blocking(sshc->ssh_session, 0);

    state(conn, SSH_HOSTKEY);

    /* fall-through */
  case SSH_HOSTKEY:

#ifdef CURL_LIBSSH2_DEBUG
    /*
     * Before we authenticate we should check the hostkey's fingerprint
@@ -527,12 +549,121 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
      }
    }

#ifdef HAVE_LIBSSH2_KNOWNHOST_API
    if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
      /* we're asked to verify the host against a file */
      int keytype;
      size_t keylen;
      const char *remotekey = libssh2_session_hostkey(sshc->ssh_session,
                                                      &keylen, &keytype);
      int keycheck;
      int keybit;

      if(remotekey) {
        /*
         * A subject to figure out is what host name we need to pass in here.
         * What host name does OpenSSH store in its file if an IDN name is
         * used?
         */
        struct libssh2_knownhost *host;
        enum curl_khmatch keymatch;
        curl_sshkeycallback func =
          data->set.ssh_keyfunc?data->set.ssh_keyfunc:sshkeycallback;
        struct curl_khkey knownkey;
        struct curl_khkey *knownkeyp = NULL;
        struct curl_khkey foundkey;

        keybit = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
          LIBSSH2_KNOWNHOST_KEY_SSHRSA:LIBSSH2_KNOWNHOST_KEY_SSHDSS;

        keycheck = libssh2_knownhost_check(sshc->kh,
                                           conn->host.name,
                                           remotekey, keylen,
                                           LIBSSH2_KNOWNHOST_TYPE_PLAIN|
                                           LIBSSH2_KNOWNHOST_KEYENC_RAW|
                                           keybit,
                                           &host);

        infof(data, "SSH host check: %d, key: %s\n", keycheck,
              (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)?
              host->key:"<none>");

        /* setup 'knownkey' */
        if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) {
          knownkey.key = host->key;
          knownkey.len = 0;
          knownkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
            CURLKHTYPE_RSA : CURLKHTYPE_DSS;
          knownkeyp = &knownkey;
        }

        /* setup 'foundkey' */
        foundkey.key = remotekey;
        foundkey.len = keylen;
        foundkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
          CURLKHTYPE_RSA : CURLKHTYPE_DSS;

        /*
         * if any of the LIBSSH2_KNOWNHOST_CHECK_* defines and the
         * curl_khmatch enum are ever modified, we need to introduce a
         * translation table here!
         */
        keymatch = (enum curl_khmatch)keycheck;

        /* Ask the callback how to behave */
        rc = func(data, knownkeyp, /* from the knownhosts file */
                  &foundkey, /* from the remote host */
                  keymatch, data->set.ssh_keyfunc_userp);
      }
      else
        /* no remotekey means failure! */
        rc = CURLKHSTAT_REJECT;

      switch(rc) {
      default: /* unknown return codes will equal reject */
      case CURLKHSTAT_REJECT:
        state(conn, SSH_SESSION_FREE);
      case CURLKHSTAT_DEFER:
        /* DEFER means bail out but keep the SSH_HOSTKEY state */
        result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION;
        break;
      case CURLKHSTAT_FINE:
      case CURLKHSTAT_FINE_ADD_TO_FILE:
        /* proceed */
        if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) {
          /* the found host+key didn't match but has been told to be fine
             anyway so we add it in memory */
          int addrc = libssh2_knownhost_add(sshc->kh,
                                            conn->host.name, NULL,
                                            remotekey, keylen,
                                            LIBSSH2_KNOWNHOST_TYPE_PLAIN|
                                            LIBSSH2_KNOWNHOST_KEYENC_RAW|
                                            keybit, NULL);
          if(addrc)
            infof(data, "Warning adding the known host %s failed!\n",
                  conn->host.name);
          else if(rc == CURLKHSTAT_FINE_ADD_TO_FILE) {
            /* now we write the entire in-memory list of known hosts to the
               known_hosts file */
            int wrc =
              libssh2_knownhost_writefile(sshc->kh,
                                          data->set.str[STRING_SSH_KNOWNHOSTS],
                                          LIBSSH2_KNOWNHOST_FILE_OPENSSH);
            if(wrc) {
              infof(data, "Warning, writing %s failed!\n",
                    data->set.str[STRING_SSH_KNOWNHOSTS]);
            }
          }
        }
        break;
      }
    }
#endif /* HAVE_LIBSSH2_KNOWNHOST_API */

    state(conn, SSH_AUTHLIST);
    break;

  case SSH_AUTHLIST:
    /* TBD - methods to check the host keys need to be done */

    /*
     * Figure out authentication methods
     * NB: As soon as we have provided a username to an openssh server we
@@ -2278,6 +2409,26 @@ static CURLcode ssh_connect(struct connectdata *conn, bool *done)
    return CURLE_FAILED_INIT;
  }

#ifdef HAVE_LIBSSH2_KNOWNHOST_API
  if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
    int rc;
    ssh->kh = libssh2_knownhost_init(ssh->ssh_session);
    if(!ssh->kh) {
      /* eeek. TODO: free the ssh_session! */
      return CURLE_FAILED_INIT;
    }

    /* read all known hosts from there */
    rc = libssh2_knownhost_readfile(ssh->kh,
                                    data->set.str[STRING_SSH_KNOWNHOSTS],
                                    LIBSSH2_KNOWNHOST_FILE_OPENSSH);
    if(rc) {
      infof(data, "Failed to read known hosts from %s\n",
            data->set.str[STRING_SSH_KNOWNHOSTS]);
    }
  }
#endif /* HAVE_LIBSSH2_KNOWNHOST_API */

#ifdef CURL_LIBSSH2_DEBUG
  libssh2_trace(ssh->ssh_session, ~0);
  infof(data, "SSH socket: %d\n", sock);
Loading