/* * Copyright (c) 2009 by Daiki Ueno * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the copyright holder nor the names * of any other contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "libssh2_priv.h" #include "misc.h" #include #ifdef HAVE_SYS_UN_H #include #else /* Use the existence of sys/un.h as a test if Unix domain socket is supported. winsock*.h define PF_UNIX/AF_UNIX but do not actually support them. */ #undef PF_UNIX #endif /* Requests from client to agent for protocol 1 key operations */ #define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 #define SSH_AGENTC_RSA_CHALLENGE 3 #define SSH_AGENTC_ADD_RSA_IDENTITY 7 #define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 #define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 #define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED 24 /* Requests from client to agent for protocol 2 key operations */ #define SSH2_AGENTC_REQUEST_IDENTITIES 11 #define SSH2_AGENTC_SIGN_REQUEST 13 #define SSH2_AGENTC_ADD_IDENTITY 17 #define SSH2_AGENTC_REMOVE_IDENTITY 18 #define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 #define SSH2_AGENTC_ADD_ID_CONSTRAINED 25 /* Key-type independent requests from client to agent */ #define SSH_AGENTC_ADD_SMARTCARD_KEY 20 #define SSH_AGENTC_REMOVE_SMARTCARD_KEY 21 #define SSH_AGENTC_LOCK 22 #define SSH_AGENTC_UNLOCK 23 #define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26 /* Generic replies from agent to client */ #define SSH_AGENT_FAILURE 5 #define SSH_AGENT_SUCCESS 6 /* Replies from agent to client for protocol 1 key operations */ #define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 #define SSH_AGENT_RSA_RESPONSE 4 /* Replies from agent to client for protocol 2 key operations */ #define SSH2_AGENT_IDENTITIES_ANSWER 12 #define SSH2_AGENT_SIGN_RESPONSE 14 /* Key constraint identifiers */ #define SSH_AGENT_CONSTRAIN_LIFETIME 1 #define SSH_AGENT_CONSTRAIN_CONFIRM 2 /* non-blocking mode on agent connection is not yet implemented, but for future use. */ typedef enum { agent_NB_state_init = 0, agent_NB_state_request_created, agent_NB_state_request_length_sent, agent_NB_state_request_sent, agent_NB_state_response_length_received, agent_NB_state_response_received } agent_nonblocking_states; typedef struct agent_transaction_ctx { unsigned char *request; size_t request_len; unsigned char *response; size_t response_len; agent_nonblocking_states state; } *agent_transaction_ctx_t; typedef int (*agent_connect_func)(LIBSSH2_AGENT *agent); typedef int (*agent_transact_func)(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx); typedef int (*agent_disconnect_func)(LIBSSH2_AGENT *agent); struct agent_publickey { struct list_node node; /* this is the struct we expose externally */ struct libssh2_agent_publickey external; }; struct agent_ops { agent_connect_func connect; agent_transact_func transact; agent_disconnect_func disconnect; }; struct _LIBSSH2_AGENT { LIBSSH2_SESSION *session; /* the session this "belongs to" */ int fd; /* -1 when not connected */ struct agent_ops *ops; struct agent_transaction_ctx transctx; struct agent_publickey *identity; struct list_head head; /* list of public keys */ }; #ifdef PF_UNIX static int agent_connect_unix(LIBSSH2_AGENT *agent) { const char *path; struct sockaddr_un sun; path = getenv("SSH_AUTH_SOCK"); if (!path) { return -1; } agent->fd = socket(PF_UNIX, SOCK_STREAM, 0); if (agent->fd < 0) { return -1; } sun.sun_family = AF_UNIX; strncpy (sun.sun_path, path, sizeof sun.sun_path); if (connect(agent->fd, (struct sockaddr*)(&sun), sizeof sun) != 0) { close (agent->fd); return -1; } return 0; } static int agent_transact_unix(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx) { unsigned char buf[4], *s; int rc; /* Send the length of the request */ if (transctx->state == agent_NB_state_request_created) { _libssh2_htonu32(buf, transctx->request_len); rc = send(agent->fd, buf, sizeof buf, 0); if (rc < 0) { if (errno == EAGAIN) return LIBSSH2_ERROR_EAGAIN; return -1; } transctx->state = agent_NB_state_request_length_sent; } /* Send the request body */ if (transctx->state == agent_NB_state_request_length_sent) { rc = send(agent->fd, transctx->request, transctx->request_len, 0); if (rc < 0) { if (errno == EAGAIN) return LIBSSH2_ERROR_EAGAIN; return -1; } transctx->state = agent_NB_state_request_sent; } /* Receive the length of a response */ if (transctx->state == agent_NB_state_request_sent) { rc = recv(agent->fd, buf, sizeof buf, 0); if (rc < 0) { if (errno == EAGAIN) return LIBSSH2_ERROR_EAGAIN; return -1; } transctx->response_len = _libssh2_ntohu32(buf); s = transctx->response = LIBSSH2_ALLOC(agent->session, transctx->response_len); if (!transctx->response) { return LIBSSH2_ERROR_ALLOC; } transctx->state = agent_NB_state_response_length_received; } /* Receive the response body */ if (transctx->state == agent_NB_state_response_length_received) { rc = recv(agent->fd, transctx->response, transctx->response_len, 0); if (rc < 0) { if (errno == EAGAIN) return LIBSSH2_ERROR_EAGAIN; return -1; } transctx->state = agent_NB_state_response_received; } return 0; } static int agent_disconnect_unix(LIBSSH2_AGENT *agent) { return close(agent->fd); } struct agent_ops agent_ops_unix = { agent_connect_unix, agent_transact_unix, agent_disconnect_unix }; #endif /* PF_UNIX */ #ifdef WIN32 /* Code to talk to Pageant was taken from PuTTY. * * Portions copyright Robert de Bath, Joris van Rantwijk, Delian * Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas * Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, * Markus Kuhn, Colin Watson, and CORE SDI S.A. */ #define PAGEANT_COPYDATA_ID 0x804e50ba /* random goop */ #define PAGEANT_MAX_MSGLEN 8192 static int agent_connect_pageant(LIBSSH2_AGENT *agent) { HWND hwnd; hwnd = FindWindow("Pageant", "Pageant"); if (!hwnd) return -1; return 0; } static int agent_transact_pageant(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx) { HWND hwnd; char mapname[23]; HANDLE filemap; unsigned char *p; int id; COPYDATASTRUCT cds; if (!transctx || 4 + transctx->request_len > PAGEANT_MAX_MSGLEN) { return LIBSSH2_ERROR_INVAL; } hwnd = FindWindow("Pageant", "Pageant"); if (!hwnd) { return -1; } sprintf(mapname, "PageantRequest%08x", (unsigned)GetCurrentThreadId()); filemap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, PAGEANT_MAX_MSGLEN, mapname); if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) { return -1; } p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); _libssh2_htonu32(p, transctx->request_len); memcpy(p + 4, transctx->request, transctx->request_len); cds.dwData = PAGEANT_COPYDATA_ID; cds.cbData = 1 + strlen(mapname); cds.lpData = mapname; id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds); if (id > 0) { transctx->response_len = _libssh2_ntohu32(p); if (transctx->response_len > PAGEANT_MAX_MSGLEN) { UnmapViewOfFile(p); CloseHandle(filemap); return LIBSSH2_ERROR_AGENT_PROTOCOL; } transctx->response = LIBSSH2_ALLOC(agent->session, transctx->response_len); if (!transctx->response) { UnmapViewOfFile(p); CloseHandle(filemap); return LIBSSH2_ERROR_ALLOC; } memcpy(transctx->response, p + 4, transctx->response_len); } UnmapViewOfFile(p); CloseHandle(filemap); return 0; } static int agent_disconnect_pageant(LIBSSH2_AGENT *agent) { (void)agent; return 0; } struct agent_ops agent_ops_pageant = { agent_connect_pageant, agent_transact_pageant, agent_disconnect_pageant }; #endif /* WIN32 */ static struct { const char *name; struct agent_ops *ops; } supported_backends[] = { #ifdef WIN32 {"Pageant", &agent_ops_pageant}, #endif /* WIN32 */ #ifdef PF_UNIX {"Unix", &agent_ops_unix}, #endif /* PF_UNIX */ {NULL} }; static int agent_sign(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, const unsigned char *data, size_t data_len, void **abstract) { LIBSSH2_AGENT *agent = (LIBSSH2_AGENT *) (*abstract); agent_transaction_ctx_t transctx = &agent->transctx; struct agent_publickey *identity = agent->identity; ssize_t len = 1 + 4 + identity->external.blob_len + 4 + data_len + 4; ssize_t method_len; unsigned char *s; int rc; /* Create a request to sign the data */ if (transctx->state == agent_NB_state_init) { s = transctx->request = LIBSSH2_ALLOC(session, len); if (!transctx->request) { return LIBSSH2_ERROR_ALLOC; } *s++ = SSH2_AGENTC_SIGN_REQUEST; /* key blob */ _libssh2_htonu32(s, identity->external.blob_len); s += 4; memcpy(s, identity->external.blob, identity->external.blob_len); s += identity->external.blob_len; /* data */ _libssh2_htonu32(s, data_len); s += 4; memcpy(s, data, data_len); s += data_len; /* flags */ _libssh2_htonu32(s, 0); s += 4; transctx->request_len = s - transctx->request; transctx->state = agent_NB_state_request_created; } /* Make sure to be re-called as a result of EAGAIN. */ if (*transctx->request != SSH2_AGENTC_SIGN_REQUEST) { return LIBSSH2_ERROR_BAD_USE; } rc = agent->ops->transact(agent, transctx); if (rc) { goto error; } LIBSSH2_FREE(session, transctx->request); transctx->request = NULL; len = transctx->response_len; s = transctx->response; len--; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } if (*s != SSH2_AGENT_SIGN_RESPONSE) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } s++; /* Skip the entire length of the signature */ len -= 4; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } s += 4; /* Skip signing method */ len -= 4; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } method_len = _libssh2_ntohu32(s); s += 4; len -= method_len; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } s += method_len; /* Read the signature */ len -= 4; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } *sig_len = _libssh2_ntohu32(s); s += 4; len -= *sig_len; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } *sig = LIBSSH2_ALLOC(session, *sig_len); if (!*sig) { rc = LIBSSH2_ERROR_ALLOC; goto error; } memcpy(*sig, s, *sig_len); error: LIBSSH2_FREE(session, transctx->request); transctx->request = NULL; LIBSSH2_FREE(session, transctx->response); transctx->response = NULL; return rc; } static int agent_list_identities(LIBSSH2_AGENT *agent) { agent_transaction_ctx_t transctx = &agent->transctx; ssize_t len, num_identities; unsigned char *s; int rc; /* Create a request to list identities */ if (transctx->state == agent_NB_state_init) { unsigned char c = SSH2_AGENTC_REQUEST_IDENTITIES; transctx->request = &c; transctx->request_len = 1; transctx->state = agent_NB_state_request_created; } /* Make sure to be re-called as a result of EAGAIN. */ if (*transctx->request != SSH2_AGENTC_REQUEST_IDENTITIES) { return LIBSSH2_ERROR_BAD_USE; } rc = agent->ops->transact(agent, transctx); if (rc) { goto error; } transctx->request = NULL; len = transctx->response_len; s = transctx->response; len--; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } if (*s != SSH2_AGENT_IDENTITIES_ANSWER) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } s++; /* Read the length of identities */ len -= 4; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } num_identities = _libssh2_ntohu32(s); s += 4; while (num_identities--) { struct agent_publickey *identity; ssize_t comment_len; identity = LIBSSH2_ALLOC(agent->session, sizeof *identity); if (!identity) { rc = LIBSSH2_ERROR_ALLOC; goto error; } /* Read the length of the blob */ len -= 4; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } identity->external.blob_len = _libssh2_ntohu32(s); s += 4; /* Read the blob */ len -= identity->external.blob_len; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } identity->external.blob = LIBSSH2_ALLOC(agent->session, identity->external.blob_len); if (!identity->external.blob) { rc = LIBSSH2_ERROR_ALLOC; goto error; } memcpy(identity->external.blob, s, identity->external.blob_len); s += identity->external.blob_len; /* Read the length of the comment */ len -= 4; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } comment_len = _libssh2_ntohu32(s); s += 4; /* Read the comment */ len -= comment_len; if (len < 0) { rc = LIBSSH2_ERROR_AGENT_PROTOCOL; goto error; } identity->external.comment = LIBSSH2_ALLOC(agent->session, comment_len + 1); if (!identity->external.comment) { rc = LIBSSH2_ERROR_ALLOC; goto error; } identity->external.comment[comment_len] = '\0'; memcpy(identity->external.comment, s, comment_len); s += comment_len; _libssh2_list_add(&agent->head, &identity->node); } error: LIBSSH2_FREE(agent->session, transctx->response); transctx->response = NULL; return rc; } static void agent_free_identities(LIBSSH2_AGENT *agent) { struct agent_publickey *node; struct agent_publickey *next; for (node = _libssh2_list_first(&agent->head); node; node = next) { next = _libssh2_list_next(&node->node); LIBSSH2_FREE(agent->session, node->external.blob); LIBSSH2_FREE(agent->session, node->external.comment); LIBSSH2_FREE(agent->session, node); } _libssh2_list_init(&agent->head); } #define AGENT_PUBLICKEY_MAGIC 0x3bdefed2 /* * agent_publickey_to_external() * * Copies data from the internal to the external representation struct. * */ static struct libssh2_agent_publickey * agent_publickey_to_external(struct agent_publickey *node) { struct libssh2_agent_publickey *ext = &node->external; ext->magic = AGENT_PUBLICKEY_MAGIC; ext->node = node; return ext; } /* * libssh2_agent_init * * Init an ssh-agent handle. Returns the pointer to the handle. * */ LIBSSH2_API LIBSSH2_AGENT * libssh2_agent_init(LIBSSH2_SESSION *session) { LIBSSH2_AGENT *agent; agent = LIBSSH2_ALLOC(session, sizeof *agent); if (!agent) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate space for agent connection", 0); return NULL; } memset(agent, 0, sizeof *agent); agent->session = session; _libssh2_list_init(&agent->head); return agent; } /* * libssh2_agent_connect() * * Connect to an ssh-agent. * * Returns 0 if succeeded, or a negative value for error. */ LIBSSH2_API int libssh2_agent_connect(LIBSSH2_AGENT *agent) { int i, rc = -1; for (i = 0; supported_backends[i].name; i++) { agent->ops = supported_backends[i].ops; rc = agent->ops->connect(agent); if (!rc) return 0; } return rc; } /* * libssh2_agent_list_identities() * * Request ssh-agent to list identities. * * Returns 0 if succeeded, or a negative value for error. */ LIBSSH2_API int libssh2_agent_list_identities(LIBSSH2_AGENT *agent) { memset(&agent->transctx, 0, sizeof agent->transctx); /* Abondon the last fetched identities */ agent_free_identities(agent); return agent_list_identities(agent); } /* * libssh2_agent_get_identity() * * Traverse the internal list of public keys. Pass NULL to 'prev' to get * the first one. Or pass a poiner to the previously returned one to get the * next. * * Returns: * 0 if a fine public key was stored in 'store' * 1 if end of public keys * [negative] on errors */ LIBSSH2_API int libssh2_agent_get_identity(LIBSSH2_AGENT *agent, struct libssh2_agent_publickey **ext, struct libssh2_agent_publickey *oprev) { struct agent_publickey *node; if (oprev && oprev->node) { /* we have a starting point */ struct agent_publickey *prev = oprev->node; /* get the next node in the list */ node = _libssh2_list_next(&prev->node); } else node = _libssh2_list_first(&agent->head); if (!node) /* no (more) node */ return 1; *ext = agent_publickey_to_external(node); return 0; } /* * libssh2_agent_userauth() * * Do publickey user authentication with the help of ssh-agent. * * Returns 0 if succeeded, or a negative value for error. */ LIBSSH2_API int libssh2_agent_userauth(LIBSSH2_AGENT *agent, const char *username, struct libssh2_agent_publickey *identity) { void *abstract = agent; if (agent->session->userauth_pblc_state == libssh2_NB_state_idle) { memset(&agent->transctx, 0, sizeof agent->transctx); agent->identity = identity->node; } return libssh2_userauth_publickey(agent->session, username, identity->blob, identity->blob_len, agent_sign, &abstract); } /* * libssh2_agent_disconnect() * * Close a connection to an ssh-agent. * * Returns 0 if succeeded, or a negative value for error. */ LIBSSH2_API int libssh2_agent_disconnect(LIBSSH2_AGENT *agent) { if (agent->ops && agent->fd >= 0) return agent->ops->disconnect(agent); return 0; } /* * libssh2_agent_free() * * Free an ssh-agent handle. This function also frees the internal * collection of public keys. */ LIBSSH2_API void libssh2_agent_free(LIBSSH2_AGENT *agent) { /* Allow connection freeing when the socket has lost its connection */ if (agent->fd >= 0) { libssh2_agent_disconnect(agent); } agent_free_identities(agent); LIBSSH2_FREE(agent->session, agent); }