Browse Source

[mod_gnutls] add SNI

personal/stbuehler/wip
Stefan Bühler 8 years ago
parent
commit
392e7bb823
  1. 17
      configure.ac
  2. 14
      src/CMakeLists.txt
  3. 8
      src/modules/Makefile.am
  4. 199
      src/modules/mod_gnutls.c
  5. 380
      src/modules/ssl_sni_parser.h

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)])],

14
src/CMakeLists.txt

@ -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})

8
src/modules/Makefile.am

@ -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

199
src/modules/mod_gnutls.c

@ -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,

380
src/modules/ssl_sni_parser.h

@ -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…
Cancel
Save