Hello,

I'm trying to write a program which does user authentication with the
help of ssh-agent.  The interaction between the program and ssh-agent is
as follows: the program sends ssh-agent a public key and some
authentication data, and ssh-agent signs the data with the appropriate
private key and sends it back.

However, libssh2 API does not offer such an "external" signing function.

The attached are:

- a patch which adds callback-based function for "publickey" authentication
  (The existing file-based function is now implemented with the
  callback-based function)

- a sample program which demonstrates authentication using ssh-agent
  (To play with this, add ssh2_agent.c to noinst_PROGRAMS in
  example/simple/Makefile.am, rebuild, and run it as "ssh2_agent host user")

>From fcdcd5258b9ad414d1f4ed692c61591887004eb8 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <[email protected]>
Date: Tue, 8 Dec 2009 14:59:13 +0900
Subject: [PATCH] Add generalized API for publickey user authentication.

---
 include/libssh2.h |   20 ++++++
 src/userauth.c    |  180 +++++++++++++++++++++++++++++++++++++++--------------
 2 files changed, 152 insertions(+), 48 deletions(-)

diff --git a/include/libssh2.h b/include/libssh2.h
index 51a00c5..18ef59c 100644
--- a/include/libssh2.h
+++ b/include/libssh2.h
@@ -165,6 +165,10 @@ typedef long long libssh2_int64_t;
                                               void **abstract)
 #define LIBSSH2_FREE_FUNC(name)    void name(void *ptr, void **abstract)
 
+#define LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC(name) \
+  int name(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, \
+	   const unsigned char *data, size_t data_len, void **abstract)
+
 typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT
 {
     char* text;
@@ -437,6 +441,22 @@ libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session,
                                          (privatekey), (passphrase))
 
 LIBSSH2_API int
+libssh2_userauth_publickey_ex(LIBSSH2_SESSION *session,
+                              const char *username,
+                              unsigned int username_len,
+                              const unsigned char *pubkeydata,
+			      size_t pubkeydata_len,
+                              LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+                              void **abstract);
+
+#define libssh2_userauth_publickey(session, username,                   \
+                                   pubkeydata, pubkeydata_len,          \
+                                   sign_callback, abstract)             \
+  libssh2_userauth_publickey_ex((session), (username), strlen(username), \
+                                (pubkeydata), (pubkeydata_len),         \
+                                (sign_callback), (abstract))
+
+LIBSSH2_API int
 libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session,
                                        const char *username,
                                        unsigned int username_len,
diff --git a/src/userauth.c b/src/userauth.c
index 9aed20e..54c47df 100644
--- a/src/userauth.c
+++ b/src/userauth.c
@@ -613,6 +613,45 @@ file_read_privatekey(LIBSSH2_SESSION * session,
     return 0;
 }
 
+struct privkey_file {
+    const char *filename;
+    const char *passphrase;
+};
+
+static int
+sign_fromfile(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len,
+              const unsigned char *data, size_t data_len, void **abstract)
+{
+    struct privkey_file *privkey_file = (struct privkey_file *) (*abstract);
+    const LIBSSH2_HOSTKEY_METHOD *privkeyobj;
+    void *hostkey_abstract;
+    struct iovec datavec;
+
+    if (file_read_privatekey(session, &privkeyobj, &hostkey_abstract,
+                             session->userauth_pblc_method,
+                             session->userauth_pblc_method_len,
+                             privkey_file->filename,
+                             privkey_file->passphrase)) {
+        return -1;
+    }
+
+    datavec.iov_base = (unsigned char *)data;
+    datavec.iov_len = data_len;
+
+    if (privkeyobj->signv(session, sig, sig_len, 1, &datavec,
+                          &hostkey_abstract)) {
+        if (privkeyobj->dtor) {
+            privkeyobj->dtor(session, abstract);
+        }
+        return -1;
+    }
+
+    if (privkeyobj->dtor) {
+        privkeyobj->dtor(session, &hostkey_abstract);
+    }
+    return 0;
+}
+
 
 
 /* userauth_hostbased_fromfile
@@ -885,19 +924,15 @@ libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session,
 
 
 
-/*
- * userauth_publickey_fromfile
- * Authenticate using a keypair found in the named files
- */
 static int
