Add support to libtls for client-side TLS session resumption.
authorjsing <jsing@openbsd.org>
Sat, 10 Feb 2018 04:41:24 +0000 (04:41 +0000)
committerjsing <jsing@openbsd.org>
Sat, 10 Feb 2018 04:41:24 +0000 (04:41 +0000)
A libtls client can specify a session file descriptor (a regular file
with appropriate ownership and permissions) and libtls will manage reading
and writing of session data across TLS handshakes.

Discussed at length with deraadt@ and tedu@.

Rides previous minor bump.

ok beck@

lib/libtls/Symbols.list
lib/libtls/tls.h
lib/libtls/tls_client.c
lib/libtls/tls_config.c
lib/libtls/tls_conninfo.c
lib/libtls/tls_internal.h

index 1e7538c..923924f 100644 (file)
@@ -42,6 +42,7 @@ tls_config_set_ocsp_staple_file
 tls_config_set_protocols
 tls_config_set_session_id
 tls_config_set_session_lifetime
+tls_config_set_session_fd
 tls_config_set_verify_depth
 tls_config_skip_private_key_check
 tls_config_verify
@@ -51,6 +52,7 @@ tls_configure
 tls_conn_alpn_selected
 tls_conn_cipher
 tls_conn_servername
+tls_conn_session_resumed
 tls_conn_version
 tls_connect
 tls_connect_cbs
