diff options
author | Stef Walter <stefw@gnome.org> | 2013-02-20 12:24:03 +0100 |
---|---|---|
committer | Stef Walter <stef@thewalter.net> | 2014-07-08 08:57:31 +0200 |
commit | 7ec80ff13adb167705a999b7d082c76219adc909 (patch) | |
tree | 2c9872e6154466209e14e1b08e1fa14a548f0aae | |
parent | 5ecfe2c8aa58a170aac2d9a9c22d7ffb3cc9442a (diff) |
rpc: Implement execution of another tool to transport PKCS#11 RPC
-rw-r--r-- | p11-kit/Makefile.am | 3 | ||||
-rw-r--r-- | p11-kit/modules.c | 70 | ||||
-rw-r--r-- | p11-kit/rpc-transport.c | 850 | ||||
-rw-r--r-- | p11-kit/rpc.h | 36 | ||||
-rw-r--r-- | p11-kit/tests/Makefile.am | 19 | ||||
-rw-r--r-- | p11-kit/tests/frob-server.c | 173 | ||||
-rw-r--r-- | p11-kit/tests/mock-module-ep2.c | 56 | ||||
-rw-r--r-- | p11-kit/tests/test-rpc.c | 156 | ||||
-rw-r--r-- | p11-kit/tests/test-transport.c | 281 |
9 files changed, 1600 insertions, 44 deletions
diff --git a/p11-kit/Makefile.am b/p11-kit/Makefile.am index da195ac..88883b5 100644 --- a/p11-kit/Makefile.am +++ b/p11-kit/Makefile.am @@ -36,8 +36,9 @@ MODULE_SRCS = \ proxy.c proxy.h \ private.h \ messages.c \ + rpc-transport.c rpc.h \ rpc-message.c rpc-message.h \ - rpc-client.c rpc-server.c rpc.h \ + rpc-client.c rpc-server.c \ uri.c \ virtual.c virtual.h \ $(inc_HEADERS) diff --git a/p11-kit/modules.c b/p11-kit/modules.c index cc382b2..7dbb6ed 100644 --- a/p11-kit/modules.c +++ b/p11-kit/modules.c @@ -51,6 +51,7 @@ #include "p11-kit.h" #include "private.h" #include "proxy.h" +#include "rpc.h" #include "virtual.h" #include <sys/stat.h> @@ -391,6 +392,36 @@ load_module_from_file_inlock (const char *name, return CKR_OK; } +static CK_RV +setup_module_for_remote_inlock (const char *name, + const char *remote, + Module **result) +{ + p11_rpc_transport *rpc; + Module *mod; + + p11_debug ("remoting module %s using: %s", name, remote); + + mod = alloc_module_unlocked (); + return_val_if_fail (mod != NULL, CKR_HOST_MEMORY); + + rpc = p11_rpc_transport_new (&mod->virt, remote, name); + if (rpc == NULL) { + free_module_unlocked (mod); + return CKR_DEVICE_ERROR; + } + + mod->loaded_module = rpc; + mod->loaded_destroy = p11_rpc_transport_free; + + /* This takes ownership of the module */ + if (!p11_dict_set (gl.modules, mod, mod)) + return_val_if_reached (CKR_HOST_MEMORY); + + *result = mod; + return CKR_OK; +} + static int is_list_delimiter (char ch) { @@ -452,6 +483,7 @@ take_config_and_load_module_inlock (char **name, bool critical) { const char *filename; + const char *remote; Module *mod; CK_RV rv; @@ -463,15 +495,30 @@ take_config_and_load_module_inlock (char **name, if (!is_module_enabled_unlocked (*name, *config)) return CKR_OK; - filename = p11_dict_get (*config, "module"); - if (filename == NULL) { - p11_debug ("no module path for module, skipping: %s", *name); - return CKR_OK; - } + remote = p11_dict_get (*config, "remote"); + if (remote != NULL) { + rv = setup_module_for_remote_inlock (*name, remote, &mod); + if (rv != CKR_OK) + return rv; - rv = load_module_from_file_inlock (*name, filename, &mod); - if (rv != CKR_OK) - return CKR_OK; + } else { + filename = p11_dict_get (*config, "module"); + if (filename == NULL) { + p11_debug ("no module path for module, skipping: %s", *name); + return CKR_OK; + } + + rv = load_module_from_file_inlock (*name, filename, &mod); + if (rv != CKR_OK) + return CKR_OK; + + /* + * We support setting of CK_C_INITIALIZE_ARGS.pReserved from + * 'x-init-reserved' setting in the config. This only works with specific + * PKCS#11 modules, and is non-standard use of that field. + */ + mod->init_args.pReserved = p11_dict_get (*config, "x-init-reserved"); + } /* Take ownership of thes evariables */ p11_dict_free (mod->config); @@ -482,13 +529,6 @@ take_config_and_load_module_inlock (char **name, *name = NULL; mod->critical = critical; - /* - * We support setting of CK_C_INITIALIZE_ARGS.pReserved from - * 'x-init-reserved' setting in the config. This only works with specific - * PKCS#11 modules, and is non-standard use of that field. - */ - mod->init_args.pReserved = p11_dict_get (mod->config, "x-init-reserved"); - return CKR_OK; } diff --git a/p11-kit/rpc-transport.c b/p11-kit/rpc-transport.c new file mode 100644 index 0000000..0ff82d8 --- /dev/null +++ b/p11-kit/rpc-transport.c @@ -0,0 +1,850 @@ +/* + * Copyright (C) 2012 Stefan Walter + * Copyright (C) 2013 Red Hat Inc. + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stefw@gnome.org> + */ + +#include "config.h" + +#include "argv.h" +#include "compat.h" +#define P11_DEBUG_FLAG P11_DEBUG_RPC +#include "debug.h" +#include "message.h" +#include "pkcs11.h" +#include "private.h" +#include "rpc.h" +#include "rpc-message.h" + +#include <sys/types.h> + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> + +#ifdef OS_UNIX +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <signal.h> +#include <unistd.h> +#endif + +#ifdef OS_WIN32 +#include <winsock2.h> +#endif + +typedef struct { + /* Never changes */ + int fd; + + /* Protected by the lock */ + p11_mutex_t write_lock; + int refs; + int last_code; + bool sent_creds; + + /* This data is protected by read mutex */ + p11_mutex_t read_lock; + bool read_creds; + uint32_t read_code; + uint32_t read_olen; + uint32_t read_dlen; +} rpc_socket; + +static rpc_socket * +rpc_socket_new (int fd) +{ + rpc_socket *sock; + + sock = calloc (1, sizeof (rpc_socket)); + return_val_if_fail (sock != NULL, NULL); + + sock->fd = fd; + sock->last_code = 0x10; + sock->read_creds = false; + sock->sent_creds = false; + sock->refs = 1; + + p11_mutex_init (&sock->write_lock); + p11_mutex_init (&sock->read_lock); + + return sock; +} + +#if 0 +static rpc_socket * +rpc_socket_ref (rpc_socket *sock) +{ + assert (sock != NULL); + + p11_mutex_lock (&sock->write_lock); + sock->refs++; + p11_mutex_unlock (&sock->write_lock); + + return sock; +} + +static bool +rpc_socket_is_open (rpc_socket *sock) +{ + assert (sock != NULL); + return sock->fd >= 0; +} +#endif + +static void +rpc_socket_close (rpc_socket *sock) +{ + assert (sock != NULL); + if (sock->fd != -1) + close (sock->fd); + sock->fd = -1; +} + +static void +rpc_socket_unref (rpc_socket *sock) +{ + int release = 0; + + assert (sock != NULL); + + p11_mutex_lock (&sock->write_lock); + if (--sock->refs == 0) + release = 1; + p11_mutex_unlock (&sock->write_lock); + + if (!release) + return; + + assert (sock != NULL); + assert (sock->refs == 0); + + rpc_socket_close (sock); + p11_mutex_uninit (&sock->write_lock); + p11_mutex_uninit (&sock->read_lock); +} + +static bool +write_all (int fd, + unsigned char* data, + size_t len) +{ + int r; + + while (len > 0) { + r = write (fd, data, len); + if (r == -1) { + if (errno == EPIPE) { + p11_message ("couldn't send data: closed connection"); + return false; + } else if (errno != EAGAIN && errno != EINTR) { + p11_message_err (errno, "couldn't send data"); + return false; + } + } else { + p11_debug ("wrote %d bytes", r); + data += r; + len -= r; + } + } + + return true; +} + +static bool +read_all (int fd, + unsigned char* data, + size_t len) +{ + int r; + + while (len > 0) { + r = read (fd, data, len); + if (r == 0) { + p11_message ("couldn't receive data: closed connection"); + return false; + } else if (r == -1) { + if (errno != EAGAIN && errno != EINTR) { + p11_message_err (errno, "couldn't receive data"); + return false; + } + } else { + p11_debug ("read %d bytes", r); + data += r; + len -= r; + } + } + + return true; +} + +static CK_RV +rpc_socket_write_inlock (rpc_socket *sock, + int code, + p11_buffer *options, + p11_buffer *buffer) +{ + unsigned char header[12]; + unsigned char dummy = '\0'; + + /* The socket is locked and referenced at this point */ + assert (buffer != NULL); + + /* Place holder byte, will later carry unix credentials (on some systems) */ + if (!sock->sent_creds) { + if (write_all (sock->fd, &dummy, 1) != 1) { + p11_message_err (errno, "couldn't send socket credentials"); + return CKR_DEVICE_ERROR; + } + sock->sent_creds = true; + } + + p11_rpc_buffer_encode_uint32 (header, code); + p11_rpc_buffer_encode_uint32 (header + 4, options->len); + p11_rpc_buffer_encode_uint32 (header + 8, buffer->len); + + if (!write_all (sock->fd, header, 12) || + !write_all (sock->fd, options->data, options->len) || + !write_all (sock->fd, buffer->data, buffer->len)) + return CKR_DEVICE_ERROR; + + return CKR_OK; +} + +static p11_rpc_status +write_at (int fd, + unsigned char *data, + size_t len, + size_t offset, + size_t *at) +{ + p11_rpc_status status; + ssize_t num; + size_t from; + int errn; + + assert (*at >= offset); + + if (*at >= offset + len) + return P11_RPC_OK; + + from = *at - offset; + assert (from < len); + + num = write (fd, data + from, len - from); + errn = errno; + + /* Update state */ + if (num > 0) + *at += num; + + /* Completely written out this block */ + if (num == len - from) { + p11_debug ("ok: wrote block of %d", (int)num); + status = P11_RPC_OK; + + /* Partially written out this block */ + } else if (num >= 0) { + p11_debug ("again: partial read of %d", (int)num); + status = P11_RPC_AGAIN; + + /* Didn't write out block due to transient issue */ + } else if (errn == EINTR || errn == EAGAIN || errn == EWOULDBLOCK) { + p11_debug ("again: due to %d", errn); + status = P11_RPC_AGAIN; + + /* Failure */ + } else { + p11_debug ("error: due to %d", errn); + status = P11_RPC_ERROR; + } + + errno = errn; + return status; +} + +p11_rpc_status +p11_rpc_transport_write (int fd, + size_t *state, + int call_code, + p11_buffer *options, + p11_buffer *buffer) +{ + unsigned char header[12] = { 0, }; + p11_rpc_status status; + + assert (state != NULL); + assert (options != NULL); + assert (buffer != NULL); + + if (*state < 12) { + p11_rpc_buffer_encode_uint32 (header, call_code); + p11_rpc_buffer_encode_uint32 (header + 4, options->len); + p11_rpc_buffer_encode_uint32 (header + 8, buffer->len); + } + + status = write_at (fd, header, 12, 0, state); + + if (status == P11_RPC_OK) { + status = write_at (fd, options->data, options->len, + 12, state); + } + + if (status == P11_RPC_OK) { + status = write_at (fd, buffer->data, buffer->len, + 12 + options->len, state); + } + + /* All done */ + if (status == P11_RPC_OK) + *state = 0; + + return status; +} + +static int +rpc_socket_read (rpc_socket *sock, + int *code, + p11_buffer *buffer) +{ + CK_RV ret = CKR_DEVICE_ERROR; + unsigned char header[12]; + unsigned char dummy; + fd_set rfds; + + assert (code != NULL); + assert (buffer != NULL); + + /* + * We are not in the main socket lock here, but the socket + * is referenced, and won't go away + */ + + p11_mutex_lock (&sock->read_lock); + + if (!sock->read_creds) { + if (read_all (sock->fd, &dummy, 1) != 1) + return CKR_DEVICE_ERROR; + sock->read_creds = true; + } + + for (;;) { + /* No message header has been read yet? ... read one in */ + if (sock->read_code == 0) { + if (!read_all (sock->fd, header, 12)) + break; + + /* Decode and check the message header */ + sock->read_code = p11_rpc_buffer_decode_uint32 (header); + sock->read_olen = p11_rpc_buffer_decode_uint32 (header + 4); + sock->read_dlen = p11_rpc_buffer_decode_uint32 (header + 8); + if (sock->read_code == 0) { + p11_message ("received invalid rpc header values: perhaps wrong protocol"); + break; + } + } + + /* If it's our header (or caller doesn't care), then yay! */ + if (*code == -1 || sock->read_code == *code) { + + /* We ignore the options, so read into the same as buffer */ + if (!p11_buffer_reset (buffer, sock->read_olen) || + !p11_buffer_reset (buffer, sock->read_dlen)) { + warn_if_reached (); + break; + } + + /* Read in the the options first, and then data */ + if (!read_all (sock->fd, buffer->data, sock->read_olen) || + !read_all (sock->fd, buffer->data, sock->read_dlen)) + break; + + buffer->len = sock->read_dlen; + *code = sock->read_code; + + /* Yay, we got our data, off we go */ + sock->read_code = 0; + sock->read_olen = 0; + sock->read_dlen = 0; + ret = CKR_OK; + break; + } + + /* Give another thread the chance to read data for this header */ + if (sock->read_code != 0) { + p11_debug ("received header in wrong thread"); + p11_mutex_unlock (&sock->read_lock); + + /* Used as a simple wait */ + FD_ZERO (&rfds); + FD_SET (sock->fd, &rfds); + if (select (sock->fd + 1, &rfds, NULL, NULL, NULL) < 0) + p11_message ("couldn't use select to wait on rpc socket"); + + p11_mutex_lock (&sock->read_lock); + } + } + + p11_mutex_unlock (&sock->read_lock); + return ret; +} + +static p11_rpc_status +read_at (int fd, + unsigned char *data, + size_t len, + size_t offset, + size_t *at) +{ + p11_rpc_status status; + int errn; + ssize_t num; + size_t from; + + assert (*at >= offset); + + if (*at >= offset + len) + return P11_RPC_OK; + + from = *at - offset; + assert (from < len); + + num = read (fd, data + from, len - from); + errn = errno; + + /* Update state */ + if (num > 0) + *at += num; + + /* Completely read out this block */ + if (num == len - from) { + p11_debug ("ok: read block of %d", (int)num); + status = P11_RPC_OK; + + /* Partially read out this block */ + } else if (num > 0) { + p11_debug ("again: partial read of %d", (int)num); + status = P11_RPC_AGAIN; + + /* End of file, valid if at offset zero */ + } else if (num == 0) { + if (offset == 0) { + p11_debug ("eof: read zero bytes"); + status = P11_RPC_EOF; + } else { + p11_debug ("error: early truncate"); + errn = EPROTO; + status = P11_RPC_ERROR; + } + + /* Didn't read out block due to transient issue */ + } else if (errn == EINTR || errn == EAGAIN || errn == EWOULDBLOCK) { + p11_debug ("again: due to %d", errn); + status = P11_RPC_AGAIN; + + /* Failure */ + } else { + p11_debug ("error: due to %d", errn); + status = P11_RPC_ERROR; + } + + errno = errn; + return status; +} + +p11_rpc_status +p11_rpc_transport_read (int fd, + size_t *state, + int *call_code, + p11_buffer *options, + p11_buffer *buffer) +{ + unsigned char *header; + p11_rpc_status status; + size_t len; + + assert (state != NULL); + assert (call_code != NULL); + assert (options != NULL); + assert (buffer != NULL); + + /* Reading the header, we read it into @buffer */ + if (*state < 12) { + if (!p11_buffer_reset (buffer, 12)) + return_val_if_reached (P11_RPC_ERROR); + status = read_at (fd, buffer->data, 12, 0, state); + if (status != P11_RPC_OK) + return status; + + /* Parse out the header */ + header = buffer->data; + *call_code = p11_rpc_buffer_decode_uint32 (header); + len = p11_rpc_buffer_decode_uint32 (header + 4); + if (!p11_buffer_reset (options, len)) + return_val_if_reached (P11_RPC_ERROR); + options->len = len; + len = p11_rpc_buffer_decode_uint32 (header + 8); + if (!p11_buffer_reset (buffer, len)) + return_val_if_reached (P11_RPC_ERROR); + buffer->len = len; + } + + /* At this point options has a valid len field */ + status = read_at (fd, options->data, options->len, 12, state); + if (status == P11_RPC_OK) { + status = read_at (fd, buffer->data, buffer->len, + 12 + options->len, state); + } + + if (status == P11_RPC_OK) + *state = 0; + + return status; +} + +struct _p11_rpc_transport { + p11_rpc_client_vtable vtable; + p11_destroyer destroyer; + rpc_socket *socket; + p11_buffer options; +}; + +static void +on_rpc_disconnect (p11_rpc_client_vtable *vtable, + void *init_reserved) +{ + p11_rpc_transport *rpc = (p11_rpc_transport *)vtable; + + if (rpc->socket) + rpc_socket_unref (rpc->socket); + rpc->socket = NULL; +} + +static bool +rpc_transport_init (p11_rpc_transport *rpc, + const char *module_name, + p11_destroyer destroyer) +{ + rpc->destroyer = destroyer; + + p11_buffer_init_null (&rpc->options, 0); + p11_buffer_add (&rpc->options, module_name, -1); + return_val_if_fail (p11_buffer_ok (&rpc->options), false); + + return true; +} + +static void +rpc_transport_uninit (p11_rpc_transport *rpc) +{ + p11_buffer_uninit (&rpc->options); +} + +static CK_RV +on_rpc_transport (p11_rpc_client_vtable *vtable, + p11_buffer *request, + p11_buffer *response) +{ + p11_rpc_transport *rpc = (p11_rpc_transport *)vtable; + CK_RV rv = CKR_OK; + rpc_socket *sock; + int call_code; + + assert (rpc != NULL); + assert (request != NULL); + assert (response != NULL); + + sock = rpc->socket; + assert (sock != NULL); + + p11_mutex_lock (&sock->write_lock); + assert (sock->refs > 0); + sock->refs++; + + /* Get the next socket reply code */ + call_code = sock->last_code++; + + if (sock->fd == -1) + rv = CKR_DEVICE_ERROR; + if (rv == CKR_OK) + rv = rpc_socket_write_inlock (sock, call_code, &rpc->options, request); + + /* We unlock the socket mutex while reading a response */ + if (rv == CKR_OK) { + p11_mutex_unlock (&sock->write_lock); + + rv = rpc_socket_read (sock, &call_code, response); + + p11_mutex_lock (&sock->write_lock); + } + + if (rv != CKR_OK && sock->fd != -1) { + p11_message ("closing socket due to protocol failure"); + close (sock->fd); + sock->fd = -1; + } + + sock->refs--; + assert (sock->refs > 0); + p11_mutex_unlock (&sock->write_lock); + + return rv; +} + +#ifdef OS_UNIX + +typedef struct { + p11_rpc_transport base; + p11_array *argv; + pid_t pid; +} rpc_exec; + +static void +wait_or_terminate (pid_t pid) +{ + bool terminated = false; + int status; + int sig; + int ret; + int i; + + + for (i = 0; i < 3 * 1000; i += 100) { + ret = waitpid (pid, &status, WNOHANG); + if (ret != 0) + break; + p11_sleep_ms (100); + } + + if (ret == 0) { + p11_message ("process %d did not exit, terminating", (int)pid); + kill (pid, SIGTERM); + terminated = true; + ret = waitpid (pid, &status, 0); + } + + if (ret < 0) { + p11_message_err (errno, "failed to wait for executed child: %d", (int)pid); + status = 0; + } else if (WIFEXITED (status)) { + status = WEXITSTATUS (status); + if (status == 0) + p11_debug ("process %d exited with status 0", (int)pid); + else + p11_message ("process %d exited with status %d", (int)pid, status); + } else if (WIFSIGNALED (status)) { + sig = WTERMSIG (status); + if (!terminated || sig != SIGTERM) + p11_message ("process %d was terminated with signal %d", (int)pid, sig); + } +} + +static void +on_rpc_exec_disconnect (p11_rpc_client_vtable *vtable, + void *fini_reserved) +{ + rpc_exec *rex = (rpc_exec *)vtable; + + if (rex->base.socket) + rpc_socket_close (rex->base.socket); + + if (rex->pid) + wait_or_terminate (rex->pid); + rex->pid = 0; + + /* Do the common disconnect stuff */ + on_rpc_disconnect (vtable, fini_reserved); +} + +static int +set_cloexec_on_fd (void *data, + int fd) +{ + int *max_fd = data; + if (fd >= *max_fd) + fcntl (fd, F_SETFD, FD_CLOEXEC); + return 0; +} + +static CK_RV +on_rpc_exec_connect (p11_rpc_client_vtable *vtable, + void *init_reserved) +{ + rpc_exec *rex = (rpc_exec *)vtable; + pid_t pid; + int max_fd; + int fds[2]; + int errn; + + p11_debug ("executing rpc transport: %s", (char *)rex->argv->elem[0]); + + if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + p11_message_err (errno, "failed to create pipe for remote"); + return CKR_DEVICE_ERROR; + } + + pid = fork (); + switch (pid) { + + /* Failure */ + case -1: + close (fds[0]); + close (fds[1]); + p11_message_err (errno, "failed to fork for remote"); + return CKR_DEVICE_ERROR; + + /* Child */ + case 0: + if (dup2 (fds[1], STDIN_FILENO) < 0 || + dup2 (fds[1], STDOUT_FILENO) < 0) { + errn = errno; + p11_message_err (errn, "couldn't dup file descriptors in remote child"); + _exit (errn); + } + + /* Close file descriptors, except for above on exec */ + max_fd = STDERR_FILENO + 1; + fdwalk (set_cloexec_on_fd, &max_fd); + execvp (rex->argv->elem[0], (char **)rex->argv->elem); + + errn = errno; + p11_message_err (errn, "couldn't execute program for rpc: %s", + (char *)rex->argv->elem[0]); + _exit (errn); + + /* The parent */ + default: + break; + } + + close (fds[1]); + rex->pid = pid; + rex->base.socket = rpc_socket_new (fds[0]); + return_val_if_fail (rex->base.socket != NULL, CKR_GENERAL_ERROR); + + return CKR_OK; +} + +static void +rpc_exec_free (void *data) +{ + rpc_exec *rex = data; + on_rpc_exec_disconnect (data, NULL); + rpc_transport_uninit (&rex->base); + p11_array_free (rex->argv); + free (rex); +} + +static void +on_argv_parsed (char *argument, + void *data) +{ + p11_array *argv = data; + + if (!p11_array_push (argv, strdup (argument))) + return_if_reached (); +} + +static p11_rpc_transport * +rpc_exec_init (const char *remote, + const char *name) +{ + p11_array *argv; + rpc_exec *rex; + + argv = p11_array_new (free); + if (!p11_argv_parse (remote, on_argv_parsed, argv) || argv->num < 1) { + p11_message ("invalid remote command line: %s", remote); + p11_array_free (argv); + return NULL; + } + + rex = calloc (1, sizeof (rpc_exec)); + return_val_if_fail (rex != NULL, NULL); + + p11_array_push (argv, NULL); + rex->argv = argv; + + rex->base.vtable.connect = on_rpc_exec_connect; + rex->base.vtable.disconnect = on_rpc_exec_disconnect; + rex->base.vtable.transport = on_rpc_transport; + rpc_transport_init (&rex->base, name, rpc_exec_free); + + p11_debug ("initialized rpc exec: %s", remote); + return &rex->base; +} + +#endif /* OS_UNIX */ + +p11_rpc_transport * +p11_rpc_transport_new (p11_virtual *virt, + const char *remote, + const char *name) +{ + p11_rpc_transport *rpc; + + return_val_if_fail (virt != NULL, NULL); + return_val_if_fail (remote != NULL, NULL); + return_val_if_fail (name != NULL, NULL); + +#ifdef OS_UNIX + /* For now we assume it's all a command line */ + rpc = rpc_exec_init (remote, name); + +#else /* !OS_WIN32 */ + rpc = NULL; + p11_message ("Windows not yet supported for remote"); + +#endif /* OS_WIN32 */ + + if (!rpc) + return NULL; + + if (!p11_rpc_client_init (virt, &rpc->vtable)) + return_val_if_reached (NULL); + + return rpc; +} + +void +p11_rpc_transport_free (void *data) +{ + p11_rpc_transport *rpc = data; + + if (rpc != NULL) { + assert (rpc->destroyer); + (rpc->destroyer) (data); + } +} diff --git a/p11-kit/rpc.h b/p11-kit/rpc.h index a86e796..b129e61 100644 --- a/p11-kit/rpc.h +++ b/p11-kit/rpc.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Stefan Walter + * Copyright (C) 2013 Stefan Walter * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -32,8 +33,8 @@ * Author: Stef Walter <stefw@gnome.org> */ -#ifndef __P11_KIT_RPC_H__ -#define __P11_KIT_RPC_H__ +#ifndef __P11_RPC_H__ +#define __P11_RPC_H__ #include "pkcs11.h" #include "buffer.h" @@ -53,8 +54,6 @@ struct _p11_rpc_client_vtable { void (* disconnect) (p11_rpc_client_vtable *vtable, void *fini_reserved); - - void *reserved[16]; }; bool p11_rpc_client_init (p11_virtual *virt, @@ -66,4 +65,31 @@ bool p11_rpc_server_handle (CK_X_FUNCTION_LIST *funcs, extern CK_MECHANISM_TYPE * p11_rpc_mechanisms_override_supported; -#endif /* __P11_KIT_RPC_H__ */ +typedef struct _p11_rpc_transport p11_rpc_transport; + +p11_rpc_transport * p11_rpc_transport_new (p11_virtual *virt, + const char *remote, + const char *name); + +void p11_rpc_transport_free (void *transport); + +typedef enum { + P11_RPC_OK, + P11_RPC_EOF, + P11_RPC_AGAIN, + P11_RPC_ERROR +} p11_rpc_status; + +p11_rpc_status p11_rpc_transport_read (int fd, + size_t *state, + int *call_code, + p11_buffer *options, + p11_buffer *buffer); + +p11_rpc_status p11_rpc_transport_write (int fd, + size_t *state, + int call_code, + p11_buffer *options, + p11_buffer *buffer); + +#endif /* __P11_RPC_H__ */ diff --git a/p11-kit/tests/Makefile.am b/p11-kit/tests/Makefile.am index 2192fed..0672d62 100644 --- a/p11-kit/tests/Makefile.am +++ b/p11-kit/tests/Makefile.am @@ -29,20 +29,24 @@ CHECK_PROGS = \ test-rpc \ $(NULL) +noinst_PROGRAMS = \ + print-messages \ + frob-setuid \ + $(CHECK_PROGS) + if WITH_FFI CHECK_PROGS += \ test-virtual \ test-managed \ test-log \ + test-transport \ $(NULL) -endif +noinst_PROGRAMS += \ + frob-server -noinst_PROGRAMS = \ - print-messages \ - frob-setuid \ - $(CHECK_PROGS) +endif TESTS = $(CHECK_PROGS) @@ -67,7 +71,9 @@ mock_one_la_LDFLAGS = \ -module -avoid-version -rpath /nowhere \ -no-undefined -export-symbols-regex 'C_GetFunctionList' -mock_two_la_SOURCES = $(mock_one_la_SOURCES) +mock_two_la_SOURCES = \ + mock-module-ep2.c + mock_two_la_CFLAGS = $(mock_one_la_CFLAGS) mock_two_la_LDFLAGS = $(mock_one_la_LDFLAGS) mock_two_la_LIBADD = $(mock_one_la_LIBADD) @@ -78,6 +84,7 @@ mock_three_la_LDFLAGS = $(mock_one_la_LDFLAGS) mock_three_la_LIBADD = $(mock_one_la_LIBADD) mock_four_la_SOURCES = $(mock_one_la_SOURCES) +mock_four_la_CFLAGS = $(mock_one_la_CFLAGS) mock_four_la_LDFLAGS = $(mock_one_la_LDFLAGS) mock_four_la_LIBADD = $(mock_one_la_LIBADD) diff --git a/p11-kit/tests/frob-server.c b/p11-kit/tests/frob-server.c new file mode 100644 index 0000000..e0e7020 --- /dev/null +++ b/p11-kit/tests/frob-server.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2013 Red Hat Inc. + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +#include "config.h" + +#include "buffer.h" +#include "compat.h" +#include "debug.h" +#include "p11-kit.h" +#include "rpc.h" +#include "virtual.h" + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int +main (int argc, + char *argv[]) +{ + CK_FUNCTION_LIST *funcs; + CK_C_GetFunctionList gfl; + p11_rpc_status status; + unsigned char version; + p11_virtual virt; + p11_buffer options; + p11_buffer buffer; + dl_module_t dl; + size_t state; + int code; + CK_RV rv; + + p11_debug_init (); + + if (argc != 2) { + fprintf (stderr, "usage: frob-server module\n"); + exit (2); + } + + dl = p11_dl_open (argv[1]); + if (dl == NULL) { + fprintf (stderr, "couldn't load module: %s: %s\n", + argv[1], p11_dl_error ()); + exit (1); + } + + gfl = p11_dl_symbol (dl, "C_GetFunctionList"); + if (!gfl) { + fprintf (stderr, "couldn't find C_GetFunctionList entry point in module: %s: %s\n", + argv[1], p11_dl_error ()); + exit (1); + } + + rv = gfl (&funcs); + if (rv != CKR_OK) { + fprintf (stderr, "call to C_GetFunctiontList failed in module: %s: %s\n", + argv[1], p11_kit_strerror (rv)); + exit (1); + } + + p11_virtual_init (&virt, &p11_virtual_base, funcs, NULL); + p11_buffer_init (&options, 0); + p11_buffer_init (&buffer, 0); + + switch (read (0, &version, 1)) { + case 0: + status = P11_RPC_EOF; + break; + case 1: + if (version != 0) { + fprintf (stderr, "unspported version received: %d", (int)version); + exit (1); + } + break; + default: + fprintf (stderr, "couldn't read creds: %s", strerror (errno)); + exit (1); + } + + version = 0; + switch (write (1, &version, 1)) { + case 1: + break; + default: + fprintf (stderr, "couldn't read creds: %s", strerror (errno)); + exit (1); + } + + status = P11_RPC_OK; + while (status == P11_RPC_OK) { + state = 0; + code = 0; + + do { + status = p11_rpc_transport_read (0, &state, &code, + &options, &buffer); + } while (status == P11_RPC_AGAIN); + + switch (status) { + case P11_RPC_OK: + break; + case P11_RPC_EOF: + continue; + case P11_RPC_AGAIN: + assert_not_reached (); + case P11_RPC_ERROR: + fprintf (stderr, "failed to read rpc message: %s\n", strerror (errno)); + exit (1); + } + + if (!p11_rpc_server_handle (&virt.funcs, &buffer, &buffer)) { + fprintf (stderr, "unexpected error handling rpc message\n"); + exit (1); + } + + state = 0; + options.len = 0; + do { + status = p11_rpc_transport_write (1, &state, code, + &options, &buffer); + } while (status == P11_RPC_AGAIN); + + switch (status) { + case P11_RPC_OK: + break; + case P11_RPC_EOF: + case P11_RPC_AGAIN: + assert_not_reached (); + case P11_RPC_ERROR: + fprintf (stderr, "failed to write rpc message: %s\n", strerror (errno)); + exit (1); + } + } + + p11_buffer_uninit (&buffer); + p11_buffer_uninit (&options); + p11_dl_close (dl); + + return 0; +} diff --git a/p11-kit/tests/mock-module-ep2.c b/p11-kit/tests/mock-module-ep2.c new file mode 100644 index 0000000..ee71711 --- /dev/null +++ b/p11-kit/tests/mock-module-ep2.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2012 Stefan Walter + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stef@thewalter.net> + */ + +#include "config.h" + +#define CRYPTOKI_EXPORTS 1 +#include "pkcs11.h" + +#include "mock.h" + +#include <stdio.h> + +#ifdef OS_WIN32 +__declspec(dllexport) +#endif +CK_RV +C_GetFunctionList (CK_FUNCTION_LIST_PTR_PTR list) +{ + mock_module_init (); + mock_module.C_GetFunctionList = C_GetFunctionList; + if (list == NULL) + return CKR_ARGUMENTS_BAD; + *list = &mock_module; + return CKR_OK; +} diff --git a/p11-kit/tests/test-rpc.c b/p11-kit/tests/test-rpc.c index d945efd..0ce2c55 100644 --- a/p11-kit/tests/test-rpc.c +++ b/p11-kit/tests/test-rpc.c @@ -47,6 +47,7 @@ #include "virtual.h" #include <sys/types.h> +#include <sys/wait.h> #include <assert.h> #include <string.h> #include <stdio.h> @@ -350,15 +351,17 @@ test_byte_array_static (void) } static p11_virtual base; -static bool rpc_initialized = false; +static pid_t rpc_initialized = 0; static CK_RV rpc_initialize (p11_rpc_client_vtable *vtable, void *init_reserved) { + pid_t pid = getpid (); + assert_str_eq (vtable->data, "vtable-data"); - assert_num_eq (false, rpc_initialized); - rpc_initialized = true; + assert_num_cmp (pid, !=, rpc_initialized); + rpc_initialized = pid; return CKR_OK; } @@ -367,8 +370,10 @@ static CK_RV rpc_initialize_fails (p11_rpc_client_vtable *vtable, void *init_reserved) { + pid_t pid = getpid (); + assert_str_eq (vtable->data, "vtable-data"); - assert_num_eq (false, rpc_initialized); + assert_num_cmp (pid, !=, rpc_initialized); return CKR_FUNCTION_FAILED; } @@ -376,8 +381,10 @@ static CK_RV rpc_initialize_device_removed (p11_rpc_client_vtable *vtable, void *init_reserved) { + pid_t pid = getpid (); + assert_str_eq (vtable->data, "vtable-data"); - assert_num_eq (false, rpc_initialized); + assert_num_cmp (pid, !=, rpc_initialized); return CKR_DEVICE_REMOVED; } @@ -401,21 +408,24 @@ static void rpc_finalize (p11_rpc_client_vtable *vtable, void *fini_reserved) { + pid_t pid = getpid (); + assert_str_eq (vtable->data, "vtable-data"); - assert_num_eq (true, rpc_initialized); - rpc_initialized = false; + assert_num_cmp (pid, ==, rpc_initialized); + rpc_initialized = 0; } static void test_initialize (void) { p11_rpc_client_vtable vtable = { "vtable-data", rpc_initialize, rpc_transport, rpc_finalize }; + pid_t pid = getpid (); p11_virtual mixin; bool ret; CK_RV rv; /* Build up our own function list */ - rpc_initialized = false; + rpc_initialized = 0; p11_virtual_init (&base, &p11_virtual_base, &mock_module_no_slots, NULL); ret = p11_rpc_client_init (&mixin, &vtable); @@ -423,11 +433,11 @@ test_initialize (void) rv = mixin.funcs.C_Initialize (&mixin.funcs, NULL); assert (rv == CKR_OK); - assert_num_eq (true, rpc_initialized); + assert_num_eq (pid, rpc_initialized); rv = mixin.funcs.C_Finalize (&mixin.funcs, NULL); assert (rv == CKR_OK); - assert_num_eq (false, rpc_initialized); + assert_num_cmp (pid, !=, rpc_initialized); p11_virtual_uninit (&mixin); } @@ -442,7 +452,7 @@ test_not_initialized (void) CK_RV rv; /* Build up our own function list */ - rpc_initialized = false; + rpc_initialized = 0; p11_virtual_init (&base, &p11_virtual_base, &mock_module_no_slots, NULL); ret = p11_rpc_client_init (&mixin, &vtable); @@ -463,7 +473,7 @@ test_initialize_fails_on_client (void) CK_RV rv; /* Build up our own function list */ - rpc_initialized = false; + rpc_initialized = 0; p11_virtual_init (&base, &p11_virtual_base, &mock_module_no_slots, NULL); ret = p11_rpc_client_init (&mixin, &vtable); @@ -471,7 +481,7 @@ test_initialize_fails_on_client (void) rv = (mixin.funcs.C_Initialize) (&mixin.funcs, NULL); assert (rv == CKR_FUNCTION_FAILED); - assert_num_eq (false, rpc_initialized); + assert_num_eq (0, rpc_initialized); p11_virtual_uninit (&mixin); } @@ -493,7 +503,7 @@ test_transport_fails (void) CK_RV rv; /* Build up our own function list */ - rpc_initialized = false; + rpc_initialized = 0; p11_virtual_init (&base, &p11_virtual_base, &mock_module_no_slots, NULL); ret = p11_rpc_client_init (&mixin, &vtable); @@ -501,7 +511,7 @@ test_transport_fails (void) rv = (mixin.funcs.C_Initialize) (&mixin.funcs, NULL); assert (rv == CKR_FUNCTION_REJECTED); - assert_num_eq (false, rpc_initialized); + assert_num_eq (0, rpc_initialized); p11_virtual_uninit (&mixin); } @@ -523,7 +533,7 @@ test_initialize_fails_on_server (void) rv = (mixin.funcs.C_Initialize) (&mixin.funcs, NULL); assert (rv == CKR_FUNCTION_FAILED); - assert_num_eq (false, rpc_initialized); + assert_num_eq (0, rpc_initialized); p11_virtual_uninit (&mixin); } @@ -555,7 +565,7 @@ test_transport_bad_parse (void) CK_RV rv; /* Build up our own function list */ - rpc_initialized = false; + rpc_initialized = 0; p11_virtual_init (&base, &p11_virtual_base, &mock_module_no_slots, NULL); ret = p11_rpc_client_init (&mixin, &vtable); @@ -886,6 +896,116 @@ test_get_slot_list_no_device (void) teardown_mock_module (rpc_module); } +static void * +invoke_in_thread (void *arg) +{ + CK_FUNCTION_LIST *rpc_module = arg; + CK_INFO info; + CK_RV rv; + + rv = (rpc_module->C_GetInfo) (&info); + assert_num_eq (rv, CKR_OK); + + assert (memcmp (info.manufacturerID, MOCK_INFO.manufacturerID, + sizeof (info.manufacturerID)) == 0); + + return NULL; +} + +static p11_mutex_t delay_mutex; + +static CK_RV +delayed_C_GetInfo (CK_INFO_PTR info) +{ + CK_RV rv; + + p11_sleep_ms (rand () % 100); + + p11_mutex_lock (&delay_mutex); + rv = mock_C_GetInfo (info); + p11_mutex_unlock (&delay_mutex); + + return rv; +} + +static void +test_simultaneous_functions (void) +{ + CK_FUNCTION_LIST real_module; + CK_FUNCTION_LIST *rpc_module; + const int num_threads = 128; + p11_thread_t threads[num_threads]; + int i, ret; + + p11_mutex_init (&delay_mutex); + + memcpy (&real_module, &mock_module_no_slots, sizeof (CK_FUNCTION_LIST)); + real_module.C_GetInfo = delayed_C_GetInfo; + + rpc_module = setup_test_rpc_module (&test_normal_vtable, + &real_module, NULL); + + /* Make the invoked function (above) wait */ + p11_mutex_lock (&delay_mutex); + + for (i = 0; i < num_threads; i++) { + ret = p11_thread_create (threads + i, invoke_in_thread, rpc_module); + assert_num_eq (0, ret); + } + + /* Let the invoked functions return */ + p11_mutex_unlock (&delay_mutex); + + for (i = 0; i < num_threads; i++) + p11_thread_join (threads[i]); + + teardown_mock_module (rpc_module); + p11_mutex_uninit (&delay_mutex); +} + +static void +test_fork_and_reinitialize (void) +{ + CK_FUNCTION_LIST *rpc_module; + CK_INFO info; + int status; + CK_RV rv; + pid_t pid; + int i; + + rpc_module = setup_test_rpc_module (&test_normal_vtable, + &mock_module_no_slots, NULL); + + pid = fork (); + assert_num_cmp (pid, >=, 0); + + /* The child */ + if (pid == 0) { + rv = (rpc_module->C_Initialize) (NULL); + assert_num_eq (CKR_OK, rv); + + for (i = 0; i < 32; i++) { + rv = (rpc_module->C_GetInfo) (&info); + assert_num_eq (CKR_OK, rv); + } + + rv = (rpc_module->C_Finalize) (NULL); + assert_num_eq (CKR_OK, rv); + + _exit (66); + } + + for (i = 0; i < 128; i++) { + rv = (rpc_module->C_GetInfo) (&info); + assert_num_eq (CKR_OK, rv); + } + + assert_num_eq (waitpid (pid, &status, 0), pid); + assert_num_eq (WEXITSTATUS (status), 66); + + teardown_mock_module (rpc_module); +} + #include "test-mock.c" int @@ -932,6 +1052,8 @@ main (int argc, p11_test (test_transport_bad_contents, "/rpc/transport-bad-contents"); p11_test (test_get_info_stand_in, "/rpc/get-info-stand-in"); p11_test (test_get_slot_list_no_device, "/rpc/get-slot-list-no-device"); + p11_test (test_simultaneous_functions, "/rpc/simultaneous-functions"); + p11_test (test_fork_and_reinitialize, "/rpc/fork-and-reinitialize"); test_mock_add_tests ("/rpc"); diff --git a/p11-kit/tests/test-transport.c b/p11-kit/tests/test-transport.c new file mode 100644 index 0000000..32ec02a --- /dev/null +++ b/p11-kit/tests/test-transport.c @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2012 Stefan Walter + * Copyright (c) 2012 Red Hat Inc. + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stef@thewalter.net> + */ + +#include "config.h" +#include "test.h" + +#include "library.h" +#include "mock.h" +#include "path.h" +#include "private.h" +#include "p11-kit.h" +#include "rpc.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <stdio.h> + +struct { + char *directory; + char *user_config; + char *user_modules; +} test; + +static void +setup_remote (void *unused) +{ + const char *data; + + test.directory = p11_test_directory ("p11-test-config"); + test.user_modules = p11_path_build (test.directory, "modules", NULL); + if (mkdir (test.user_modules, 0700) < 0) + assert_not_reached (); + + data = "user-config: only\n"; + test.user_config = p11_path_build (test.directory, "pkcs11.conf", NULL); + p11_test_file_write (NULL, test.user_config, data, strlen (data)); + + data = "remote: " BUILDDIR "/frob-server " BUILDDIR "/.libs/mock-two.so\n"; + p11_test_file_write (test.user_modules, "remote.module", data, strlen (data)); + + p11_config_user_modules = test.user_modules; + p11_config_user_file = test.user_config; +} + +static void +teardown_remote (void *unused) +{ + p11_test_directory_delete (test.user_modules); + p11_test_directory_delete (test.directory); + + free (test.directory); + free (test.user_config); + free (test.user_modules); +} + +static CK_FUNCTION_LIST * +setup_mock_module (CK_SESSION_HANDLE *session) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + CK_RV rv; + int i; + + setup_remote (NULL); + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + if (session) { + rv = (module->C_OpenSession) (MOCK_SLOT_ONE_ID, CKF_RW_SESSION | CKF_SERIAL_SESSION, + NULL, NULL, session); + assert (rv == CKR_OK); + } + + /* Release all the other modules */ + for (i = 0; modules[i] != NULL; i++) { + if (modules[i] != module) + p11_kit_module_release (modules[i]); + } + + free (modules); + return module; +} + +static void +teardown_mock_module (CK_FUNCTION_LIST *module) +{ + p11_kit_module_finalize (module); + teardown_remote (NULL); +} + +static void +test_basic_exec (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + CK_RV rv; + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); +} + +static void * +invoke_in_thread (void *arg) +{ + CK_FUNCTION_LIST *rpc_module = arg; + CK_INFO info; + CK_RV rv; + + rv = (rpc_module->C_GetInfo) (&info); + assert_num_eq (rv, CKR_OK); + + assert (memcmp (info.manufacturerID, MOCK_INFO.manufacturerID, + sizeof (info.manufacturerID)) == 0); + + return NULL; +} + +static void +test_simultaneous_functions (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + const int num_threads = 128; + p11_thread_t threads[num_threads]; + int i, ret; + CK_RV rv; + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + for (i = 0; i < num_threads; i++) { + ret = p11_thread_create (threads + i, invoke_in_thread, module); + assert_num_eq (0, ret); + } + + for (i = 0; i < num_threads; i++) + p11_thread_join (threads[i]); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); +} + +static void +test_fork_and_reinitialize (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + CK_INFO info; + int status; + CK_RV rv; + pid_t pid; + int i; + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + pid = fork (); + assert_num_cmp (pid, >=, 0); + + /* The child */ + if (pid == 0) { + rv = (module->C_Initialize) (NULL); + assert_num_eq (CKR_OK, rv); + + for (i = 0; i < 32; i++) { + rv = (module->C_GetInfo) (&info); + assert_num_eq (CKR_OK, rv); + } + + rv = (module->C_Finalize) (NULL); + assert_num_eq (CKR_OK, rv); + + _exit (66); + } + + for (i = 0; i < 128; i++) { + rv = (module->C_GetInfo) (&info); + assert_num_eq (CKR_OK, rv); + } + + assert_num_eq (waitpid (pid, &status, 0), pid); + assert_num_eq (WEXITSTATUS (status), 66); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); +} + + +#include "test-mock.c" + +int +main (int argc, + char *argv[]) +{ + CK_MECHANISM_TYPE mechanisms[] = { + CKM_MOCK_CAPITALIZE, + CKM_MOCK_PREFIX, + CKM_MOCK_GENERATE, + CKM_MOCK_WRAP, + CKM_MOCK_DERIVE, + CKM_MOCK_COUNT, + 0, + }; + + p11_library_init (); + + /* Override the mechanisms that the RPC mechanism will handle */ + p11_rpc_mechanisms_override_supported = mechanisms; + + p11_fixture (setup_remote, teardown_remote); + p11_test (test_basic_exec, "/transport/basic"); + p11_test (test_simultaneous_functions, "/transport/simultaneous-functions"); + p11_test (test_fork_and_reinitialize, "/transport/fork-and-reinitialize"); + + test_mock_add_tests ("/transport"); + + return p11_test_run (argc, argv); +} |