-userauth_publickey_fromfile(LIBSSH2_SESSION *session,
-                            const char *username,
-                            unsigned int username_len,
-                            const char *publickey,
-                            const char *privatekey,
-                            const char *passphrase)
+userauth_publickey(LIBSSH2_SESSION *session,
+                   const char *username,
+                   unsigned int username_len,
+                   const unsigned char *pubkeydata,
+                   unsigned long pubkeydata_len,
+                   LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+                   void *abstract)
 {
-    unsigned long pubkeydata_len = 0;
     unsigned char reply_codes[4] =
         { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE,
           SSH_MSG_USERAUTH_PK_OK, 0
@@ -905,17 +940,20 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
     int rc;
 
     if (session->userauth_pblc_state == libssh2_NB_state_idle) {
-        unsigned char *pubkeydata;
-
         /* Zero the whole thing out */
         memset(&session->userauth_pblc_packet_requirev_state, 0,
                sizeof(session->userauth_pblc_packet_requirev_state));
 
-        if (file_read_publickey(session, &session->userauth_pblc_method,
-                                &session->userauth_pblc_method_len,
-                                &pubkeydata, &pubkeydata_len,publickey)) {
+        session->userauth_pblc_method_len = _libssh2_ntohu32(pubkeydata);
+        session->userauth_pblc_method =
+            LIBSSH2_ALLOC(session, session->userauth_pblc_method_len);
+        if (!session->userauth_pblc_method) {
+            libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+                          "Unable to allocate memory for public key data", 0);
             return -1;
         }
+        memcpy(session->userauth_pblc_method, pubkeydata + 4,
+               session->userauth_pblc_method_len);
 
         /*
          * 45 = packet_type(1) + username_len(4) + servicename_len(4) +
@@ -940,7 +978,6 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
         if (!session->userauth_pblc_packet) {
             LIBSSH2_FREE(session, session->userauth_pblc_method);
             session->userauth_pblc_method = NULL;
-            LIBSSH2_FREE(session, pubkeydata);
             return -1;
         }
 
@@ -975,7 +1012,6 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
         session->userauth_pblc_s += 4;
         memcpy(session->userauth_pblc_s, pubkeydata, pubkeydata_len);
         session->userauth_pblc_s += pubkeydata_len;
-        LIBSSH2_FREE(session, pubkeydata);
 
         _libssh2_debug(session, LIBSSH2_DBG_AUTH,
                        "Attempting publickey authentication");
@@ -1003,10 +1039,7 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
     }
 
     if (session->userauth_pblc_state == libssh2_NB_state_sent) {
-        const LIBSSH2_HOSTKEY_METHOD *privkeyobj;
-        void *abstract;
-        unsigned char buf[5];
-        struct iovec datavec[4];
+        unsigned char *buf, *s;
         unsigned char *sig;
         unsigned long sig_len;
 
@@ -1063,43 +1096,37 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
         LIBSSH2_FREE(session, session->userauth_pblc_data);
         session->userauth_pblc_data = NULL;
 
-        if (file_read_privatekey(session, &privkeyobj, &abstract,
-                                 session->userauth_pblc_method,
-                                 session->userauth_pblc_method_len,
-                                 privatekey, passphrase)) {
-            LIBSSH2_FREE(session, session->userauth_pblc_method);
-            session->userauth_pblc_method = NULL;
-            LIBSSH2_FREE(session, session->userauth_pblc_packet);
-            session->userauth_pblc_packet = NULL;
-            session->userauth_pblc_state = libssh2_NB_state_idle;
-            return -1;
-        }
-
         *session->userauth_pblc_b = 0x01;
 
-        _libssh2_htonu32(buf, session->session_id_len);
-        datavec[0].iov_base = buf;
-        datavec[0].iov_len = 4;
-        datavec[1].iov_base = session->session_id;
-        datavec[1].iov_len = session->session_id_len;
-        datavec[2].iov_base = session->userauth_pblc_packet;
-        datavec[2].iov_len = session->userauth_pblc_packet_len;
+        s = buf = LIBSSH2_ALLOC(session, 4 + session->session_id_len
+                                + session->userauth_pblc_packet_len);
+        if (!buf) {
+            libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+                          "Unable to allocate memory for userauth-publickey "
+                          "signed data", 0);
+            return -1;
+        }
 
-        if (privkeyobj->signv(session, &sig, &sig_len, 3, datavec, &abstract)) {
+        _libssh2_htonu32(s, session->session_id_len);
+        s += 4;
+        memcpy (s, session->session_id, session->session_id_len);
+        s += session->session_id_len;
+        memcpy (s, session->userauth_pblc_packet,
+                session->userauth_pblc_packet_len);
+        s += session->userauth_pblc_packet_len;
+
+        if (sign_callback(session, &sig, &sig_len, buf, s - buf, abstract)) {
+            LIBSSH2_FREE(session, buf);
             LIBSSH2_FREE(session, session->userauth_pblc_method);
             session->userauth_pblc_method = NULL;
             LIBSSH2_FREE(session, session->userauth_pblc_packet);
             session->userauth_pblc_packet = NULL;
-            if (privkeyobj->dtor) {
-                privkeyobj->dtor(session, &abstract);
-            }
             session->userauth_pblc_state = libssh2_NB_state_idle;
             return -1;
         }
 
-        if (privkeyobj->dtor) {
-            privkeyobj->dtor(session, &abstract);
-        }
+
+        LIBSSH2_FREE(session, buf);
 
         /*
          * If this function was restarted, pubkeydata_len might still be 0
@@ -1214,6 +1241,43 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
     return -1;
 }
 
+/*
+ * userauth_publickey_fromfile
+ * Authenticate using a keypair found in the named files
+ */
+static int
+userauth_publickey_fromfile(LIBSSH2_SESSION *session,
+                            const char *username,
+                            unsigned int username_len,
+                            const char *publickey,
+                            const char *privatekey,
+                            const char *passphrase)
+{
+    unsigned char *pubkeydata = NULL;
+    unsigned long pubkeydata_len = 0;
+    struct privkey_file privkey_file;
+    void *abstract = &privkey_file;
+    int rc;
+
+    privkey_file.filename = privatekey;
+    privkey_file.passphrase = passphrase;
+
+    if (session->userauth_pblc_state == libssh2_NB_state_idle) {
+        if (file_read_publickey(session, &session->userauth_pblc_method,
+                                &session->userauth_pblc_method_len,
+                                &pubkeydata, &pubkeydata_len, publickey)) {
+            return -1;
+        }
+    }
+
+    rc = userauth_publickey(session, username, username_len,
+                            pubkeydata, pubkeydata_len,
+                            sign_fromfile, &abstract);
+    LIBSSH2_FREE(session, pubkeydata);
+
+    return rc;
+}
+
 /* libssh2_userauth_publickey_fromfile_ex
  * Authenticate using a keypair found in the named files
  */
@@ -1233,6 +1297,26 @@ libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session,
     return rc;
 }
 
+/* libssh2_userauth_publickey_ex
+ * Authenticate using an external callback function
+ */
+LIBSSH2_API int
+libssh2_userauth_publickey_ex(LIBSSH2_SESSION *session,
+                              const char *user,
+                              unsigned int user_len,
+                              const unsigned char *pubkeydata,
+                              size_t pubkeydata_len,
+                              LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+                              void **abstract)
+{
+    int rc;
+    BLOCK_ADJUST(rc, session,
+                 userauth_publickey(session, user, user_len,
+                                    pubkeydata, pubkeydata_len,
+                                    sign_callback, abstract));
+    return rc;
+}
+
 
 
 /*
-- 
1.6.5.3

/*
 * Sample showing how to do SSH2 connect using ssh-agent.
 *
 * The sample code has default values for host name, user name:
 *
 * "ssh2_agent host user"
 */

#include "libssh2_config.h"
#include <libssh2.h>
#include <libssh2_sftp.h>

#ifdef HAVE_WINDOWS_H
# include <windows.h>
#endif
#ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
# ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
# ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/un.h>
#include <stdlib.h>

#define SSH2_AGENTC_REQUEST_IDENTITIES 11
#define SSH2_AGENT_IDENTITIES_ANSWER 12
#define SSH2_AGENTC_SIGN_REQUEST 13
#define SSH2_AGENT_SIGN_RESPONSE 14

const char *username="username";
static int auth_sock = -1;

struct identity {
    unsigned char *blob;
    unsigned long blob_len;
    char *comment;
};

static int
sign(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len,
     const unsigned char *data, size_t data_len, void **abstract)
{
    struct identity *identity = (struct identity *) (*abstract);
    int rc;
    unsigned char buf[BUFSIZ], *s;
    unsigned long len = 1 + 4 + identity->blob_len + 4 + data_len + 4;

    /* Send a request to sign the data */
    s = buf;
    *(unsigned long *)s = htonl(len);
    s += 4;
    *s++ = SSH2_AGENTC_SIGN_REQUEST;
    *(unsigned long *)s = htonl(identity->blob_len);
    s += 4;
    memcpy(s, identity->blob, identity->blob_len);
    s += identity->blob_len;
    *(unsigned long *)s = htonl(data_len);
    s += 4;
    memcpy(s, data, data_len);
    s += data_len;
    *(unsigned long *)s = 0;
    s += 4;
    rc = send(auth_sock, buf, s - buf, 0);
    if (rc == -1) {
        fprintf(stderr, "failed to request ssh-agent to sign data\n");
        return -1;
    }

    /* Receive a generated signature */
    rc = recv(auth_sock, buf, sizeof buf, 0);
    if (rc == -1) {
        fprintf(stderr, "failed to recv a response from ssh-agent\n");
        return -1;
    }
    s = buf;
    len = ntohl(*(unsigned long *)s);
    s += 4;
    if (len < 5 || len > sizeof buf - 4) {
        fprintf(stderr, "invalid response from ssh-agent\n");
        return -1;
    }
    if (*s++ != SSH2_AGENT_SIGN_RESPONSE) {
        fprintf(stderr, "failed to request identities\n");
        return -1;
    }

    /* Skip the entire length of the signature */
    s += 4;
    /* Skip signing method */
    len = ntohl(*(unsigned long *)s);
    s += 4 + len;
    *sig_len = ntohl(*(unsigned long *)s);
    s += 4;
    *sig = malloc(*sig_len);
    if (!*sig) {
      fprintf(stderr, "failed to allocate memory for signature\n");
      return -1;
    }
    memcpy(*sig, s, *sig_len);
    return 0;
}

int main(int argc, char *argv[])
{
    unsigned long hostaddr;
    int sock = -1, i, j, rc;
    struct sockaddr_in sin;
    const char *fingerprint;
    char *userauthlist;
    LIBSSH2_SESSION *session;
    LIBSSH2_CHANNEL *channel;
    struct sockaddr_un sun;
    const char *auth_sock_path;
    unsigned char buf[BUFSIZ], *s;
    struct identity *identities;
    unsigned long len, num_identities;
#ifdef WIN32
    WSADATA wsadata;

    WSAStartup(MAKEWORD(2,0), &wsadata);
#endif

    if (argc > 1) {
        hostaddr = inet_addr(argv[1]);
    } else {
        hostaddr = htonl(0x7F000001);
    }

    if(argc > 2) {
        username = argv[2];
    }

    /* Connect to ssh-agent */
    auth_sock_path = getenv("SSH_AUTH_SOCK");
    if (!auth_sock_path) {
        fprintf(stderr, "ssh-agent is not running\n");
        return -1;
    }

    auth_sock = socket(PF_UNIX, SOCK_STREAM, 0);
    if (auth_sock == -1) {
        fprintf(stderr, "failed to create socket!\n");
        rc = -1;
        return -1;
    }

    sun.sun_family = AF_UNIX;
    strncpy (sun.sun_path, auth_sock_path, sizeof sun.sun_path);
    if (connect(auth_sock, (struct sockaddr*)(&sun),
                sizeof(struct sockaddr_un)) != 0) {
        close (auth_sock);
        fprintf(stderr, "failed to connect!\n");
        return -1;
    }

    /* Send a request to list identities */
    memset(buf, 0, sizeof buf);
    s = buf;
    *(unsigned long *)s = htonl(1);
    s += 4;
    *s++ = SSH2_AGENTC_REQUEST_IDENTITIES;
    rc = send(auth_sock, buf, s - buf, 0);
    if (rc == -1) {
        fprintf(stderr, "failed to request ssh-agent to list identities\n");
        goto shutdown;
    }

    /* Receive the list of identities */
    rc = recv(auth_sock, buf, sizeof buf, 0);
    if (rc == -1) {
        fprintf(stderr, "failed to recv a response from ssh-agent\n");
        goto shutdown;
    }
    s = buf;
    len = ntohl(*(unsigned long *)s);
    s += 4;
    if (len < 1 || len > sizeof buf) {
        fprintf(stderr, "invalid response from ssh-agent\n");
        rc = -1;
        goto shutdown;
    }

    if (*s++ != SSH2_AGENT_IDENTITIES_ANSWER) {
        fprintf(stderr, "failed to request identities\n");
        rc = -1;
        goto shutdown;
    }
    num_identities = ntohl(*(unsigned long *)s);
    s += 4;

    identities = calloc(num_identities, sizeof(struct identity));
    for (i = 0; i < num_identities; i++) {
        identities[i].blob_len = ntohl(*(unsigned long *)s);
        s += 4;
        identities[i].blob = malloc(identities[i].blob_len);
        if (!identities[i].blob) {
            rc = -1;
            goto shutdown;
        }
        memcpy(identities[i].blob, s, identities[i].blob_len);
        s += identities[i].blob_len;

        len = ntohl(*(unsigned long *)s);
        s += 4;
        identities[i].comment = malloc(len + 1);
        if (!identities[i].comment) {
            rc = -1;
            goto shutdown;
        }
        identities[i].comment[len] = '\0';
        memcpy(identities[i].comment, s, len);
        s += len;
    }

    /* Ultra basic "connect to port 22 on localhost".  Your code is
     * responsible for creating the socket establishing the connection
     */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        fprintf(stderr, "failed to create socket!\n");
        rc = -1;
        goto shutdown;
    }

    sin.sin_family = AF_INET;
    sin.sin_port = htons(22);
    sin.sin_addr.s_addr = hostaddr;
    if (connect(sock, (struct sockaddr*)(&sin),
                sizeof(struct sockaddr_in)) != 0) {
        fprintf(stderr, "failed to connect!\n");
        goto shutdown;
    }

    /* Create a session instance and start it up. This will trade welcome
     * banners, exchange keys, and setup crypto, compression, and MAC layers
     */
    session = libssh2_session_init();
    if (libssh2_session_startup(session, sock)) {
        fprintf(stderr, "Failure establishing SSH session\n");
        return -1;
    }

    for (i = 0; i < num_identities; i++) {
        void *abstract = &identities[i];
        if (libssh2_userauth_publickey(session, username,
                                       identities[i].blob,
                                       identities[i].blob_len,
                                       sign,
                                       &abstract)) {
            printf("\tAuthentication by public key %s failed!\n",
                   identities[i].comment);
        } else {
            printf("\tAuthentication by public key %s succeeded!\n",
                   identities[i].comment);
#ifdef WIN32
            closesocket(auth_sock);
#else
            close(auth_sock);
#endif
            auth_sock = -1;
            break;
        }
    }
    if (i == num_identities) {
      fprintf(stderr, "Couldn't authenticate to the host\n");
      rc = -1;
      goto shutdown;
    }

    /* At this point we havn't authenticated. The first thing to do is check
     * the hostkey's fingerprint against our known hosts Your app may have it
     * hard coded, may go to a file, may present it to the user, that's your
     * call
     */
    fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
    printf("Fingerprint: ");
    for(i = 0; i < 16; i++) {
        printf("%02X ", (unsigned char)fingerprint[i]);
    }
    printf("\n");

    /* check what authentication methods are available */
    userauthlist = libssh2_userauth_list(session, username, strlen(username));
    printf("Authentication methods: %s\n", userauthlist);
    if (strstr(userauthlist, "publickey") == NULL) {
        fprintf(stderr, "\"publickey\" authentication is not supported\n");
        goto shutdown;
    }

    /* Request a shell */
    if (!(channel = libssh2_channel_open_session(session))) {
        fprintf(stderr, "Unable to open a session\n");
        goto shutdown;
    }

    /* Some environment variables may be set,
     * It's up to the server which ones it'll allow though
     */
    libssh2_channel_setenv(channel, "FOO", "bar");

    /* Request a terminal with 'vanilla' terminal emulation
     * See /etc/termcap for more options
     */
    if (libssh2_channel_request_pty(channel, "vanilla")) {
        fprintf(stderr, "Failed requesting pty\n");
        goto skip_shell;
    }

    /* Open a SHELL on that pty */
    if (libssh2_channel_shell(channel)) {
        fprintf(stderr, "Unable to request shell on allocated pty\n");
        goto shutdown;
    }

    /* At this point the shell can be interacted with using
     * libssh2_channel_read()
     * libssh2_channel_read_stderr()
     * libssh2_channel_write()
     * libssh2_channel_write_stderr()
     *
     * Blocking mode may be (en|dis)abled with: libssh2_channel_set_blocking()
     * If the server send EOF, libssh2_channel_eof() will return non-0
     * To send EOF to the server use: libssh2_channel_send_eof()
     * A channel can be closed with: libssh2_channel_close()
     * A channel can be freed with: libssh2_channel_free()
     */

  skip_shell:
    if (channel) {
        libssh2_channel_free(channel);
        channel = NULL;
    }

    /* Other channel types are supported via:
     * libssh2_scp_send()
     * libssh2_scp_recv()
     * libssh2_channel_direct_tcpip()
     */

  shutdown:

    libssh2_session_disconnect(session,
                               "Normal Shutdown, Thank you for playing");
    libssh2_session_free(session);

    if (sock != -1) {
#ifdef WIN32
        closesocket(sock);
#else
        close(sock);
#endif
    }

    if (auth_sock != -1) {
#ifdef WIN32
      closesocket(auth_sock);
#else
      close(auth_sock);
#endif
    }

    for (i = 0; i < num_identities; i++) {
        free(identities[i].blob);
        free(identities[i].comment);
    }
    free(identities);
    printf("all done!\n");
    return rc;
}
Regards,
-- 
Daiki Ueno
_______________________________________________
libssh2-devel http://cool.haxx.se/cgi-bin/mailman/listinfo/libssh2-devel

Reply via email to