summaryrefslogtreecommitdiffstats
path: root/ssh-pkcs11-helper.c
diff options
context:
space:
mode:
Diffstat (limited to 'ssh-pkcs11-helper.c')
-rw-r--r--ssh-pkcs11-helper.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/ssh-pkcs11-helper.c b/ssh-pkcs11-helper.c
new file mode 100644
index 000000000..f9962709b
--- /dev/null
+++ b/ssh-pkcs11-helper.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2010 Markus Friedl. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "xmalloc.h"
+#include "buffer.h"
+#include "log.h"
+#include "misc.h"
+#include "key.h"
+#include "authfd.h"
+#include "ssh-pkcs11.h"
+
+/* borrows code from sftp-server and ssh-agent */
+
+struct pkcs11_keyinfo {
+ Key *key;
+ char *providername;
+ TAILQ_ENTRY(pkcs11_keyinfo) next;
+};
+
+TAILQ_HEAD(, pkcs11_keyinfo) pkcs11_keylist;
+
+#define MAX_MSG_LENGTH 10240 /*XXX*/
+
+/* helper */
+#define get_int() buffer_get_int(&iqueue);
+#define get_string(lenp) buffer_get_string(&iqueue, lenp);
+
+/* input and output queue */
+Buffer iqueue;
+Buffer oqueue;
+
+static void
+add_key(Key *k, char *name)
+{
+ struct pkcs11_keyinfo *ki;
+
+ ki = xcalloc(1, sizeof(*ki));
+ ki->providername = xstrdup(name);
+ ki->key = k;
+ TAILQ_INSERT_TAIL(&pkcs11_keylist, ki, next);
+}
+
+static void
+del_keys_by_name(char *name)
+{
+ struct pkcs11_keyinfo *ki, *nxt;
+
+ for (ki = TAILQ_FIRST(&pkcs11_keylist); ki; ki = nxt) {
+ nxt = TAILQ_NEXT(ki, next);
+ if (!strcmp(ki->providername, name)) {
+ TAILQ_REMOVE(&pkcs11_keylist, ki, next);
+ xfree(ki->providername);
+ key_free(ki->key);
+ free(ki);
+ }
+ }
+}
+
+/* lookup matching 'private' key */
+static Key *
+lookup_key(Key *k)
+{
+ struct pkcs11_keyinfo *ki;
+
+ TAILQ_FOREACH(ki, &pkcs11_keylist, next) {
+ debug("check %p %s", ki, ki->providername);
+ if (key_equal(k, ki->key))
+ return (ki->key);
+ }
+ return (NULL);
+}
+
+static void
+send_msg(Buffer *m)
+{
+ int mlen = buffer_len(m);
+
+ buffer_put_int(&oqueue, mlen);
+ buffer_append(&oqueue, buffer_ptr(m), mlen);
+ buffer_consume(m, mlen);
+}
+
+static void
+process_add(void)
+{
+ char *name, *pin;
+ Key **keys;
+ int i, nkeys;
+ u_char *blob;
+ u_int blen;
+ Buffer msg;
+
+ buffer_init(&msg);
+ name = get_string(NULL);
+ pin = get_string(NULL);
+ if ((nkeys = pkcs11_add_provider(name, pin, &keys)) > 0) {
+ buffer_put_char(&msg, SSH2_AGENT_IDENTITIES_ANSWER);
+ buffer_put_int(&msg, nkeys);
+ for (i = 0; i < nkeys; i++) {
+ key_to_blob(keys[i], &blob, &blen);
+ buffer_put_string(&msg, blob, blen);
+ buffer_put_cstring(&msg, name);
+ xfree(blob);
+ add_key(keys[i], name);
+ }
+ xfree(keys);
+ } else {
+ buffer_put_char(&msg, SSH_AGENT_FAILURE);
+ }
+ xfree(pin);
+ xfree(name);
+ send_msg(&msg);
+ buffer_free(&msg);
+}
+
+static void
+process_del(void)
+{
+ char *name, *pin;
+ Buffer msg;
+
+ buffer_init(&msg);
+ name = get_string(NULL);
+ pin = get_string(NULL);
+ del_keys_by_name(name);
+ if (pkcs11_del_provider(name) == 0)
+ buffer_put_char(&msg, SSH_AGENT_SUCCESS);
+ else
+ buffer_put_char(&msg, SSH_AGENT_FAILURE);
+ xfree(pin);
+ xfree(name);
+ send_msg(&msg);
+ buffer_free(&msg);
+}
+
+static void
+process_sign(void)
+{
+ u_char *blob, *data, *signature = NULL;
+ u_int blen, dlen, slen = 0;
+ int ok = -1, flags, ret;
+ Key *key, *found;
+ Buffer msg;
+
+ blob = get_string(&blen);
+ data = get_string(&dlen);
+ flags = get_int(); /* XXX ignore */
+
+ if ((key = key_from_blob(blob, blen)) != NULL) {
+ if ((found = lookup_key(key)) != NULL) {
+ slen = RSA_size(key->rsa);
+ signature = xmalloc(slen);
+ if ((ret = RSA_private_encrypt(dlen, data, signature,
+ found->rsa, RSA_PKCS1_PADDING)) != -1) {
+ slen = ret;
+ ok = 0;
+ }
+ }
+ key_free(key);
+ }
+ buffer_init(&msg);
+ if (ok == 0) {
+ buffer_put_char(&msg, SSH2_AGENT_SIGN_RESPONSE);
+ buffer_put_string(&msg, signature, slen);
+ } else {
+ buffer_put_char(&msg, SSH_AGENT_FAILURE);
+ }
+ xfree(data);
+ xfree(blob);
+ if (signature != NULL)
+ xfree(signature);
+ send_msg(&msg);
+ buffer_free(&msg);
+}
+
+static void
+process(void)
+{
+ u_int msg_len;
+ u_int buf_len;
+ u_int consumed;
+ u_int type;
+ u_char *cp;
+
+ buf_len = buffer_len(&iqueue);
+ if (buf_len < 5)
+ return; /* Incomplete message. */
+ cp = buffer_ptr(&iqueue);
+ msg_len = get_u32(cp);
+ if (msg_len > MAX_MSG_LENGTH) {
+ error("bad message len %d", msg_len);
+ cleanup_exit(11);
+ }
+ if (buf_len < msg_len + 4)
+ return;
+ buffer_consume(&iqueue, 4);
+ buf_len -= 4;
+ type = buffer_get_char(&iqueue);
+ switch (type) {
+ case SSH_AGENTC_ADD_SMARTCARD_KEY:
+ debug("process_add");
+ process_add();
+ break;
+ case SSH_AGENTC_REMOVE_SMARTCARD_KEY:
+ debug("process_del");
+ process_del();
+ break;
+ case SSH2_AGENTC_SIGN_REQUEST:
+ debug("process_sign");
+ process_sign();
+ break;
+ default:
+ error("Unknown message %d", type);
+ break;
+ }
+ /* discard the remaining bytes from the current packet */
+ if (buf_len < buffer_len(&iqueue)) {
+ error("iqueue grew unexpectedly");
+ cleanup_exit(255);
+ }
+ consumed = buf_len - buffer_len(&iqueue);
+ if (msg_len < consumed) {
+ error("msg_len %d < consumed %d", msg_len, consumed);
+ cleanup_exit(255);
+ }
+ if (msg_len > consumed)
+ buffer_consume(&iqueue, msg_len - consumed);
+}
+
+void
+cleanup_exit(int i)
+{
+ /* XXX */
+ _exit(i);
+}
+
+int
+main(int argc, char **argv)
+{
+ fd_set *rset, *wset;
+ int in, out, max, log_stderr = 0;
+ ssize_t len, olen, set_size;
+ SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
+ LogLevel log_level = SYSLOG_LEVEL_ERROR;
+ char buf[4*4096];
+
+ TAILQ_INIT(&pkcs11_keylist);
+ pkcs11_init(0);
+
+ extern char *optarg;
+ extern char *__progname;
+
+ log_init(__progname, log_level, log_facility, log_stderr);
+
+ in = STDIN_FILENO;
+ out = STDOUT_FILENO;
+
+ max = 0;
+ if (in > max)
+ max = in;
+ if (out > max)
+ max = out;
+
+ buffer_init(&iqueue);
+ buffer_init(&oqueue);
+
+ set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
+ rset = (fd_set *)xmalloc(set_size);
+ wset = (fd_set *)xmalloc(set_size);
+
+ for (;;) {
+ memset(rset, 0, set_size);
+ memset(wset, 0, set_size);
+
+ /*
+ * Ensure that we can read a full buffer and handle
+ * the worst-case length packet it can generate,
+ * otherwise apply backpressure by stopping reads.
+ */
+ if (buffer_check_alloc(&iqueue, sizeof(buf)) &&
+ buffer_check_alloc(&oqueue, MAX_MSG_LENGTH))
+ FD_SET(in, rset);
+
+ olen = buffer_len(&oqueue);
+ if (olen > 0)
+ FD_SET(out, wset);
+
+ if (select(max+1, rset, wset, NULL, NULL) < 0) {
+ if (errno == EINTR)
+ continue;
+ error("select: %s", strerror(errno));
+ cleanup_exit(2);
+ }
+
+ /* copy stdin to iqueue */
+ if (FD_ISSET(in, rset)) {
+ len = read(in, buf, sizeof buf);
+ if (len == 0) {
+ debug("read eof");
+ cleanup_exit(0);
+ } else if (len < 0) {
+ error("read: %s", strerror(errno));
+ cleanup_exit(1);
+ } else {
+ buffer_append(&iqueue, buf, len);
+ }
+ }
+ /* send oqueue to stdout */
+ if (FD_ISSET(out, wset)) {
+ len = write(out, buffer_ptr(&oqueue), olen);
+ if (len < 0) {
+ error("write: %s", strerror(errno));
+ cleanup_exit(1);
+ } else {
+ buffer_consume(&oqueue, len);
+ }
+ }
+
+ /*
+ * Process requests from client if we can fit the results
+ * into the output buffer, otherwise stop processing input
+ * and let the output queue drain.
+ */
+ if (buffer_check_alloc(&oqueue, MAX_MSG_LENGTH))
+ process();
+ }
+}