diff --git a/opts.c b/opts.c index b4f4703..69c3d79 100644 --- a/opts.c +++ b/opts.c @@ -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; } @@ -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); @@ -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; } diff --git a/opts.h b/opts.h index 35f5467..76b7b7f 100644 --- a/opts.h +++ b/opts.h @@ -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; diff --git a/pxyconn.c b/pxyconn.c index 63168c8..e776fcf 100644 --- a/pxyconn.c +++ b/pxyconn.c @@ -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; @@ -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; @@ -1505,6 +1511,50 @@ deny: } } +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) { @@ -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)); @@ -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); diff --git a/ssl.c b/ssl.c index cf2bd38..cb8fffe 100644 --- a/ssl.c +++ b/ssl.c @@ -1902,6 +1902,88 @@ out: 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: */ diff --git a/ssl.h b/ssl.h index 74e9638..322a06b 100644 --- a/ssl.h +++ b/ssl.h @@ -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; diff --git a/sslsplit.1 b/sslsplit.1 index 20235a6..32af7bd 100644 --- a/sslsplit.1 +++ b/sslsplit.1 @@ -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 @@ -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