Skip to content

Commit

Permalink
connection upgrade feature: upgrade tcp to ssl on client hello
Browse files Browse the repository at this point in the history
This code looks at the beginning of each read from the src for something
that looks like an ssl client hello message; if it finds one it tries to
upgrade the connection to proxied ssl. So it works only in the simple
case where the connection has no binary data before the upgrade attempt
(so there are no false positives), and where the client hello comes at
the beginning of a packet from the source.
  • Loading branch information
RichardPoole42 committed Apr 18, 2015
1 parent aaa4e94 commit 5c8b5e3
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 4 deletions.
14 changes: 12 additions & 2 deletions opts.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ opts_has_ssl_spec(opts_t *opts)
proxyspec_t *p = opts->spec;

while (p) {
if (p->ssl)
if (p->ssl || p->tlspeek)
return 1;
p = p->next;
}
Expand Down Expand Up @@ -284,18 +284,27 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine)
if (!strcmp(**argv, "tcp")) {
spec->ssl = 0;
spec->http = 0;
spec->tlspeek = 0;
} else
if (!strcmp(**argv, "ssl")) {
spec->ssl = 1;
spec->http = 0;
spec->tlspeek = 0;
} else
if (!strcmp(**argv, "http")) {
spec->ssl = 0;
spec->http = 1;
spec->tlspeek = 0;
} else
if (!strcmp(**argv, "https")) {
spec->ssl = 1;
spec->http = 1;
spec->tlspeek = 0;
} else
if (!strcmp(**argv, "genericstarttls")) {
spec->ssl = 0;
spec->http = 0;
spec->tlspeek = 1;
} else {
fprintf(stderr, "Unknown connection "
"type '%s'\n", **argv);
Expand Down Expand Up @@ -459,9 +468,10 @@ proxyspec_str(proxyspec_t *spec)
return NULL;
}
}
if (asprintf(&s, "[%s]:%s %s %s %s", lhbuf, lpbuf,
if (asprintf(&s, "[%s]:%s %s %s %s %s", lhbuf, lpbuf,
(spec->ssl ? "ssl" : "tcp"),
(spec->http ? "http" : "plain"),
(spec->tlspeek ? "peeking" : ""),
(spec->natengine ? spec->natengine : cbuf)) < 0) {
s = NULL;
}
Expand Down
1 change: 1 addition & 0 deletions opts.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
typedef struct proxyspec {
unsigned int ssl : 1;
unsigned int http : 1;
unsigned int tlspeek: 1;
struct sockaddr_storage listen_addr;
socklen_t listen_addrlen;
/* connect_addr and connect_addrlen are set: static mode;
Expand Down
73 changes: 71 additions & 2 deletions pxyconn.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ typedef struct pxy_conn_ctx {
unsigned int ocsp_denied : 1; /* 1 if OCSP was denied */
unsigned int enomem : 1; /* 1 if out of memory */
unsigned int sni_peek_retries : 6; /* max 64 SNI parse retries */
unsigned int looking_for_client_hello : 1; /* 1 if waiting for hello */
unsigned int pending_ssl_upgrade : 1; /* 1 if ssl upgrade in progress */

/* server name indicated by client in SNI TLS extension */
char *sni;
Expand Down Expand Up @@ -198,6 +200,10 @@ pxy_conn_ctx_new(proxyspec_t *spec, opts_t *opts,
memset(ctx, 0, sizeof(pxy_conn_ctx_t));
ctx->spec = spec;
ctx->opts = opts;
ctx->looking_for_client_hello = spec->tlspeek;
if (OPTS_DEBUG(opts)) {
log_dbg_printf("looking status is %d\n", ctx->looking_for_client_hello);
}
ctx->fd = fd;
ctx->thridx = pxy_thrmgr_attach(thrmgr, &ctx->evbase, &ctx->dnsbase);
ctx->thrmgr = thrmgr;
Expand Down Expand Up @@ -1505,6 +1511,50 @@ pxy_ocsp_deny(pxy_conn_ctx_t *ctx)
}
}

int
pxy_conn_check_and_upgrade(pxy_conn_ctx_t *ctx)
{
struct evbuffer *inbuf;
struct evbuffer_iovec vec_out[1];
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("Checking for a client hello\n");
}
/* peek the buffer */
inbuf = bufferevent_get_input(ctx->src.bev);
if(evbuffer_peek(inbuf, 1024, 0, vec_out, 1)) {
if(ssl_tls_clienthello_identify(vec_out->iov_base, &(vec_out->iov_len))) {
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("Found a clienthello in midstream\n");
}
ctx->dst.ssl = pxy_dstssl_create(ctx);
if(!ctx->dst.ssl) {
log_err_printf("Error creating SSL for upgrade\n");
return 0;
}
ctx->dst.bev = bufferevent_openssl_filter_new(ctx->evbase, ctx->dst.bev, ctx->dst.ssl, BUFFEREVENT_SSL_CONNECTING, 0);
bufferevent_setcb(ctx->dst.bev, pxy_bev_readcb, pxy_bev_writecb, pxy_bev_eventcb, ctx);
bufferevent_enable(ctx->dst.bev, EV_READ|EV_WRITE);
if(!ctx->dst.bev) {
return 0;
}
if( OPTS_DEBUG(ctx->opts)) {
log_err_printf("Replaced dst bufferevent, new one is %p\n", ctx->dst.bev);
}

ctx->spec->ssl = 1;
ctx->looking_for_client_hello = 0;
ctx->pending_ssl_upgrade = 1;
return 1;
} else {
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("checked buffer, no client hello found\n");
}
return 0;
}
}
return 0;
}

