diff --git a/config.h b/config.h index 671c0eb88e837098473b7e2bd100f9ab3bf4b27f..88806d5099a1956ce2a80352fd5a37c33b84ecd1 100644 --- a/config.h +++ b/config.h @@ -3,6 +3,9 @@ # define DEBUG_OUTPUT 0 // change to 1 to enable debugging +# define PKCS11PROXY_LISTEN_BACKLOG 128 +# define PKCS11PROXY_MAX_SESSION_COUNT 256 + #ifdef __MINGW32__ # include <stdint.h> diff --git a/gck-rpc-daemon-standalone.c b/gck-rpc-daemon-standalone.c index ae10932b2b5a2817ae2b916b5f23b5c53cecba52..9b89f50c46bd8809553b4271e061f838bc4907be 100644 --- a/gck-rpc-daemon-standalone.c +++ b/gck-rpc-daemon-standalone.c @@ -170,6 +170,7 @@ int main(int argc, char *argv[]) fd_set read_fds; int sock, ret; CK_RV rv; + CK_C_INITIALIZE_ARGS init_args; if (install_syscall_reporter()) @@ -212,7 +213,10 @@ int main(int argc, char *argv[]) } /* RPC layer expects initialized module */ - rv = (funcs->C_Initialize) (NULL /* &p11_init_args */); + memset(&init_args, 0, sizeof(init_args)); + init_args.flags = CKF_OS_LOCKING_OK; + + rv = (funcs->C_Initialize) (&init_args); if (rv != CKR_OK) { fprintf(stderr, "couldn't initialize module: %s: 0x%08x\n", argv[1], (int)rv); diff --git a/gck-rpc-dispatch.c b/gck-rpc-dispatch.c index a0152f3880d011dc94719e4e01b8c574a109d128..a6f76343986527374e91b6fc46f54b86c34fa0b9 100644 --- a/gck-rpc-dispatch.c +++ b/gck-rpc-dispatch.c @@ -40,6 +40,8 @@ # include <arpa/inet.h> # include <netinet/in.h> # include <netinet/tcp.h> +# include <sys/types.h> +# include <netdb.h> #endif #include <pthread.h> @@ -57,6 +59,11 @@ static CK_FUNCTION_LIST_PTR pkcs11_module = NULL; #define PARSE_ERROR CKR_DEVICE_ERROR #define PREP_ERROR CKR_DEVICE_MEMORY +typedef struct { + CK_SESSION_HANDLE id; + CK_SLOT_ID slot; +} SessionState; + typedef struct _CallState { GckRpcMessage *req; GckRpcMessage *resp; @@ -66,6 +73,12 @@ typedef struct _CallState { int sock; int (*read)(int, unsigned char *,size_t); int (*write)(int, unsigned char *,size_t); + struct sockaddr_storage addr; + socklen_t addrlen; + /* XXX Maybe sessions should be a linked list instead, to remove the hard + * upper limit and reduce typical memory use. + */ + SessionState sessions[PKCS11PROXY_MAX_SESSION_COUNT]; } CallState; typedef struct _DispatchState { @@ -460,6 +473,16 @@ proto_read_attribute_array(CallState * cs, CK_ATTRIBUTE_PTR * result, (&msg->buffer, msg->parsed, &msg->parsed, &n_attrs)) return PARSE_ERROR; + if (! n_attrs) { + /* If there are no attributes, it makes most sense to make result + * a NULL pointer. What use could one have of a potentially dangling + * pointer anyways? + */ + *result = NULL_PTR; + *n_result = n_attrs; + return CKR_OK; + } + /* Allocate memory for the attribute structures */ attrs = call_alloc(cs, n_attrs * sizeof(CK_ATTRIBUTE)); if (!attrs) @@ -549,7 +572,7 @@ proto_write_attribute_array(CallState * cs, CK_ATTRIBUTE_PTR array, return CKR_OK; } -static CK_RV proto_read_null_string(CallState * cs, CK_UTF8CHAR_PTR * val) +static CK_RV proto_read_space_string(CallState * cs, CK_UTF8CHAR_PTR * val, CK_ULONG length) { GckRpcMessage *msg; const unsigned char *data; @@ -561,19 +584,18 @@ static CK_RV proto_read_null_string(CallState * cs, CK_UTF8CHAR_PTR * val) msg = cs->req; /* Check that we're supposed to have this at this point */ - assert(!msg->signature || gck_rpc_message_verify_part(msg, "z")); + assert(!msg->signature || gck_rpc_message_verify_part(msg, "s")); if (!egg_buffer_get_byte_array (&msg->buffer, msg->parsed, &msg->parsed, &data, &n_data)) return PARSE_ERROR; - /* Allocate a block of memory for it. The +1 accomodates the NULL byte. */ - *val = call_alloc(cs, n_data + 1); + /* Allocate a block of memory for it. */ + *val = call_alloc(cs, n_data); if (!*val) return CKR_DEVICE_MEMORY; memcpy(*val, data, n_data); - (*val)[n_data] = 0; return CKR_OK; } @@ -754,8 +776,8 @@ static CK_RV proto_write_session_info(CallState * cs, CK_SESSION_INFO_PTR info) if (!gck_rpc_message_read_ulong (cs->req, &val)) \ { _ret = PARSE_ERROR; goto _cleanup; } -#define IN_STRING(val) \ - _ret = proto_read_null_string (cs, &val); \ +#define IN_SPACE_STRING(val, len) \ + _ret = proto_read_space_string (cs, &val, len); \ if (_ret != CKR_OK) goto _cleanup; #define IN_BYTE_BUFFER(buffer, buffer_len_ptr) \ @@ -838,6 +860,7 @@ static CK_RV rpc_C_Initialize(CallState * cs) /* Check to make sure the header matches */ if (n_handshake != GCK_RPC_HANDSHAKE_LEN || + handshake == NULL_PTR || memcmp(handshake, GCK_RPC_HANDSHAKE, n_handshake) != 0) { gck_rpc_warn ("invalid handshake received from connecting module"); @@ -858,9 +881,7 @@ static CK_RV rpc_C_Initialize(CallState * cs) static CK_RV rpc_C_Finalize(CallState * cs) { - CK_SLOT_ID_PTR slots; - CK_ULONG n_slots, i; - CK_SLOT_ID appartment; + CK_ULONG i; CK_RV ret; DispatchState *ds, *next; @@ -874,27 +895,17 @@ static CK_RV rpc_C_Finalize(CallState * cs) * We don't actually C_Finalize lower layers, since this would finalize * for all appartments, client applications. Anyway this is done by * the code that loaded us. - * - * But we do need to cleanup resources used by this client, so instead - * we call C_CloseAllSessions for each appartment for this client. */ - ret = (pkcs11_module->C_GetSlotList) (TRUE, NULL, &n_slots); - if (ret == CKR_OK) { - slots = calloc(n_slots, sizeof(CK_SLOT_ID)); - if (slots == NULL) { - ret = CKR_DEVICE_MEMORY; - } else { - ret = - (pkcs11_module->C_GetSlotList) (TRUE, slots, - &n_slots); - for (i = 0; ret == CKR_OK && i < n_slots; ++i) { - appartment = slots[i]; - ret = - (pkcs11_module-> - C_CloseAllSessions) (appartment); - } - free(slots); + /* Close all sessions that have been opened by this thread, regardless of slot */ + for (i = 0; i < PKCS11PROXY_MAX_SESSION_COUNT; i++) { + if (cs->sessions[i].id) { + gck_rpc_log("Closing session %li on position %i", cs->sessions[i].id, i); + + ret = (pkcs11_module->C_CloseSession) (cs->sessions[i].id); + if (ret != CKR_OK) + break; + cs->sessions[i].id = 0; } } @@ -1021,7 +1032,7 @@ static CK_RV rpc_C_InitToken(CallState * cs) BEGIN_CALL(C_InitToken); IN_ULONG(slot_id); IN_BYTE_ARRAY(pin, pin_len); - IN_STRING(label); + IN_SPACE_STRING(label, 32); PROCESS_CALL((slot_id, pin, pin_len, label)); END_CALL; } @@ -1053,6 +1064,21 @@ static CK_RV rpc_C_OpenSession(CallState * cs) IN_ULONG(slot_id); IN_ULONG(flags); PROCESS_CALL((slot_id, flags, NULL, NULL, &session)); + if (_ret == CKR_OK) { + int i; + /* Remember this thread opened this session. Needed for C_CloseAllSessions. */ + for (i = 0; i < PKCS11PROXY_MAX_SESSION_COUNT; i++) { + if (! cs->sessions[i].id) { + cs->sessions[i].id = session; + cs->sessions[i].slot = slot_id; + gck_rpc_log("Session %li stored in position %i", session, i); + break; + } + } + if (i == PKCS11PROXY_MAX_SESSION_COUNT) { + _ret = CKR_SESSION_COUNT; goto _cleanup; + } + } OUT_ULONG(session); END_CALL; } @@ -1064,18 +1090,61 @@ static CK_RV rpc_C_CloseSession(CallState * cs) BEGIN_CALL(C_CloseSession); IN_ULONG(session); PROCESS_CALL((session)); + if (_ret == CKR_OK) { + int i; + /* Remove this session from this threads list */ + for (i = 0; i < PKCS11PROXY_MAX_SESSION_COUNT; i++) { + if (cs->sessions[i].id == session) { + gck_rpc_log("Session %li removed from position %i", session, i); + cs->sessions[i].id = 0; + break; + } + } + if (i == PKCS11PROXY_MAX_SESSION_COUNT) { + /* Ignore errors, like with close() */ + gck_rpc_log("C_CloseSession on unknown session"); + } + } END_CALL; } static CK_RV rpc_C_CloseAllSessions(CallState * cs) { CK_SLOT_ID slot_id; + CK_SLOT_INFO slotInfo; + int i; - /* Slot id becomes appartment so lower layers can tell clients apart. */ + /* Close all sessions that have been opened by this thread. PKCS#11 (v2.2) says + * C_CloseAllSessions closes all the sessions opened by one application, leaving + * sessions opened by other applications alone even if the sessions share slot. + * + * Each application on the client side of pkcs11-proxy will mean different thread + * on the server side, so we should close all sessions for a slot opened in this + * thread. + */ BEGIN_CALL(C_CloseAllSessions); IN_ULONG(slot_id); - PROCESS_CALL((slot_id)); + + /* To emulate real C_CloseAllSessions (well, the SoftHSM one) we check if slot_id is valid. */ + _ret = pkcs11_module->C_GetSlotInfo(slot_id, &slotInfo); + if (_ret != CKR_OK) + goto _cleanup; + + for (i = 0; i < PKCS11PROXY_MAX_SESSION_COUNT; i++) { + if (cs->sessions[i].id && (cs->sessions[i].slot == slot_id)) { + gck_rpc_log("Closing session %li on position %i with slot %i", cs->sessions[i].id, i, slot_id); + + _ret = (pkcs11_module->C_CloseSession) (cs->sessions[i].id); + if (_ret == CKR_OK || + _ret == CKR_SESSION_CLOSED || + _ret == CKR_SESSION_HANDLE_INVALID) { + cs->sessions[i].id = 0; + } + if (_ret != CKR_OK) + goto _cleanup; + } + } END_CALL; } @@ -1909,6 +1978,9 @@ static CK_RV rpc_C_GenerateRandom(CallState * cs) BEGIN_CALL(C_GenerateRandom); IN_ULONG(session); IN_BYTE_BUFFER(random_data, random_len); + if (random_len == NULL_PTR) { + _ret = PARSE_ERROR; goto _cleanup; + } PROCESS_CALL((session, random_data, *random_len)); OUT_BYTE_ARRAY(random_data, random_len); END_CALL; @@ -2127,17 +2199,27 @@ static int write_all(int sock, unsigned char *data, size_t len) static void run_dispatch_loop(CallState *cs) { unsigned char buf[4]; - uint32_t len; + uint32_t len, res; + char hoststr[NI_MAXHOST], portstr[NI_MAXSERV]; assert(cs->sock != -1); + if ((res = getnameinfo((struct sockaddr *) & cs->addr, cs->addrlen, + hoststr, sizeof(hoststr), portstr, sizeof(portstr), + NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { + gck_rpc_warn("couldn't call getnameinfo on client addr: %.100s", + gai_strerror(res)); + hoststr[0] = portstr[0] = '\0'; + } + /* The client application */ if (!cs->read(cs->sock, (unsigned char *)&cs->appid, sizeof (cs->appid))) { gck_rpc_warn("Can't read appid\n"); return ; } - gck_rpc_log("New session %d-%d\n", (uint32_t) (cs->appid >> 32), - (uint32_t) cs->appid); + + gck_rpc_log("New session %d-%d (client %s, port %s)\n", (uint32_t) (cs->appid >> 32), + (uint32_t) cs->appid, hoststr, portstr); /* Setup our buffers */ if (!call_init(cs)) { @@ -2219,7 +2301,7 @@ static char pkcs11_socket_path[MAXPATHLEN] = { 0, }; void gck_rpc_layer_accept(void) { - struct sockaddr_un addr; + struct sockaddr_storage addr; DispatchState *ds, **here; int error; socklen_t addrlen; @@ -2258,6 +2340,8 @@ void gck_rpc_layer_accept(void) ds->cs.sock = new_fd; ds->cs.read = &read_all; ds->cs.write = &write_all; + ds->cs.addr = addr; + ds->cs.addrlen = addrlen; error = pthread_create(&ds->thread, NULL, run_dispatch_thread, &(ds->cs)); @@ -2287,6 +2371,99 @@ void gck_rpc_layer_inetd(CK_FUNCTION_LIST_PTR module) run_dispatch_thread(&cs); } +/* + * Try to get a listening socket for host and port (host may be either a name or + * an IP address, as string) and port (a string with a service name or a port + * number). + * + * Returns -1 on failure, and the socket fd otherwise. + */ +static int _get_listening_socket(char *host, char *port) +{ + char hoststr[NI_MAXHOST], portstr[NI_MAXSERV]; + struct addrinfo *ai, *first, hints; + int res, sock, one = 1; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = AI_PASSIVE; /* Want addr for bind() */ + hints.ai_family = AF_UNSPEC; /* Either IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; /* Only stream oriented sockets */ + + if ((res = getaddrinfo(host, port, &hints, &ai)) < 0) { + gck_rpc_warn("couldn't resolve host '%.100s' or service '%.100s' : %.100s\n", + host, port, gai_strerror(res)); + return -1; + } + + sock = -1; + first = ai; + + /* Loop through the sockets returned and see if we can find one that accepts + * our options and bind() + */ + while (ai) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + + if (sock >= 0) { + if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + (char *)&one, sizeof (one)) == -1) { + gck_rpc_warn("couldn't set pkcs11 " + "socket protocol options (%.100s %.100s): %.100s", + host, port, strerror (errno)); + goto next; + } + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char *)&one, sizeof(one)) == -1) { + gck_rpc_warn + ("couldn't set pkcs11 socket options (%.100s %.100s): %.100s", + host, port, strerror(errno)); + goto next; + } + + if (bind(sock, ai->ai_addr, ai->ai_addrlen) == 0) + break; + + next: + close(sock); + sock = -1; + } + ai = ai->ai_next; + } + + if (sock < 0) { + gck_rpc_warn("couldn't create pkcs11 socket (%.100s %.100s): %.100s\n", + host, port, strerror(errno)); + sock = -1; + goto out; + } + + if (listen(sock, PKCS11PROXY_LISTEN_BACKLOG) < 0) { + gck_rpc_warn("couldn't listen on pkcs11 socket (%.100s %.100s): %.100s", + host, port, strerror(errno)); + sock = -1; + goto out; + } + + /* Format a string describing the socket we're listening on into pkcs11_socket_path */ + if ((res = getnameinfo(ai->ai_addr, ai->ai_addrlen, + hoststr, sizeof(hoststr), portstr, sizeof(portstr), + NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { + gck_rpc_warn("couldn't call getnameinfo on pkcs11 socket (%.100s %.100s): %.100s", + host, port, gai_strerror(res)); + sock = -1; + goto out; + } + + snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), + (ai->ai_family == AF_INET6) ? "[%s]:%s" : "%s:%s", hoststr, portstr); + + out: + freeaddrinfo(first); + + return sock; +} + int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module) { struct sockaddr_un addr; @@ -2320,60 +2497,26 @@ int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module) #endif if (!strncmp("tcp://", prefix, 6)) { - int one = 1, port; - char *p = NULL; - char *ip; - - ip = strdup(prefix + 6); - if (ip == NULL) { - gck_rpc_warn("out of memory"); - return -1; - } - - p = strchr(ip, ':'); - - if (p) { - *p = '\0'; - port = strtol(p + 1, NULL, 0); - } else - port = 0; - - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - - if (sock < 0) { - gck_rpc_warn("couldn't create pkcs11 socket: %s", - strerror(errno)); - return -1; - } - - if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, - (char *)&one, sizeof (one)) == -1) { - gck_rpc_warn("couldn't set pkcs11 " - "socket options : %s", strerror (errno)); - return -1; - } + /* + * TCP socket + */ + char *host, *port; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - (char *)&one, sizeof(one)) == -1) { - gck_rpc_warn - ("couldn't set pkcs11 socket options : %s", - strerror(errno)); + if (! gck_rpc_parse_host_port(prefix + 6, &host, &port)) { + free(host); return -1; } - addr.sun_family = AF_INET; - if (inet_aton(ip, &((struct sockaddr_in *)&addr)->sin_addr) == - 0) { - gck_rpc_warn("bad inet address : %s", ip); + if ((sock = _get_listening_socket(host, port)) == -1) { + free(host); return -1; } - ((struct sockaddr_in *)&addr)->sin_port = htons(port); - snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), - "%s", prefix); - - free(ip); + free(host); } else { + /* + * UNIX domain socket + */ snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), "%s/socket.pkcs11", prefix); @@ -2389,34 +2532,18 @@ int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module) unlink(pkcs11_socket_path); strncpy(addr.sun_path, pkcs11_socket_path, sizeof(addr.sun_path)); - } - - if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - gck_rpc_warn("couldn't bind to pkcs11 socket: %s: %s", - pkcs11_socket_path, strerror(errno)); - return -1; - } - - if (listen(sock, 128) < 0) { - gck_rpc_warn("couldn't listen on pkcs11 socket: %s: %s", - pkcs11_socket_path, strerror(errno)); - return -1; - } - - if (!strncmp("tcp://", prefix, 6)) { - struct sockaddr_in sa; - socklen_t sa_len; - - sa_len = sizeof(sa); - if (getsockname(sock, (struct sockaddr *)&sa, &sa_len) == -1) { - gck_rpc_warn("getsockname failed on pkcs11 socket: %s: %s", + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + gck_rpc_warn("couldn't bind to pkcs11 socket: %s: %s", pkcs11_socket_path, strerror(errno)); return -1; } - snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), - "%s:%d", inet_ntoa(sa.sin_addr), ntohs(sa.sin_port)); + if (listen(sock, PKCS11PROXY_LISTEN_BACKLOG) < 0) { + gck_rpc_warn("couldn't listen on pkcs11 socket: %s: %s", + pkcs11_socket_path, strerror(errno)); + return -1; + } } gck_rpc_log("Listening on: %s\n", pkcs11_socket_path); diff --git a/gck-rpc-message.c b/gck-rpc-message.c index 440d88dbdbae992ea942072da563685bc806d2fe..9bb10f18614dabaf5ae8c3a6a6b566497fa2d9aa 100644 --- a/gck-rpc-message.c +++ b/gck-rpc-message.c @@ -475,15 +475,8 @@ gck_rpc_message_write_space_string(GckRpcMessage * msg, CK_UTF8CHAR * buffer, assert(!msg->signature || gck_rpc_message_verify_part(msg, "s")); + /* XXX it's not really right to treat UTF-8 input as a byte buffer, + * although CK_UTF8CHAR is currently typedef'd to unsigned char in pkcs11.h. + */ return egg_buffer_add_byte_array(&msg->buffer, buffer, length); } - -int gck_rpc_message_write_zero_string(GckRpcMessage * msg, CK_UTF8CHAR * string) -{ - assert(msg); - assert(string); - - assert(!msg->signature || gck_rpc_message_verify_part(msg, "z")); - - return egg_buffer_add_string(&msg->buffer, (const char *)string); -} diff --git a/gck-rpc-module.c b/gck-rpc-module.c index fe315ac5b3d016ae5f990bc3754de2769a2a5225..1ce0665af712e9d1fc5569cc11895d35abfd3026 100644 --- a/gck-rpc-module.c +++ b/gck-rpc-module.c @@ -38,6 +38,7 @@ #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/tcp.h> +# include <netdb.h> #endif #include <stdlib.h> @@ -130,8 +131,7 @@ static void parse_arguments(const char *string) return; } - arg = at = src; - for (src = dup; *src; src++) { + for (arg = at = src; *src; src++) { /* Matching quote */ if (quote == *src) { @@ -312,6 +312,89 @@ static CK_RV call_read(CallState * cs, unsigned char *data, size_t len) return CKR_OK; } +static int _connect_to_host_port(char *host, char *port) +{ + char hoststr[NI_MAXHOST], portstr[NI_MAXSERV], hostport[NI_MAXHOST + NI_MAXSERV + 1]; + struct addrinfo *ai, *first, hints; + int res, sock, one = 1; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Either IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; /* Only stream oriented sockets */ + + if ((res = getaddrinfo(host, port, &hints, &ai)) < 0) { + gck_rpc_warn("couldn't resolve host '%.100s' or service '%.100s' : %.100s\n", + host, port, gai_strerror(res)); + return -1; + } + + sock = -1; + first = ai; + + /* Loop through the sockets returned and see if we can find one that accepts + * our options and connect() + */ + while (ai) { + if ((res = getnameinfo(ai->ai_addr, ai->ai_addrlen, + hoststr, sizeof(hoststr), portstr, sizeof(portstr), + NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { + gck_rpc_warn("couldn't call getnameinfo on pkcs11 socket (%.100s %.100s): %.100s", + host, port, gai_strerror(res)); + sock = -1; + continue; + } + + snprintf(hostport, sizeof(hostport), + (ai->ai_family == AF_INET6) ? "[%s]:%s" : "%s:%s", hoststr, portstr); + + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + + if (sock >= 0) { + if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + (char *)&one, sizeof (one)) == -1) { + gck_rpc_warn("couldn't set pkcs11 " + "socket protocol options (%.100s): %.100s", + hostport, strerror (errno)); + goto next; + } + +#ifndef __MINGW32__ + /* close on exec */ + if (fcntl(sock, F_SETFD, 1) == -1) { + gck_rpc_warn("couldn't secure socket (%.100s): %.100s", + hostport, strerror(errno)); + goto next; + } +#endif + + if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) { + close(sock); + warning(("couldn't connect (%.100s): %s", + hostport, strerror(errno))); + goto next; + } + + break; + next: + close(sock); + sock = -1; + } + ai = ai->ai_next; + } + + if (sock < 0) { + gck_rpc_warn("couldn't create pkcs11 socket (%.100s): %.100s\n", + pkcs11_socket_path, strerror(errno)); + sock = -1; + goto out; + } + + out: + freeaddrinfo(first); + + return sock; +} + static CK_RV call_connect(CallState * cs) { struct sockaddr_un addr; @@ -327,45 +410,20 @@ static CK_RV call_connect(CallState * cs) memset(&addr, 0, sizeof(addr)); if (!strncmp("tcp://", pkcs11_socket_path, 6)) { - int one = 1, port; - char *p = NULL; - const char *ip; + char *host, *port; - ip = strdup(pkcs11_socket_path + 6); - if (ip) - p = strchr(ip, ':'); - - if (!p || !ip) { - gck_rpc_warn("invalid syntax for pkcs11 socket : %s", + if (! gck_rpc_parse_host_port(pkcs11_socket_path + 6, &host, &port)) { + gck_rpc_warn("failed parsing pkcs11 socket : %s", pkcs11_socket_path); return CKR_DEVICE_ERROR; } - *p = '\0'; - port = strtol(p + 1, NULL, 0); - - sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) { - gck_rpc_warn("couldn't create pkcs11 socket: %s", - strerror(errno)); + if ((sock = _connect_to_host_port(host, port)) == -1) { + free(host); return CKR_DEVICE_ERROR; } - if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, - (char *)&one, sizeof(one)) == -1) { - gck_rpc_warn - ("couldn't create set pkcs11 socket options : %s", - strerror(errno)); - return CKR_DEVICE_ERROR; - } - - addr.sun_family = AF_INET; - if (inet_aton(ip, &((struct sockaddr_in *)&addr)->sin_addr) == - 0) { - gck_rpc_warn("bad inet address : %s", ip); - return CKR_DEVICE_ERROR; - } - ((struct sockaddr_in *)&addr)->sin_port = htons(port); + free(host); } else { addr.sun_family = AF_UNIX; strncpy(addr.sun_path, pkcs11_socket_path, @@ -376,22 +434,23 @@ static CK_RV call_connect(CallState * cs) warning(("couldn't open socket: %s", strerror(errno))); return CKR_DEVICE_ERROR; } - } + #ifndef __MINGW32__ - /* close on exec */ - if (fcntl(sock, F_SETFD, 1) == -1) { - close(sock); - warning(("couldn't secure socket: %s", strerror(errno))); - return CKR_DEVICE_ERROR; - } + /* close on exec */ + if (fcntl(sock, F_SETFD, 1) == -1) { + close(sock); + warning(("couldn't secure socket: %s", strerror(errno))); + return CKR_DEVICE_ERROR; + } #endif - if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - close(sock); - warning(("couldn't connect to: %s: %s", pkcs11_socket_path, - strerror(errno))); - return CKR_DEVICE_ERROR; + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(sock); + warning(("couldn't connect to: %s: %s", pkcs11_socket_path, + strerror(errno))); + return CKR_DEVICE_ERROR; + } } cs->socket = sock; @@ -741,6 +800,9 @@ proto_read_attribute_array(GckRpcMessage * msg, CK_ATTRIBUTE_PTR arr, return PARSE_ERROR; } attrlen = value; + } else { + warning(("failed reading byte array")); + return PARSE_ERROR; } } @@ -1068,8 +1130,8 @@ proto_read_sesssion_info(GckRpcMessage * msg, CK_SESSION_INFO_PTR info) if (!gck_rpc_message_write_ulong (_cs->req, val)) \ { _ret = CKR_HOST_MEMORY; goto _cleanup; } -#define IN_STRING(val) \ - if (!gck_rpc_message_write_zero_string (_cs->req, val)) \ +#define IN_SPACE_STRING(val, len) \ + if (!gck_rpc_message_write_space_string (_cs->req, val, len)) \ { _ret = CKR_HOST_MEMORY; goto _cleanup; } #define IN_BYTE_BUFFER(arr, len) \ @@ -1085,6 +1147,9 @@ proto_read_sesssion_info(GckRpcMessage * msg, CK_SESSION_INFO_PTR info) #define IN_ULONG_BUFFER(arr, len) \ if (len == NULL) \ { _ret = CKR_ARGUMENTS_BAD; goto _cleanup; } \ + IN_ULONG_BUFFER2(arr, len); + +#define IN_ULONG_BUFFER2(arr, len) \ if (!gck_rpc_message_write_ulong_buffer (_cs->req, arr ? *len : 0)) \ { _ret = CKR_HOST_MEMORY; goto _cleanup; } @@ -1127,7 +1192,10 @@ proto_read_sesssion_info(GckRpcMessage * msg, CK_SESSION_INFO_PTR info) #define OUT_BYTE_ARRAY(arr, len) \ if (len == NULL) \ _ret = CKR_ARGUMENTS_BAD; \ - if (_ret == CKR_OK) \ + OUT_BYTE_ARRAY2(arr, len); + +#define OUT_BYTE_ARRAY2(arr, len) \ + if (_ret == CKR_OK) \ _ret = proto_read_byte_array (_cs->resp, (arr), (len), *(len)); #define OUT_ULONG_ARRAY(a, len) \ @@ -1204,6 +1272,7 @@ static CK_RV rpc_C_Initialize(CK_VOID_PTR init_args) /* pReserved must be NULL */ args = init_args; + /* XXX since we're never going to call the supplied mutex functions, shouldn't we reject them? */ /* ALL supplied function pointers need to have the value either NULL or non-NULL. */ supplied_ok = (args->CreateMutex == NULL && args->DestroyMutex == NULL @@ -1247,9 +1316,8 @@ static CK_RV rpc_C_Initialize(CK_VOID_PTR init_args) } } - /* Lookup the socket path, append '.pkcs11' */ + /* Lookup the socket path, append '.pkcs11' if it is a domain socket. */ if (pkcs11_socket_path[0] == 0) { - pkcs11_socket_path[0] = 0; path = getenv("PKCS11_PROXY_SOCKET"); if (path && path[0]) { if (!strncmp("tcp://", path, 6)) @@ -1431,7 +1499,7 @@ rpc_C_InitToken(CK_SLOT_ID id, CK_UTF8CHAR_PTR pin, CK_ULONG pin_len, BEGIN_CALL(C_InitToken); IN_ULONG(id); IN_BYTE_ARRAY(pin, pin_len); - IN_STRING(label); + IN_SPACE_STRING(label, 32); PROCESS_CALL; END_CALL; } @@ -1713,7 +1781,7 @@ rpc_C_FindObjects(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE_PTR objects, BEGIN_CALL(C_FindObjects); IN_ULONG(session); - IN_ULONG_BUFFER(objects, &max_count); + IN_ULONG_BUFFER2(objects, &max_count); PROCESS_CALL; *count = max_count; OUT_ULONG_ARRAY(objects, count); @@ -2272,7 +2340,7 @@ rpc_C_GenerateRandom(CK_SESSION_HANDLE session, CK_BYTE_PTR random_data, IN_ULONG(session); IN_BYTE_BUFFER(random_data, &random_len); PROCESS_CALL; - OUT_BYTE_ARRAY(random_data, &random_len); + OUT_BYTE_ARRAY2(random_data, &random_len); END_CALL; } diff --git a/gck-rpc-private.h b/gck-rpc-private.h index 02c1e9d2a591c22793ac8cabe8c02ccc8967aa05..f888c418b6cb29472b296bc420fc94a758feb109 100644 --- a/gck-rpc-private.h +++ b/gck-rpc-private.h @@ -130,7 +130,6 @@ typedef struct _GckRpcCall { * s = space padded string * v = CK_VERSION * y = CK_BYTE - * z = null terminated string */ static const GckRpcCall gck_rpc_calls[] = { @@ -144,7 +143,7 @@ static const GckRpcCall gck_rpc_calls[] = { "ssssuuuuuuuuuuuvvs"}, {GCK_RPC_CALL_C_GetMechanismList, "C_GetMechanismList", "ufu", "au"}, {GCK_RPC_CALL_C_GetMechanismInfo, "C_GetMechanismInfo", "uu", "uuu"}, - {GCK_RPC_CALL_C_InitToken, "C_InitToken", "uayz", ""}, + {GCK_RPC_CALL_C_InitToken, "C_InitToken", "uays", ""}, {GCK_RPC_CALL_C_WaitForSlotEvent, "C_WaitForSlotEvent", "u", "u"}, {GCK_RPC_CALL_C_OpenSession, "C_OpenSession", "uu", "u"}, {GCK_RPC_CALL_C_CloseSession, "C_CloseSession", "u", ""}, @@ -216,7 +215,7 @@ static const GckRpcCall gck_rpc_calls[] = { #endif #define GCK_RPC_HANDSHAKE \ - "PRIVATE-GNOME-KEYRING-PKCS11-PROTOCOL-V-2" + "PRIVATE-GNOME-KEYRING-PKCS11-PROTOCOL-V-3" #define GCK_RPC_HANDSHAKE_LEN \ (sizeof (GCK_RPC_HANDSHAKE) - 1) @@ -263,9 +262,6 @@ int gck_rpc_message_write_byte(GckRpcMessage * msg, CK_BYTE val); int gck_rpc_message_write_ulong(GckRpcMessage * msg, CK_ULONG val); -int gck_rpc_message_write_zero_string(GckRpcMessage * msg, - CK_UTF8CHAR * string); - int gck_rpc_message_write_space_string(GckRpcMessage * msg, CK_UTF8CHAR * buffer, CK_ULONG length); @@ -328,4 +324,7 @@ int gck_rpc_mechanism_has_no_parameters(CK_MECHANISM_TYPE mech); int gck_rpc_has_bad_sized_ulong_parameter(CK_ATTRIBUTE_PTR attr); int gck_rpc_has_ulong_parameter(CK_ATTRIBUTE_TYPE type); +/* Parses strings (prefix) to host and port components. */ +int gck_rpc_parse_host_port(const char *prefix, char **host, char **port); + #endif /* GCK_RPC_CALLS_H */ diff --git a/gck-rpc-util.c b/gck-rpc-util.c index 85a458755df9d9232499d15e3cf0ae2b4e3d85b8..cf3c745cd3be996ca03094e2a089e427f4b738fa 100644 --- a/gck-rpc-util.c +++ b/gck-rpc-util.c @@ -229,3 +229,55 @@ gck_rpc_has_bad_sized_ulong_parameter(CK_ATTRIBUTE_PTR attr) return 0; return gck_rpc_has_ulong_parameter(attr->type); } + +/* + * Parses prefix into two strings (host and port). Port may be a NULL pointer + * if none is specified. Since this code does not decode port in any way, a + * service name works too (but requires other code (like + * _get_listening_socket()) able to resolve service names). + * + * This should work for IPv4 and IPv6 inputs : + * + * 0.0.0.0:2345 + * 0.0.0.0 + * [::]:2345 + * [::] + * [::1]:2345 + * localhost:2345 + * localhost + * localhost:p11proxy (if p11proxy is a known service name) + * + * Returns 0 on failure, and 1 on success. + */ +int gck_rpc_parse_host_port(const char *prefix, char **host, char **port) +{ + char *p = NULL; + int is_ipv6; + + is_ipv6 = (prefix[0] == '[') ? 1 : 0; + + *host = strdup(prefix + is_ipv6); + *port = NULL; + + if (*host == NULL) { + gck_rpc_warn("out of memory"); + return 0; + } + + if (is_ipv6 && prefix[0] == '[') + p = strchr(*host, ']'); + else + p = strchr(*host, ':'); + + if (p) { + is_ipv6 = (*p == ']'); /* remember if separator was ']' */ + + *p = '\0'; /* replace separator will NULL to terminate *host */ + *port = p + 1; + + if (is_ipv6 && (**port == ':')) + *port = p + 2; + } + + return 1; +} diff --git a/p11proxy-mitm b/p11proxy-mitm new file mode 100755 index 0000000000000000000000000000000000000000..a64015093ebd3d03c181ff41a36d1d7548461d91 --- /dev/null +++ b/p11proxy-mitm @@ -0,0 +1,528 @@ +#!/usr/bin/python +# +# Copyright (c) 2012, 2013, NORDUnet A/S +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. 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. +# 3. Neither the name of the NORDUnet nor the names of its +# 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 HOLDER 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. +# +# Author : Fredrik Thulin <fredrik@thulin.net> +# +""" +MITM (man in the middle) proxy of pkcs11-proxy. Written while learning +how the (not very documented) wire format worked, as well as to help +diagnose (and fix) a couple of bugs in the C implementation. + +Usage : + + On console 1 : + + $ PKCS11_DAEMON_SOCKET="tcp://127.0.0.1:2345" pkcs11-daemon /usr/lib/libsofthsm.so + + On console 2 : + + $ ./p11proxy-mitm --debug + + On console 3 : + + $ PKCS11_PROXY_SOCKET="tcp://127.0.0.1:2344" pkcs11-tool --show-info --module libpkcs11-proxy.so + +""" + +import sys +import struct +import socket +import logging +import argparse +import SocketServer +import PyKCS11 # used to get all the PKCS#11 defines + +default_host = "localhost" +default_port_in = 2344 +default_port_out = 2345 +default_timeout = 3 +default_debug = False + +args = None +yhsm = None +logger = None + +# Extracted from gck-rpc-private.h. The last two elements of the tuples describe +# the requests/responses. +# +# /* +# * a_ = prefix denotes array of _ +# * A = CK_ATTRIBUTE +# * f_ = prefix denotes buffer for _ +# * M = CK_MECHANISM +# * u = CK_ULONG +# * s = space padded string +# * v = CK_VERSION +# * y = CK_BYTE +# * z = null terminated string +# */ +request_list = [ + (0, "ERROR", None, None), + (1, "C_Initialize", "ay", ""), + (2, "C_Finalize", "", ""), + (3, "C_GetInfo", "", "vsusv"), + (4, "C_GetSlotList", "yfu", "au"), + (5, "C_GetSlotInfo", "u", "ssuvv"), + (6, "C_GetTokenInfo", "u", "ssssuuuuuuuuuuuvvs"), + (7, "C_GetMechanismList", "ufu", "au"), + (8, "C_GetMechanismInfo", "uu", "uuu"), + (9, "C_InitToken", "uays", ""), + (10, "C_WaitForSlotEvent", "u", "u"), + (11, "C_OpenSession", "uu", "u"), + (12, "C_CloseSession", "u", ""), + (13, "C_CloseAllSessions", "u", ""), + (14, "C_GetFunctionStatus", "u", ""), + (15, "C_CancelFunction", "u", ""), + (16, "C_GetSessionInfo", "u", "uuuu"), + (17, "C_InitPIN", "uay", ""), + (18, "C_SetPIN", "uayay", ""), + (19, "C_GetOperationState", "ufy", "ay"), + (20, "C_SetOperationState", "uayuu", ""), + (21, "C_Login", "uuay", ""), + (22, "C_Logout", "u", ""), + (23, "C_CreateObject", "uaA", "u"), + (24, "C_CopyObject", "uuaA", "u"), + (25, "C_DestroyObject", "uu", ""), + (26, "C_GetObjectSize", "uu", "u"), + (27, "C_GetAttributeValue", "uufA", "aAu"), + (28, "C_SetAttributeValue", "uuaA", ""), + (29, "C_FindObjectsInit", "uaA", ""), + (30, "C_FindObjects", "ufu", "au"), + (31, "C_FindObjectsFinal", "u", ""), + (32, "C_EncryptInit", "uMu", ""), + (33, "C_Encrypt", "uayfy", "ay"), + (34, "C_EncryptUpdate", "uayfy", "ay"), + (35, "C_EncryptFinal", "ufy", "ay"), + (36, "C_DecryptInit", "uMu", ""), + (37, "C_Decrypt", "uayfy", "ay"), + (38, "C_DecryptUpdate", "uayfy", "ay"), + (39, "C_DecryptFinal", "ufy", "ay"), + (40, "C_DigestInit", "uM", ""), + (41, "C_Digest", "uayfy", "ay"), + (42, "C_DigestUpdate", "uay", ""), + (43, "C_DigestKey", "uu", ""), + (44, "C_DigestFinal", "ufy", "ay"), + (45, "C_SignInit", "uMu", ""), + (46, "C_Sign", "uayfy", "ay"), + (47, "C_SignUpdate", "uay", ""), + (48, "C_SignFinal", "ufy", "ay"), + (49, "C_SignRecoverInit", "uMu", ""), + (50, "C_SignRecover", "uayfy", "ay"), + (51, "C_VerifyInit", "uMu", ""), + (52, "C_Verify", "uayay", ""), + (53, "C_VerifyUpdate", "uay", ""), + (54, "C_VerifyFinal", "uay", ""), + (55, "C_VerifyRecoverInit", "uMu", ""), + (56, "C_VerifyRecover", "uayfy", "ay"), + (57, "C_DigestEncryptUpdate", "uayfy", "ay"), + (58, "C_DecryptDigestUpdate", "uayfy", "ay"), + (59, "C_SignEncryptUpdate", "uayfy", "ay"), + (60, "C_DecryptVerifyUpdate", "uayfy", "ay"), + (61, "C_GenerateKey", "uMaA", "u"), + (62, "C_GenerateKeyPair", "uMaAaA", "uu"), + (63, "C_WrapKey", "uMuufy", "ay"), + (64, "C_UnwrapKey", "uMuayaA", "u"), + (65, "C_DeriveKey", "uMuaA", "u"), + (66, "C_SeedRandom", "uay", ""), + (67, "C_GenerateRandom", "ufy", "ay"), + ] + +def parse_args(): + """ + Parse the command line arguments. + """ + parser = argparse.ArgumentParser(description = "pkcs11-proxy man-in-the-middle", + add_help = True, + formatter_class = argparse.ArgumentDefaultsHelpFormatter, + ) + + parser.add_argument('-H', '--host', + dest='listen_host', + default=default_host, + help='Host address to listen on', + metavar='HOST', + ) + + parser.add_argument('--host-out', + dest='connect_to_host', + default=default_host, + help='Host address to connect to', + metavar='HOST', + ) + + parser.add_argument('--port-in', + dest='listen_port', + type=int, + default=default_port_in, + help='Port to listen on', + metavar='PORT', + ) + + parser.add_argument('--port-out', + dest='connect_to_port', + type=int, + default=default_port_out, + help='Port to connect to', + metavar='PORT', + ) + + parser.add_argument('--timeout', + dest='timeout', + type=int, default=default_timeout, + required=False, + help='Request timeout in seconds', + metavar='SECONDS', + ) + + parser.add_argument('--debug', + dest='debug', + action='store_true', default=default_debug, + help='Enable debug operation', + ) + + return parser.parse_args() + +class ProxyHandler(SocketServer.StreamRequestHandler): + """ + The RequestHandler class for our server. + + It is instantiated once per connection to the server, and must + override the handle() method to implement communication to the + client. + """ + + def __init__(self, *other_args, **kwargs): + self.timeout = args.timeout + + # Outgoing connection to the real server (pkcs11-daemon) + self.p11proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.p11proxy.connect((args.connect_to_host, args.connect_to_port)) + + self.logger = logger + SocketServer.BaseRequestHandler.__init__(self, *other_args, **kwargs) + + def handle(self): + """ + Handle an incoming connection. Read requests/responses and decode them after passing + them on to the real pkcs11-proxy client/server. + """ + # self.request is the TCP socket connected to the client + try: + # 64 bits app id. Randomized on the client, and not really used in the server. + self.appid = self.request.recv(8) + self.p11proxy.send(self.appid) + + while True: + # Read request. First 32 bits are length. + length = self.request.recv(4) + if not length: + break + # Pass length to real server. + self.p11proxy.send(length) + self.logger.debug("\n\nRequest length is {}".format(length.encode('hex'))) + self._handle_request(struct.unpack('>L', length)[0]) + + # Read response. Again, first 32 bits are length. + length = self.p11proxy.recv(4) + # Pass length to client. + self.request.sendall(length) + self.logger.debug("\n\nAnswer length is {}".format(length.encode('hex'))) + self._handle_answer(struct.unpack('>L', length)[0]) + except Exception, e: + self.logger.exception("Got exception handling request from {}".format(self.client_address[0])) + return None + + def _handle_request(self, length): + """ + Given expected length, read the rest of a request, pass it on and decode whatever we can. + """ + self.logger.debug("Request({})".format(length)) + data = self.request.recv(length) + self.logger.debug(" R: {}".format(data.encode('hex'))) + self.p11proxy.send(data) + # parse request + (call_id, rest) = self._get_uint32(data) + if call_id > len(request_list): + return None + self.logger.debug(" R: call_id {}".format(call_id)) + rinfo = request_list[call_id] + self.logger.debug(" R: name {}".format(rinfo[1])) + fmt = rinfo[2] + if fmt: + self.logger.debug(" R: fmt {} ({})".format(fmt, fmt.encode('hex'))) + return self._parse_message('R', fmt, rest) + + def _handle_answer(self, length): + """ + Given expected length, read the rest of a response, pass it on and decode whatever we can. + """ + self.logger.debug("Answer({})".format(length)) + data = self.p11proxy.recv(length) + self.logger.debug(" A: {}".format(data.encode('hex'))) + self.request.sendall(data) + (call_id, rest) = self._get_uint32(data) + if call_id > len(request_list): + return None + # parse answer + (call_id, rest) = self._get_uint32(data) + if call_id > len(request_list): + return None + self.logger.debug(" A: call_id {}".format(call_id)) + rinfo = request_list[call_id] + self.logger.debug(" A: name {}".format(rinfo[1])) + if not call_id: + # This is ERROR + (ckr, _) = self._get_uint32(data[-4:]) + self.logger.debug(" A: {}".format(PyKCS11.CKR[ckr])) + fmt = rinfo[3] + if fmt: + self.logger.debug(" A: fmt {} ({})".format(fmt, fmt.encode('hex'))) + self._parse_message('A', fmt, rest) + + def _parse_message(self, msgtype, fmt, data): + """ + Parse the data in the request/response, which share format. + """ + # Check that the data is of the format we expect it to be. The format of every request/answer + # is redundantly included in the data sent over the network, although we already know what + # it should be. I guess this is a way to reduce the likeliness of undetected stream de-sync, + # because it would be wasteful if it was just used to make developers forget to update the + # handshake string when changing the format for a request/response. + (parsed_fmt, rest) = self._get_byte_array(data) + if parsed_fmt != fmt: + self.logger.error(" {}: Format mismatch, mine is {} and received is {}".format(msgtype, fmt, parsed_fmt)) + return None + self.logger.debug(" {}: format match".format(msgtype)) + try: + res = self._value_dump(msgtype, parsed_fmt, rest) + return res + except Exception: + self.logger.exception("Got exception trying to dump all data") + return [] + + def _value_dump(self, msgtype, fmt, data, res = []): + """ + Decode the data in a request/answer according to fmt. + + Will output the decoded data using self.logger.debug(). + """ + if not fmt: + if data: + self.logger.warning("{} bytes left after processing : {}".format(len(data), data.encode('hex'))) + return [] + #self.logger.debug("PARSING {} FROM {}".format(fmt[0], data.encode('hex'))) + if fmt[0] == 'u': + (this, rest) = self._get_uint64(data) + res.append(this) + self.logger.debug(" {}: uint64 {}".format(msgtype, hex(this))) + elif fmt[0] == 'a': + #self.logger.debug("PARSING {} FROM {}".format(fmt[0], data.encode('hex'))) + if fmt[1] == 'A': + valid = 1 + rest = data + else: + (valid, rest) = self._get_uint8(data) + self.logger.debug(" {}: Byte-Array valid : {}".format(msgtype, valid)) + array_num = 0 + (array_num, rest) = self._get_uint32(rest) + self.logger.debug(" {}: Byte-Array length : {}/{}".format(msgtype, array_num, hex(array_num))) + if array_num > 256: + raise Exception("Unreasonable array length : {}".format(hex(array_num))) + if not valid: + # /* If not valid, then just the length is encoded, this can signify CKR_BUFFER_TOO_SMALL */ + self.logger.debug(" {}: Array length {}/{}, not valid - maybe CKR_BUFFER_TOO_SMALL".format(msgtype, array_num, array_num)) + # array is now fully handled, remove next element of fmt + fmt = fmt[1:] + if rest: + self.logger.debug(" {}: CKR_BUFFER_TOO_SMALL leaving data {} ({} bytes) for fmt {}".format(msgtype, rest.encode('hex'), len(rest), fmt[1:])) + else: + if fmt[1] == 'y': + # simple byte array (i.e. string to us) + rest = struct.pack('>L', array_num) + rest # restore length before calling _get_byte_array + (this, rest) = self._get_byte_array(rest) + self.logger.debug(" {}: Byte-Array({}) {}".format(msgtype, len(this), repr(this))) + res.append(this) + fmt = fmt[1:] + else: + if array_num > 0: + # modify fmt to match the array length + placeholder = 'X' + array_elements = fmt[1] * (array_num - 1) + fmt_rest = fmt[1:] + fmt = placeholder + array_elements + fmt_rest + else: + # chomp the next fmt too + fmt = fmt[1:] + self.logger.debug(" {}: Array length {}, new fmt {}".format(msgtype, array_num, fmt)) + elif fmt[0] == 's': + (this, rest) = self._get_byte_array(data) + res.append(this) + self.logger.debug(" {}: string({}/{}) {}".format(msgtype, len(this), hex(len(this)), repr(this))) + elif fmt[0] == 'v': + # version, struct ck_version from pkcs11.h + (major, rest) = self._get_uint8(data) + (minor, rest) = self._get_uint8(rest) + self.logger.debug(" {}: CK_Version {}.{}".format(msgtype, major, minor)) + elif fmt[0] == 'y': + (this, rest) = self._get_uint8(data) + self.logger.debug(" {}: CK_Byte {}".format(msgtype, hex(this))) + elif fmt[0] == 'z': + # NULL-terminated string, len == -1 for empty string + (length, rest) = self._get_uint32(data) + if length == 0xffffffff: + res.append('') + else: + (this, rest) = self._get_byte_array(data) + res.append(this) + self.logger.debug(" {}: NULL-string({}/{}) {}".format(msgtype, len(this), hex(len(this)), repr(this))) + elif fmt[0] == 'M': + # Mechanism + #self.logger.debug("PARSING {} FROM {}".format(fmt[0], data.encode('hex'))) + (mech_type, rest) = self._get_uint32(data) + self.logger.debug(" {}: Mechanism {}/{}".format(msgtype, repr(mech_type), PyKCS11.CKM.get(mech_type))) + (this, rest) = self._get_byte_array(rest) + self.logger.debug(" {}: Mechanism parameter {}".format(msgtype, repr(this))) + res.append((mech_type, this)) + elif fmt[0] == 'A': + # Attribute + (this, rest) = self._get_attribute(data) + res.append(this) + #self.logger.debug(" {}: Attribute {}".format(msgtype, this)) + vl = "None" + if this['uValueLen'] is not None: + vl = hex(this['uValueLen']) + self.logger.debug(" {}: Attribute type {}/{}, valid {}, len {}, data {}".format(\ + msgtype, hex(this['type']), PyKCS11.CKA.get(this['type']), this['valid'], vl, this['data'].encode('hex'))) + elif fmt[0] == 'f': + # array buffer + if fmt[1] == 'y': + (flags, rest) = self._get_uint8(data) + else: + rest = data + (num_items, rest) = self._get_uint32(rest) + if fmt[1] == 'A': + self.logger.debug(" {}: Attribute buffer, {} elements".format(msgtype, hex(num_items))) + while num_items: + (attr_type, rest) = self._get_uint32(rest) + (buffer_len, rest) = self._get_uint32(rest) + self.logger.debug(" {}: type {}/{} len {}".format(msgtype, hex(attr_type), PyKCS11.CKA.get(attr_type), hex(buffer_len))) + res.append(('attribute buffer', attr_type, buffer_len)) + num_items -= 1 + elif fmt[1] == 'y': + self.logger.debug(" {}: Byte buffer, length {} (flags {})".format(msgtype, hex(num_items), flags)) + # just a number of bytes to alloc + pass + elif fmt[1] == 'u': + # ulong buffer, just an uint32 (?) + self.logger.debug("PARSING {} FROM {}".format(fmt[0], data.encode('hex'))) + (this, rest) = self._get_uint32(data) + self.logger.debug(" {}: ulong buffer {}".format(msgtype, hex(this))) + else: + raise Exception("Unknown array buffer fmt '{}'".format(fmt[1])) + # need to munch an extra fmt + fmt = fmt[1:] + else: + self.logger.warn(" {}: STOPPING at fmt {}, data {}".format(msgtype, fmt[0], data.encode('hex'))) + return [] + return self._value_dump(msgtype, fmt[1:], rest, res) + + def _get_uint64(self, data): + (a, rest) = self._get_uint32(data) + (b, rest) = self._get_uint32(rest) + return (a << 32 | b, data[8:]) + + def _get_uint32(self, data): + res = struct.unpack('>L', data[:4])[0] + return (res, data[4:]) + + def _get_uint8(self, data): + res = ord(data[0]) + return (res, data[1:]) + + def _get_byte_array(self, data): + (length, rest) = self._get_uint32(data) + if length == 0xffffffff: + return('', rest) + if length > len(rest): + raise Exception('Parse error, not enough bytes left for byte-array') + res = rest[:length] + return (res, rest[length:]) + + def _get_attribute(self, data): + attr = {} + (attr_type, rest) = self._get_uint32(data) + (attr_valid, rest) = self._get_uint8(rest) + attr_vallen = None + data = '' + if attr_valid: + (attr_vallen, rest) = self._get_uint32(rest) + # length doubly included, in byte array again? + (data, rest) = self._get_byte_array(rest) + # read data here! + attr['type'] = attr_type + attr['valid'] = attr_valid + attr['uValueLen'] = attr_vallen + attr['data'] = data + return (attr, rest) + +def main(): + global args + args = parse_args() + + level = logging.INFO + if args.debug: + level = logging.DEBUG + logging.basicConfig(level=level) + global logger + logger = logging.getLogger('p11proxy-mitm') + + # Create the server + server = SocketServer.TCPServer((args.listen_host, args.listen_port), \ + ProxyHandler, \ + bind_and_activate=False, \ + ) + server.allow_reuse_address = True + server.timeout = args.timeout + server.server_bind() # Manually bind, to support allow_reuse_address + server.server_activate() # (see above comment) + + # Activate the server; this will keep running until you + # interrupt the program with Ctrl-C + server.serve_forever() + +if __name__ == '__main__': + try: + if main(): + sys.exit(0) + sys.exit(1) + except KeyboardInterrupt: + pass