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