index cc8627f..8d66c2f 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls.h,v 1.51 2017/08/10 18:18:30 jsing Exp $ */
+/* $OpenBSD: tls.h,v 1.52 2018/02/10 04:41:24 jsing Exp $ */
 /*
  * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
  *
@@ -128,6 +128,7 @@ int tls_config_set_ocsp_staple_mem(struct tls_config *_config,
 int tls_config_set_ocsp_staple_file(struct tls_config *_config,
     const char *_staple_file);
 int tls_config_set_protocols(struct tls_config *_config, uint32_t _protocols);
+int tls_config_set_session_fd(struct tls_config *_config, int _session_fd);
 int tls_config_set_verify_depth(struct tls_config *_config, int _verify_depth);
 
 void tls_config_prefer_ciphers_client(struct tls_config *_config);
@@ -188,6 +189,7 @@ const uint8_t *tls_peer_cert_chain_pem(struct tls *_ctx, size_t *_len);
 const char *tls_conn_alpn_selected(struct tls *_ctx);
 const char *tls_conn_cipher(struct tls *_ctx);
 const char *tls_conn_servername(struct tls *_ctx);
+int tls_conn_session_resumed(struct tls *_ctx);
 const char *tls_conn_version(struct tls *_ctx);
 
 uint8_t *tls_load_file(const char *_file, size_t *_len, char *_password);
index c79f462..14c716f 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls_client.c,v 1.43 2017/08/10 18:18:30 jsing Exp $ */
+/* $OpenBSD: tls_client.c,v 1.44 2018/02/10 04:41:24 jsing Exp $ */
 /*
  * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
  *
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 
 #include <arpa/inet.h>
 #include <netinet/in.h>
 
+#include <limits.h>
 #include <netdb.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -158,6 +160,118 @@ tls_connect_servername(struct tls *ctx, const char *host, const char *port,
        return (rv);
 }
 
+static int
+tls_client_read_session(struct tls *ctx)
+{
+       int sfd = ctx->config->session_fd;
+       uint8_t *session = NULL;
+       size_t session_len = 0;
+       SSL_SESSION *ss = NULL;
+       BIO *bio = NULL;
+       struct stat sb;
+       ssize_t n;
+       int rv = -1;
+
+       if (fstat(sfd, &sb) == -1) {
+               tls_set_error(ctx, "failed to stat session file");
+               goto err;
+       }
+       if (sb.st_size < 0 || sb.st_size > INT_MAX) {
+               tls_set_errorx(ctx, "invalid session file size");
+               goto err;
+       }
+       session_len = (size_t)sb.st_size;
+
+       /* A zero size file means that we do not yet have a valid session. */
+       if (session_len == 0)
+               goto done;
+
+       if ((session = malloc(session_len)) == NULL)
+               goto err;
+
+       n = pread(sfd, session, session_len, 0);
+       if (n < 0 || (size_t)n != session_len) {
+               tls_set_error(ctx, "failed to read session file");
+               goto err;
+       }
+       if ((bio = BIO_new_mem_buf(session, session_len)) == NULL)
+               goto err;
+       if ((ss = PEM_read_bio_SSL_SESSION(bio, NULL, tls_password_cb,
+           NULL)) == NULL) {
+               tls_set_errorx(ctx, "failed to parse session");
+               goto err;
+       }
+
+       if (SSL_set_session(ctx->ssl_conn, ss) != 1) {
+               tls_set_errorx(ctx, "failed to set session");
+               goto err;
+       }
+
+ done:
+       rv = 0;
+
+ err:
+       freezero(session, session_len);
+       SSL_SESSION_free(ss);
+       BIO_free(bio);
+
+       return rv;
+}
+
+static int
+tls_client_write_session(struct tls *ctx)
+{
+       int sfd = ctx->config->session_fd;
+       SSL_SESSION *ss = NULL;
+       BIO *bio = NULL;
+       long data_len;
+       char *data;
+       off_t offset;
+       size_t len;
+       ssize_t n;
+       int rv = -1;
+
+       if ((ss = SSL_get1_session(ctx->ssl_conn)) == NULL) {
+               if (ftruncate(sfd, 0) == -1) {
+                       tls_set_error(ctx, "failed to truncate session file");
+                       goto err;
+               }
+               goto done;
+       }
+
+       if ((bio = BIO_new(BIO_s_mem())) == NULL)
+               goto err;
+       if (PEM_write_bio_SSL_SESSION(bio, ss) == 0)
+               goto err;
+       if ((data_len = BIO_get_mem_data(bio, &data)) <= 0)
+               goto err;
+
+       len = (size_t)data_len;
+       offset = 0;
+
+       if (ftruncate(sfd, len) == -1) {
+               tls_set_error(ctx, "failed to truncate session file");
+               goto err;
+       }
+       while (len > 0) {
+               if ((n = pwrite(sfd, data + offset, len, offset)) == -1) {
+                       tls_set_error(ctx, "failed to write session file");
+                       goto err;
+               }
+               offset += n;
+               len -= n;
+       }
+
+ done:
+       rv = 0;
+
+ err:
+       SSL_SESSION_free(ss);
+       BIO_free_all(bio);
+
+       return (rv);
+}
+
 static int
 tls_connect_common(struct tls *ctx, const char *servername)
 {
@@ -221,6 +335,12 @@ tls_connect_common(struct tls *ctx, const char *servername)
                goto err;
        }
 
+       if (ctx->config->session_fd != -1) {
+               SSL_clear_options(ctx->ssl_conn, SSL_OP_NO_TICKET);
+               if (tls_client_read_session(ctx) == -1)
+                       goto err;
+       }
+
        if (SSL_set_tlsext_status_type(ctx->ssl_conn, TLSEXT_STATUSTYPE_ocsp) != 1) {
                tls_set_errorx(ctx, "ssl OCSP extension setup failure");
                goto err;
@@ -336,6 +456,12 @@ tls_handshake_client(struct tls *ctx)
        }
 
        ctx->state |= TLS_HANDSHAKE_COMPLETE;
+
+       if (ctx->config->session_fd != -1) {
+               if (tls_client_write_session(ctx) == -1)
+                       goto err;
+       }
+
        rv = 0;
 
  err:
