diff --git a/CMakeLists.txt b/CMakeLists.txt index eb92f80a6d3d3ab801856446e3b345f05ee52f2e..3367172f04754598671a3a82d1a2fd6c52e96d87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,8 @@ endif(COMMAND cmake_policy) project (pkcs11 C) -set(PKCS11_PROXY_SRCS gck-rpc-module.c gck-rpc-message.c gck-rpc-util.c egg-buffer.c) -set(PKCS11_DAEMON_SRCS egg-buffer.c gck-rpc-daemon-standalone.c gck-rpc-dispatch.c gck-rpc-message.c gck-rpc-util.c syscall-reporter.c syscall-names.h) +set(PKCS11_PROXY_SRCS gck-rpc-module.c gck-rpc-message.c gck-rpc-util.c egg-buffer.c gck-rpc-tls-psk.c) +set(PKCS11_DAEMON_SRCS egg-buffer.c gck-rpc-daemon-standalone.c gck-rpc-dispatch.c gck-rpc-message.c gck-rpc-util.c syscall-reporter.c syscall-names.h gck-rpc-tls-psk.c) add_definitions(-Wall) add_library(pkcs11-proxy SHARED ${PKCS11_PROXY_SRCS}) @@ -33,8 +33,8 @@ if (WIN32) target_link_libraries (pkcs11-proxy ws2_32) endif (WIN32) -target_link_libraries (pkcs11-proxy pthread) -target_link_libraries (pkcs11-daemon dl pthread) +target_link_libraries (pkcs11-proxy pthread ssl) +target_link_libraries (pkcs11-daemon dl pthread ssl) install_targets (/lib pkcs11-proxy) install_targets (/bin pkcs11-daemon) diff --git a/README.rst b/README.rst index 8f258c7ebb1329571c74536fd2850c93bc8d0b65..fd5bb2ae94fad0d161f743ef97e3c0b27c6bed2e 100644 --- a/README.rst +++ b/README.rst @@ -6,5 +6,10 @@ This fork has the following additional features: - support for running in "inetd mode", useful for calling directly from stunnel - seccomp syscall filtering (only tested in inetd-mode) +- getaddrinfo support for IPv6, fallback and DNS resolution +- TLS-PSK support to optionally encrypt communication + +Plus a number of important bug fixes. This version passes the SoftHSM test +suite. An ubuntu PPA that tracks this version is ppa:leifj diff --git a/USAGE b/USAGE index c1784c7608ddefe654db621159f5be1bd368992d..f79297522dc0684c2331684553d96ef686de5e4b 100644 --- a/USAGE +++ b/USAGE @@ -5,9 +5,10 @@ dependencies and other features. The proxy tunnels PKCS11-requests over the network. One possible use is to store cryptograhic information on a seperate server. This way -the crypto it can be isolated from the rest of the system. Beware: -the connection is not encrypted and can easily be sniffed. You should -use a secure communication-channel, for example stunnel. +the crypto can be isolated from the rest of the system. + +Example +======= Here is an example of using pkcs11-proxy together with SoftHSM (from the OpenDNSSEC project). The benefit of this setup is that no extra hardware @@ -30,3 +31,52 @@ slots: Slot 0 SoftHSM token label: test token manuf: SoftHSM token model: SoftHSM token flags: rng, login required, PIN initialized, token initialized, other flags=0x40 serial num : 1 + + +IPv6 and DNS +============ + +The PKCS11_DAEMON_SOCKET and PKCS11_PROXY_SOCKET environment variables can +have both hostnames and IPv6 addresses in them. getaddrinfo(3) is used to +resolve any DNS name. + +$ PKCS11_DAEMON_SOCKET="tcp://server.example.com:2345" ... + +If `server.example.com' resolves to more than one IP address (such as one +IPv4 and one IPv6 address), these will be tried sequentially. Currently, +no attempt is made to speed up connection establishment using Happy Eyeballs +(RFC 6555) or similar, so timeouts in case of unreachable addresses could +be expected to be quite problematic. + +IPv6 addresses should be enclosed by square brackets. + +$ PKCS11_DAEMON_SOCKET="tcp://[::1]:2345" ... + + +Encryption +========== + +This version supports encrypting the communication between the client and +proxy using OpenSSL TLS-PSK (pre-shared key). The PSK is read from a file +that usees the GnuTLS psktool format. This format includes PSK identity +as well as key. + +Currently, there is no way to specify the identity to use on the client +side (client tells server what identity should be used), and the first +identity found in the PSK file is used. The server side will correctly +look up the identity requested by the client in it's PSK file, so it is +possible to have one unique PSK identity and key per PKCS11 client, and +have all the identitys and keys in the PSK file for the PKCS11 daemon. + +$ cat test.psk +test:e9622c85018998993fcc16f5ce9c15e9 +$ PKCS11_PROXY_TLS_PSK_FILE="test.psk" \ + PKCS11_DAEMON_SOCKET="tls://server.example.com:2345" \ + pkcs11-daemon /usr/lib/libsofthsm.so +$ PKCS11_PROXY_TLS_PSK_FILE="test.psk" \ + PKCS11_PROXY_SOCKET="tls://server.example.com:2345" \ + pkcs11-tool --module=/usr/lib/libpkcs11-proxy.so -L Available +slots: Slot 0 SoftHSM + token label: test token manuf: SoftHSM token model: SoftHSM + token flags: rng, login required, PIN initialized, token initialized, + other flags=0x40 serial num : 1 diff --git a/config.h b/config.h index 88806d5099a1956ce2a80352fd5a37c33b84ecd1..7ebadeecf181b47e251215e2384a737eb5986b0b 100644 --- a/config.h +++ b/config.h @@ -6,6 +6,8 @@ # define PKCS11PROXY_LISTEN_BACKLOG 128 # define PKCS11PROXY_MAX_SESSION_COUNT 256 +# define PKCS11PROXY_TLS_PSK_CIPHERS "PSK-AES128-CBC-SHA:PSK-AES256-CBC-SHA"; + #ifdef __MINGW32__ # include <stdint.h> diff --git a/gck-rpc-daemon-standalone.c b/gck-rpc-daemon-standalone.c index 9b89f50c46bd8809553b4271e061f838bc4907be..f0ffdfa720370bbcd595091693655e5d9baa55ce 100644 --- a/gck-rpc-daemon-standalone.c +++ b/gck-rpc-daemon-standalone.c @@ -26,6 +26,7 @@ #include "pkcs11/pkcs11.h" #include "gck-rpc-layer.h" +#include "gck-rpc-tls-psk.h" #include <stdio.h> #include <errno.h> @@ -166,12 +167,12 @@ int main(int argc, char *argv[]) CK_C_GetFunctionList func_get_list; CK_FUNCTION_LIST_PTR funcs; void *module; - const char *path; + const char *path, *tls_psk_keyfile; fd_set read_fds; int sock, ret; CK_RV rv; CK_C_INITIALIZE_ARGS init_args; - + GckRpcTlsPskState *tls; if (install_syscall_reporter()) return 1; @@ -229,6 +230,27 @@ int main(int argc, char *argv[]) if (!path) path = SOCKET_PATH; + /* Initialize TLS, if appropriate */ + tls = NULL; + if (! strncmp("tls://", path, 6)) { + tls_psk_keyfile = getenv("PKCS11_PROXY_TLS_PSK_FILE"); + if (! tls_psk_keyfile || ! tls_psk_keyfile[0]) { + fprintf(stderr, "key file must be specified for tls:// socket.\n"); + exit(1); + } + + tls = calloc(1, sizeof(GckRpcTlsPskState)); + if (tls == NULL) { + fprintf(stderr, "can't allocate memory for TLS-PSK"); + exit(1); + } + + if (! gck_rpc_init_tls_psk(tls, tls_psk_keyfile, NULL, GCK_RPC_TLS_PSK_SERVER)) { + fprintf(stderr, "TLS-PSK initialization failed"); + exit(1); + } + } + if (strcmp(path,"-") == 0) { gck_rpc_layer_inetd(funcs); } else { @@ -254,7 +276,7 @@ int main(int argc, char *argv[]) } if (FD_ISSET(sock, &read_fds)) - gck_rpc_layer_accept(); + gck_rpc_layer_accept(tls); } gck_rpc_layer_uninitialize(); @@ -267,5 +289,8 @@ int main(int argc, char *argv[]) dlclose(module); + if (tls) + gck_rpc_close_tls(tls); + return 0; } diff --git a/gck-rpc-dispatch.c b/gck-rpc-dispatch.c index a6f76343986527374e91b6fc46f54b86c34fa0b9..c604eca986d30d8a6db8f1dc998ed709015b02a7 100644 --- a/gck-rpc-dispatch.c +++ b/gck-rpc-dispatch.c @@ -25,6 +25,7 @@ #include "gck-rpc-layer.h" #include "gck-rpc-private.h" +#include "gck-rpc-tls-psk.h" #include "pkcs11/pkcs11.h" #include "pkcs11/pkcs11g.h" @@ -71,14 +72,15 @@ typedef struct _CallState { uint64_t appid; int call; int sock; - int (*read)(int, unsigned char *,size_t); - int (*write)(int, unsigned char *,size_t); + int (*read)(void *cs, unsigned char *,size_t); + int (*write)(void *cs, 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]; + GckRpcTlsPskState *tls; } CallState; typedef struct _DispatchState { @@ -2137,17 +2139,20 @@ static int dispatch_call(CallState * cs) return 1; } -static int read_all(int sock, unsigned char *data, size_t len) +static int read_all(CallState *cs, void *data, size_t len) { int r; - assert(sock >= 0); + assert(cs->sock >= 0); assert(data); assert(len > 0); while (len > 0) { - r = recv(sock, (void *)data, len, 0); + if (cs->tls) + r = gck_rpc_tls_read_all(cs->tls, data, len); + else + r = recv(cs->sock, data, len, 0); if (r == 0) { /* Connection was closed on client */ @@ -2166,17 +2171,20 @@ static int read_all(int sock, unsigned char *data, size_t len) return 1; } -static int write_all(int sock, unsigned char *data, size_t len) +static int write_all(CallState *cs, void *data, size_t len) { int r; - assert(sock >= 0); + assert(cs->sock >= 0); assert(data); assert(len > 0); while (len > 0) { - r = send(sock, (void *)data, len, MSG_NOSIGNAL); + if (cs->tls) + r = gck_rpc_tls_write_all(cs->tls, (void *) data, len); + else + r = send(cs->sock, data, len, MSG_NOSIGNAL); if (r == -1) { if (errno == EPIPE) { @@ -2212,8 +2220,16 @@ static void run_dispatch_loop(CallState *cs) hoststr[0] = portstr[0] = '\0'; } + /* Enable TLS for this socket */ + if (cs->tls) { + if (! gck_rpc_start_tls(cs->tls, cs->sock)) { + gck_rpc_warn("Can't enable TLS"); + return ; + } + } + /* The client application */ - if (!cs->read(cs->sock, (unsigned char *)&cs->appid, sizeof (cs->appid))) { + if (! cs->read(cs, (void *)&cs->appid, sizeof (cs->appid))) { gck_rpc_warn("Can't read appid\n"); return ; } @@ -2233,7 +2249,7 @@ static void run_dispatch_loop(CallState *cs) call_reset(cs); /* Read the number of bytes ... */ - if (!cs->read(cs->sock, buf, 4)) + if (! cs->read(cs, buf, 4)) break; /* Calculate the number of bytes */ @@ -2252,7 +2268,7 @@ static void run_dispatch_loop(CallState *cs) } /* ... and read/parse in the actual message */ - if (!cs->read(cs->sock, cs->req->buffer.buf, len)) + if (!cs->read(cs, cs->req->buffer.buf, len)) break; egg_buffer_add_empty(&cs->req->buffer, len); @@ -2266,8 +2282,8 @@ static void run_dispatch_loop(CallState *cs) /* .. send back response length, and then response data */ egg_buffer_encode_uint32(buf, cs->resp->buffer.len); - if (!cs->write(cs->sock, buf, 4) || - !cs->write(cs->sock, cs->resp->buffer.buf, cs->resp->buffer.len)) + if (!cs->write(cs, buf, 4) || + !cs->write(cs, cs->resp->buffer.buf, cs->resp->buffer.len)) break; } @@ -2299,7 +2315,7 @@ static int pkcs11_socket = -1; /* The unix socket path, that we listen on */ static char pkcs11_socket_path[MAXPATHLEN] = { 0, }; -void gck_rpc_layer_accept(void) +void gck_rpc_layer_accept(GckRpcTlsPskState *tls) { struct sockaddr_storage addr; DispatchState *ds, **here; @@ -2342,6 +2358,7 @@ void gck_rpc_layer_accept(void) ds->cs.write = &write_all; ds->cs.addr = addr; ds->cs.addrlen = addrlen; + ds->cs.tls = tls; error = pthread_create(&ds->thread, NULL, run_dispatch_thread, &(ds->cs)); @@ -2357,14 +2374,26 @@ void gck_rpc_layer_accept(void) pthread_mutex_unlock(&pkcs11_dispatchers_mutex); } +static int _inetd_read(CallState *cs, void *data, size_t len) +{ + assert(cs->sock >= 0); + return read(cs->sock, data, len); +} + +static int _inetd_write(CallState *cs, void *data, size_t len) +{ + assert(cs->sock >= 0); + return write(cs->sock, data, len); +} + void gck_rpc_layer_inetd(CK_FUNCTION_LIST_PTR module) { CallState cs; memset(&cs, 0, sizeof(cs)); cs.sock = STDIN_FILENO; - cs.read = &read; - cs.write = &write; + cs.read = &_inetd_read; + cs.write = &_inetd_write; pkcs11_module = module; @@ -2496,7 +2525,8 @@ int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module) } #endif - if (!strncmp("tcp://", prefix, 6)) { + if (!strncmp("tcp://", prefix, 6) || + !strncmp("tls://", prefix, 6)) { /* * TCP socket */ diff --git a/gck-rpc-layer.h b/gck-rpc-layer.h index 5d2766c4fe5e466de380aab99fe04f848da6b80c..8ea653b55f1e86c3862857e841fad39192fa93d9 100644 --- a/gck-rpc-layer.h +++ b/gck-rpc-layer.h @@ -3,6 +3,8 @@ #include "pkcs11/pkcs11.h" +#include "gck-rpc-tls-psk.h" + /* ------------------------------------------------------------------ * DISPATCHER */ @@ -14,7 +16,7 @@ int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR funcs); void gck_rpc_layer_uninitialize(void); /* Accept a new connection. Should be called when above fd has read */ -void gck_rpc_layer_accept(void); +void gck_rpc_layer_accept(GckRpcTlsPskState *tls); /* Run a single connection off of STDIN - call from inetd or stunnel */ void gck_rpc_layer_inetd(CK_FUNCTION_LIST_PTR funcs); diff --git a/gck-rpc-module.c b/gck-rpc-module.c index 1ce0665af712e9d1fc5569cc11895d35abfd3026..84adf067023bc0c52b93427e4821fa3afed481ea 100644 --- a/gck-rpc-module.c +++ b/gck-rpc-module.c @@ -25,6 +25,7 @@ #include "gck-rpc-layer.h" #include "gck-rpc-private.h" +#include "gck-rpc-tls-psk.h" #include "pkcs11/pkcs11.h" @@ -67,6 +68,8 @@ static uint64_t pkcs11_app_id = 0; /* The socket to connect to */ static char pkcs11_socket_path[MAXPATHLEN] = { 0, }; +/* The TLS-PSK keyfile name */ +static char tls_psk_key_filename[MAXPATHLEN] = { 0, }; /* The error used by us when parsing of rpc message fails */ #define PARSE_ERROR CKR_DEVICE_ERROR @@ -113,6 +116,9 @@ static void parse_argument(char *arg) if (strcmp(arg, "socket") == 0) snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), "%s", value); + else if (strcmp(arg, "tls_psk_file") == 0) + snprintf(tls_psk_key_filename, sizeof(tls_psk_key_filename), "%s", + value); else warning(("unrecognized argument: %s", arg)); } @@ -201,6 +207,7 @@ typedef struct _CallState { GckRpcMessage *req; /* The current request */ GckRpcMessage *resp; /* The current response */ int call_status; + GckRpcTlsPskState *tls; struct _CallState *next; /* For pooling of completed sockets */ } CallState; @@ -251,7 +258,10 @@ static CK_RV call_write(CallState * cs, unsigned char *data, size_t len) return CKR_DEVICE_ERROR; } - r = send(fd, (void *)data, len, 0); + if (cs->tls) + r = gck_rpc_tls_write_all(cs->tls, (void *) data, len); + else + r = send(fd, (void *) data, len, 0); if (r == -1) { if (errno == EPIPE) { @@ -290,7 +300,10 @@ static CK_RV call_read(CallState * cs, unsigned char *data, size_t len) return CKR_DEVICE_ERROR; } - r = recv(fd, (void *)data, len, 0); + if (cs->tls) + r = gck_rpc_tls_read_all(cs->tls, (void *) data, len); + else + r = recv(fd, (void *) data, len, 0); if (r == 0) { warning(("couldn't receive data: daemon closed connection")); @@ -409,7 +422,8 @@ static CK_RV call_connect(CallState * cs) memset(&addr, 0, sizeof(addr)); - if (!strncmp("tcp://", pkcs11_socket_path, 6)) { + if (! strncmp("tcp://", pkcs11_socket_path, 6) || + ! strncmp("tls://", pkcs11_socket_path, 6)) { char *host, *port; if (! gck_rpc_parse_host_port(pkcs11_socket_path + 6, &host, &port)) { @@ -424,6 +438,24 @@ static CK_RV call_connect(CallState * cs) } free(host); + + if (! strncmp("tls://", pkcs11_socket_path, 6)) { + cs->tls = calloc(1, sizeof(GckRpcTlsPskState)); + if (cs->tls == NULL) { + warning(("can't allocate memory for TLS-PSK")); + return CKR_HOST_MEMORY; + } + + if (! gck_rpc_init_tls_psk(cs->tls, tls_psk_key_filename, NULL, GCK_RPC_TLS_PSK_CLIENT)) { + warning(("TLS-PSK initialization failed")); + return CKR_DEVICE_ERROR; + } + + if (! gck_rpc_start_tls(cs->tls, sock)) { + gck_rpc_warn("failed starting TLS"); + return CKR_DEVICE_ERROR; + } + } } else { addr.sun_family = AF_UNIX; strncpy(addr.sun_path, pkcs11_socket_path, @@ -472,6 +504,9 @@ static void call_destroy(void *value) gck_rpc_message_free(cs->req); gck_rpc_message_free(cs->resp); + if (cs->tls) + gck_rpc_close_tls(cs->tls); + free(cs); debug(("destroyed state")); @@ -1320,7 +1355,8 @@ static CK_RV rpc_C_Initialize(CK_VOID_PTR init_args) if (pkcs11_socket_path[0] == 0) { path = getenv("PKCS11_PROXY_SOCKET"); if (path && path[0]) { - if (!strncmp("tcp://", path, 6)) + if ((! strncmp("tcp://", path, 6)) || + (! strncmp("tls://", path, 6))) snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), "%s", path); @@ -1335,6 +1371,23 @@ static CK_RV rpc_C_Initialize(CK_VOID_PTR init_args) } } + /* If socket path indicates TLS, make sure tls_psk_key_filename is populated. */ + if (! strncmp("tls://", pkcs11_socket_path, 6)) { + if (! tls_psk_key_filename[0]) { + path = getenv("PKCS11_PROXY_TLS_PSK_FILE"); + if (path && path[0]) { + snprintf(tls_psk_key_filename, sizeof(tls_psk_key_filename), + "%s", path); + } + } + + if (! tls_psk_key_filename[0]) { + warning(("can't handle tls:// path without a tls-psk file")); + ret = CKR_FUNCTION_NOT_SUPPORTED; + goto done; + } + } + srand(time(NULL) ^ pid); pkcs11_app_id = (uint64_t) rand() << 32 | rand(); diff --git a/gck-rpc-tls-psk.c b/gck-rpc-tls-psk.c new file mode 100644 index 0000000000000000000000000000000000000000..23d5dd4b34c8f18d4139049ffabf890f7edb3f2f --- /dev/null +++ b/gck-rpc-tls-psk.c @@ -0,0 +1,376 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* gck-rpc-tls-psk.c - TLS-PSK functionality to protect communication + + Copyright (C) 2013, NORDUnet A/S + + pkcs11-proxy is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + pkcs11-proxy is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Fredrik Thulin <fredrik@thulin.net> +*/ + +#include "config.h" + +#include "gck-rpc-private.h" +#include "gck-rpc-tls-psk.h" + +#include <sys/param.h> +#include <assert.h> + +/* TLS pre-shared key */ +static char tls_psk_identity[128] = { 0, }; +static char tls_psk_key_filename[MAXPATHLEN] = { 0, }; + +/* ----------------------------------------------------------------------------- + * LOGGING and DEBUGGING + */ +#ifndef DEBUG_OUTPUT +#define DEBUG_OUTPUT 0 +#endif +#if DEBUG_OUTPUT +#define debug(x) gck_rpc_debug x +#else +#define debug(x) +#endif +#define warning(x) gck_rpc_warn x + + +/* ----------------------------------------------------------------------------- + * TLS-PSK (pre-shared key) functionality + */ + +/* Utility function to decode a single hex char. + * + * Returns value as integer, or -1 on invalid hex char (not 0-9, a-f or A-F). + */ +static int +_tls_psk_to_hex(char val) +{ + if (val >= '0' && val <= '9') + return val - '0'; + if (val >= 'a' && val <= 'f') + return val - 'a' + 10; + if (val >= 'A' && val <= 'F') + return val - 'A' + 10; + return -1; +} + +/* Hex decode the key from an entry in the TLS-PSK key file. Entrys are of the form + * + * identity:hex-key\n + * + * Logging debug/error messages here is a bit problematic since the key is sensitive + * and should not be logged to syslog for example. This code avoids logging the key + * part and only logs identity. + * + * Returns 0 on failure, number of bytes in hex-decoded key on success. + */ +static int +_tls_psk_decode_key(const char *identity, const char *hexkey, unsigned char *psk, unsigned int max_psk_len) +{ + int psk_len, i; + + /* check that length of the key is even */ + if ((strlen(hexkey) % 2) != 0) { + warning(("un-even length TLS-PSK key")); + return 0; + } + + memset(psk, 0, max_psk_len); + psk_len = 0; + + while (*hexkey && (psk_len < max_psk_len)) { + /* decode first half of byte, check for errors */ + if ((i = _tls_psk_to_hex(*hexkey)) < 0) { + warning(("bad TLS-PSK '%.100s' hex char at position %i (%c)", + identity, psk_len + 1, *hexkey)); + return 0; + } + *psk = i << 4; + hexkey++; + + /* decode second half of byte, check for errors */ + if ((i = _tls_psk_to_hex(*hexkey)) < 0) { + warning(("bad TLS-PSK '%.100s' hex char at position %i (%c)", + identity, psk_len + 1, *hexkey)); + return 0; + } + *psk |= i; + hexkey++; + + psk_len++; + psk++; + } + if (*hexkey) { + warning(("too long TLS-PSK '%.100s' key (max %i)", identity, max_psk_len)); + return 0; + } + + return psk_len; +} + +/* + * Callbacks invoked by OpenSSL PSK initialization. + */ + +/* Server side TLS-PSK initialization callback. Given an identity (chosen by the client), + * locate a pre-shared key and put it in psk. + * + * Returns the number of bytes put in psk, or 0 on failure. + */ +static unsigned int +_tls_psk_server_cb(SSL *ssl, const char *identity, + unsigned char *psk, unsigned int max_psk_len) +{ + char line[1024], *hexkey; + unsigned int psk_len; + FILE *fd; + int i; + + debug(("Initializing TLS-PSK with keyfile '%.100s', identity '%.100s'", + tls_psk_key_filename, identity)); + + if ((fd = fopen(tls_psk_key_filename, "r")) == NULL) { + gck_rpc_warn("can't open TLS-PSK keyfile '%.100s' for reading", tls_psk_key_filename); + return 0; + } + + /* Format of PSK file is that of GnuTLS psktool. + * + * identity:hex-key + * other:another-hex-key + */ + psk_len = 0; + + while (fgets(line, sizeof(line) - 1, fd)) { + /* Find first colon and replace it with NULL */ + hexkey = strchr(line, ':'); + if (! hexkey) + continue; + *hexkey = 0; + hexkey++; + + /* Remove newline(s) at the end */ + for (i = strlen(hexkey) - 1; i && (hexkey[i] == '\n' || hexkey[i] == '\r'); i--) + hexkey[i] = 0; + + if (identity == NULL || ! identity[0] || ! strcmp(line, identity)) { + /* If the line starts with identity: or identity is not provided, parse this line. */ + psk_len = _tls_psk_decode_key(line, hexkey, psk, max_psk_len); + if (psk_len) + debug(("Loaded TLS-PSK '%.100s' from keyfile '%.100s'", + line, tls_psk_key_filename)); + else + warning(("Failed loading TLS-PSK '%.100s' from keyfile '%.100s'", + line, tls_psk_key_filename)); + break; + } + } + fclose(fd); + + return psk_len; +} + +/* Client side TLS-PSK initialization callback. Indicate to OpenSSL what identity to + * use, and the pre-shared key for that identity. + * + * Returns the number of bytes put in psk, or 0 on failure. + */ +static unsigned int +_tls_psk_client_cb(SSL *ssl, const char *hint, + char *identity, unsigned int max_identity_len, + unsigned char *psk, unsigned int max_psk_len) +{ + /* Client tells server which identity it wants to use in ClientKeyExchange */ + snprintf(identity, max_identity_len, "%s", tls_psk_identity); + + /* We currently just discard the hint sent to us by the server */ + return _tls_psk_server_cb(ssl, identity, psk, max_psk_len); +} + + +/* Initialize OpenSSL and create an SSL CTX. Should be called just once. + * + * Returns 0 on failure and 1 on success. + */ +int +gck_rpc_init_tls_psk(GckRpcTlsPskState *state, const char *key_filename, + const char *identity, enum gck_rpc_tls_psk_caller caller) +{ + char *tls_psk_ciphers = PKCS11PROXY_TLS_PSK_CIPHERS; + + if (state->initialized == 1) { + warning(("TLS state already initialized")); + return 0; + } + + /* Global OpenSSL initialization */ + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_ssl_algorithms(); + + assert(caller == GCK_RPC_TLS_PSK_CLIENT || caller == GCK_RPC_TLS_PSK_SERVER); + + if (caller == GCK_RPC_TLS_PSK_CLIENT) + state->ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + else + state->ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + + if (state->ssl_ctx == NULL) { + gck_rpc_warn("can't initialize SSL_CTX"); + return 0; + } + + /* Set up callback for TLS-PSK initialization */ + if (caller == GCK_RPC_TLS_PSK_CLIENT) + SSL_CTX_set_psk_client_callback(state->ssl_ctx, _tls_psk_client_cb); + else + SSL_CTX_set_psk_server_callback(state->ssl_ctx, _tls_psk_server_cb); + + /* Disable compression, for security (CRIME Attack). */ + SSL_CTX_set_options(state->ssl_ctx, SSL_OP_NO_COMPRESSION); + + /* Specify ciphers to use */ + SSL_CTX_set_cipher_list(state->ssl_ctx, tls_psk_ciphers); + + snprintf(tls_psk_key_filename, sizeof(tls_psk_key_filename), "%s", key_filename); + snprintf(tls_psk_identity, sizeof(tls_psk_identity), "%s", identity ? identity : ""); + + state->type = caller; + state->initialized = 1; + + debug(("Initialized TLS-PSK %s", caller == GCK_RPC_TLS_PSK_CLIENT ? "client" : "server")); + + return 1; +} + +/* Set up SSL for a new socket. Call this after accept() or connect(). + * + * When a socket has been created, call gck_rpc_start_tls() with the TLS state + * initialized using gck_rpc_init_tls_psk() and the new socket. + * + * Returns 1 on success and 0 on failure. + */ +int +gck_rpc_start_tls(GckRpcTlsPskState *state, int sock) +{ + int res; + char buf[256]; + + state->ssl = SSL_new(state->ssl_ctx); + if (! state->ssl) { + warning(("can't initialize SSL")); + return 0; + } + + state->bio = BIO_new_socket(sock, BIO_NOCLOSE); + if (! state->bio) { + warning(("can't initialize SSL BIO")); + return 0; + } + + SSL_set_bio(state->ssl, state->bio, state->bio); + + /* Set up callback for TLS-PSK initialization */ + if (state->type == GCK_RPC_TLS_PSK_CLIENT) + res = SSL_connect(state->ssl); + else + res = SSL_accept(state->ssl); + + if (res != 1) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + warning(("can't start TLS : %i/%i (%s perhaps)", + res, SSL_get_error(state->ssl, res), strerror(errno))); + warning(("SSL ERR: %s", buf)); + return 0; + } + + return 1; +} + +/* Un-initialize everything SSL related. Call this on application shut down. + */ +void +gck_rpc_close_tls(GckRpcTlsPskState *state) +{ + if (state->ssl_ctx) { + SSL_CTX_free(state->ssl_ctx); + state->ssl_ctx = NULL; + } + + if (state->ssl) { + SSL_free(state->ssl); + state->ssl = NULL; + } + + if (state->bio) { + BIO_free(state->bio); + state->bio = NULL; + } +} + +/* Send data using SSL. + * + * Returns the number of bytes written. + */ +int +gck_rpc_tls_write_all(GckRpcTlsPskState *state, void *data, unsigned int len) +{ + int bytes, error; + char buf[256]; + + assert(state); + assert(data); + assert(len > 0); + + bytes = SSL_write(state->ssl, data, len); + + if (bytes <= 0) { + while ((error = ERR_get_error())) { + ERR_error_string_n(error, buf, sizeof(buf)); + warning(("SSL_write error: %s", buf)); + } + return 0; + } + + return bytes; +} + +/* Read data using SSL. + * + * Returns the number of bytes read. + */ +int +gck_rpc_tls_read_all(GckRpcTlsPskState *state, void *data, unsigned int len) +{ + int bytes, error; + char buf[256]; + + assert(state); + assert(data); + assert(len > 0); + + bytes = SSL_read(state->ssl, data, len); + + if (bytes <= 0) { + while ((error = ERR_get_error())) { + ERR_error_string_n(error, buf, sizeof(buf)); + warning(("SSL_read error: %s", buf)); + } + return 0; + } + + return bytes; +} diff --git a/gck-rpc-tls-psk.h b/gck-rpc-tls-psk.h new file mode 100644 index 0000000000000000000000000000000000000000..b878376ae367f7041bfe0deea54c48a80f9c80c1 --- /dev/null +++ b/gck-rpc-tls-psk.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +#ifndef GCKRPC_TLS_PSK_H_ +#define GCKRPC_TLS_PSK_H_ + +#include "openssl/bio.h" +#include "openssl/ssl.h" +#include "openssl/err.h" + +#if OPENSSL_VERSION_NUMBER < 0x10000000 +# error "OpenSSL version >= 1.0.0 required" +#endif + +enum gck_rpc_tls_psk_caller { + GCK_RPC_TLS_PSK_CLIENT, + GCK_RPC_TLS_PSK_SERVER +}; + +typedef struct { + int initialized; + SSL_CTX *ssl_ctx; + BIO *bio; + SSL *ssl; + enum gck_rpc_tls_psk_caller type; +} GckRpcTlsPskState; + + +int gck_rpc_init_tls_psk(GckRpcTlsPskState *state, const char *key_filename, + const char *identity, enum gck_rpc_tls_psk_caller caller); +int gck_rpc_start_tls(GckRpcTlsPskState *state, int sock); + +int gck_rpc_tls_write_all(GckRpcTlsPskState *state, void *data, unsigned int len); +int gck_rpc_tls_read_all(GckRpcTlsPskState *state, void *data, unsigned int len); + +void gck_rpc_close_tls(GckRpcTlsPskState *state); + +#endif /* GCKRPC_TLS_PSK_H_ */