[mod_gnutls] add SNI
This commit is contained in:
parent
95f63fc0cf
commit
392e7bb823
17
configure.ac
17
configure.ac
|
@ -132,6 +132,23 @@ fi
|
|||
AM_CONDITIONAL([USE_GNUTLS], [test "$USE_GNUTLS" = "true"])
|
||||
|
||||
|
||||
dnl Check for libidn, needed to decode SNI names
|
||||
AC_ARG_WITH([sni], [AS_HELP_STRING([--with-sni],[SNI support for gnutls/openssl, needs libidn])],
|
||||
[WITH_SNI=$withval],[WITH_SNI=no])
|
||||
|
||||
if test "$WITH_SNI" != "no"; then
|
||||
PKG_CHECK_MODULES([IDN], [Libidn],[],[
|
||||
AC_MSG_ERROR([libidn not found])
|
||||
])
|
||||
|
||||
AC_SUBST([IDN_CFLAGS])
|
||||
AC_SUBST([IDN_LIBS])
|
||||
USE_SNI=true
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL([USE_SNI], [test "$USE_SNI" = "true"])
|
||||
|
||||
|
||||
dnl Check for lua
|
||||
AC_MSG_CHECKING([for lua])
|
||||
AC_ARG_WITH([lua], [AS_HELP_STRING([--with-lua],[lua engine (recommended)])],
|
||||
|
|
|
@ -21,6 +21,7 @@ OPTION(WITH_LUA "with lua 5.1 for lua-configfile [default: on]" ON)
|
|||
OPTION(WITHOUT_CONFIG_PARSER "without standard config parser [default: off]" OFF)
|
||||
OPTION(WITH_OPENSSL "with openssl support [default: off]")
|
||||
OPTION(WITH_GNUTLS "with gnutls support [default: off]")
|
||||
OPTION(WITH_SNI "with SNI support for gnutls/openssl, needs libidn [default: off]")
|
||||
OPTION(BUILD_STATIC "build a static lighttpd with all modules added")
|
||||
OPTION(BUILD_EXTRA_WARNINGS "extra warnings")
|
||||
OPTION(WITH_BZIP "with bzip2 support for mod_deflate")
|
||||
|
@ -119,6 +120,11 @@ IF(WITH_OPENSSL)
|
|||
ENDIF(HAVE_OPENSSL_SSL_H)
|
||||
ENDIF(WITH_OPENSSL)
|
||||
|
||||
IF(WITH_SNI)
|
||||
pkg_search_module(IDN REQUIRED libidn)
|
||||
ADD_DEFINITIONS(-DUSE_SNI)
|
||||
ENDIF(WITH_SNI)
|
||||
|
||||
IF(WITH_BZIP)
|
||||
CHECK_INCLUDE_FILES(bzlib.h HAVE_BZLIB_H)
|
||||
CHECK_LIBRARY_EXISTS(bz2 BZ2_bzCompressInit "" HAVE_LIBBZ2)
|
||||
|
@ -371,14 +377,14 @@ ENDIF(WITH_LUA)
|
|||
|
||||
IF(WITH_GNUTLS)
|
||||
ADD_AND_INSTALL_LIBRARY(mod_gnutls "modules/mod_gnutls.c;modules/gnutls_filter.c")
|
||||
TARGET_LINK_LIBRARIES(mod_gnutls ${GNUTLS_LDFLAGS})
|
||||
ADD_TARGET_PROPERTIES(mod_gnutls COMPILE_FLAGS ${GNUTLS_CFLAGS})
|
||||
TARGET_LINK_LIBRARIES(mod_gnutls ${GNUTLS_LDFLAGS} ${IDN_LDFLAGS})
|
||||
ADD_TARGET_PROPERTIES(mod_gnutls COMPILE_FLAGS ${GNUTLS_CFLAGS} ${IDN_CFLAGS})
|
||||
ENDIF(WITH_GNUTLS)
|
||||
|
||||
IF(WITH_OPENSSL)
|
||||
ADD_AND_INSTALL_LIBRARY(mod_openssl "modules/mod_openssl.c;modules/openssl_filter.c")
|
||||
TARGET_LINK_LIBRARIES(mod_openssl ssl)
|
||||
TARGET_LINK_LIBRARIES(mod_openssl crypto)
|
||||
TARGET_LINK_LIBRARIES(mod_openssl ssl crypto ${IDN_LDFLAGS})
|
||||
ADD_TARGET_PROPERTIES(mod_openssl COMPILE_FLAGS ${IDN_CFLAGS})
|
||||
ENDIF(WITH_OPENSSL)
|
||||
|
||||
TARGET_LINK_LIBRARIES(lighttpd-${PACKAGE_VERSION}-common ${COMMON_LDFLAGS})
|
||||
|
|
|
@ -76,10 +76,10 @@ libmod_fortune_la_LIBADD = $(common_libadd)
|
|||
|
||||
if USE_GNUTLS
|
||||
install_libs += libmod_gnutls.la
|
||||
libmod_gnutls_la_CPPFLAGS = $(AM_CPPFLAGS) $(GNUTLS_CFLAGS)
|
||||
libmod_gnutls_la_CPPFLAGS = $(AM_CPPFLAGS) $(GNUTLS_CFLAGS) $(IDN_FLAGS)
|
||||
libmod_gnutls_la_SOURCES = mod_gnutls.c gnutls_filter.c
|
||||
libmod_gnutls_la_LDFLAGS = $(common_ldflags)
|
||||
libmod_gnutls_la_LIBADD = $(common_libadd) $(GNUTLS_LIBS)
|
||||
libmod_gnutls_la_LIBADD = $(common_libadd) $(GNUTLS_LIBS) $(IDN_LIBS)
|
||||
endif
|
||||
EXTRA_DIST += gnutls_filter.h
|
||||
|
||||
|
@ -103,10 +103,10 @@ libmod_memcached_la_LIBADD = $(common_libadd)
|
|||
|
||||
if USE_OPENSSL
|
||||
install_libs += libmod_openssl.la
|
||||
libmod_openssl_la_CPPFLAGS = $(AM_CPPFLAGS) $(OPENSSL_CFLAGS)
|
||||
libmod_openssl_la_CPPFLAGS = $(AM_CPPFLAGS) $(OPENSSL_CFLAGS) $(IDN_FLAGS)
|
||||
libmod_openssl_la_SOURCES = mod_openssl.c openssl_filter.c
|
||||
libmod_openssl_la_LDFLAGS = $(common_ldflags)
|
||||
libmod_openssl_la_LIBADD = $(common_libadd) $(OPENSSL_LIBS)
|
||||
libmod_openssl_la_LIBADD = $(common_libadd) $(OPENSSL_LIBS) $(IDN_LIBS)
|
||||
endif
|
||||
EXTRA_DIST += openssl_filter.h
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
#include <lighttpd/throttle.h>
|
||||
|
||||
#include "gnutls_filter.h"
|
||||
#ifdef USE_SNI
|
||||
#include "ssl_sni_parser.h"
|
||||
#endif
|
||||
#include "ssl-session-db.h"
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
@ -18,6 +21,93 @@ LI_API gboolean mod_gnutls_free(liModules *mods, liModule *mod);
|
|||
|
||||
static int load_dh_params_4096(gnutls_dh_params_t *dh_params);
|
||||
|
||||
typedef struct fetch_cert_backend_lookup fetch_cert_backend_lookup;
|
||||
struct fetch_cert_backend_lookup {
|
||||
liFetchDatabase *backend;
|
||||
liFetchWait *wait;
|
||||
liFetchEntry *entry;
|
||||
};
|
||||
|
||||
static gnutls_certificate_credentials_t creds_from_gstring(GString *str) {
|
||||
gnutls_certificate_credentials_t creds = NULL;
|
||||
gnutls_datum_t pemfile;
|
||||
int r;
|
||||
|
||||
if (NULL == str) return NULL;
|
||||
|
||||
if (GNUTLS_E_SUCCESS != (r = gnutls_certificate_allocate_credentials(&creds))) return NULL;
|
||||
|
||||
pemfile.data = (unsigned char*) str->str;
|
||||
pemfile.size = str->len;
|
||||
|
||||
if (GNUTLS_E_SUCCESS != (r = gnutls_certificate_set_x509_key_mem(creds, &pemfile, &pemfile, GNUTLS_X509_FMT_PEM))) {
|
||||
goto error_free_creds;
|
||||
}
|
||||
|
||||
return creds;
|
||||
|
||||
error_free_creds:
|
||||
gnutls_certificate_free_credentials(creds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void fetch_cert_backend_ready(gpointer data) {
|
||||
liFetchEntry *be;
|
||||
fetch_cert_backend_lookup *lookup = (fetch_cert_backend_lookup*) data;
|
||||
|
||||
be = li_fetch_get2(lookup->backend, lookup->entry->key, fetch_cert_backend_ready, lookup, &lookup->wait);
|
||||
if (NULL != be) {
|
||||
liFetchEntry *entry = lookup->entry;
|
||||
li_fetch_database_release(lookup->backend);
|
||||
g_slice_free(fetch_cert_backend_lookup, lookup);
|
||||
|
||||
entry->backend_data = be;
|
||||
entry->data = creds_from_gstring((GString*) be->data);
|
||||
li_fetch_entry_ready(entry);
|
||||
}
|
||||
}
|
||||
|
||||
static void fetch_cert_lookup(liFetchDatabase* db, gpointer data, liFetchEntry *entry) {
|
||||
fetch_cert_backend_lookup *lookup = g_slice_new0(fetch_cert_backend_lookup);
|
||||
UNUSED(db);
|
||||
|
||||
lookup->backend = (liFetchDatabase*) data;
|
||||
li_fetch_database_acquire(lookup->backend);
|
||||
lookup->entry = entry;
|
||||
fetch_cert_backend_ready(lookup);
|
||||
}
|
||||
static gboolean fetch_cert_revalidate(liFetchDatabase* db, gpointer data, liFetchEntry *entry) {
|
||||
UNUSED(db); UNUSED(data);
|
||||
return li_fetch_entry_revalidate((liFetchEntry*) entry->backend_data);
|
||||
}
|
||||
static void fetch_cert_refresh(liFetchDatabase* db, gpointer data, liFetchEntry *cur_entry, liFetchEntry *new_entry) {
|
||||
UNUSED(db); UNUSED(data);
|
||||
li_fetch_entry_refresh((liFetchEntry*) cur_entry->backend_data);
|
||||
li_fetch_entry_refresh_skip(new_entry);
|
||||
}
|
||||
static void fetch_cert_free_entry(gpointer data, liFetchEntry *entry) {
|
||||
gnutls_certificate_credentials_t creds = entry->data;
|
||||
UNUSED(data);
|
||||
|
||||
if (NULL != creds) gnutls_certificate_free_credentials(creds);
|
||||
li_fetch_entry_release((liFetchEntry*) entry->backend_data);
|
||||
}
|
||||
static void fetch_cert_free_db(gpointer data) {
|
||||
liFetchDatabase *backend = (liFetchDatabase*) data;
|
||||
li_fetch_database_release(backend);
|
||||
}
|
||||
|
||||
|
||||
static const liFetchCallbacks fetch_cert_callbacks = {
|
||||
fetch_cert_lookup,
|
||||
fetch_cert_revalidate,
|
||||
fetch_cert_refresh,
|
||||
fetch_cert_free_entry,
|
||||
fetch_cert_free_db
|
||||
};
|
||||
|
||||
|
||||
|
||||
typedef struct mod_connection_ctx mod_connection_ctx;
|
||||
typedef struct mod_context mod_context;
|
||||
|
||||
|
@ -30,6 +120,15 @@ struct mod_connection_ctx {
|
|||
|
||||
liIOStream *sock_stream;
|
||||
gpointer simple_socket_data;
|
||||
|
||||
#ifdef USE_SNI
|
||||
liStream *sni_stream;
|
||||
liJob sni_job;
|
||||
liJobRef *sni_jobref;
|
||||
liFetchEntry *sni_entry;
|
||||
liFetchWait *sni_db_wait;
|
||||
GString *sni_server_name;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct mod_context {
|
||||
|
@ -45,6 +144,10 @@ struct mod_context {
|
|||
gnutls_datum_t ticket_key;
|
||||
#endif
|
||||
|
||||
#ifdef USE_SNI
|
||||
liFetchDatabase *sni_db;
|
||||
#endif
|
||||
|
||||
unsigned int protect_against_beast:1;
|
||||
};
|
||||
|
||||
|
@ -67,6 +170,14 @@ static void mod_gnutls_context_release(mod_context *ctx) {
|
|||
li_ssl_session_db_free(ctx->session_db);
|
||||
ctx->session_db = NULL;
|
||||
|
||||
#ifdef USE_SNI
|
||||
if (NULL != ctx->sni_db) {
|
||||
li_fetch_database_release(ctx->sni_db);
|
||||
ctx->sni_db = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
g_slice_free(mod_context, ctx);
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +300,7 @@ static void close_cb(liGnuTLSFilter *f, gpointer data) {
|
|||
assert(con->con_sock.data == conctx);
|
||||
conctx->con = NULL;
|
||||
con->con_sock.data = NULL;
|
||||
con->con_sock.callbacks = NULL;
|
||||
li_stream_acquire(raw_in);
|
||||
li_stream_reset(raw_out);
|
||||
li_stream_reset(raw_in);
|
||||
|
@ -200,6 +312,30 @@ static void close_cb(liGnuTLSFilter *f, gpointer data) {
|
|||
conctx->sock_stream = NULL;
|
||||
li_iostream_release(stream);
|
||||
}
|
||||
|
||||
#ifdef USE_SNI
|
||||
if (NULL != conctx->sni_db_wait) {
|
||||
li_fetch_cancel(&conctx->sni_db_wait);
|
||||
}
|
||||
if (NULL != conctx->sni_entry) {
|
||||
li_fetch_entry_release(conctx->sni_entry);
|
||||
conctx->sni_entry = NULL;
|
||||
}
|
||||
|
||||
if (NULL != conctx->sni_stream) {
|
||||
li_ssn_sni_stream_ready(conctx->sni_stream);
|
||||
li_stream_release(conctx->sni_stream);
|
||||
conctx->sni_stream = NULL;
|
||||
}
|
||||
li_job_ref_release(conctx->sni_jobref);
|
||||
conctx->sni_jobref = NULL;
|
||||
li_job_clear(&conctx->sni_job);
|
||||
|
||||
if (NULL != conctx->sni_server_name) {
|
||||
g_string_free(conctx->sni_server_name, TRUE);
|
||||
conctx->sni_server_name = NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static int post_client_hello_cb(liGnuTLSFilter *f, gpointer data) {
|
||||
|
@ -286,6 +422,31 @@ static const liConnectionSocketCallbacks gnutls_tcp_cbs = {
|
|||
gnutls_tcp_throttle_in
|
||||
};
|
||||
|
||||
#ifdef USE_SNI
|
||||
static void sni_job_cb(liJob *job) {
|
||||
mod_connection_ctx *conctx = LI_CONTAINER_OF(job, mod_connection_ctx, sni_job);
|
||||
|
||||
assert(NULL != conctx->sni_stream);
|
||||
|
||||
conctx->sni_entry = li_fetch_get(conctx->ctx->sni_db, conctx->sni_server_name, conctx->sni_jobref, &conctx->sni_db_wait);
|
||||
if (conctx->sni_entry != NULL) {
|
||||
gnutls_certificate_credentials_t creds = conctx->sni_entry->data;
|
||||
if (NULL != creds) {
|
||||
gnutls_credentials_set(conctx->session, GNUTLS_CRD_CERTIFICATE, creds);
|
||||
}
|
||||
li_ssn_sni_stream_ready(conctx->sni_stream);
|
||||
}
|
||||
}
|
||||
|
||||
static void gnutls_sni_cb(gpointer data, GString *server_name) {
|
||||
mod_connection_ctx *conctx = data;
|
||||
|
||||
conctx->sni_server_name = g_string_new_len(GSTR_LEN(server_name));
|
||||
|
||||
sni_job_cb(&conctx->sni_job);
|
||||
}
|
||||
#endif
|
||||
|
||||
static gboolean mod_gnutls_con_new(liConnection *con, int fd) {
|
||||
liEventLoop *loop = &con->wrk->loop;
|
||||
liServer *srv = con->srv;
|
||||
|
@ -331,8 +492,24 @@ static gboolean mod_gnutls_con_new(liConnection *con, int fd) {
|
|||
conctx = g_slice_new0(mod_connection_ctx);
|
||||
conctx->session = session;
|
||||
conctx->sock_stream = li_iostream_new(con->wrk, fd, tcp_io_cb, conctx);
|
||||
conctx->tls_filter = li_gnutls_filter_new(srv, con->wrk, &filter_callbacks, conctx, conctx->session,
|
||||
&conctx->sock_stream->stream_in, &conctx->sock_stream->stream_out);
|
||||
|
||||
#ifdef USE_SNI
|
||||
if (NULL != ctx->sni_db) {
|
||||
conctx->sni_stream = li_ssn_sni_stream(&con->wrk->loop, gnutls_sni_cb, conctx);
|
||||
li_job_init(&conctx->sni_job, sni_job_cb);
|
||||
conctx->sni_jobref = li_job_ref(&con->wrk->loop.jobqueue, &conctx->sni_job);
|
||||
|
||||
li_stream_connect(&conctx->sock_stream->stream_in, conctx->sni_stream);
|
||||
|
||||
conctx->tls_filter = li_gnutls_filter_new(srv, con->wrk, &filter_callbacks, conctx, conctx->session,
|
||||
conctx->sni_stream, &conctx->sock_stream->stream_out);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
conctx->tls_filter = li_gnutls_filter_new(srv, con->wrk, &filter_callbacks, conctx, conctx->session,
|
||||
&conctx->sock_stream->stream_in, &conctx->sock_stream->stream_out);
|
||||
}
|
||||
|
||||
conctx->con = con;
|
||||
conctx->ctx = ctx;
|
||||
|
||||
|
@ -390,7 +567,8 @@ static gboolean gnutls_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
|
|||
GString *ipstr = NULL;
|
||||
const char
|
||||
*priority = NULL, *dh_params_file = NULL,
|
||||
*pemfile = NULL, *ca_file = NULL;
|
||||
*pemfile = NULL, *ca_file = NULL,
|
||||
*sni_backend = NULL;
|
||||
gboolean
|
||||
protect_against_beast = TRUE;
|
||||
gint64 session_db_size = 256;
|
||||
|
@ -448,6 +626,12 @@ static gboolean gnutls_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
|
|||
return FALSE;
|
||||
}
|
||||
session_db_size = htval->data.number;
|
||||
} else if (g_str_equal(htkey->str, "sni-backend")) {
|
||||
if (htval->type != LI_VALUE_STRING) {
|
||||
ERROR(srv, "%s", "gnutls sni-backend expects a string as parameter");
|
||||
return FALSE;
|
||||
}
|
||||
sni_backend = htval->data.string->str;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -465,6 +649,15 @@ static gboolean gnutls_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
|
|||
|
||||
ctx->protect_against_beast = protect_against_beast;
|
||||
|
||||
if (sni_backend != NULL) {
|
||||
liFetchDatabase *backend = li_server_get_fetch_database(srv, sni_backend);
|
||||
if (NULL == backend) {
|
||||
ERROR(srv, "gnutls: no fetch backend with name '%s' registered", sni_backend);
|
||||
goto error_free_ctx;
|
||||
}
|
||||
ctx->sni_db = li_fetch_database_new(&fetch_cert_callbacks, backend, 64, 16);
|
||||
}
|
||||
|
||||
if (GNUTLS_E_SUCCESS != (r = gnutls_certificate_set_x509_key_file(ctx->server_cert, pemfile, pemfile, GNUTLS_X509_FMT_PEM))) {
|
||||
ERROR(srv, "gnutls_certificate_set_x509_key_file failed(certfile '%s', keyfile '%s', PEM) (%s): %s",
|
||||
pemfile, pemfile,
|
||||
|
|
|
@ -0,0 +1,380 @@
|
|||
#ifndef _LIGHTTPD_SSL_SNI_PARSER_H_
|
||||
#define _LIGHTTPD_SSL_SNI_PARSER_H_
|
||||
|
||||
#include <idna.h>
|
||||
|
||||
typedef void (*liSSLSniCB)(gpointer data, GString *server_name);
|
||||
|
||||
typedef enum {
|
||||
LI_SSL_SNI_NOT_FOUND, /* or some error */
|
||||
LI_SSL_SNI_FOUND,
|
||||
LI_SSL_SNI_WAIT /* need more data */
|
||||
} liSSLSniParserResult;
|
||||
|
||||
typedef struct liSSLSniParser liSSLSniParser;
|
||||
struct liSSLSniParser {
|
||||
guint finished:1, sni_ready:1;
|
||||
|
||||
liChunkParserCtx ctx;
|
||||
|
||||
guint8 record_state; /* how many record header bytes we have (0-4) */
|
||||
guint8 record_type;
|
||||
guint8 record_protocol_major, record_protocol_minor;
|
||||
guint16 record_remaining_length;
|
||||
|
||||
guint8 handshake_state; /* how many handshake header bytes we have (0-3) */
|
||||
guint8 handshake_type;
|
||||
guint32 handshake_remaining_length;
|
||||
|
||||
guint32 client_hello_state; /* some states (0-12) */
|
||||
guint8 client_hello_protocol_major, client_hello_protocol_minor;
|
||||
guint16 client_hello_remaining; /* dynamic length stuff */
|
||||
|
||||
guint16 extension_state; /* how many extension header bytes we have (0-4) */
|
||||
guint16 extension_type;
|
||||
guint16 extension_remaining;
|
||||
|
||||
guint8 sni_state;
|
||||
guint8 sni_type;
|
||||
guint16 sni_hostname_remaining;
|
||||
};
|
||||
|
||||
typedef struct liSSLSniParserStream liSSLSniParserStream;
|
||||
struct liSSLSniParserStream {
|
||||
liStream stream;
|
||||
liSSLSniCB callback;
|
||||
gpointer data;
|
||||
liSSLSniParser parser;
|
||||
GString *server_name;
|
||||
};
|
||||
|
||||
INLINE liSSLSniParserResult li_ssl_sni_parse(liSSLSniParser *context, GString *server_name);
|
||||
|
||||
|
||||
INLINE liStream* li_ssn_sni_stream(liEventLoop *loop, liSSLSniCB callback, gpointer data);
|
||||
INLINE void li_ssn_sni_stream_ready(liStream *stream); /* key loaded, ready to forward data */
|
||||
|
||||
|
||||
INLINE void li_ssn_sni_stream_cb(liStream *stream, liStreamEvent event); /* private */
|
||||
|
||||
|
||||
/* inline implementations */
|
||||
|
||||
INLINE liStream* li_ssn_sni_stream(liEventLoop *loop, liSSLSniCB callback, gpointer data) {
|
||||
liSSLSniParserStream *pstream = g_slice_new0(liSSLSniParserStream);
|
||||
pstream->server_name = g_string_sized_new(0);
|
||||
pstream->callback = callback;
|
||||
pstream->data = data;
|
||||
li_stream_init(&pstream->stream, loop, li_ssn_sni_stream_cb);
|
||||
return &pstream->stream;
|
||||
}
|
||||
|
||||
INLINE void li_ssn_sni_stream_ready(liStream *stream) {
|
||||
liSSLSniParserStream *pstream = LI_CONTAINER_OF(stream, liSSLSniParserStream, stream);
|
||||
assert(li_ssn_sni_stream_cb == stream->cb);
|
||||
|
||||
pstream->parser.finished = pstream->parser.sni_ready = TRUE;
|
||||
li_stream_again(stream);
|
||||
}
|
||||
|
||||
INLINE void li_ssn_sni_stream_cb(liStream *stream, liStreamEvent event) {
|
||||
liSSLSniParserStream *pstream = LI_CONTAINER_OF(stream, liSSLSniParserStream, stream);
|
||||
|
||||
switch (event) {
|
||||
case LI_STREAM_NEW_DATA:
|
||||
if (NULL == stream->source) return;
|
||||
if (!pstream->parser.finished) {
|
||||
switch (li_ssl_sni_parse(&pstream->parser, pstream->server_name)) {
|
||||
case LI_SSL_SNI_NOT_FOUND:
|
||||
pstream->parser.sni_ready = TRUE;
|
||||
break;
|
||||
case LI_SSL_SNI_FOUND:
|
||||
pstream->callback(pstream->data, pstream->server_name);
|
||||
break;
|
||||
case LI_SSL_SNI_WAIT:
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pstream->parser.sni_ready) {
|
||||
li_chunkqueue_steal_all(stream->out, stream->source->out);
|
||||
if (stream->source->out->is_closed) stream->out->is_closed = TRUE;
|
||||
li_stream_notify(stream);
|
||||
}
|
||||
break;
|
||||
case LI_STREAM_NEW_CQLIMIT:
|
||||
break;
|
||||
case LI_STREAM_CONNECTED_DEST:
|
||||
break;
|
||||
case LI_STREAM_CONNECTED_SOURCE:
|
||||
li_chunk_parser_init(&pstream->parser.ctx, stream->source->out);
|
||||
break;
|
||||
case LI_STREAM_DISCONNECTED_DEST:
|
||||
pstream->parser.finished = pstream->parser.sni_ready = TRUE;
|
||||
li_stream_disconnect(stream);
|
||||
break;
|
||||
case LI_STREAM_DISCONNECTED_SOURCE:
|
||||
pstream->parser.finished = pstream->parser.sni_ready = TRUE;
|
||||
li_stream_disconnect_dest(stream);
|
||||
break;
|
||||
case LI_STREAM_DESTROY:
|
||||
pstream->callback = NULL;
|
||||
pstream->data = NULL;
|
||||
g_string_free(pstream->server_name, TRUE);
|
||||
pstream->server_name = NULL;
|
||||
g_slice_free(liSSLSniParserStream, pstream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
INLINE liSSLSniParserResult li_ssl_sni_parse(liSSLSniParser *context, GString *server_name) {
|
||||
guint8 *p = NULL, *global_pe = NULL;
|
||||
|
||||
if (context->finished) goto fail;
|
||||
|
||||
li_chunk_parser_prepare(&context->ctx);
|
||||
|
||||
for (;;) {
|
||||
guint8 *record_pe;
|
||||
|
||||
while (NULL == p || p >= global_pe) {
|
||||
liHandlerResult res = li_chunk_parser_next(&context->ctx, (gchar**) &p, (gchar**) &global_pe, NULL);
|
||||
if (LI_HANDLER_WAIT_FOR_EVENT == res && !context->ctx.cq->is_closed) return context->finished ? LI_SSL_SNI_NOT_FOUND : LI_SSL_SNI_WAIT;
|
||||
if (LI_HANDLER_GO_ON != res) goto fail;
|
||||
}
|
||||
|
||||
switch (context->record_state) {
|
||||
case 0:
|
||||
if (context->record_remaining_length > 0) break;
|
||||
li_chunk_parser_done(&context->ctx, 1);
|
||||
context->record_type = *p++;
|
||||
++context->record_state;
|
||||
if (22 != context->record_type) goto fail; /* abort if not handshake */
|
||||
continue;
|
||||
case 1:
|
||||
li_chunk_parser_done(&context->ctx, 1);
|
||||
context->record_protocol_major = *p++;
|
||||
++context->record_state;
|
||||
continue;
|
||||
case 2:
|
||||
li_chunk_parser_done(&context->ctx, 1);
|
||||
context->record_protocol_minor = *p++;
|
||||
++context->record_state;
|
||||
continue;
|
||||
case 3:
|
||||
li_chunk_parser_done(&context->ctx, 1);
|
||||
context->record_remaining_length = (*p++) << 8;
|
||||
++context->record_state;
|
||||
continue;
|
||||
case 4:
|
||||
li_chunk_parser_done(&context->ctx, 1);
|
||||
context->record_remaining_length += (*p++);
|
||||
context->record_state = 0;
|
||||
if (context->record_remaining_length > ((1u << 14) + 2048u)) goto fail;
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
/* we will parse all available bytes */
|
||||
guint take = MIN(context->record_remaining_length, (guint) (global_pe - p));
|
||||
context->record_remaining_length -= take;
|
||||
li_chunk_parser_done(&context->ctx, take);
|
||||
record_pe = p + take;
|
||||
}
|
||||
|
||||
while (p < record_pe) {
|
||||
guint8 *client_hello_pe;
|
||||
|
||||
/* we only parse handshake records */
|
||||
switch (context->handshake_state) {
|
||||
case 0:
|
||||
if (context->handshake_remaining_length > 0) break;
|
||||
context->handshake_type = *p++;
|
||||
++context->handshake_state;
|
||||
if (1 != context->handshake_type) goto fail; /* abort if not client_hello */
|
||||
continue;
|
||||
case 1:
|
||||
context->handshake_remaining_length = (*p++) << 16;
|
||||
++context->handshake_state;
|
||||
continue;
|
||||
case 2:
|
||||
context->handshake_remaining_length += (*p++) << 8;
|
||||
++context->handshake_state;
|
||||
continue;
|
||||
case 3:
|
||||
context->handshake_remaining_length += (*p++);
|
||||
context->handshake_state = 0;
|
||||
context->client_hello_state = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* we only parse client_hello handshakes */
|
||||
{
|
||||
guint take = MIN(context->handshake_remaining_length, (guint) (record_pe - p));
|
||||
context->handshake_remaining_length -= take;
|
||||
client_hello_pe = p + take;
|
||||
if (0 == context->handshake_remaining_length) context->finished = TRUE; /* after this there is no point searching */
|
||||
}
|
||||
|
||||
while (p < client_hello_pe) {
|
||||
switch (context->client_hello_state) {
|
||||
case 2: /* skip random */
|
||||
case 4: /* skip session id */
|
||||
case 7: /* skip ciphers */
|
||||
case 9: /* skip compression methods */
|
||||
{
|
||||
guint take = MIN(context->client_hello_remaining, (guint) (client_hello_pe - p));
|
||||
context->client_hello_remaining -= take;
|
||||
p += take;
|
||||
}
|
||||
|
||||
if (0 == context->client_hello_remaining) {
|
||||
++context->client_hello_state;
|
||||
}
|
||||
continue;
|
||||
case 0: /* protocol major */
|
||||
context->client_hello_protocol_major = (*p++);
|
||||
++context->client_hello_state;
|
||||
continue;
|
||||
case 1: /* protocol minor */
|
||||
context->client_hello_protocol_minor = (*p++);
|
||||
++context->client_hello_state;
|
||||
context->client_hello_remaining = 32; /* length of "random" data is constant */
|
||||
continue;
|
||||
case 3: /* session id length */
|
||||
context->client_hello_remaining = (*p++);
|
||||
++context->client_hello_state;
|
||||
continue;
|
||||
case 5: /* cipher length upper byte */
|
||||
context->client_hello_remaining = (*p++) << 16;
|
||||
++context->client_hello_state;
|
||||
continue;
|
||||
case 6: /* cipher length lower byte */
|
||||
context->client_hello_remaining += (*p++);
|
||||
++context->client_hello_state;
|
||||
if (0 != context->client_hello_remaining % 2) goto fail;
|
||||
continue;
|
||||
case 8: /* compression methods length */
|
||||
context->client_hello_remaining = (*p++);
|
||||
++context->client_hello_state;
|
||||
continue;
|
||||
case 10: /* extensions length upper byte */
|
||||
context->client_hello_remaining = (*p++) << 16;
|
||||
++context->client_hello_state;
|
||||
continue;
|
||||
case 11: /* extensions length lower byte */
|
||||
context->client_hello_remaining += (*p++);
|
||||
++context->client_hello_state;
|
||||
|
||||
/* extensions fill the record exactly if present */
|
||||
if (context->handshake_remaining_length + (guint) (client_hello_pe - p) != context->client_hello_remaining) goto fail;
|
||||
context->extension_state = 0;
|
||||
continue;
|
||||
case 12: /* extensions data */
|
||||
break;
|
||||
}
|
||||
|
||||
/* parse extensions */
|
||||
{
|
||||
guint take = MIN(context->client_hello_remaining, (guint) (client_hello_pe - p));
|
||||
context->client_hello_remaining -= take;
|
||||
assert(client_hello_pe == p + take);
|
||||
if (0 == context->client_hello_remaining) context->finished = TRUE; /* after this there is no point searching */
|
||||
}
|
||||
|
||||
while (p < client_hello_pe) {
|
||||
guint8 *extension_pe;
|
||||
|
||||
switch (context->extension_state) {
|
||||
case 0:
|
||||
context->extension_type = (*p++) << 8;
|
||||
++context->extension_state;
|
||||
continue;
|
||||
case 1:
|
||||
context->extension_type += (*p++);
|
||||
++context->extension_state;
|
||||
continue;
|
||||
case 2:
|
||||
context->extension_remaining = (*p++) << 8;
|
||||
++context->extension_state;
|
||||
continue;
|
||||
case 3:
|
||||
context->extension_remaining += (*p++);
|
||||
++context->extension_state;
|
||||
continue;
|
||||
case 4:
|
||||
if (0 == context->extension_type) break; /* parse this one: SNI */
|
||||
{
|
||||
/* ignore extension */
|
||||
guint take = MIN(context->extension_remaining, (guint) (client_hello_pe - p));
|
||||
context->extension_remaining -= take;
|
||||
p += take;
|
||||
if (0 == context->extension_remaining) context->extension_state = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
guint take = MIN(context->extension_remaining, (guint) (client_hello_pe - p));
|
||||
context->extension_remaining -= take;
|
||||
extension_pe = p + take;
|
||||
if (0 == context->extension_remaining) context->finished = TRUE; /* after this there is no point searching */
|
||||
}
|
||||
|
||||
while (p < extension_pe) {
|
||||
/* SNI extension. append data to string */
|
||||
switch (context->sni_state) {
|
||||
case 0: /* ignore list length */
|
||||
case 1: /* ignore list length */
|
||||
p++;
|
||||
++context->sni_state;
|
||||
continue;
|
||||
case 2: /* type of first entry */
|
||||
context->sni_type = (*p++);
|
||||
if (0 != context->sni_type) goto fail; /* no spec how to parse unknown entries, can't skip them as we don't know their length */
|
||||
++context->sni_state;
|
||||
continue;
|
||||
case 3:
|
||||
context->sni_hostname_remaining = (*p++) << 8;
|
||||
++context->sni_state;
|
||||
continue;
|
||||
case 4:
|
||||
context->sni_hostname_remaining += (*p++);
|
||||
++context->sni_state;
|
||||
continue;
|
||||
case 5:
|
||||
{
|
||||
guint take = MIN(context->sni_hostname_remaining, (guint) (extension_pe - p));
|
||||
g_string_append_len(server_name, (gchar*) p, take);
|
||||
context->sni_hostname_remaining -= take;
|
||||
p += take;
|
||||
if (0 == context->sni_hostname_remaining) goto found_sni;
|
||||
}
|
||||
}
|
||||
}
|
||||
} /* while (p >= client_hello_pe) -- extension subloop */
|
||||
} /* while (p >= client_hello_pe) */
|
||||
|
||||
if (0 == context->handshake_remaining_length) goto fail; /* client_hello ended, no sni found */
|
||||
} /* while (p >= record_pe) */
|
||||
|
||||
}
|
||||
|
||||
found_sni:
|
||||
{
|
||||
char *ascii_sni;
|
||||
if (IDNA_SUCCESS == idna_to_ascii_8z(server_name->str, &ascii_sni, IDNA_ALLOW_UNASSIGNED)) {
|
||||
g_string_assign(server_name, ascii_sni);
|
||||
free(ascii_sni);
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
context->finished = TRUE;
|
||||
return LI_SSL_SNI_FOUND;
|
||||
|
||||
fail:
|
||||
context->finished = TRUE;
|
||||
return LI_SSL_SNI_NOT_FOUND;
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue