From 0dd084b9141c9e9630fcc3821432486c3580d798 Mon Sep 17 00:00:00 2001 From: jsing Date: Sat, 10 Feb 2018 04:41:24 +0000 Subject: [PATCH] Add support to libtls for client-side TLS session resumption. 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 | 2 + lib/libtls/tls.h | 4 +- lib/libtls/tls_client.c | 128 +++++++++++++++++++++++++++++++++++++- lib/libtls/tls_config.c | 41 +++++++++++- lib/libtls/tls_conninfo.c | 21 ++++++- lib/libtls/tls_internal.h | 4 +- 6 files changed, 195 insertions(+), 5 deletions(-) diff --git a/lib/libtls/Symbols.list b/lib/libtls/Symbols.list index 1e7538cfd43..923924fc400 100644 --- a/lib/libtls/Symbols.list +++ b/lib/libtls/Symbols.list @@ -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 diff --git a/lib/libtls/tls.h b/lib/libtls/tls.h index cc8627f2af2..8d66c2fbaad 100644 --- a/lib/libtls/tls.h +++ b/lib/libtls/tls.h @@ -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 * @@ -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); diff --git a/lib/libtls/tls_client.c b/lib/libtls/tls_client.c index c79f462a3a5..14c716fa171 100644 --- a/lib/libtls/tls_client.c +++ b/lib/libtls/tls_client.c @@ -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 * @@ -17,10 +17,12 @@ #include #include +#include #include #include +#include #include #include #include @@ -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: diff --git a/lib/libtls/tls_config.c b/lib/libtls/tls_config.c index 3db75dc62fc..6dfebfaebf9 100644 --- a/lib/libtls/tls_config.c +++ b/lib/libtls/tls_config.c @@ -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 * @@ -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) { diff --git a/lib/libtls/tls_conninfo.c b/lib/libtls/tls_conninfo.c index 685ed194e4e..34535b5668c 100644 --- a/lib/libtls/tls_conninfo.c +++ b/lib/libtls/tls_conninfo.c @@ -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 * Copyright (c) 2015 Bob Beck @@ -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) { diff --git a/lib/libtls/tls_internal.h b/lib/libtls/tls_internal.h index eb08d470740..14265037eb5 100644 --- a/lib/libtls/tls_internal.h +++ b/lib/libtls/tls_internal.h @@ -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 * Copyright (c) 2014 Joel Sing @@ -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; -- 2.20.1