[nginx] SSL: support for TLSv1.3 early data with OpenSSL.
Sergey Kandaurov
pluknet at nginx.com
Fri Sep 21 18:59:38 UTC 2018
details: http://hg.nginx.org/nginx/rev/548a63b354a2
branches:
changeset: 7357:548a63b354a2
user: Sergey Kandaurov <pluknet at nginx.com>
date: Fri Sep 21 20:49:12 2018 +0300
description:
SSL: support for TLSv1.3 early data with OpenSSL.
In collaboration with Maxim Dounin.
diffstat:
src/event/ngx_event_openssl.c | 490 ++++++++++++++++++++++++++++++++++++++---
src/event/ngx_event_openssl.h | 5 +
2 files changed, 451 insertions(+), 44 deletions(-)
diffs (604 lines):
diff -r e3ba4026c02d -r 548a63b354a2 src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c Fri Sep 21 20:31:32 2018 +0300
+++ b/src/event/ngx_event_openssl.c Fri Sep 21 20:49:12 2018 +0300
@@ -26,9 +26,23 @@ static void ngx_ssl_info_callback(const
static void ngx_ssl_passwords_cleanup(void *data);
static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn,
ngx_ssl_session_t *sess);
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c);
+#endif
+#if (NGX_DEBUG)
+static void ngx_ssl_handshake_log(ngx_connection_t *c);
+#endif
static void ngx_ssl_handshake_handler(ngx_event_t *ev);
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf,
+ size_t size);
+#endif
static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n);
static void ngx_ssl_write_handler(ngx_event_t *wev);
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+static ssize_t ngx_ssl_write_early(ngx_connection_t *c, u_char *data,
+ size_t size);
+#endif
static void ngx_ssl_read_handler(ngx_event_t *rev);
static void ngx_ssl_shutdown_handler(ngx_event_t *ev);
static void ngx_ssl_connection_error(ngx_connection_t *c, int sslerr,
@@ -340,6 +354,10 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_COMPRESSION);
#endif
+#ifdef SSL_OP_NO_ANTI_REPLAY
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_ANTI_REPLAY);
+#endif
+
#ifdef SSL_MODE_RELEASE_BUFFERS
SSL_CTX_set_mode(ssl->ctx, SSL_MODE_RELEASE_BUFFERS);
#endif
@@ -1185,6 +1203,12 @@ ngx_ssl_early_data(ngx_conf_t *cf, ngx_s
SSL_CTX_set_early_data_enabled(ssl->ctx, 1);
+#elif defined SSL_READ_EARLY_DATA_SUCCESS
+
+ /* OpenSSL */
+
+ SSL_CTX_set_max_early_data(ssl->ctx, NGX_SSL_BUFSIZE);
+
#else
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
"\"ssl_early_data\" is not supported on this platform, "
@@ -1246,6 +1270,12 @@ ngx_ssl_create_connection(ngx_ssl_t *ssl
sc->session_ctx = ssl->ctx;
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+ if (SSL_CTX_get_max_early_data(ssl->ctx)) {
+ sc->try_early_data = 1;
+ }
+#endif
+
sc->connection = SSL_new(ssl->ctx);
if (sc->connection == NULL) {
@@ -1325,6 +1355,12 @@ ngx_ssl_handshake(ngx_connection_t *c)
int n, sslerr;
ngx_err_t err;
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+ if (c->ssl->try_early_data) {
+ return ngx_ssl_try_early_data(c);
+ }
+#endif
+
ngx_ssl_clear_error(c->log);
n = SSL_do_handshake(c->ssl->connection);
@@ -1342,50 +1378,7 @@ ngx_ssl_handshake(ngx_connection_t *c)
}
#if (NGX_DEBUG)
- {
- char buf[129], *s, *d;
-#if OPENSSL_VERSION_NUMBER >= 0x10000000L
- const
-#endif
- SSL_CIPHER *cipher;
-
- cipher = SSL_get_current_cipher(c->ssl->connection);
-
- if (cipher) {
- SSL_CIPHER_description(cipher, &buf[1], 128);
-
- for (s = &buf[1], d = buf; *s; s++) {
- if (*s == ' ' && *d == ' ') {
- continue;
- }
-
- if (*s == LF || *s == CR) {
- continue;
- }
-
- *++d = *s;
- }
-
- if (*d != ' ') {
- d++;
- }
-
- *d = '\0';
-
- ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
- "SSL: %s, cipher: \"%s\"",
- SSL_get_version(c->ssl->connection), &buf[1]);
-
- if (SSL_session_reused(c->ssl->connection)) {
- ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
- "SSL reused session");
- }
-
- } else {
- ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
- "SSL no shared ciphers");
- }
- }
+ ngx_ssl_handshake_log(c);
#endif
c->ssl->handshaked = 1;
@@ -1468,6 +1461,173 @@ ngx_ssl_handshake(ngx_connection_t *c)
}
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+
+static ngx_int_t
+ngx_ssl_try_early_data(ngx_connection_t *c)
+{
+ int n, sslerr;
+ u_char buf;
+ size_t readbytes;
+ ngx_err_t err;
+
+ ngx_ssl_clear_error(c->log);
+
+ readbytes = 0;
+
+ n = SSL_read_early_data(c->ssl->connection, &buf, 1, &readbytes);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL_read_early_data: %d, %uz", n, readbytes);
+
+ if (n == SSL_READ_EARLY_DATA_FINISH) {
+ c->ssl->try_early_data = 0;
+ return ngx_ssl_handshake(c);
+ }
+
+ if (n == SSL_READ_EARLY_DATA_SUCCESS) {
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+#if (NGX_DEBUG)
+ ngx_ssl_handshake_log(c);
+#endif
+
+ c->ssl->try_early_data = 0;
+
+ c->ssl->early_buf = buf;
+ c->ssl->early_preread = 1;
+
+ c->ssl->handshaked = 1;
+ c->ssl->in_early = 1;
+
+ c->recv = ngx_ssl_recv;
+ c->send = ngx_ssl_write;
+ c->recv_chain = ngx_ssl_recv_chain;
+ c->send_chain = ngx_ssl_send_chain;
+
+ return NGX_OK;
+ }
+
+ /* SSL_READ_EARLY_DATA_ERROR */
+
+ sslerr = SSL_get_error(c->ssl->connection, n);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
+
+ if (sslerr == SSL_ERROR_WANT_READ) {
+ c->read->ready = 0;
+ c->read->handler = ngx_ssl_handshake_handler;
+ c->write->handler = ngx_ssl_handshake_handler;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ if (sslerr == SSL_ERROR_WANT_WRITE) {
+ c->write->ready = 0;
+ c->read->handler = ngx_ssl_handshake_handler;
+ c->write->handler = ngx_ssl_handshake_handler;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+ c->ssl->no_wait_shutdown = 1;
+ c->ssl->no_send_shutdown = 1;
+ c->read->eof = 1;
+
+ if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
+ ngx_connection_error(c, err,
+ "peer closed connection in SSL handshake");
+
+ return NGX_ERROR;
+ }
+
+ c->read->error = 1;
+
+ ngx_ssl_connection_error(c, sslerr, err, "SSL_read_early_data() failed");
+
+ return NGX_ERROR;
+}
+
+#endif
+
+
+#if (NGX_DEBUG)
+
+static void
+ngx_ssl_handshake_log(ngx_connection_t *c)
+{
+ char buf[129], *s, *d;
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+ const
+#endif
+ SSL_CIPHER *cipher;
+
+ cipher = SSL_get_current_cipher(c->ssl->connection);
+
+ if (cipher) {
+ SSL_CIPHER_description(cipher, &buf[1], 128);
+
+ for (s = &buf[1], d = buf; *s; s++) {
+ if (*s == ' ' && *d == ' ') {
+ continue;
+ }
+
+ if (*s == LF || *s == CR) {
+ continue;
+ }
+
+ *++d = *s;
+ }
+
+ if (*d != ' ') {
+ d++;
+ }
+
+ *d = '\0';
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL: %s, cipher: \"%s\"",
+ SSL_get_version(c->ssl->connection), &buf[1]);
+
+ if (SSL_session_reused(c->ssl->connection)) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL reused session");
+ }
+
+ } else {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL no shared ciphers");
+ }
+}
+
+#endif
+
+
static void
ngx_ssl_handshake_handler(ngx_event_t *ev)
{
@@ -1555,6 +1715,12 @@ ngx_ssl_recv(ngx_connection_t *c, u_char
{
int n, bytes;
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+ if (c->ssl->in_early) {
+ return ngx_ssl_recv_early(c, buf, size);
+ }
+#endif
+
if (c->ssl->last == NGX_ERROR) {
c->read->error = 1;
return NGX_ERROR;
@@ -1628,6 +1794,123 @@ ngx_ssl_recv(ngx_connection_t *c, u_char
}
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+
+static ssize_t
+ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ int n, bytes;
+ size_t readbytes;
+
+ if (c->ssl->last == NGX_ERROR) {
+ c->read->error = 1;
+ return NGX_ERROR;
+ }
+
+ if (c->ssl->last == NGX_DONE) {
+ c->read->ready = 0;
+ c->read->eof = 1;
+ return 0;
+ }
+
+ bytes = 0;
+
+ ngx_ssl_clear_error(c->log);
+
+ if (c->ssl->early_preread) {
+
+ if (size == 0) {
+ c->read->ready = 0;
+ c->read->eof = 1;
+ return 0;
+ }
+
+ *buf = c->ssl->early_buf;
+
+ c->ssl->early_preread = 0;
+
+ bytes = 1;
+ size -= 1;
+ buf += 1;
+ }
+
+ /*
+ * SSL_read_early_data() may return data in parts, so try to read
+ * until SSL_read_early_data() would return no data
+ */
+
+ for ( ;; ) {
+
+ readbytes = 0;
+
+ n = SSL_read_early_data(c->ssl->connection, buf, size, &readbytes);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL_read_early_data: %d, %uz", n, readbytes);
+
+ if (n == SSL_READ_EARLY_DATA_SUCCESS) {
+
+ c->ssl->last = ngx_ssl_handle_recv(c, 1);
+
+ bytes += readbytes;
+ size -= readbytes;
+
+ if (size == 0) {
+ c->read->ready = 1;
+ return bytes;
+ }
+
+ buf += readbytes;
+
+ continue;
+ }
+
+ if (n == SSL_READ_EARLY_DATA_FINISH) {
+
+ c->ssl->last = ngx_ssl_handle_recv(c, 1);
+ c->ssl->in_early = 0;
+
+ if (bytes) {
+ c->read->ready = 1;
+ return bytes;
+ }
+
+ return ngx_ssl_recv(c, buf, size);
+ }
+
+ /* SSL_READ_EARLY_DATA_ERROR */
+
+ c->ssl->last = ngx_ssl_handle_recv(c, 0);
+
+ if (bytes) {
+ if (c->ssl->last != NGX_AGAIN) {
+ c->read->ready = 1;
+ }
+
+ return bytes;
+ }
+
+ switch (c->ssl->last) {
+
+ case NGX_DONE:
+ c->read->ready = 0;
+ c->read->eof = 1;
+ return 0;
+
+ case NGX_ERROR:
+ c->read->error = 1;
+
+ /* fall through */
+
+ case NGX_AGAIN:
+ return c->ssl->last;
+ }
+ }
+}
+
+#endif
+
+
static ngx_int_t
ngx_ssl_handle_recv(ngx_connection_t *c, int n)
{
@@ -1923,6 +2206,12 @@ ngx_ssl_write(ngx_connection_t *c, u_cha
int n, sslerr;
ngx_err_t err;
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+ if (c->ssl->in_early) {
+ return ngx_ssl_write_early(c, data, size);
+ }
+#endif
+
ngx_ssl_clear_error(c->log);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size);
@@ -2010,6 +2299,107 @@ ngx_ssl_write(ngx_connection_t *c, u_cha
}
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+
+ssize_t
+ngx_ssl_write_early(ngx_connection_t *c, u_char *data, size_t size)
+{
+ int n, sslerr;
+ size_t written;
+ ngx_err_t err;
+
+ ngx_ssl_clear_error(c->log);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size);
+
+ written = 0;
+
+ n = SSL_write_early_data(c->ssl->connection, data, size, &written);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL_write_early_data: %d, %uz", n, written);
+
+ if (n > 0) {
+
+ if (c->ssl->saved_read_handler) {
+
+ c->read->handler = c->ssl->saved_read_handler;
+ c->ssl->saved_read_handler = NULL;
+ c->read->ready = 1;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_post_event(c->read, &ngx_posted_events);
+ }
+
+ c->sent += written;
+
+ return written;
+ }
+
+ sslerr = SSL_get_error(c->ssl->connection, n);
+
+ err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
+
+ if (sslerr == SSL_ERROR_WANT_WRITE) {
+
+ if (c->ssl->saved_read_handler) {
+
+ c->read->handler = c->ssl->saved_read_handler;
+ c->ssl->saved_read_handler = NULL;
+ c->read->ready = 1;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_post_event(c->read, &ngx_posted_events);
+ }
+
+ c->write->ready = 0;
+ return NGX_AGAIN;
+ }
+
+ if (sslerr == SSL_ERROR_WANT_READ) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL_write_early_data: want read");
+
+ c->read->ready = 0;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /*
+ * we do not set the timer because there is already
+ * the write event timer
+ */
+
+ if (c->ssl->saved_read_handler == NULL) {
+ c->ssl->saved_read_handler = c->read->handler;
+ c->read->handler = ngx_ssl_read_handler;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ c->ssl->no_wait_shutdown = 1;
+ c->ssl->no_send_shutdown = 1;
+ c->write->error = 1;
+
+ ngx_ssl_connection_error(c, sslerr, err, "SSL_write_early_data() failed");
+
+ return NGX_ERROR;
+}
+
+#endif
+
+
static void
ngx_ssl_read_handler(ngx_event_t *rev)
{
@@ -3694,9 +4084,21 @@ ngx_ssl_get_early_data(ngx_connection_t
s->len = 0;
#ifdef SSL_ERROR_EARLY_DATA_REJECTED
+
+ /* BoringSSL */
+
if (SSL_in_early_data(c->ssl->connection)) {
ngx_str_set(s, "1");
}
+
+#elif defined SSL_READ_EARLY_DATA_SUCCESS
+
+ /* OpenSSL */
+
+ if (!SSL_is_init_finished(c->ssl->connection)) {
+ ngx_str_set(s, "1");
+ }
+
#endif
return NGX_OK;
diff -r e3ba4026c02d -r 548a63b354a2 src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h Fri Sep 21 20:31:32 2018 +0300
+++ b/src/event/ngx_event_openssl.h Fri Sep 21 20:49:12 2018 +0300
@@ -87,12 +87,17 @@ struct ngx_ssl_connection_s {
ngx_event_handler_pt saved_read_handler;
ngx_event_handler_pt saved_write_handler;
+ u_char early_buf;
+
unsigned handshaked:1;
unsigned renegotiation:1;
unsigned buffer:1;
unsigned no_wait_shutdown:1;
unsigned no_send_shutdown:1;
unsigned handshake_buffer_set:1;
+ unsigned try_early_data:1;
+ unsigned in_early:1;
+ unsigned early_preread:1;
};
More information about the nginx-devel
mailing list