void
pxy_conn_terminate_free(pxy_conn_ctx_t *ctx)
{
Expand Down Expand Up @@ -1544,6 +1594,12 @@ pxy_bev_readcb(struct bufferevent *bev, void *arg)
exit(EXIT_FAILURE);
}

if(ctx->looking_for_client_hello) {
if(pxy_conn_check_and_upgrade(ctx)) {
return;
}
}

struct evbuffer *inbuf = bufferevent_get_input(bev);
if (other->closed) {
evbuffer_drain(inbuf, evbuffer_get_length(inbuf));
Expand Down Expand Up @@ -1772,8 +1828,21 @@ pxy_bev_eventcb(struct bufferevent *bev, short events, void *arg)
return;
}
}
ctx->src.bev = pxy_bufferevent_setup(ctx, ctx->fd,
ctx->src.ssl);
if(ctx->pending_ssl_upgrade) {
if (OPTS_DEBUG(ctx->opts)) {
log_dbg_printf("completing ssl upgrade\n");
}
ctx->src.bev = bufferevent_openssl_filter_new(ctx->evbase,
ctx->src.bev, ctx->src.ssl,
BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_DEFER_CALLBACKS);
bufferevent_setcb(ctx->src.bev, pxy_bev_readcb,
pxy_bev_writecb, pxy_bev_eventcb, ctx);
bufferevent_enable(ctx->src.bev, EV_READ|EV_WRITE);
ctx->pending_ssl_upgrade = 0;
} else {
ctx->src.bev = pxy_bufferevent_setup(ctx, ctx->fd,
ctx->src.ssl);
}
if (!ctx->src.bev) {
if (ctx->src.ssl) {
SSL_free(ctx->src.ssl);
Expand Down
82 changes: 82 additions & 0 deletions ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,88 @@ ssl_tls_clienthello_parse_sni(const unsigned char *buf, ssize_t *sz)
DBG_printf("%zd bytes unparsed\n", n);
return servername;
}

int
ssl_tls_clienthello_identify(const unsigned char *buf, ssize_t *sz)
{
#ifdef DEBUG_SNI_PARSER
#define DBG_printf(...) log_dbg_printf("SNI Parser: " __VA_ARGS__)
#else /* !DEBUG_SNI_PARSER */
#define DBG_printf(...)
#endif /* !DEBUG_SNI_PARSER */
const unsigned char *p = buf;
ssize_t n = *sz;

DBG_printf("buffer length %zd\n", n);

if (n < 1) {
*sz = -1;
goto out2;
}
DBG_printf("byte 0: %02x\n", *p);
/* first byte 0x80, third byte 0x01 is SSLv2 clientHello;
* first byte 0x22, second byte 0x03 is SSLv3/TLSv1.x clientHello */
if (*p != 22) /* record type: handshake protocol */
goto out2;
p++; n--;

if (n < 2) {
*sz = -1;
goto out2;
}
DBG_printf("version: %02x %02x\n", p[0], p[1]);
if (p[0] != 3)
goto out2;
p += 2; n -= 2;

if (n < 2) {
*sz = -1;
goto out2;
}
DBG_printf("length: %02x %02x\n", p[0], p[1]);
#ifdef DEBUG_SNI_PARSER
ssize_t recordlen = p[1] + (p[0] << 8);
DBG_printf("recordlen=%zd\n", recordlen);
#endif /* DEBUG_SNI_PARSER */
p += 2; n -= 2;

if (n < 1) {
*sz = -1;
goto out2;
}
DBG_printf("message type: %i\n", *p);
if (*p != 1) /* message type: ClientHello */
goto out2;
p++; n--;

if (n < 3) {
*sz = -1;
goto out2;
}
DBG_printf("message len: %02x %02x %02x\n", p[0], p[1], p[2]);
ssize_t msglen = p[2] + (p[1] << 8) + (p[0] << 16);
DBG_printf("msglen=%zd\n", msglen);
if (msglen < 4)
goto out2;
p += 3; n -= 3;

if (n < msglen) {
*sz = -1;
goto out2;
}
n = msglen; /* only parse first message */

if (n < 2)
goto out2;
DBG_printf("clienthello version %02x %02x\n", p[0], p[1]);
if (p[0] != 3)
goto out2;
p += 2; n -= 2;
return 1;
out2:
DBG_printf("%zd bytes unparsed\n", n);
return 0;
}
#endif /* !OPENSSL_NO_TLSEXT */

/* vim: set noet ft=c: */
2 changes: 2 additions & 0 deletions ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ int ssl_is_ocspreq(const unsigned char *, size_t) NONNULL(1) WUNRES;
#ifndef OPENSSL_NO_TLSEXT
char * ssl_tls_clienthello_parse_sni(const unsigned char *, ssize_t *)
NONNULL(1,2) MALLOC;
int ssl_tls_clienthello_identify(const unsigned char *, ssize_t *)
NONNULL(1,2);
#endif /* !OPENSSL_NO_TLSEXT */
int ssl_dnsname_match(const char *, size_t, const char *, size_t)
NONNULL(1,3) WUNRES;
Expand Down
8 changes: 8 additions & 0 deletions sslsplit.1
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,9 @@ SNI DNS lookup):
.br
\fBtcp\fP \fIlistenaddr port\fP
[\fInat-engine\fP|\fIfwdaddr port\fP]
.br
\fBgenericstarttls\fP \fIlistenaddr port\fP
[\fInat-engine\fP|\fIfwdaddr port\fP]
.ad
.TP
\fBhttps\fP
Expand All @@ -350,6 +353,11 @@ the removal of HPKP, HSTS and Alternate Protocol response headers.
Plain TCP connection without SSL/TLS and without any lower level protocol
decoding; decrypted connection content is treated as opaque stream of bytes
and not modified.
.TP
\fBgenericstarttls\fP
Plain TCP connection until an SSL client hello appears in the byte stream;
then starts SSL/TLS interception.

.TP
.I listenaddr port
IPv4 or IPv6 address and port or service name to listen on. This is the
Expand Down

0 comments on commit 5c8b5e3

Please sign in to comment.