index 3db75dc..6dfebfa 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls_config.c,v 1.47 2018/02/08 05:56:49 jsing Exp $ */
+/* $OpenBSD: tls_config.c,v 1.48 2018/02/10 04:41:24 jsing Exp $ */
 /*
  * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
  *
@@ -89,6 +89,7 @@ tls_config_new(void)
                goto err;
 
        config->refcount = 1;
+       config->session_fd = -1;
 
        /*
         * Default configuration.
@@ -669,6 +670,44 @@ tls_config_set_protocols(struct tls_config *config, uint32_t protocols)
        return (0);
 }
 
+int
+tls_config_set_session_fd(struct tls_config *config, int session_fd)
+{
+       struct stat sb;
+       mode_t mugo;
+
+       if (session_fd == -1) {
+               config->session_fd = session_fd;
+               return (0);
+       }
+
+       if (fstat(session_fd, &sb) == -1) {
+               tls_config_set_error(config, "failed to stat session file");
+               return (-1);
+       }
+       if (!S_ISREG(sb.st_mode)) {
+               tls_config_set_errorx(config,
+                   "session file is not a regular file");
+               return (-1);
+       }
+
+       if (sb.st_uid != getuid()) {
+               tls_config_set_errorx(config, "session file has incorrect "
+                   "owner (uid %i != %i)", sb.st_uid, getuid());
+               return (-1);
+       }
+       mugo = sb.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO);
+       if (mugo != (S_IRUSR|S_IWUSR)) {
+               tls_config_set_errorx(config, "session file has incorrect "
+                   "permissions (%o != 600)", mugo);
+               return (-1);
+       }
+
+       config->session_fd = session_fd;
+
+       return (0);
+}
+
 int
 tls_config_set_verify_depth(struct tls_config *config, int verify_depth)
 {
index 685ed19..34535b5 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls_conninfo.c,v 1.17 2018/02/08 10:02:48 jsing Exp $ */
+/* $OpenBSD: tls_conninfo.c,v 1.18 2018/02/10 04:41:24 jsing Exp $ */
 /*
  * Copyright (c) 2015 Joel Sing <jsing@openbsd.org>
  * Copyright (c) 2015 Bob Beck <beck@openbsd.org>
@@ -221,6 +221,14 @@ tls_conninfo_cert_pem(struct tls *ctx)
        return rv;
 }
 
+static int
+tls_conninfo_session(struct tls *ctx)
+{
+       ctx->conninfo->session_resumed = SSL_session_reused(ctx->ssl_conn);
+
+       return 0;
+}
+
 int
 tls_conninfo_populate(struct tls *ctx)
 {
@@ -260,6 +268,9 @@ tls_conninfo_populate(struct tls *ctx)
        if (tls_conninfo_cert_pem(ctx) == -1)
                goto err;
 
+       if (tls_conninfo_session(ctx) == -1)
+               goto err;
+
        return (0);
 
  err:
@@ -313,6 +324,14 @@ tls_conn_servername(struct tls *ctx)
        return (ctx->conninfo->servername);
 }
 
+int
+tls_conn_session_resumed(struct tls *ctx)
+{
+       if (ctx->conninfo == NULL)
+               return (0);
+       return (ctx->conninfo->session_resumed);
+}
+
 const char *
 tls_conn_version(struct tls *ctx)
 {
index eb08d47..1426503 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls_internal.h,v 1.68 2018/02/08 10:19:31 jsing Exp $ */
+/* $OpenBSD: tls_internal.h,v 1.69 2018/02/10 04:41:24 jsing Exp $ */
 /*
  * Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org>
  * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
@@ -95,6 +95,7 @@ struct tls_config {
        int ocsp_require_stapling;
        uint32_t protocols;
        unsigned char session_id[TLS_MAX_SESSION_ID_LENGTH];
+       int session_fd;
        int session_lifetime;
        struct tls_ticket_key ticket_keys[TLS_NUM_TICKETS];
        uint32_t ticket_keyrev;
@@ -111,6 +112,7 @@ struct tls_conninfo {
        char *alpn;
        char *cipher;
        char *servername;
+       int session_resumed;
        char *version;
 
        char *hash;