From 4cfdef405a15c4449d502516cbf82d09a5fc636a Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Fri, 13 Apr 2012 14:47:30 +0200 Subject: [PATCH] Initial import of sslsplit-0.4.2 --- .gitignore | 19 + BSDmakefile | 6 + GNUmakefile | 332 +++++++++ README | 75 ++ TODO | 9 + attrib.h | 53 ++ cache.c | 158 ++++ cache.h | 83 +++ cachedsess.c | 236 ++++++ cachedsess.h | 47 ++ cachedsess.t | 168 +++++ cachefkcrt.c | 183 +++++ cachefkcrt.h | 44 ++ cachefkcrt.t | 139 ++++ cachemgr.c | 150 ++++ cachemgr.h | 83 +++ cachemgr.t | 58 ++ cachessess.c | 206 ++++++ cachessess.h | 44 ++ cachessess.t | 160 ++++ cachetgcrt.c | 161 +++++ cachetgcrt.h | 43 ++ cachetgcrt.t | 137 ++++ cert.c | 209 ++++++ cert.h | 57 ++ cert.t | 88 +++ dynbuf.c | 138 ++++ dynbuf.h | 49 ++ extra/pki/GNUmakefile | 92 +++ extra/pki/x509v3ca.cnf | 9 + extra/sslsplit.sh.in | 4 + khash.h | 548 ++++++++++++++ log.c | 474 ++++++++++++ log.h | 77 ++ logbuf.c | 180 +++++ logbuf.h | 57 ++ logger.c | 229 ++++++ logger.h | 56 ++ main.c | 634 ++++++++++++++++ main.t | 102 +++ nat.c | 629 ++++++++++++++++ nat.h | 55 ++ opts.c | 275 +++++++ opts.h | 91 +++ opts.t | 382 ++++++++++ proxy.c | 410 +++++++++++ proxy.h | 43 ++ pxyconn.c | 1466 +++++++++++++++++++++++++++++++++++++ pxyconn.h | 47 ++ pxysslshut.c | 178 +++++ pxysslshut.h | 42 ++ pxythrmgr.c | 236 ++++++ pxythrmgr.h | 52 ++ ssl.c | 1565 ++++++++++++++++++++++++++++++++++++++++ ssl.h | 111 +++ ssl.t | 450 ++++++++++++ sslsplit.1 | 487 +++++++++++++ sys.c | 327 +++++++++ sys.h | 57 ++ sys.t | 140 ++++ thrqueue.c | 215 ++++++ thrqueue.h | 50 ++ 62 files changed, 12905 insertions(+) create mode 100644 .gitignore create mode 100644 BSDmakefile create mode 100644 GNUmakefile create mode 100644 README create mode 100644 TODO create mode 100644 attrib.h create mode 100644 cache.c create mode 100644 cache.h create mode 100644 cachedsess.c create mode 100644 cachedsess.h create mode 100644 cachedsess.t create mode 100644 cachefkcrt.c create mode 100644 cachefkcrt.h create mode 100644 cachefkcrt.t create mode 100644 cachemgr.c create mode 100644 cachemgr.h create mode 100644 cachemgr.t create mode 100644 cachessess.c create mode 100644 cachessess.h create mode 100644 cachessess.t create mode 100644 cachetgcrt.c create mode 100644 cachetgcrt.h create mode 100644 cachetgcrt.t create mode 100644 cert.c create mode 100644 cert.h create mode 100644 cert.t create mode 100644 dynbuf.c create mode 100644 dynbuf.h create mode 100644 extra/pki/GNUmakefile create mode 100644 extra/pki/x509v3ca.cnf create mode 100644 extra/sslsplit.sh.in create mode 100644 khash.h create mode 100644 log.c create mode 100644 log.h create mode 100644 logbuf.c create mode 100644 logbuf.h create mode 100644 logger.c create mode 100644 logger.h create mode 100644 main.c create mode 100644 main.t create mode 100644 nat.c create mode 100644 nat.h create mode 100644 opts.c create mode 100644 opts.h create mode 100644 opts.t create mode 100644 proxy.c create mode 100644 proxy.h create mode 100644 pxyconn.c create mode 100644 pxyconn.h create mode 100644 pxysslshut.c create mode 100644 pxysslshut.h create mode 100644 pxythrmgr.c create mode 100644 pxythrmgr.h create mode 100644 ssl.c create mode 100644 ssl.h create mode 100644 ssl.t create mode 100644 sslsplit.1 create mode 100644 sys.c create mode 100644 sys.h create mode 100644 sys.t create mode 100644 thrqueue.c create mode 100644 thrqueue.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7d7dc2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +/*.o +/*.ot +/*.dSYM +/sslsplit +/sslsplit.test +/extra/pki/dh1024.param +/extra/pki/dh512.param +/extra/pki/dsa.pem +/extra/pki/dsa.crt +/extra/pki/dsa.key +/extra/pki/dsa.param +/extra/pki/ec.pem +/extra/pki/ec.crt +/extra/pki/ec.key +/extra/pki/rsa.pem +/extra/pki/rsa.crt +/extra/pki/rsa.key +/extra/pki/session.pem +/extra/pki/targets/* diff --git a/BSDmakefile b/BSDmakefile new file mode 100644 index 0000000..5e7470c --- /dev/null +++ b/BSDmakefile @@ -0,0 +1,6 @@ +USE_GNU: + @gmake $(.TARGETS) + +$(.TARGETS): USE_GNU + +.PHONY: USE_GNU diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..41e9ab3 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,332 @@ +### OpenSSL tweaking + +# Define to use dubious hacks to decrease OpenSSL memory consumption. +#FEATURES+= -DUSE_FOOTPRINT_HACKS + +# Define to disable server-mode SSL session caching for SSLv2 clients. +# This is needed if SSL session resumption fails with a bufferevent error: +# "illegal padding in SSL routines SSL2_READ_INTERNAL". +FEATURES+= -DDISABLE_SSLV2_SESSION_CACHE + +# Define to disable server-mode SSLv2 completely, but still use SSL23 method. +#FEATURES+= -DDISABLE_SSLV2_SERVER + +# Define to make SSLsplit set a session id context in server mode. +#FEATURES+= -DUSE_SSL_SESSION_ID_CONTEXT + + +### Debugging + +# These CFLAGS are added when building from git +DEBUG_CFLAGS?= -g +#DEBUG_CFLAGS+= -Werror + +# Define to remove false positives when debugging memory allocation. +# Note that you probably want to build OpenSSL with -DPURIFY too. +#FEATURES+= -DPURIFY + +# Define to add proxy state machine debugging; dump state in debug mode. +#FEATURES+= -DDEBUG_PROXY + +# Define to add certificate debugging; dump all certificates in debug mode. +#FEATURES+= -DDEBUG_CERTIFICATE + +# Define to add SSL session cache debugging; dump all sessions in debug mode. +#FEATURES+= -DDEBUG_SESSION_CACHE + +# Define to add debugging of parsing the SNI from the SSL ClientHello. +#FEATURES+= -DDEBUG_SNI_PARSER + +# Define to add thread debugging; dump thread state when choosing a thread. +#FEATURES+= -DDEBUG_THREAD + + +### Autodetected features + +# Autodetect pf +ifneq ($(wildcard /usr/include/net/pfvar.h),) +FEATURES+= -DHAVE_PF +endif + +# Autodetect ipfw +ifneq ($(wildcard /sbin/ipfw),) +FEATURES+= -DHAVE_IPFW +endif + +# Autodetect ipfilter +ifneq ($(wildcard /usr/include/netinet/ip_fil.h),) +FEATURES+= -DHAVE_IPFILTER +endif + +# Autodetect netfilter +ifneq ($(wildcard /usr/include/linux/netfilter.h),) +FEATURES+= -DHAVE_NETFILTER +endif + +# Autodetect glibc +ifneq ($(wildcard /usr/include/gnu/libc-version.h),) +CPPFLAGS+= -D_GNU_SOURCE +endif + + +### Variables you might need to override + +PREFIX?= /usr/local + +OPENSSL?= openssl +PKGCONFIG?= pkg-config + +BASENAME?= basename +CAT?= cat +GREP?= grep +INSTALL?= install +MKDIR?= mkdir +SED?= sed + + +### Variables only used for developer targets + +KHASH_URL?= https://github.com/attractivechaos/klib/raw/master/khash.h + +CPPCHECK?= cppcheck +GIT?= git +WGET?= wget + +BZIP2?= bzip2 +LN?= ln +MAN?= man +TAR?= tar + + +### You should not need to touch anything below this line + +TARGET:= sslsplit +PNAME:= SSLsplit +SRCS:= $(wildcard *.c) +HDRS:= $(wildcard *.h) +OBJS:= $(SRCS:.c=.o) + +TSRCS:= $(wildcard *.t) +TOBJS:= $(TSRCS:.t=.ot) +TOBJS+= $(filter-out main.o,$(OBJS)) + +VFILE:= $(wildcard VERSION) +GITDIR:= $(wildcard .git) +ifdef VFILE +VERSION:= $(shell $(CAT) VERSION) +else +ifndef GITDIR +VERSION:= $(shell $(BASENAME) $(PWD)|\ + $(GREP) $(TARGET)-|\ + $(SED) 's/.*$(TARGET)-\(.*\)/\1/g') +else +VERSION:= $(shell $(GIT) describe --tags --dirty --always) +endif +CFLAGS+= $(DEBUG_CFLAGS) +endif +BUILD_DATE:= $(shell date +%Y-%m-%d) + +CFLAGS+= $(PKG_CFLAGS) -pthread \ + -std=c99 -Wall -Wextra -pedantic -D_FORTIFY_SOURCE=2 +CPPFLAGS+= $(PKG_CPPFLAGS) $(FEATURES) -D"BNAME=\"$(TARGET)\"" \ + -D"PNAME=\"$(PNAME)\"" -D"VERSION=\"$(VERSION)\"" \ + -D"FEATURES=\"$(FEATURES)\"" -D"BUILD_DATE=\"$(BUILD_DATE)\"" +LDFLAGS+= $(PKG_LDFLAGS) -pthread +LIBS+= $(PKG_LIBS) + +# Autodetect dependencies known to pkg-config +PKGS:= +ifndef OPENSSL_BASE +PKGS+= $(shell $(PKGCONFIG) --exists openssl && echo openssl) +endif +ifndef LIBEVENT_BASE +PKGS+= $(shell $(PKGCONFIG) --exists libevent && echo libevent) +PKGS+= $(shell $(PKGCONFIG) --exists libevent_openssl \ + && echo libevent_openssl) +PKGS+= $(shell $(PKGCONFIG) --exists libevent_pthreads \ + && echo libevent_pthreads) +endif +TPKGS:= +ifndef CHECK_BASE +TPKGS+= $(shell $(PKGCONFIG) --exists check && echo check) +endif + +# Autodetect dependencies not known to pkg-config +ifeq (,$(filter openssl,$(PKGS))) +OPENSSL_PAT:= include/openssl/ssl.h +ifdef OPENSSL_BASE +OPENSSL_FIND:= $(wildcard $(OPENSSL_BASE)/$(OPENSSL_PAT)) +else +OPENSSL_FIND:= $(wildcard \ + /opt/local/$(OPENSSL_PAT) \ + /usr/local/$(OPENSSL_PAT) \ + /usr/$(OPENSSL_PAT)) +endif +OPENSSL_FOUND:= $(OPENSSL_FIND:/$(OPENSSL_PAT)=) +ifndef OPENSSL_FOUND +$(error OpenSSL not found; please point OPENSSL_BASE to base path) +endif +endif +ifeq (,$(filter libevent,$(PKGS))) +LIBEVENT_PAT:= include/event2/event.h +ifdef LIBEVENT_BASE +LIBEVENT_FIND:= $(wildcard $(LIBEVENT_BASE)/$(LIBEVENT_PAT)) +else +LIBEVENT_FIND:= $(wildcard \ + /opt/local/$(LIBEVENT_PAT) \ + /usr/local/$(LIBEVENT_PAT) \ + /usr/$(LIBEVENT_PAT)) +endif +LIBEVENT_FOUND:=$(LIBEVENT_FIND:/$(LIBEVENT_PAT)=) +ifndef LIBEVENT_FOUND +$(error libevent 2.x not found; please point LIBEVENT_BASE to base path) +endif +endif +ifeq (,$(filter check,$(TPKGS))) +CHECK_PAT:= include/check.h +ifdef CHECK_BASE +CHECK_FIND:= $(wildcard $(CHECK_BASE)/$(CHECK_PAT)) +else +CHECK_FIND:= $(wildcard \ + /opt/local/$(CHECK_PAT) \ + /usr/local/$(CHECK_PAT) \ + /usr/$(CHECK_PAT)) +endif +CHECK_FOUND:= $(CHECK_FIND:/$(CHECK_PAT)=) +ifndef CHECK_FOUND +$(warning check not found; please point CHECK_BASE to base path [optional]) +endif +endif + +ifdef OPENSSL_FOUND +PKG_CPPFLAGS+= -I$(OPENSSL_FOUND)/include +PKG_LDFLAGS+= -L$(OPENSSL_FOUND)/lib +PKG_LIBS+= -lssl -lcrypto -lz +endif +ifdef LIBEVENT_FOUND +PKG_CPPFLAGS+= -I$(LIBEVENT_FOUND)/include +PKG_LDFLAGS+= -L$(LIBEVENT_FOUND)/lib +PKG_LIBS+= -levent +endif +ifeq (,$(filter libevent_openssl,$(PKGS))) +PKG_LIBS+= -levent_openssl +endif +ifeq (,$(filter libevent_pthreads,$(PKGS))) +PKG_LIBS+= -levent_pthreads +endif +ifdef CHECK_FOUND +TPKG_CPPFLAGS+= -I$(CHECK_FOUND)/include +TPKG_LDFLAGS+= -L$(CHECK_FOUND)/lib +TPKG_LIBS+= -lcheck +endif + +ifneq (,$(strip $(PKGS))) +PKG_CFLAGS+= $(shell $(PKGCONFIG) --cflags-only-other $(PKGS)) +PKG_CPPFLAGS+= $(shell $(PKGCONFIG) --cflags-only-I $(PKGS)) +PKG_LDFLAGS+= $(shell $(PKGCONFIG) --libs-only-L --libs-only-other $(PKGS)) +PKG_LIBS+= $(shell $(PKGCONFIG) --libs-only-l $(PKGS)) +endif +ifneq (,$(strip $(TPKGS))) +TPKG_CFLAGS+= $(shell $(PKGCONFIG) --cflags-only-other $(TPKGS)) +TPKG_CPPFLAGS+= $(shell $(PKGCONFIG) --cflags-only-I $(TPKGS)) +TPKG_LDFLAGS+= $(shell $(PKGCONFIG) --libs-only-L --libs-only-other $(TPKGS)) +TPKG_LIBS+= $(shell $(PKGCONFIG) --libs-only-l $(TPKGS)) +endif + +export VERSION +export OPENSSL +export MKDIR + +all: version config $(TARGET) + +version: + @echo "$(PNAME) $(VERSION)" + +config: + @echo "via pkg-config: $(strip $(PKGS) $(TPKGS))" +ifdef OPENSSL_FOUND + @echo "OPENSSL_BASE: $(strip $(OPENSSL_FOUND))" +endif +ifdef LIBEVENT_FOUND + @echo "LIBEVENT_BASE: $(strip $(LIBEVENT_FOUND))" +endif +ifdef CHECK_FOUND + @echo "CHECK_BASE: $(strip $(CHECK_FOUND))" +endif + @echo "Build options: $(FEATURES)" + +$(TARGET): $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +%.o: %.c $(HDRS) GNUmakefile $(VFILE) + $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $< + +extra/pki/rsa.pem: + $(MAKE) -C extra/pki + +test: extra/pki/rsa.pem $(TARGET).test + $(RM) extra/pki/session.pem + $(MAKE) -C extra/pki session + ./$(TARGET).test + +$(TARGET).test: $(TOBJS) + $(CC) $(LDFLAGS) $(TPKG_LDFLAGS) -o $@ $^ $(LIBS) $(TPKG_LIBS) + +%.ot: %.t $(HDRS) GNUmakefile $(VFILE) + $(CC) -c $(CPPFLAGS) $(TPKG_CPPFLAGS) $(CFLAGS) $(TPKG_CFLAGS) -o $@ \ + -x c $< + +lint: + $(CPPCHECK) --force --enable=all --error-exitcode=1 . + +clean: + $(RM) -f $(TARGET) *.o $(TARGET).test *.ot *.core *~ + $(RM) -rf *.dSYM + +install: $(TARGET) + test -d $(PREFIX)/bin || $(MKDIR) -p $(PREFIX)/bin + test -d $(PREFIX)/share/man/man1 || \ + $(MKDIR) -p $(PREFIX)/share/man/man1 + $(INSTALL) -o 0 -g 0 -m 0755 $(TARGET) $(PREFIX)/bin/ + $(INSTALL) -o 0 -g 0 -m 0644 $(TARGET).1 $(PREFIX)/share/man/man1/ + +deinstall: + $(RM) -f $(PREFIX)/bin/$(TARGET) $(PREFIX)/share/man/man1/$(TARGET).1 + +ifdef GITDIR +mantest: + $(RM) -f man1 + $(LN) -sf . man1 + $(MAN) -M . 1 $(TARGET) + $(RM) man1 + +fetchdeps: + $(WGET) --no-check-certificate -O- $(KHASH_URL) >khash.h + +dist: $(TARGET)-$(VERSION).tar.bz2 + +$(TARGET)-$(VERSION).tar.bz2: + $(MKDIR) -p $(TARGET)-$(VERSION) + echo $(VERSION) >$(TARGET)-$(VERSION)/VERSION + $(GIT) archive --prefix=$(TARGET)-$(VERSION)/ HEAD \ + >$(TARGET)-$(VERSION).tar + $(TAR) -f $(TARGET)-$(VERSION).tar -r $(TARGET)-$(VERSION)/VERSION + $(BZIP2) <$(TARGET)-$(VERSION).tar >$(TARGET)-$(VERSION).tar.bz2 + $(RM) $(TARGET)-$(VERSION).tar + $(RM) -r $(TARGET)-$(VERSION) + +disttest: $(TARGET)-$(VERSION).tar.bz2 + $(BZIP2) -d < $(TARGET)-$(VERSION).tar.bz2 | $(TAR) -x -f - + cd $(TARGET)-$(VERSION) && $(MAKE) && $(MAKE) test && ./$(TARGET) -V + $(RM) -r $(TARGET)-$(VERSION) + +distclean: clean + $(RM) -f $(TARGET)-*.tar.bz2 + +realclean: distclean + $(MAKE) -C extra/pki clean +endif + +.PHONY: all config clean test lint install deinstall \ + mantest fetchdeps dist disttest distclean realclean + diff --git a/README b/README new file mode 100644 index 0000000..ff415bf --- /dev/null +++ b/README @@ -0,0 +1,75 @@ +SSLsplit - transparent and scalable SSL/TLS interception +Copyright (C) 2009-2012, Daniel Roethlisberger +http://www.roe.ch/SSLsplit + + +## Overview + +SSLsplit is a tool for man-in-the-middle attacks against SSL/TLS encrypted +network connections. Connections are transparently intercepted through a +network address translation engine and redirected to SSLsplit. SSLsplit +terminates SSL/TLS and initiates a new SSL/TLS connection to the original +destination address, while logging all data transmitted. + +SSLsplit supports plain TCP, plain SSL, HTTP and HTTPS connections over both +IPv4 and IPv6. For SSL and HTTPS connections, SSLsplit generates and signs +forged X509v3 certificates on-the-fly, based on the original server certificate +subject DN and subjectAltName extension. SSLsplit fully supports Server Name +Indication (SNI) and is able to work with RSA, DSA and ECDSA keys and DHE and +ECDHE cipher suites. SSLsplit can also use existing certificates of which the +private key is available, instead of generating forged ones. SSLsplit supports +NULL-prefix CN certificates. + +See the manual page sslsplit(1) for details on using SSLsplit and setting up +the various NAT engines. + + +## Requirements + +SSLsplit depends on the OpenSSL and libevent 2.x libraries. +The build depends on GNU make and a POSIX.2 environment in PATH. +The (optional) unit tests depend on check. + +SSLsplit currently supports the following operating systems and NAT engines: +- FreeBSD: pf rdr, ipfw fwd, ipfilter rdr +- OpenBSD: pf rdr +- Linux: netfilter REDIRECT and TPROXY +- Mac OS X: ipfw fwd + + +## Installation + +make +make test # optional unit tests +make install # optional install + +Dependencies are autoconfigured using pkgconfig. If dependencies are not +picked up, you can specify their respective locations manually by setting +OPENSSL_BASE, LIBEVENT_BASE and/or CHECK_BASE to the respective prefixes. + +You can override the default install prefix (/usr/local) by setting PREFIX. + + +## Development + +SSLsplit is being developed on Github. For bug reports, please use the Github +issue tracker. For patch submissions, please send me pull requests. + +https://github.com/droe/sslsplit + + +## License + +SSLsplit is provided under the simplified BSD license. +SSLsplit contains components licensed under the MIT license. +See the respective source file headers for details. + + +## Credits + +SSLsplit was inspired by ssl-mitm by Claes M. Nyberg and sslsniff by Moxie +Marlinspike, but shares no source code with them. + +SSLsplit includes khash by Attractive Chaos (khash.h). + + diff --git a/TODO b/TODO new file mode 100644 index 0000000..08a5439 --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ +- Parse some information from HTTP responses (status, size) +- Handle renego & client cert authentication more gracefully +- Separate orig cert retrieval from actual fwd address/proto config +- OCSP denial mode based on targetdir cert's OCSP servers +- CRL "denial" mode based on targetdir cert's CDPs +- Client fingerprinting: only intercept clients with headers matching regex +- Configurable and/or scriptable modification of requests and/or responses +- STARTTLS for various protocols +- Sample scripts for single file/fifo content log postprocessing diff --git a/attrib.h b/attrib.h new file mode 100644 index 0000000..ffa3f1d --- /dev/null +++ b/attrib.h @@ -0,0 +1,53 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ATTRIB_H +#define ATTRIB_H + +/* + * GCC attributes. + * These serve to improve the compiler warnings or optimizations. + * They are fully optional and are automatically disabled when + * building with a non-GCC (and non-LLVM) compiler. + */ + +#if !defined(__GNUC__) && !defined(__clang__) +#define __attribute__(x) +#endif + +#define UNUSED __attribute__((unused)) +#define NORET __attribute__((noreturn)) +#define PRINTF(f,a) __attribute__((format(printf,(f),(a)))) +#define SCANF(f,a) __attribute__((format(scanf,(f),(a)))) +#define WUNRES __attribute__((warn_unused_result)) +#define MALLOC __attribute__((malloc)) +#define NONNULL(...) __attribute__((nonnull(__VA_ARGS__))) + +#endif /* !ATTRIB_H */ + +/* vim: set noet ft=c: */ diff --git a/cache.c b/cache.c new file mode 100644 index 0000000..3166a80 --- /dev/null +++ b/cache.c @@ -0,0 +1,158 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cache.h" + +#include "log.h" +#include "khash.h" + +#include + +/* + * Generic, thread-safe cache. + */ + +/* + * Create a new cache based on the initializer callback init_cb. + */ +cache_t * +cache_new(cache_init_cb_t init_cb) +{ + cache_t *cache; + + if (!(cache = malloc(sizeof(cache_t)))) + return NULL; + + init_cb(cache); + + pthread_mutex_init(&cache->mutex, NULL); + + return cache; +} + +/* + * Free a cache and all associated resources. + * This function is not thread-safe. + */ +void +cache_free(cache_t *cache) +{ + khiter_t it; + + for (it = cache->begin_cb(); it != cache->end_cb(); it++) { + if (cache->exist_cb(it)) { + cache->free_key_cb(cache->get_key_cb(it)); + cache->free_val_cb(cache->get_val_cb(it)); + } + } + cache->fini_cb(); + pthread_mutex_destroy(&cache->mutex); + free(cache); +} + +void +cache_gc(cache_t *cache) +{ + khiter_t it; + cache_val_t val; + + pthread_mutex_lock(&cache->mutex); + for (it = cache->begin_cb(); it != cache->end_cb(); it++) { + if (cache->exist_cb(it)) { + val = cache->get_val_cb(it); + if (!cache->unpackverify_val_cb(val, 0)) { + cache->free_val_cb(val); + cache->free_key_cb(cache->get_key_cb(it)); + cache->del_cb(it); + } + } + } + pthread_mutex_unlock(&cache->mutex); +} + +cache_val_t +cache_get(cache_t *cache, cache_key_t key) +{ + cache_val_t rval = NULL; + cache_val_t val; + khiter_t it; + + if (!key) + return NULL; + + pthread_mutex_lock(&cache->mutex); + it = cache->get_cb(key); + if (it != cache->end_cb()) { + val = cache->get_val_cb(it); + if (!(rval = cache->unpackverify_val_cb(val, 1))) { + cache->free_val_cb(val); + cache->free_key_cb(cache->get_key_cb(it)); + cache->del_cb(it); + } + } + cache->free_key_cb(key); + pthread_mutex_unlock(&cache->mutex); + return rval; +} + +void +cache_set(cache_t *cache, cache_key_t key, cache_val_t val) +{ + khiter_t it; + int ret; + + if (!key || !val) + return; + + pthread_mutex_lock(&cache->mutex); + it = cache->put_cb(key, &ret); + if (!ret) { + cache->free_key_cb(key); + cache->free_val_cb(cache->get_val_cb(it)); + } + cache->set_val_cb(it, val); + pthread_mutex_unlock(&cache->mutex); +} + +void +cache_del(cache_t *cache, cache_key_t key) +{ + khiter_t it; + + pthread_mutex_lock(&cache->mutex); + it = cache->get_cb(key); + if (it != cache->end_cb()) { + cache->free_val_cb(cache->get_val_cb(it)); + cache->free_key_cb(cache->get_key_cb(it)); + cache->del_cb(it); + } + cache->free_key_cb(key); + pthread_mutex_unlock(&cache->mutex); +} + +/* vim: set noet ft=c: */ diff --git a/cache.h b/cache.h new file mode 100644 index 0000000..71147e9 --- /dev/null +++ b/cache.h @@ -0,0 +1,83 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CACHE_H +#define CACHE_H + +#include "attrib.h" + +#include + +typedef void * cache_val_t; +typedef void * cache_key_t; +typedef unsigned int cache_iter_t; /* must match khiter_t */ + +typedef cache_iter_t (*cache_begin_cb_t)(void); +typedef cache_iter_t (*cache_end_cb_t)(void); +typedef int (*cache_exist_cb_t)(cache_iter_t); +typedef void (*cache_del_cb_t)(cache_iter_t); +typedef cache_iter_t (*cache_get_cb_t)(cache_key_t); +typedef cache_iter_t (*cache_put_cb_t)(cache_key_t, int *); +typedef void (*cache_free_key_cb_t)(cache_key_t); +typedef void (*cache_free_val_cb_t)(cache_val_t); +typedef cache_key_t (*cache_get_key_cb_t)(cache_iter_t); +typedef cache_val_t (*cache_get_val_cb_t)(cache_iter_t); +typedef void (*cache_set_val_cb_t)(cache_iter_t, cache_val_t); +typedef cache_val_t (*cache_unpackverify_val_cb_t)(cache_val_t, int); +typedef void (*cache_fini_cb_t)(void); + +typedef struct cache { + pthread_mutex_t mutex; + + cache_begin_cb_t begin_cb; + cache_end_cb_t end_cb; + cache_exist_cb_t exist_cb; + cache_del_cb_t del_cb; + cache_get_cb_t get_cb; + cache_put_cb_t put_cb; + cache_free_key_cb_t free_key_cb; + cache_free_val_cb_t free_val_cb; + cache_get_key_cb_t get_key_cb; + cache_get_val_cb_t get_val_cb; + cache_set_val_cb_t set_val_cb; + cache_unpackverify_val_cb_t unpackverify_val_cb; + cache_fini_cb_t fini_cb; +} cache_t; + +typedef void (*cache_init_cb_t)(struct cache *); + +cache_t * cache_new(cache_init_cb_t) MALLOC; +void cache_free(cache_t *) NONNULL(); +void cache_gc(cache_t *) NONNULL(); +cache_val_t cache_get(cache_t *, cache_key_t) NONNULL(); +void cache_set(cache_t *, cache_key_t, cache_val_t) NONNULL(); +void cache_del(cache_t *, cache_key_t) NONNULL(); + +#endif /* !CACHE_H */ + +/* vim: set noet ft=c: */ diff --git a/cachedsess.c b/cachedsess.c new file mode 100644 index 0000000..9e3ff99 --- /dev/null +++ b/cachedsess.c @@ -0,0 +1,236 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cachedsess.h" + +#include "dynbuf.h" +#include "ssl.h" +#include "khash.h" + +#include + +/* + * Cache for outgoing dst connection SSL sessions. + * + * key: dynbuf_t * original destination IP address, port and SNI string + * val: dynbuf_t * ASN.1 serialized SSL_SESSION + */ + +static inline khint_t +kh_dynbuf_hash_func(dynbuf_t *b) +{ + khint_t *p = (khint_t *)b->buf; + khint_t h; + int rem; + + if ((rem = b->sz % sizeof(khint_t))) { + memcpy(&h, b->buf + b->sz - rem, rem); + } else { + h = 0; + } + + while (p < (khint_t*)(b->buf + b->sz - rem)) { + h ^= *p++; + } + + return h; +} + +#define kh_dynbuf_hash_equal(a, b) \ + (((a)->sz == (b)->sz) && \ + (memcmp((a)->buf, (b)->buf, (a)->sz) == 0)) + +KHASH_INIT(dynbufmap_t, dynbuf_t*, dynbuf_t*, 1, kh_dynbuf_hash_func, + kh_dynbuf_hash_equal) + +static khash_t(dynbufmap_t) *dstsessmap; + +static cache_iter_t +cachedsess_begin_cb(void) +{ + return kh_begin(dstsessmap); +} + +static cache_iter_t +cachedsess_end_cb(void) +{ + return kh_end(dstsessmap); +} + +static int +cachedsess_exist_cb(cache_iter_t it) +{ + return kh_exist(dstsessmap, it); +} + +static void +cachedsess_del_cb(cache_iter_t it) +{ + kh_del(dynbufmap_t, dstsessmap, it); +} + +static cache_iter_t +cachedsess_get_cb(cache_key_t key) +{ + return kh_get(dynbufmap_t, dstsessmap, key); +} + +static cache_iter_t +cachedsess_put_cb(cache_key_t key, int *ret) +{ + return kh_put(dynbufmap_t, dstsessmap, key, ret); +} + +static void +cachedsess_free_key_cb(cache_key_t key) +{ + dynbuf_free(key); +} + +static void +cachedsess_free_val_cb(cache_val_t val) +{ + dynbuf_free(val); +} + +static cache_key_t +cachedsess_get_key_cb(cache_iter_t it) +{ + return kh_key(dstsessmap, it); +} + +static cache_val_t +cachedsess_get_val_cb(cache_iter_t it) +{ + return kh_val(dstsessmap, it); +} + +static void +cachedsess_set_val_cb(cache_iter_t it, cache_val_t val) +{ + kh_val(dstsessmap, it) = val; +} + +static cache_val_t +cachedsess_unpackverify_val_cb(cache_val_t val, int copy) +{ + dynbuf_t *valbuf = val; + SSL_SESSION *sess; + const unsigned char *p; + + p = (const unsigned char *)valbuf->buf; + sess = d2i_SSL_SESSION(NULL, &p, valbuf->sz); /* increments p */ + if (!sess) + return NULL; + if (!ssl_session_is_valid(sess)) { + SSL_SESSION_free(sess); + return NULL; + } + if (copy) + return sess; + SSL_SESSION_free(sess); + return ((cache_val_t)!NULL); +} + +static void +cachedsess_fini_cb(void) +{ + kh_destroy(dynbufmap_t, dstsessmap); +} + +void +cachedsess_init_cb(cache_t *cache) +{ + dstsessmap = kh_init(dynbufmap_t); + + cache->begin_cb = cachedsess_begin_cb; + cache->end_cb = cachedsess_end_cb; + cache->exist_cb = cachedsess_exist_cb; + cache->del_cb = cachedsess_del_cb; + cache->get_cb = cachedsess_get_cb; + cache->put_cb = cachedsess_put_cb; + cache->free_key_cb = cachedsess_free_key_cb; + cache->free_val_cb = cachedsess_free_val_cb; + cache->get_key_cb = cachedsess_get_key_cb; + cache->get_val_cb = cachedsess_get_val_cb; + cache->set_val_cb = cachedsess_set_val_cb; + cache->unpackverify_val_cb = cachedsess_unpackverify_val_cb; + cache->fini_cb = cachedsess_fini_cb; +} + +cache_key_t +cachedsess_mkkey(const struct sockaddr *addr, UNUSED const socklen_t addrlen, + const char *sni) +{ + dynbuf_t tmp, *db; + short port; + size_t snilen; + + switch (((struct sockaddr_storage *)addr)->ss_family) { + case AF_INET: + tmp.buf = (unsigned char *) + &((struct sockaddr_in*)addr)->sin_addr; + tmp.sz = sizeof(struct in_addr); + port = ((struct sockaddr_in*)addr)->sin_port; + break; + case AF_INET6: + tmp.buf = (unsigned char *) + &((struct sockaddr_in6*)addr)->sin6_addr; + tmp.sz = sizeof(struct in6_addr); + port = ((struct sockaddr_in6*)addr)->sin6_port; + break; + default: + return NULL; + } + + snilen = sni ? strlen(sni) : 0; + if (!(db = dynbuf_new_alloc(tmp.sz + sizeof(port) + snilen))) + return NULL; + memcpy(db->buf, tmp.buf, tmp.sz); + memcpy(db->buf + tmp.sz, (char*)&port, sizeof(port)); + memcpy(db->buf + tmp.sz + sizeof(port), sni, snilen); + return db; +} + +cache_val_t +cachedsess_mkval(SSL_SESSION *sess) +{ + dynbuf_t *db; + unsigned char *p; + size_t asn1sz; + + asn1sz = i2d_SSL_SESSION(sess, NULL); + if (!asn1sz || !(db = dynbuf_new_alloc(asn1sz))) { + return NULL; + } + p = db->buf; + i2d_SSL_SESSION(sess, &p); /* updates p */ + return db; +} + +/* vim: set noet ft=c: */ diff --git a/cachedsess.h b/cachedsess.h new file mode 100644 index 0000000..141d334 --- /dev/null +++ b/cachedsess.h @@ -0,0 +1,47 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CACHEDSESS_H +#define CACHEDSESS_H + +#include "cache.h" +#include "attrib.h" + +#include +#include + +#include + +void cachedsess_init_cb(struct cache *) NONNULL(); + +cache_key_t cachedsess_mkkey(const struct sockaddr *, const socklen_t, const char *) NONNULL(); +cache_val_t cachedsess_mkval(SSL_SESSION *) NONNULL(); + +#endif /* !CACHEDSESS_H */ + +/* vim: set noet ft=c: */ diff --git a/cachedsess.t b/cachedsess.t new file mode 100644 index 0000000..3b3ed54 --- /dev/null +++ b/cachedsess.t @@ -0,0 +1,168 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "ssl.h" +#include "cachemgr.h" + +#define TMP_SESS_FILE "extra/pki/session.pem" + +static SSL_SESSION * +ssl_session_from_file(const char *filename) +{ + SSL_SESSION *sess; + FILE *f; + + f = fopen(filename, "r"); + if (!f) + return NULL; + sess = PEM_read_SSL_SESSION(f, NULL, NULL, NULL); + fclose(f); + return sess; +} + +static struct sockaddr_storage addr; +static socklen_t addrlen; +static char sni[] = "daniel.roe.ch"; + +static void +cachemgr_setup(void) +{ + ssl_init(); + cachemgr_init(); + addrlen = sizeof(struct sockaddr_in); + memset(&addr, 0, addrlen); + addr.ss_family = AF_INET; +} + +static void +cachemgr_teardown(void) +{ + cachemgr_fini(); + ssl_fini(); +} + +START_TEST(cache_dsess_01) +{ + SSL_SESSION *s1, *s2; + + s1 = ssl_session_from_file(TMP_SESS_FILE); + fail_unless(!!s1, "creating session failed"); + fail_unless(ssl_session_is_valid(s1), "session invalid"); + + cachemgr_dsess_set((struct sockaddr*)&addr, addrlen, sni, s1); + s2 = cachemgr_dsess_get((struct sockaddr*)&addr, addrlen, sni); + fail_unless(!!s2, "cache returned no session"); + fail_unless(s2 != s1, "cache returned same pointer"); + SSL_SESSION_free(s1); + SSL_SESSION_free(s2); +} +END_TEST + +START_TEST(cache_dsess_02) +{ + SSL_SESSION *s1, *s2; + + s1 = ssl_session_from_file(TMP_SESS_FILE); + fail_unless(!!s1, "creating session failed"); + fail_unless(ssl_session_is_valid(s1), "session invalid"); + + s2 = cachemgr_dsess_get((struct sockaddr*)&addr, addrlen, sni); + fail_unless(s2 == NULL, "session was already in empty cache"); + SSL_SESSION_free(s1); +} +END_TEST + +START_TEST(cache_dsess_03) +{ + SSL_SESSION *s1, *s2; + + s1 = ssl_session_from_file(TMP_SESS_FILE); + fail_unless(!!s1, "creating session failed"); + fail_unless(ssl_session_is_valid(s1), "session invalid"); + + cachemgr_dsess_set((struct sockaddr*)&addr, addrlen, sni, s1); + cachemgr_dsess_del((struct sockaddr*)&addr, addrlen, sni); + s2 = cachemgr_dsess_get((struct sockaddr*)&addr, addrlen, sni); + fail_unless(s2 == NULL, "cache returned deleted session"); + SSL_SESSION_free(s1); +} +END_TEST + +START_TEST(cache_dsess_04) +{ + SSL_SESSION *s1, *s2; + + s1 = ssl_session_from_file(TMP_SESS_FILE); + fail_unless(!!s1, "creating session failed"); + fail_unless(ssl_session_is_valid(s1), "session invalid"); + + fail_unless(s1->references == 1, "refcount != 1"); + cachemgr_dsess_set((struct sockaddr*)&addr, addrlen, sni, s1); + fail_unless(s1->references == 1, "refcount != 1"); + s2 = cachemgr_dsess_get((struct sockaddr*)&addr, addrlen, sni); + fail_unless(s1->references == 1, "refcount != 1"); + fail_unless(!!s2, "cache returned no session"); + fail_unless(s2->references == 1, "refcount != 1"); + cachemgr_dsess_set((struct sockaddr*)&addr, addrlen, sni, s1); + fail_unless(s1->references == 1, "refcount != 1"); + cachemgr_dsess_del((struct sockaddr*)&addr, addrlen, sni); + fail_unless(s1->references == 1, "refcount != 1"); + cachemgr_dsess_set((struct sockaddr*)&addr, addrlen, sni, s1); + fail_unless(s1->references == 1, "refcount != 1"); + SSL_SESSION_free(s1); + SSL_SESSION_free(s2); +} +END_TEST + +Suite * +cachedsess_suite(void) +{ + Suite *s; + TCase *tc; + + s = suite_create("cachedsess"); + + tc = tcase_create("cache_dsess"); + tcase_add_checked_fixture(tc, cachemgr_setup, cachemgr_teardown); + tcase_add_test(tc, cache_dsess_01); + tcase_add_test(tc, cache_dsess_02); + tcase_add_test(tc, cache_dsess_03); + tcase_add_test(tc, cache_dsess_04); + suite_add_tcase(s, tc); + + return s; +} + +/* vim: set noet ft=c: */ diff --git a/cachefkcrt.c b/cachefkcrt.c new file mode 100644 index 0000000..c3c4440 --- /dev/null +++ b/cachefkcrt.c @@ -0,0 +1,183 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cachefkcrt.h" + +#include "ssl.h" +#include "khash.h" + +/* + * Cache for generated fake certificates. + * + * key: char[SSL_X509_FPRSZ] fingerprint of original server cert + * val: X509 * generated fake certificate + */ + +static inline khint_t +kh_x509fpr_hash_func(void *b) +{ + khint_t *p = (khint_t*)(((char*)b) + SSL_X509_FPRSZ); + khint_t h = 0; + + /* assumes fpr is uniformly distributed */ + while (--p >= (khint_t*)b) + h ^= *p; + return h; +} + +#define kh_x509fpr_hash_equal(a, b) \ + (memcmp((char*)(a), (char*)(b), SSL_X509_FPRSZ) == 0) + +KHASH_INIT(sha1map_t, void*, void*, 1, kh_x509fpr_hash_func, + kh_x509fpr_hash_equal) + +static khash_t(sha1map_t) *certmap; + +static cache_iter_t +cachefkcrt_begin_cb(void) +{ + return kh_begin(certmap); +} + +static cache_iter_t +cachefkcrt_end_cb(void) +{ + return kh_end(certmap); +} + +static int +cachefkcrt_exist_cb(cache_iter_t it) +{ + return kh_exist(certmap, it); +} + +static void +cachefkcrt_del_cb(cache_iter_t it) +{ + kh_del(sha1map_t, certmap, it); +} + +static cache_iter_t +cachefkcrt_get_cb(cache_key_t key) +{ + return kh_get(sha1map_t, certmap, key); +} + +static cache_iter_t +cachefkcrt_put_cb(cache_key_t key, int *ret) +{ + return kh_put(sha1map_t, certmap, key, ret); +} + +static void +cachefkcrt_free_key_cb(cache_key_t key) +{ + free(key); +} + +static void +cachefkcrt_free_val_cb(cache_val_t val) +{ + X509_free(val); +} + +static cache_key_t +cachefkcrt_get_key_cb(cache_iter_t it) +{ + return kh_key(certmap, it); +} + +static cache_val_t +cachefkcrt_get_val_cb(cache_iter_t it) +{ + return kh_val(certmap, it); +} + +static void +cachefkcrt_set_val_cb(cache_iter_t it, cache_val_t val) +{ + kh_val(certmap, it) = val; +} + +static cache_val_t +cachefkcrt_unpackverify_val_cb(cache_val_t val, int copy) +{ + if (!ssl_x509_is_valid(val)) + return NULL; + if (copy) { + ssl_x509_refcount_inc(val); + return val; + } + return ((cache_val_t)!NULL); +} + +static void +cachefkcrt_fini_cb(void) +{ + kh_destroy(sha1map_t, certmap); +} + +void +cachefkcrt_init_cb(cache_t *cache) +{ + certmap = kh_init(sha1map_t); + + cache->begin_cb = cachefkcrt_begin_cb; + cache->end_cb = cachefkcrt_end_cb; + cache->exist_cb = cachefkcrt_exist_cb; + cache->del_cb = cachefkcrt_del_cb; + cache->get_cb = cachefkcrt_get_cb; + cache->put_cb = cachefkcrt_put_cb; + cache->free_key_cb = cachefkcrt_free_key_cb; + cache->free_val_cb = cachefkcrt_free_val_cb; + cache->get_key_cb = cachefkcrt_get_key_cb; + cache->get_val_cb = cachefkcrt_get_val_cb; + cache->set_val_cb = cachefkcrt_set_val_cb; + cache->unpackverify_val_cb = cachefkcrt_unpackverify_val_cb; + cache->fini_cb = cachefkcrt_fini_cb; +} + +cache_key_t +cachefkcrt_mkkey(X509 *keycrt) +{ + unsigned char *fpr; + + if (!(fpr = malloc(SSL_X509_FPRSZ))) + return NULL; + ssl_x509_fingerprint_sha1(keycrt, fpr); + return fpr; +} + +cache_val_t +cachefkcrt_mkval(X509 *valcrt) +{ + ssl_x509_refcount_inc(valcrt); + return valcrt; +} + +/* vim: set noet ft=c: */ diff --git a/cachefkcrt.h b/cachefkcrt.h new file mode 100644 index 0000000..8664d15 --- /dev/null +++ b/cachefkcrt.h @@ -0,0 +1,44 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CACHEFKCRT_H +#define CACHEFKCRT_H + +#include "cache.h" +#include "attrib.h" + +#include + +void cachefkcrt_init_cb(struct cache *) NONNULL(); + +cache_key_t cachefkcrt_mkkey(X509 *) NONNULL(); +cache_val_t cachefkcrt_mkval(X509 *) NONNULL(); + +#endif /* !CACHEFKCRT_H */ + +/* vim: set noet ft=c: */ diff --git a/cachefkcrt.t b/cachefkcrt.t new file mode 100644 index 0000000..248834f --- /dev/null +++ b/cachefkcrt.t @@ -0,0 +1,139 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "ssl.h" +#include "cachemgr.h" + +#define TESTCERT "extra/pki/rsa.crt" + +static void +cachemgr_setup(void) +{ + ssl_init(); + cachemgr_init(); +} + +static void +cachemgr_teardown(void) +{ + cachemgr_fini(); + ssl_fini(); +} + +START_TEST(cache_fkcrt_01) +{ + X509 *c1, *c2; + + c1 = ssl_x509_load(TESTCERT); + fail_unless(!!c1, "loading certificate failed"); + cachemgr_fkcrt_set(c1, c1); + c2 = cachemgr_fkcrt_get(c1); + fail_unless(c2 == c1, "cache did not return same pointer"); + X509_free(c1); + X509_free(c2); +} +END_TEST + +START_TEST(cache_fkcrt_02) +{ + X509 *c1, *c2; + + c1 = ssl_x509_load(TESTCERT); + fail_unless(!!c1, "loading certificate failed"); + c2 = cachemgr_fkcrt_get(c1); + fail_unless(c2 == NULL, "certificate was already in empty cache"); + X509_free(c1); +} +END_TEST + +START_TEST(cache_fkcrt_03) +{ + X509 *c1, *c2; + + c1 = ssl_x509_load(TESTCERT); + fail_unless(!!c1, "loading certificate failed"); + cachemgr_fkcrt_set(c1, c1); + cachemgr_fkcrt_del(c1); + c2 = cachemgr_fkcrt_get(c1); + fail_unless(c2 == NULL, "cache returned deleted certificate"); + X509_free(c1); +} +END_TEST + +START_TEST(cache_fkcrt_04) +{ + X509 *c1, *c2; + + c1 = ssl_x509_load(TESTCERT); + fail_unless(!!c1, "loading certificate failed"); + fail_unless(c1->references == 1, "refcount != 1"); + cachemgr_fkcrt_set(c1, c1); + fail_unless(c1->references == 2, "refcount != 2"); + c2 = cachemgr_fkcrt_get(c1); + fail_unless(c1->references == 3, "refcount != 3"); + cachemgr_fkcrt_set(c1, c1); + fail_unless(c1->references == 3, "refcount != 3"); + cachemgr_fkcrt_del(c1); + fail_unless(c1->references == 2, "refcount != 2"); + cachemgr_fkcrt_set(c1, c1); + fail_unless(c1->references == 3, "refcount != 3"); + X509_free(c1); + fail_unless(c1->references == 2, "refcount != 2"); + cachemgr_fini(); + fail_unless(c1->references == 1, "refcount != 1"); + X509_free(c2); + /* deliberate access of free'd X509* */ + fail_unless(c1->references == 0, "refcount != 0"); + cachemgr_init(); +} +END_TEST + +Suite * +cachefkcrt_suite(void) +{ + Suite *s; + TCase *tc; + + s = suite_create("cachefkcrt"); + + tc = tcase_create("cache_fkcrt"); + tcase_add_checked_fixture(tc, cachemgr_setup, cachemgr_teardown); + tcase_add_test(tc, cache_fkcrt_01); + tcase_add_test(tc, cache_fkcrt_02); + tcase_add_test(tc, cache_fkcrt_03); + tcase_add_test(tc, cache_fkcrt_04); + suite_add_tcase(s, tc); + + return s; +} + +/* vim: set noet ft=c: */ diff --git a/cachemgr.c b/cachemgr.c new file mode 100644 index 0000000..d23e982 --- /dev/null +++ b/cachemgr.c @@ -0,0 +1,150 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cachemgr.h" + +#include "cachefkcrt.h" +#include "cachetgcrt.h" +#include "cachessess.h" +#include "cachedsess.h" +#include "log.h" +#include "attrib.h" + +#include + +#include + +cache_t *cachemgr_fkcrt; +cache_t *cachemgr_tgcrt; +cache_t *cachemgr_ssess; +cache_t *cachemgr_dsess; + +/* + * Garbage collector thread entry point. + * Calls the _gc() method on the cache passed as argument, then returns. + */ +static void * +cachemgr_gc_thread(UNUSED void * arg) +{ + cache_gc(arg); + return NULL; +} + +/* + * Initialize the caches. + * The caches may be initialized before or after libevent and OpenSSL. + * Returns -1 on error, 0 on success. + */ +int +cachemgr_init(void) +{ + if (!(cachemgr_fkcrt = cache_new(cachefkcrt_init_cb))) + goto out4; + if (!(cachemgr_tgcrt = cache_new(cachetgcrt_init_cb))) + goto out3; + if (!(cachemgr_ssess = cache_new(cachessess_init_cb))) + goto out2; + if (!(cachemgr_dsess = cache_new(cachedsess_init_cb))) + goto out1; + return 0; + +out1: + cache_free(cachemgr_ssess); +out2: + cache_free(cachemgr_tgcrt); +out3: + cache_free(cachemgr_fkcrt); +out4: + return -1; +} + +/* + * Cleanup the caches and free all memory. Since OpenSSL certificates are + * being freed, this must be done before calling the OpenSSL cleanup methods. + * Also, it is not safe to call this while cachemgr_gc() is still running. + */ +void +cachemgr_fini(void) +{ + cache_free(cachemgr_dsess); + cache_free(cachemgr_ssess); + cache_free(cachemgr_tgcrt); + cache_free(cachemgr_fkcrt); +} + +/* + * Garbage collect all the cache contents; free's up resources occupied by + * certificates and sessions which are no longer valid. + * This function returns after the cleanup completed and all threads are + * joined. + */ +void +cachemgr_gc(void) +{ + pthread_t fkcrt_thr, dsess_thr, ssess_thr; + int rv; + + /* the tgcrt cache does not need cleanup */ + + rv = pthread_create(&fkcrt_thr, NULL, cachemgr_gc_thread, + cachemgr_fkcrt); + if (rv) { + log_err_printf("cachemgr_gc: pthread_create failed: %s\n", + strerror(rv)); + } + rv = pthread_create(&ssess_thr, NULL, cachemgr_gc_thread, + cachemgr_ssess); + if (rv) { + log_err_printf("cachemgr_gc: pthread_create failed: %s\n", + strerror(rv)); + } + rv = pthread_create(&dsess_thr, NULL, cachemgr_gc_thread, + cachemgr_dsess); + if (rv) { + log_err_printf("cachemgr_gc: pthread_create failed: %s\n", + strerror(rv)); + } + + rv = pthread_join(fkcrt_thr, NULL); + if (rv) { + log_err_printf("cachemgr_gc: pthread_join failed: %s\n", + strerror(rv)); + } + rv = pthread_join(ssess_thr, NULL); + if (rv) { + log_err_printf("cachemgr_gc: pthread_join failed: %s\n", + strerror(rv)); + } + rv = pthread_join(dsess_thr, NULL); + if (rv) { + log_err_printf("cachemgr_gc: pthread_join failed: %s\n", + strerror(rv)); + } +} + +/* vim: set noet ft=c: */ diff --git a/cachemgr.h b/cachemgr.h new file mode 100644 index 0000000..9339c1d --- /dev/null +++ b/cachemgr.h @@ -0,0 +1,83 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CACHEMGR_H +#define CACHEMGR_H + +#include "cache.h" +#include "cachefkcrt.h" +#include "cachetgcrt.h" +#include "cachessess.h" +#include "cachedsess.h" + +extern cache_t *cachemgr_fkcrt; +extern cache_t *cachemgr_tgcrt; +extern cache_t *cachemgr_ssess; +extern cache_t *cachemgr_dsess; + +int cachemgr_init(void); +void cachemgr_fini(void); +void cachemgr_gc(void); + +#define cachemgr_fkcrt_get(key) \ + cache_get(cachemgr_fkcrt, cachefkcrt_mkkey(key)) +#define cachemgr_fkcrt_set(key, val) \ + cache_set(cachemgr_fkcrt, cachefkcrt_mkkey(key), cachefkcrt_mkval(val)) +#define cachemgr_fkcrt_del(key) \ + cache_del(cachemgr_fkcrt, cachefkcrt_mkkey(key)) + +#define cachemgr_tgcrt_get(key) \ + cache_get(cachemgr_tgcrt, cachetgcrt_mkkey(key)) +#define cachemgr_tgcrt_set(key, val) \ + cache_set(cachemgr_tgcrt, cachetgcrt_mkkey(key), cachetgcrt_mkval(val)) +#define cachemgr_tgcrt_del(key) \ + cache_del(cachemgr_tgcrt, cachetgcrt_mkkey(key)) + +#define cachemgr_ssess_get(key, keysz) \ + cache_get(cachemgr_ssess, cachessess_mkkey((key), (keysz))) +#define cachemgr_ssess_set(val) \ + cache_set(cachemgr_ssess, \ + cachessess_mkkey((val)->session_id, \ + (val)->session_id_length), \ + cachessess_mkval(val)) +#define cachemgr_ssess_del(val) \ + cache_del(cachemgr_ssess, \ + cachessess_mkkey((val)->session_id, \ + (val)->session_id_length)) + +#define cachemgr_dsess_get(addr, addrlen, sni) \ + cache_get(cachemgr_dsess, cachedsess_mkkey((addr), (addrlen), (sni))) +#define cachemgr_dsess_set(addr, addrlen, sni, val) \ + cache_set(cachemgr_dsess, cachedsess_mkkey((addr), (addrlen), (sni)), \ + cachedsess_mkval(val)) +#define cachemgr_dsess_del(addr, addrlen, sni) \ + cache_del(cachemgr_dsess, cachedsess_mkkey((addr), (addrlen), (sni))) + +#endif /* !CACHEMGR_H */ + +/* vim: set noet ft=c: */ diff --git a/cachemgr.t b/cachemgr.t new file mode 100644 index 0000000..d7251a8 --- /dev/null +++ b/cachemgr.t @@ -0,0 +1,58 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "cachemgr.h" +#include "khash.h" + +START_TEST(cache_types_01) +{ + fail_unless(sizeof(cache_iter_t) == sizeof(khiter_t), + "type mismatch: cache_iter_t != khiter_t"); +} +END_TEST + +Suite * +cachemgr_suite(void) +{ + Suite *s; + TCase *tc; + + s = suite_create("cachemgr"); + + tc = tcase_create("cache_types"); + tcase_add_test(tc, cache_types_01); + suite_add_tcase(s, tc); + + return s; +} + +/* vim: set noet ft=c: */ diff --git a/cachessess.c b/cachessess.c new file mode 100644 index 0000000..f6c9a80 --- /dev/null +++ b/cachessess.c @@ -0,0 +1,206 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cachessess.h" + +#include "dynbuf.h" +#include "ssl.h" +#include "khash.h" + +/* + * Cache for incoming src connection SSL sessions. + * + * key: dynbuf_t * SSL session ID + * val: dynbuf_t * ASN.1 serialized SSL_SESSION + */ + +static inline khint_t +kh_dynbuf_hash_func(dynbuf_t *b) +{ + khint_t *p = (khint_t *)b->buf; + khint_t h; + int rem; + + if ((rem = b->sz % sizeof(khint_t))) { + memcpy(&h, b->buf + b->sz - rem, rem); + } else { + h = 0; + } + + while (p < (khint_t*)(b->buf + b->sz - rem)) { + h ^= *p++; + } + + return h; +} + +#define kh_dynbuf_hash_equal(a, b) \ + (((a)->sz == (b)->sz) && \ + (memcmp((a)->buf, (b)->buf, (a)->sz) == 0)) + +KHASH_INIT(dynbufmap_t, dynbuf_t*, dynbuf_t*, 1, kh_dynbuf_hash_func, + kh_dynbuf_hash_equal) + +static khash_t(dynbufmap_t) *srcsessmap; + +static cache_iter_t +cachessess_begin_cb(void) +{ + return kh_begin(srcsessmap); +} + +static cache_iter_t +cachessess_end_cb(void) +{ + return kh_end(srcsessmap); +} + +static int +cachessess_exist_cb(cache_iter_t it) +{ + return kh_exist(srcsessmap, it); +} + +static void +cachessess_del_cb(cache_iter_t it) +{ + kh_del(dynbufmap_t, srcsessmap, it); +} + +static cache_iter_t +cachessess_get_cb(cache_key_t key) +{ + return kh_get(dynbufmap_t, srcsessmap, key); +} + +static cache_iter_t +cachessess_put_cb(cache_key_t key, int *ret) +{ + return kh_put(dynbufmap_t, srcsessmap, key, ret); +} + +static void +cachessess_free_key_cb(cache_key_t key) +{ + dynbuf_free(key); +} + +static void +cachessess_free_val_cb(cache_val_t val) +{ + dynbuf_free(val); +} + +static cache_key_t +cachessess_get_key_cb(cache_iter_t it) +{ + return kh_key(srcsessmap, it); +} + +static cache_val_t +cachessess_get_val_cb(cache_iter_t it) +{ + return kh_val(srcsessmap, it); +} + +static void +cachessess_set_val_cb(cache_iter_t it, cache_val_t val) +{ + kh_val(srcsessmap, it) = val; +} + +static cache_val_t +cachessess_unpackverify_val_cb(cache_val_t val, int copy) +{ + dynbuf_t *valbuf = val; + SSL_SESSION *sess; + const unsigned char *p; + + p = (const unsigned char *)valbuf->buf; + sess = d2i_SSL_SESSION(NULL, &p, valbuf->sz); /* increments p */ + if (!sess) + return NULL; + if (!ssl_session_is_valid(sess)) { + SSL_SESSION_free(sess); + return NULL; + } + if (copy) + return sess; + SSL_SESSION_free(sess); + return ((cache_val_t)!NULL); +} + +static void +cachessess_fini_cb(void) +{ + kh_destroy(dynbufmap_t, srcsessmap); +} + +void +cachessess_init_cb(cache_t *cache) +{ + srcsessmap = kh_init(dynbufmap_t); + + cache->begin_cb = cachessess_begin_cb; + cache->end_cb = cachessess_end_cb; + cache->exist_cb = cachessess_exist_cb; + cache->del_cb = cachessess_del_cb; + cache->get_cb = cachessess_get_cb; + cache->put_cb = cachessess_put_cb; + cache->free_key_cb = cachessess_free_key_cb; + cache->free_val_cb = cachessess_free_val_cb; + cache->get_key_cb = cachessess_get_key_cb; + cache->get_val_cb = cachessess_get_val_cb; + cache->set_val_cb = cachessess_set_val_cb; + cache->unpackverify_val_cb = cachessess_unpackverify_val_cb; + cache->fini_cb = cachessess_fini_cb; +} + +cache_key_t +cachessess_mkkey(const unsigned char *id, const size_t idlen) +{ + return dynbuf_new_copy(id, idlen); +} + +cache_val_t +cachessess_mkval(SSL_SESSION *sess) +{ + dynbuf_t *db; + unsigned char *p; + size_t asn1sz; + + asn1sz = i2d_SSL_SESSION(sess, NULL); + if (!asn1sz || !(db = dynbuf_new_alloc(asn1sz))) { + return NULL; + } + p = db->buf; + i2d_SSL_SESSION(sess, &p); /* updates p */ + return db; +} + +/* vim: set noet ft=c: */ diff --git a/cachessess.h b/cachessess.h new file mode 100644 index 0000000..160fbdc --- /dev/null +++ b/cachessess.h @@ -0,0 +1,44 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CACHESSESS_H +#define CACHESSESS_H + +#include "cache.h" +#include "attrib.h" + +#include + +void cachessess_init_cb(struct cache *) NONNULL(); + +cache_key_t cachessess_mkkey(const unsigned char *, const size_t) NONNULL(); +cache_val_t cachessess_mkval(SSL_SESSION *) NONNULL(); + +#endif /* !CACHESSESS_H */ + +/* vim: set noet ft=c: */ diff --git a/cachessess.t b/cachessess.t new file mode 100644 index 0000000..578d947 --- /dev/null +++ b/cachessess.t @@ -0,0 +1,160 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "ssl.h" +#include "cachemgr.h" + +#define TMP_SESS_FILE "extra/pki/session.pem" + +static SSL_SESSION * +ssl_session_from_file(const char *filename) +{ + SSL_SESSION *sess; + FILE *f; + + f = fopen(filename, "r"); + if (!f) + return NULL; + sess = PEM_read_SSL_SESSION(f, NULL, NULL, NULL); + fclose(f); + return sess; +} + +static void +cachemgr_setup(void) +{ + ssl_init(); + cachemgr_init(); +} + +static void +cachemgr_teardown(void) +{ + cachemgr_fini(); + ssl_fini(); +} + +START_TEST(cache_ssess_01) +{ + SSL_SESSION *s1, *s2; + + s1 = ssl_session_from_file(TMP_SESS_FILE); + fail_unless(!!s1, "creating session failed"); + fail_unless(ssl_session_is_valid(s1), "session invalid"); + + cachemgr_ssess_set(s1); + s2 = cachemgr_ssess_get(s1->session_id, s1->session_id_length); + fail_unless(!!s2, "cache returned no session"); + fail_unless(s2 != s1, "cache returned same pointer"); + SSL_SESSION_free(s1); + SSL_SESSION_free(s2); +} +END_TEST + +START_TEST(cache_ssess_02) +{ + SSL_SESSION *s1, *s2; + + s1 = ssl_session_from_file(TMP_SESS_FILE); + fail_unless(!!s1, "creating session failed"); + fail_unless(ssl_session_is_valid(s1), "session invalid"); + + s2 = cachemgr_ssess_get(s1->session_id, s1->session_id_length); + fail_unless(s2 == NULL, "session was already in empty cache"); + SSL_SESSION_free(s1); +} +END_TEST + +START_TEST(cache_ssess_03) +{ + SSL_SESSION *s1, *s2; + + s1 = ssl_session_from_file(TMP_SESS_FILE); + fail_unless(!!s1, "creating session failed"); + fail_unless(ssl_session_is_valid(s1), "session invalid"); + + cachemgr_ssess_set(s1); + cachemgr_ssess_del(s1); + s2 = cachemgr_ssess_get(s1->session_id, s1->session_id_length); + fail_unless(s2 == NULL, "cache returned deleted session"); + SSL_SESSION_free(s1); +} +END_TEST + +START_TEST(cache_ssess_04) +{ + SSL_SESSION *s1, *s2; + + s1 = ssl_session_from_file(TMP_SESS_FILE); + fail_unless(!!s1, "creating session failed"); + fail_unless(ssl_session_is_valid(s1), "session invalid"); + + fail_unless(s1->references == 1, "refcount != 1"); + cachemgr_ssess_set(s1); + fail_unless(s1->references == 1, "refcount != 1"); + s2 = cachemgr_ssess_get(s1->session_id, s1->session_id_length); + fail_unless(s1->references == 1, "refcount != 1"); + fail_unless(!!s2, "cache returned no session"); + fail_unless(s2->references == 1, "refcount != 1"); + cachemgr_ssess_set(s1); + fail_unless(s1->references == 1, "refcount != 1"); + cachemgr_ssess_del(s1); + fail_unless(s1->references == 1, "refcount != 1"); + cachemgr_ssess_set(s1); + fail_unless(s1->references == 1, "refcount != 1"); + SSL_SESSION_free(s1); + SSL_SESSION_free(s2); +} +END_TEST + +Suite * +cachessess_suite(void) +{ + Suite *s; + TCase *tc; + + s = suite_create("cachessess"); + + tc = tcase_create("cache_ssess"); + tcase_add_checked_fixture(tc, cachemgr_setup, cachemgr_teardown); + tcase_add_test(tc, cache_ssess_01); + tcase_add_test(tc, cache_ssess_02); + tcase_add_test(tc, cache_ssess_03); + tcase_add_test(tc, cache_ssess_04); + suite_add_tcase(s, tc); + + return s; +} + +/* vim: set noet ft=c: */ diff --git a/cachetgcrt.c b/cachetgcrt.c new file mode 100644 index 0000000..61fe1c1 --- /dev/null +++ b/cachetgcrt.c @@ -0,0 +1,161 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cachetgcrt.h" + +#include "ssl.h" +#include "khash.h" + +/* + * Cache for target cert / chain / key tuples read from configured directory. + * This cache does not need garbage collection. + * + * key: char * common name + * val: cert_t * cert / chain / key tuple + */ + +KHASH_INIT(cstrmap_t, char*, void*, 1, kh_str_hash_func, kh_str_hash_equal) + +static khash_t(cstrmap_t) *certmap; + +static cache_iter_t +cachetgcrt_begin_cb(void) +{ + return kh_begin(certmap); +} + +static cache_iter_t +cachetgcrt_end_cb(void) +{ + return kh_end(certmap); +} + +static int +cachetgcrt_exist_cb(cache_iter_t it) +{ + return kh_exist(certmap, it); +} + +static void +cachetgcrt_del_cb(cache_iter_t it) +{ + kh_del(cstrmap_t, certmap, it); +} + +static cache_iter_t +cachetgcrt_get_cb(cache_key_t key) +{ + return kh_get(cstrmap_t, certmap, key); +} + +static cache_iter_t +cachetgcrt_put_cb(cache_key_t key, int *ret) +{ + return kh_put(cstrmap_t, certmap, key, ret); +} + +static void +cachetgcrt_free_key_cb(cache_key_t key) +{ + free(key); +} + +static void +cachetgcrt_free_val_cb(cache_val_t val) +{ + cert_free(val); +} + +static cache_key_t +cachetgcrt_get_key_cb(cache_iter_t it) +{ + return kh_key(certmap, it); +} + +static cache_val_t +cachetgcrt_get_val_cb(cache_iter_t it) +{ + return kh_val(certmap, it); +} + +static void +cachetgcrt_set_val_cb(cache_iter_t it, cache_val_t val) +{ + kh_val(certmap, it) = val; +} + +static cache_val_t +cachetgcrt_unpackverify_val_cb(cache_val_t val, int copy) +{ + if (copy) { + cert_refcount_inc(val); + return val; + } + return ((cache_val_t)!NULL); +} + +static void +cachetgcrt_fini_cb(void) +{ + kh_destroy(cstrmap_t, certmap); +} + +void +cachetgcrt_init_cb(cache_t *cache) +{ + certmap = kh_init(cstrmap_t); + + cache->begin_cb = cachetgcrt_begin_cb; + cache->end_cb = cachetgcrt_end_cb; + cache->exist_cb = cachetgcrt_exist_cb; + cache->del_cb = cachetgcrt_del_cb; + cache->get_cb = cachetgcrt_get_cb; + cache->put_cb = cachetgcrt_put_cb; + cache->free_key_cb = cachetgcrt_free_key_cb; + cache->free_val_cb = cachetgcrt_free_val_cb; + cache->get_key_cb = cachetgcrt_get_key_cb; + cache->get_val_cb = cachetgcrt_get_val_cb; + cache->set_val_cb = cachetgcrt_set_val_cb; + cache->unpackverify_val_cb = cachetgcrt_unpackverify_val_cb; + cache->fini_cb = cachetgcrt_fini_cb; +} + +cache_key_t +cachetgcrt_mkkey(const char *keycn) +{ + return strdup(keycn); +} + +cache_val_t +cachetgcrt_mkval(cert_t *valcrt) +{ + cert_refcount_inc(valcrt); + return valcrt; +} + +/* vim: set noet ft=c: */ diff --git a/cachetgcrt.h b/cachetgcrt.h new file mode 100644 index 0000000..cebd3ec --- /dev/null +++ b/cachetgcrt.h @@ -0,0 +1,43 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CACHETGCRT_H +#define CACHETGCRT_H + +#include "cache.h" +#include "attrib.h" +#include "cert.h" + +void cachetgcrt_init_cb(struct cache *) NONNULL(); + +cache_key_t cachetgcrt_mkkey(const char *) NONNULL(); +cache_val_t cachetgcrt_mkval(cert_t *) NONNULL(); + +#endif /* !CACHETGCRT_H */ + +/* vim: set noet ft=c: */ diff --git a/cachetgcrt.t b/cachetgcrt.t new file mode 100644 index 0000000..1a00359 --- /dev/null +++ b/cachetgcrt.t @@ -0,0 +1,137 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "ssl.h" +#include "cert.h" +#include "cachemgr.h" + +#define TESTCERT "extra/pki/targets/daniel.roe.ch.pem" + +static void +cachemgr_setup(void) +{ + ssl_init(); + cachemgr_init(); +} + +static void +cachemgr_teardown(void) +{ + cachemgr_fini(); + ssl_fini(); +} + +START_TEST(cache_tgcrt_01) +{ + cert_t *c1, *c2; + + c1 = cert_new_load(TESTCERT); + fail_unless(!!c1, "loading certificate failed"); + cachemgr_tgcrt_set("daniel.roe.ch", c1); + c2 = cachemgr_tgcrt_get("daniel.roe.ch"); + fail_unless(c2 == c1, "cache did not return same pointer"); + cert_free(c1); + cert_free(c2); +} +END_TEST + +START_TEST(cache_tgcrt_02) +{ + cert_t *c; + + c = cachemgr_tgcrt_get("daniel.roe.ch"); + fail_unless(c == NULL, "certificate was already in empty cache"); +} +END_TEST + +START_TEST(cache_tgcrt_03) +{ + cert_t *c1, *c2; + + c1 = cert_new_load(TESTCERT); + fail_unless(!!c1, "loading certificate failed"); + cachemgr_tgcrt_set("daniel.roe.ch", c1); + cachemgr_tgcrt_del("daniel.roe.ch"); + c2 = cachemgr_tgcrt_get("daniel.roe.ch"); + fail_unless(c2 == NULL, "cache returned deleted certificate"); + cert_free(c1); +} +END_TEST + +START_TEST(cache_tgcrt_04) +{ + cert_t *c1, *c2; + + c1 = cert_new_load(TESTCERT); + fail_unless(!!c1, "loading certificate failed"); + fail_unless(c1->references == 1, "refcount != 1"); + cachemgr_tgcrt_set("daniel.roe.ch", c1); + fail_unless(c1->references == 2, "refcount != 2"); + c2 = cachemgr_tgcrt_get("daniel.roe.ch"); + fail_unless(c1->references == 3, "refcount != 3"); + cachemgr_tgcrt_set("daniel.roe.ch", c1); + fail_unless(c1->references == 3, "refcount != 3"); + cachemgr_tgcrt_del("daniel.roe.ch"); + fail_unless(c1->references == 2, "refcount != 2"); + cachemgr_tgcrt_set("daniel.roe.ch", c1); + fail_unless(c1->references == 3, "refcount != 3"); + cert_free(c1); + fail_unless(c1->references == 2, "refcount != 2"); + cachemgr_fini(); + fail_unless(c1->references == 1, "refcount != 1"); + cert_free(c2); + /* deliberate access of free'd cert_t* */ + fail_unless(c1->references == 0, "refcount != 0"); + cachemgr_init(); +} +END_TEST + +Suite * +cachetgcrt_suite(void) +{ + Suite *s; + TCase *tc; + + s = suite_create("cachetgcrt"); + + tc = tcase_create("cache_tgcrt"); + tcase_add_checked_fixture(tc, cachemgr_setup, cachemgr_teardown); + tcase_add_test(tc, cache_tgcrt_01); + tcase_add_test(tc, cache_tgcrt_02); + tcase_add_test(tc, cache_tgcrt_03); + tcase_add_test(tc, cache_tgcrt_04); + suite_add_tcase(s, tc); + + return s; +} + +/* vim: set noet ft=c: */ diff --git a/cert.c b/cert.c new file mode 100644 index 0000000..d9771cd --- /dev/null +++ b/cert.c @@ -0,0 +1,209 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cert.h" + +#include "ssl.h" + +#include + +/* + * Certificate, including private key and certificate chain. + */ + +cert_t * +cert_new(void) +{ + cert_t *c; + + if (!(c = malloc(sizeof(cert_t)))) + return NULL; + memset(c, 0, sizeof(cert_t)); + c->references = 1; + pthread_mutex_init(&c->mutex, NULL); + return c; +} + +/* + * Passed OpenSSL objects are owned by cert_t; refcount will not be + * incremented, stack will not be duplicated. + */ +cert_t * +cert_new3(EVP_PKEY *key, X509 *crt, STACK_OF(X509) *chain) +{ + cert_t *c; + + if (!(c = malloc(sizeof(cert_t)))) + return NULL; + c->key = key; + c->crt = crt; + c->chain = chain; + c->references = 1; + pthread_mutex_init(&c->mutex, NULL); + return c; +} + +/* + * Passed OpenSSL objects are copied by cert_t; crt/key refcount will be + * incremented, stack will be duplicated. + */ +cert_t * +cert_new3_copy(EVP_PKEY *key, X509 *crt, STACK_OF(X509) *chain) +{ + cert_t *c; + + if (!(c = malloc(sizeof(cert_t)))) + return NULL; + c->key = key; + ssl_key_refcount_inc(c->key); + c->crt = crt; + ssl_x509_refcount_inc(c->crt); + c->chain = sk_X509_dup(chain); + for (int i = 0; i < sk_X509_num(c->chain); i++) { + ssl_x509_refcount_inc(sk_X509_value(c->chain, i)); + } + c->references = 1; + pthread_mutex_init(&c->mutex, NULL); + return c; +} + +/* + * Load cert_t from file. + */ +cert_t * +cert_new_load(const char *filename) +{ + cert_t *c; + + if (!(c = malloc(sizeof(cert_t)))) + return NULL; + memset(c, 0, sizeof(cert_t)); + + if (ssl_x509chain_load(&c->crt, &c->chain, filename) == -1) { + free(c); + return NULL; + } + c->key = ssl_key_load(filename); + if (!c->key) { + X509_free(c->crt); + if (c->chain) { + sk_X509_pop_free(c->chain, X509_free); + } + free(c); + return NULL; + } + c->references = 1; + pthread_mutex_init(&c->mutex, NULL); + return c; +} + +/* + * Increment reference count. + */ +void +cert_refcount_inc(cert_t *c) +{ + pthread_mutex_lock(&c->mutex); + c->references++; + pthread_mutex_unlock(&c->mutex); +} + +/* + * Thread-safe setter functions; they copy the value (refcounts are inc'd). + */ +void +cert_set_key(cert_t *c, EVP_PKEY *key) +{ + pthread_mutex_lock(&c->mutex); + if (c->key) { + EVP_PKEY_free(c->key); + } + c->key = key; + if (c->key) { + ssl_key_refcount_inc(c->key); + } + pthread_mutex_unlock(&c->mutex); +} +void +cert_set_crt(cert_t *c, X509 *crt) +{ + pthread_mutex_lock(&c->mutex); + if (c->crt) { + X509_free(c->crt); + } + c->crt = crt; + if (c->crt) { + ssl_x509_refcount_inc(c->crt); + } + pthread_mutex_unlock(&c->mutex); +} +void +cert_set_chain(cert_t *c, STACK_OF(X509) *chain) +{ + pthread_mutex_lock(&c->mutex); + if (c->chain) { + sk_X509_pop_free(c->chain, X509_free); + } + if (chain) { + c->chain = sk_X509_dup(chain); + for (int i = 0; i < sk_X509_num(c->chain); i++) { + ssl_x509_refcount_inc(sk_X509_value(c->chain, i)); + } + } else { + c->chain = NULL; + } + pthread_mutex_unlock(&c->mutex); +} + +/* + * Free cert including internal objects. + */ +void +cert_free(cert_t *c) +{ + pthread_mutex_lock(&c->mutex); + c->references--; + if (c->references) { + pthread_mutex_unlock(&c->mutex); + return; + } + pthread_mutex_unlock(&c->mutex); + pthread_mutex_destroy(&c->mutex); + if (c->key) { + EVP_PKEY_free(c->key); + } + if (c->crt) { + X509_free(c->crt); + } + if (c->chain) { + sk_X509_pop_free(c->chain, X509_free); + } + free(c); +} + +/* vim: set noet ft=c: */ diff --git a/cert.h b/cert.h new file mode 100644 index 0000000..68c7472 --- /dev/null +++ b/cert.h @@ -0,0 +1,57 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CERT_H +#define CERT_H + +#include "attrib.h" + +#include +#include + +typedef struct cert { + EVP_PKEY *key; + X509 *crt; + STACK_OF(X509) * chain; + pthread_mutex_t mutex; + size_t references; +} cert_t; + +cert_t * cert_new(void) MALLOC; +cert_t * cert_new_load(const char *) MALLOC; +cert_t * cert_new3(EVP_PKEY *, X509 *, STACK_OF(X509) *) MALLOC; +cert_t * cert_new3_copy(EVP_PKEY *, X509 *, STACK_OF(X509) *) MALLOC; +void cert_refcount_inc(cert_t *) NONNULL(); +void cert_set_key(cert_t *, EVP_PKEY *) NONNULL(1); +void cert_set_crt(cert_t *, X509 *) NONNULL(1); +void cert_set_chain(cert_t *, STACK_OF(X509) *) NONNULL(1); +void cert_free(cert_t *) NONNULL(); + +#endif /* !CERT_H */ + +/* vim: set noet ft=c: */ diff --git a/cert.t b/cert.t new file mode 100644 index 0000000..071024f --- /dev/null +++ b/cert.t @@ -0,0 +1,88 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "ssl.h" +#include "cert.h" + +#define TESTCERT "extra/pki/targets/daniel.roe.ch.pem" + +START_TEST(cert_new_load_01) +{ + cert_t *c; + + c = cert_new_load(TESTCERT); + fail_unless(!!c, "loading PEM failed"); + fail_unless(!!c->crt, "loading crt failed"); + fail_unless(!!c->key, "loading key failed"); + fail_unless(!!c->chain, "initializing chain stack failed"); + fail_unless(sk_X509_num(c->chain) == 1, "loading chain failed"); + cert_free(c); +} +END_TEST + +START_TEST(cert_refcount_inc_01) +{ + cert_t *c; + + c = cert_new_load(TESTCERT); + fail_unless(!!c, "loading PEM failed"); + fail_unless(c->references == 1, "refcount mismatch"); + cert_refcount_inc(c); + fail_unless(c->references == 2, "refcount mismatch"); + cert_free(c); + fail_unless(c->references == 1, "refcount mismatch"); + cert_free(c); + /* deliberate access after last free() */ + fail_unless(c->references == 0, "refcount mismatch"); +} +END_TEST + +Suite * +cert_suite(void) +{ + Suite *s; + TCase *tc; + + s = suite_create("cert"); + + tc = tcase_create("cert_new_load_01"); + tcase_add_test(tc, cert_new_load_01); + suite_add_tcase(s, tc); + + tc = tcase_create("cert_refcount_inc_01"); + tcase_add_test(tc, cert_refcount_inc_01); + suite_add_tcase(s, tc); + + return s; +} + +/* vim: set noet ft=c: */ diff --git a/dynbuf.c b/dynbuf.c new file mode 100644 index 0000000..bd93ce5 --- /dev/null +++ b/dynbuf.c @@ -0,0 +1,138 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dynbuf.h" + +#include +#include + +/* + * Simple dynamic buffer, consisting of internal buffer ptr plus length. + * Dynbuf always owns the internal allocated buffer. + */ + +/* + * Allocate new dynbuf; will allocate sz bytes of memory in ->buf. + */ +dynbuf_t * +dynbuf_new_alloc(size_t sz) +{ + dynbuf_t *db; + + if (!(db = malloc(sizeof(dynbuf_t)))) + return NULL; + if (!(db->buf = malloc(sz))) { + free(db); + return NULL; + } + db->sz = sz; + return db; +} + +/* + * Create new dynbuf from provided buffer, which is copied. + */ +dynbuf_t * +dynbuf_new_copy(const unsigned char *buf, const size_t sz) +{ + dynbuf_t *db; + + if (!(db = malloc(sizeof(dynbuf_t)))) + return NULL; + if (!(db->buf = malloc(sz))) { + free(db); + return NULL; + } + memcpy(db->buf, buf, sz); + db->sz = sz; + return db; +} + +/* + * Create new dynbuf by loading a file into a newly allocated internal buffer. + * The provided buffer will be freed by dynbuf_free(). + */ +dynbuf_t * +dynbuf_new_file(const char *filename) +{ + dynbuf_t *db; + FILE *f; + + if (!(db = malloc(sizeof(dynbuf_t)))) + return NULL; + + f = fopen(filename, "rb"); + if (!f) { + free(db); + return NULL; + } + fseek(f, 0, SEEK_END); + db->sz = ftell(f); + fseek(f, 0, SEEK_SET); + if (!(db->buf = malloc(db->sz))) { + free(db); + fclose(f); + return NULL; + } + if (fread(db->buf, db->sz, 1, f) != 1) { + free(db->buf); + free(db); + fclose(f); + return NULL; + } + fclose(f); + return db; +} + +/* + * Create new dynbuf from provided, pre-allocated buffer. + * The provided buffer will be freed by dynbuf_free(). + */ +dynbuf_t * +dynbuf_new(unsigned char *buf, size_t sz) +{ + dynbuf_t *db; + + if (!(db = malloc(sizeof(dynbuf_t)))) + return NULL; + db->buf = buf; + db->sz = sz; + return db; +} + +/* + * Free dynbuf including internal buffer. + */ +void +dynbuf_free(dynbuf_t *db) +{ + free(db->buf); + free(db); +} + +/* vim: set noet ft=c: */ diff --git a/dynbuf.h b/dynbuf.h new file mode 100644 index 0000000..65d1e8a --- /dev/null +++ b/dynbuf.h @@ -0,0 +1,49 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DYNBUF_H +#define DYNBUF_H + +#include "attrib.h" + +#include + +typedef struct dynbuf { + unsigned char *buf; + size_t sz; +} dynbuf_t; + +dynbuf_t * dynbuf_new(unsigned char *, size_t) MALLOC; +dynbuf_t * dynbuf_new_alloc(size_t) MALLOC; +dynbuf_t * dynbuf_new_copy(const unsigned char *, const size_t) MALLOC; +dynbuf_t * dynbuf_new_file(const char *) MALLOC; +void dynbuf_free(dynbuf_t *) NONNULL(); + +#endif /* !DYNBUF_H */ + +/* vim: set noet ft=c: */ diff --git a/extra/pki/GNUmakefile b/extra/pki/GNUmakefile new file mode 100644 index 0000000..a996dc3 --- /dev/null +++ b/extra/pki/GNUmakefile @@ -0,0 +1,92 @@ +# inherited +VERSION?= unknown +OPENSSL?= openssl +MKDIR?= mkdir + +# OpenSSL settings +CA_SUBJECT?= '/O=SSLsplit Root CA/CN=SSLsplit Root CA/' +CA_DAYS?= 3650 +CONFIG:= x509v3ca.cnf +CONFIG_EXT:= v3_ca + +all: rsa dsa ec targets + +session: session.pem + +dh: dh512.param dh1024.param dh2048.param + +rsa: rsa.pem + +dsa: dsa.pem + +ec: ec.pem + +dh512.param: + $(OPENSSL) dhparam -out $@ -2 512 + +dh1024.param: + $(OPENSSL) dhparam -out $@ -2 1024 + +dh2048.param: + $(OPENSSL) dhparam -out $@ -2 2048 + +dsa.param: + $(OPENSSL) dsaparam -out $@ 1024 + +dsa.key: dsa.param + $(OPENSSL) gendsa -out $@ $< + +rsa.key: + $(OPENSSL) genrsa -out $@ 1024 + +ec.key: + $(OPENSSL) ecparam -out $@ -name prime192v1 -genkey + +%.crt: %.key + $(OPENSSL) req -new -nodes -x509 -sha1 -out $@ -key $< \ + -config $(CONFIG) -extensions $(CONFIG_EXT) \ + -subj $(CA_SUBJECT) \ + -set_serial 0 -days $(CA_DAYS) + +%.pem: %.crt %.key + cat $^ >$@ + +targets: rsa.crt + $(MKDIR) targets + $(OPENSSL) genrsa -out targets/daniel.roe.ch.key 1024 + $(OPENSSL) req -new -sha1 -subj '/C=CH/CN=daniel.roe.ch/' \ + -key targets/daniel.roe.ch.key \ + -out targets/daniel.roe.ch.csr + $(OPENSSL) x509 -req -sha1 -CAcreateserial -days 365 \ + -CA rsa.crt -CAkey rsa.key \ + -in targets/daniel.roe.ch.csr \ + -out targets/daniel.roe.ch.crt + cat targets/daniel.roe.ch.crt targets/daniel.roe.ch.key rsa.crt \ + >targets/daniel.roe.ch.pem + $(RM) targets/daniel.roe.ch.{key,csr,crt} + $(OPENSSL) genrsa -out targets/wildcard.roe.ch.key 1024 + $(OPENSSL) req -new -sha1 -subj '/C=CH/CN=*.roe.ch/' \ + -key targets/wildcard.roe.ch.key \ + -out targets/wildcard.roe.ch.csr + $(OPENSSL) x509 -req -sha1 -CAcreateserial -days 365 \ + -CA rsa.crt -CAkey rsa.key \ + -in targets/wildcard.roe.ch.csr \ + -out targets/wildcard.roe.ch.crt + cat targets/wildcard.roe.ch.crt targets/wildcard.roe.ch.key rsa.crt \ + >targets/wildcard.roe.ch.pem + $(RM) targets/wildcard.roe.ch.{key,csr,crt} rsa.srl + +# openssl s_server cannot be easily controlled from scripts; it is +# more robust to just connect to a real server to create a session +session.pem: + echo -e 'GET /test/SSLsplit-$(VERSION) HTTP/1.1\r\nHost:' \ + 'daniel.roe.ch\r\nConnection: close\r\n\r\n' | \ + $(OPENSSL) s_client -connect daniel.roe.ch:443 \ + -quiet -no_ign_eof -sess_out $@ >/dev/null 2>&1 + test -r $@ + +clean: + rm -rf rsa.* dsa.* ec.* dh*.param targets *.srl session.pem + +.PHONY: all clean rsa dsa ec dh dhall session + diff --git a/extra/pki/x509v3ca.cnf b/extra/pki/x509v3ca.cnf new file mode 100644 index 0000000..c299cf4 --- /dev/null +++ b/extra/pki/x509v3ca.cnf @@ -0,0 +1,9 @@ +[ req ] +distinguished_name = reqdn + +[ reqdn ] + +[ v3_ca ] +basicConstraints = CA:TRUE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always diff --git a/extra/sslsplit.sh.in b/extra/sslsplit.sh.in new file mode 100644 index 0000000..9bc5028 --- /dev/null +++ b/extra/sslsplit.sh.in @@ -0,0 +1,4 @@ +#!/bin/sh +ulimit -n @@maxfds@@ +export LD_LIBRARY_PATH=@@localbase@@/lib:"$LD_LIBRARY_PATH" +exec @@prefix@@/bin/sslsplit "$@" diff --git a/khash.h b/khash.h new file mode 100644 index 0000000..1a28e11 --- /dev/null +++ b/khash.h @@ -0,0 +1,548 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.6" + +#include +#include +#include + +/* compipler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifdef _MSC_VER +#define inline __inline +#endif + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#ifdef KHASH_LINEAR +#define __ac_inc(k, m) 1 +#else +#define __ac_inc(k, m) (((k)>>3 ^ (k)<<3) | 1) & (m) +#endif + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + SCOPE kh_##name##_t *kh_init_##name() { \ + return (kh_##name##_t*)calloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + free(h->keys); free(h->flags); \ + free(h->vals); \ + free(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t inc, k, i, last, mask; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + inc = __ac_inc(k, mask); last = i; /* inc==1 for linear probing */ \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + inc) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_bucktes bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)malloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + h->keys = (khkey_t*)realloc(h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)realloc(h->vals, new_n_buckets * sizeof(khval_t)); \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t inc, k, i; \ + k = __hash_func(key); \ + i = k & new_mask; \ + inc = __ac_inc(k, new_mask); \ + while (!__ac_isempty(new_flags, i)) i = (i + inc) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)realloc(h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)realloc(h->vals, new_n_buckets * sizeof(khval_t)); \ + } \ + free(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) kh_resize_##name(h, h->n_buckets - 1); /* clear "deleted" elements */ \ + else kh_resize_##name(h, h->n_buckets + 1); /* expand the hash table */ \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t inc, k, i, site, last, mask = h->n_buckets - 1; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + inc = __ac_inc(k, mask); last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + inc) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = *s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + *s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) is the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/* More conenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/log.c b/log.c new file mode 100644 index 0000000..b1388e2 --- /dev/null +++ b/log.c @@ -0,0 +1,474 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "log.h" + +#include "logger.h" +#include "sys.h" +#include "attrib.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Centralized logging code multiplexing thread access to the logger based + * logging in separate threads. Some log types are switchable to different + * backends, such as syslog and stderr. + */ + + +/* + * Error log. + * Switchable between stderr and syslog. + * Uses logger thread. + */ + +static logger_t *err_log = NULL; +static int err_started = 0; /* while 0, shortcut the thrqueue */ +static int err_mode = LOG_ERR_MODE_STDERR; + +static ssize_t +log_err_writecb(UNUSED int fd, const void *buf, size_t sz) +{ + switch (err_mode) { + case LOG_ERR_MODE_STDERR: + return fwrite(buf, sz - 1, 1, stderr); + case LOG_ERR_MODE_SYSLOG: + syslog(LOG_ERR, "%s", (const char *)buf); + return 0; + } + return -1; +} + +int +log_err_printf(const char *fmt, ...) +{ + va_list ap; + char *buf; + int rv; + + va_start(ap, fmt); + rv = vasprintf(&buf, fmt, ap); + va_end(ap); + if (rv == -1) + return -1; + if (err_started) { + return logger_write_freebuf(err_log, 0, buf, strlen(buf) + 1); + } else { + log_err_writecb(0, (unsigned char*)buf, strlen(buf) + 1); + free(buf); + } + return 0; +} + +void +log_err_mode(int mode) +{ + err_mode = mode; +} + + +/* + * Debug log. Redirects logging to error log. + * Switchable between error log or no logging. + * Uses the error log logger thread. + */ + +static int dbg_mode = LOG_DBG_MODE_NONE; + +int +log_dbg_write_free(void *buf, size_t sz) +{ + if (dbg_mode == LOG_DBG_MODE_NONE) + return 0; + + if (err_started) { + return logger_write_freebuf(err_log, 0, buf, sz); + } else { + log_err_writecb(0, buf, sz); + free(buf); + } + return 0; +} + +int +log_dbg_print_free(char *s) +{ + return log_dbg_write_free(s, strlen(s) + 1); +} + +int +log_dbg_printf(const char *fmt, ...) +{ + va_list ap; + char *buf; + int rv; + + if (dbg_mode == LOG_DBG_MODE_NONE) + return 0; + + va_start(ap, fmt); + rv = vasprintf(&buf, fmt, ap); + va_end(ap); + if (rv == -1) + return -1; + return log_dbg_print_free(buf); +} + +void +log_dbg_mode(int mode) +{ + dbg_mode = mode; +} + + +/* + * Connection log. Logs a one-liner to a file-based connection log. + * Uses a logger thread. + */ + +logger_t *connect_log = NULL; +static int connect_fd = -1; + +static int +log_connect_open(const char *logfile) +{ + connect_fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT, 0660); + if (connect_fd == -1) { + log_err_printf("Failed to open '%s' for writing: %s\n", + logfile, strerror(errno)); + return -1; + } + return 0; +} + +/* + * Do the actual write to the open connection log file descriptor. + * We prepend a timestamp here, which means that timestamps are slightly + * delayed from the time of actual logging. Since we only have second + * resolution that should not make any difference. + */ +static ssize_t +log_connect_writecb(UNUSED int fd, const void *buf, size_t sz) +{ + char timebuf[32]; + time_t epoch; + struct tm *utc; + size_t n; + + time(&epoch); + utc = gmtime(&epoch); + n = strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S UTC ", utc); + if (n == 0) { + log_err_printf("Error from strftime(): buffer too small\n"); + return -1; + } + if ((write(connect_fd, timebuf, n) == -1) || + (write(connect_fd, buf, sz) == -1)) { + log_err_printf("Warning: Failed to write to connect log: %s\n", + strerror(errno)); + } + return 0; +} + +static void +log_connect_close(void) +{ + close(connect_fd); +} + + +/* + * Content log. + * Logs connection content to either a single file or a directory containing + * per-connection logs. + * Uses a logger thread. + */ + +logger_t *content_log = NULL; +static int content_fd = -1; /* if set, we are in single file mode */ +static const char *content_basedir = NULL; + +static int +log_content_open_singlefile(const char *logfile) +{ + content_fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT, 0660); + if (content_fd == -1) { + log_err_printf("Failed to open '%s' for writing: %s\n", + logfile, strerror(errno)); + return -1; + } + return 0; +} + +static int +log_content_open_logdir(const char *basedir) +{ + content_basedir = basedir; + return 0; +} + +static void +log_content_close_singlefile(void) +{ + if (content_fd != -1) { + close(content_fd); + content_fd = -1; + } +} + +void +log_content_open(log_content_ctx_t *ctx, char *srcaddr, char *dstaddr) +{ + char filename[1024]; + char timebuf[24]; + time_t epoch; + struct tm *utc; + + if (ctx->open) + return; + + if (content_fd != -1) { + ctx->fd = content_fd; + asprintf(&ctx->header_in, "%s -> %s", srcaddr, dstaddr); + asprintf(&ctx->header_out, "%s -> %s", dstaddr, srcaddr); + } else { + time(&epoch); + utc = gmtime(&epoch); + strftime(timebuf, sizeof(timebuf), "%Y%m%dT%H%M%SZ", utc); + snprintf(filename, sizeof(filename), "%s/%s-%s-%s.log", + content_basedir, timebuf, srcaddr, dstaddr); + ctx->fd = open(filename, O_WRONLY|O_APPEND|O_CREAT, 0660); + if (ctx->fd == -1) { + log_err_printf("Failed to open '%s': %s\n", + filename, strerror(errno)); + } + } + ctx->open = 1; +} + +void +log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int direction) +{ + logbuf_t *head; + time_t epoch; + struct tm *utc; + char *header; + + if (!ctx->open) { + log_err_printf("log_content_submit called on closed ctx\n"); + return; + } + + if (!(header = direction ? ctx->header_out : ctx->header_in)) + goto out; + + /* prepend size tag and newline */ + head = logbuf_new_printf(lb->fd, lb, " (%zu):\n", logbuf_size(lb)); + if (!head) { + log_err_printf("Failed to allocate memory\n"); + logbuf_free(lb); + return; + } + lb = head; + + /* prepend header */ + head = logbuf_new_copy(header, strlen(header), lb->fd, lb); + if (!head) { + log_err_printf("Failed to allocate memory\n"); + logbuf_free(lb); + return; + } + lb = head; + + /* prepend timestamp */ + head = logbuf_new_alloc(32, lb->fd, lb); + if (!head) { + log_err_printf("Failed to allocate memory\n"); + logbuf_free(lb); + return; + } + lb = head; + time(&epoch); + utc = gmtime(&epoch); + lb->sz = strftime((char*)lb->buf, lb->sz, "%Y-%m-%d %H:%M:%S UTC ", + utc); + +out: + lb->fd = ctx->fd; + logger_submit(content_log, lb); +} + +void +log_content_close(log_content_ctx_t *ctx) +{ + if (!ctx->open) + return; + if (content_fd == -1) { + logger_write_freebuf(content_log, ctx->fd, NULL, 0); + } + if (ctx->header_in) { + free(ctx->header_in); + } + if (ctx->header_out) { + free(ctx->header_out); + } + ctx->open = 0; +} + +/* + * Do the actual write to the open connection log file descriptor. + * We prepend a timestamp here, which means that timestamps are slightly + * delayed from the time of actual logging. Since we only have second + * resolution that should not make any difference. + */ +static ssize_t +log_content_writecb(int fd, const void *buf, size_t sz) +{ + if (!buf) { + close(fd); + return 0; + } + + if (write(fd, buf, sz) == -1) { + log_err_printf("Warning: Failed to write to content log: %s\n", + strerror(errno)); + return -1; + } + return 0; +} + + +/* + * Initialization and destruction. + */ + +/* + * Log pre-init: open all log files but don't start any threads, since we may + * fork() after pre-initialization. + * Return -1 on errors, 0 otherwise. + */ +int +log_preinit(opts_t *opts) +{ + if (opts->contentlog) { + if (!opts->contentlogdir) { + if (log_content_open_singlefile(opts->contentlog) + == -1) + goto out; + } else { + if (log_content_open_logdir(opts->contentlog) == -1) + goto out; + } + if (!(content_log = logger_new(log_content_writecb))) { + log_content_close_singlefile(); + goto out; + } + } + if (opts->connectlog) { + if (log_connect_open(opts->connectlog) == -1) + goto out; + if (!(connect_log = logger_new(log_connect_writecb))) { + log_connect_close(); + goto out; + } + } + if (!(err_log = logger_new(log_err_writecb))) + goto out; + return 0; + +out: + if (content_log) { + log_content_close_singlefile(); + logger_free(content_log); + } + if (connect_log) { + log_connect_close(); + logger_free(connect_log); + } + return -1; +} + +/* + * Log post-init: start logging threads. + * Return -1 on errors, 0 otherwise. + */ +int +log_init(UNUSED opts_t *opts) +{ + if (err_log) + if (logger_start(err_log) == -1) + return -1; + err_started = 1; + if (connect_log) + if (logger_start(connect_log) == -1) + return -1; + if (content_log) + if (logger_start(content_log) == -1) + return -1; + return 0; +} + +/* + * Drain and cleanup. Tell all loggers to leave, then join all logger threads, + * and finally free resources and close log files. + */ +void +log_fini(void) +{ + if (content_log) + logger_leave(content_log); + if (connect_log) + logger_leave(connect_log); + logger_leave(err_log); + + if (content_log) + logger_join(content_log); + if (connect_log) + logger_join(connect_log); + logger_join(err_log); + + if (content_log) + logger_free(content_log); + if (connect_log) + logger_free(connect_log); + logger_free(err_log); + + if (content_log) + log_content_close_singlefile(); + if (connect_log) + log_connect_close(); +} + +/* vim: set noet ft=c: */ diff --git a/log.h b/log.h new file mode 100644 index 0000000..15b1376 --- /dev/null +++ b/log.h @@ -0,0 +1,77 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LOG_H +#define LOG_H + +#include "opts.h" +#include "logger.h" +#include "attrib.h" + +int log_err_printf(const char *, ...) PRINTF(1,2); +void log_err_mode(int); +#define LOG_ERR_MODE_STDERR 0 +#define LOG_ERR_MODE_SYSLOG 1 + +int log_dbg_printf(const char *, ...) PRINTF(1,2); +int log_dbg_print_free(char *); +int log_dbg_write_free(void *, size_t); +void log_dbg_mode(int); +#define LOG_DBG_MODE_NONE 0 +#define LOG_DBG_MODE_ERRLOG 1 + +extern logger_t *connect_log; +#define log_connect_printf(fmt, ...) \ + logger_printf(connect_log, -1, (fmt), __VA_ARGS__) +#define log_connect_print(s) \ + logger_print(connect_log, -1, (s)) +#define log_connect_write(buf, sz) \ + logger_write(connect_log, -1, (buf), (sz)) +#define log_connect_print_free(s) \ + logger_print_freebuf(connect_log, -1, (s)) +#define log_connect_write_free(buf, sz) \ + logger_write_freebuf(connect_log, -1, (buf), (sz)) + +typedef struct log_content_ctx { + int open; + char *basedir; + int fd; + char *header_in; + char *header_out; +} log_content_ctx_t; +void log_content_open(log_content_ctx_t *, char *, char *) NONNULL(); +void log_content_submit(log_content_ctx_t *, logbuf_t *, int) NONNULL(); +void log_content_close(log_content_ctx_t *) NONNULL(); + +int log_preinit(opts_t *) NONNULL(); +int log_init(opts_t *) NONNULL(); +void log_fini(void); + +#endif /* !LOG_H */ + +/* vim: set noet ft=c: */ diff --git a/logbuf.c b/logbuf.c new file mode 100644 index 0000000..c3f5b47 --- /dev/null +++ b/logbuf.c @@ -0,0 +1,180 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "logbuf.h" + +#include +#include +#include +#include + +/* + * Dynamic log buffer with zero-copy chaining and fd meta information. + * Logbuf always owns the internal allocated buffer. + */ + +/* + * Create new logbuf from provided, pre-allocated buffer, set fd and next. + * The provided buffer will be freed by logbuf_free() if non-NULL. + */ +logbuf_t * +logbuf_new(void *buf, size_t sz, int fd, logbuf_t *next) +{ + logbuf_t *lb; + + if (!(lb = malloc(sizeof(logbuf_t)))) + return NULL; + lb->buf = buf; + lb->sz = sz; + lb->fd = fd; + lb->next = next; + return lb; +} + +/* + * Create new logbuf, allocating sz bytes into the internal buffer. + */ +logbuf_t * +logbuf_new_alloc(size_t sz, int fd, logbuf_t *next) +{ + logbuf_t *lb; + + if (!(lb = malloc(sizeof(logbuf_t)))) + return NULL; + if (!(lb->buf = malloc(sz))) { + free(lb); + return NULL; + } + lb->sz = sz; + lb->fd = fd; + lb->next = next; + return lb; +} + +/* + * Create new logbuf, copying buf into a newly allocated internal buffer. + */ +logbuf_t * +logbuf_new_copy(const void *buf, size_t sz, int fd, logbuf_t *next) +{ + logbuf_t *lb; + + if (!(lb = malloc(sizeof(logbuf_t)))) + return NULL; + if (!(lb->buf = malloc(sz))) { + free(lb); + return NULL; + } + memcpy(lb->buf, buf, sz); + lb->sz = sz; + lb->fd = fd; + lb->next = next; + return lb; +} + +/* + * Create new logbuf using printf, setting fd and next. + */ +logbuf_t * +logbuf_new_printf(int fd, logbuf_t *next, const char *fmt, ...) +{ + va_list ap; + logbuf_t *lb; + + if (!(lb = malloc(sizeof(logbuf_t)))) + return NULL; + va_start(ap, fmt); + lb->sz = vasprintf((char**)&lb->buf, fmt, ap); + va_end(ap); + if (lb->sz == -1) { + free(lb); + return NULL; + } + lb->fd = fd; + lb->next = next; + return lb; +} + +/* + * Calculate the total size of the logbuf and all chained buffers. + */ +ssize_t +logbuf_size(logbuf_t *lb) +{ + ssize_t sz; + + sz = lb->sz; + if (lb->next) { + sz += logbuf_size(lb->next); + } + return sz; +} + +/* + * Write content of logbuf using writefunc and free all buffers. + * Returns -1 on errors and sets errno according to write(). + * Returns total of bytes written by 1 .. n write() calls on success. + */ +ssize_t +logbuf_write_free(logbuf_t *lb, writefunc_t writefunc) +{ + ssize_t rv1, rv2; + + rv1 = writefunc(lb->fd, lb->buf, lb->sz); + free(lb->buf); + if (lb->next) { + if (rv1 == -1) { + logbuf_free(lb->next); + } else { + lb->next->fd = lb->fd; + rv2 = logbuf_write_free(lb->next, writefunc); + } + } + free(lb); + if (rv1 == -1 || rv2 == -1) + return -1; + else + return rv1 + rv2; +} + +/* + * Free dynbuf including internal and chained buffers. + */ +void +logbuf_free(logbuf_t *lb) +{ + if (lb->buf) { + free(lb->buf); + } + if (lb->next) { + logbuf_free(lb->next); + } + free(lb); +} + +/* vim: set noet ft=c: */ diff --git a/logbuf.h b/logbuf.h new file mode 100644 index 0000000..68d124f --- /dev/null +++ b/logbuf.h @@ -0,0 +1,57 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LOGBUF_H +#define LOGBUF_H + +#include "attrib.h" + +#include +#include + +typedef struct logbuf { + unsigned char *buf; + ssize_t sz; + int fd; + struct logbuf *next; +} logbuf_t; + +typedef ssize_t (*writefunc_t)(int, const void *, size_t); + +logbuf_t * logbuf_new(void *, size_t, int, logbuf_t *) MALLOC; +logbuf_t * logbuf_new_alloc(size_t, int, logbuf_t *) MALLOC; +logbuf_t * logbuf_new_copy(const void *, size_t, int, logbuf_t *) MALLOC; +logbuf_t * logbuf_new_printf(int, logbuf_t *, const char *, ...) + MALLOC PRINTF(3,4); +ssize_t logbuf_size(logbuf_t *) NONNULL(); +ssize_t logbuf_write_free(logbuf_t *, writefunc_t) NONNULL(1); +void logbuf_free(logbuf_t *) NONNULL(); + +#endif /* !LOGBUF_H */ + +/* vim: set noet ft=c: */ diff --git a/logger.c b/logger.c new file mode 100644 index 0000000..d66d89f --- /dev/null +++ b/logger.c @@ -0,0 +1,229 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "logger.h" + +#include "thrqueue.h" +#include "logbuf.h" + +#include +#include +#include +#include + +/* + * Logger for multithreaded environments. Disk writes are executed in a + * writer thread. Logging threads submit buffers to be logged by adding + * them to the thrqueue. Logging threads may block on the pthread mutex + * of the thrqueue, but not on disk writes. + */ + +struct logger { + pthread_t thr; + logger_write_func_t write; + thrqueue_t *queue; +}; + +static void +logger_clear(logger_t *logger) +{ + memset(logger, 0, sizeof(logger_t)); +} + +/* + * Create new logger with a specific write function callback. + * The callback will be executed in the logger's writer thread, + * not in the thread calling logger_submit(). + */ +logger_t * +logger_new(logger_write_func_t writefunc) +{ + logger_t *logger; + + logger = malloc(sizeof(logger_t)); + if (!logger) + return NULL; + logger_clear(logger); + logger->write = writefunc; + logger->queue = thrqueue_new(1024); + return logger; +} + +/* + * Free the logger data structures. Caller must call logger_stop() + * or logger_leave() and logger_join() prior to freeing. + */ +void +logger_free(logger_t *logger) { + thrqueue_free(logger->queue); + free(logger); +} + +/* + * Submit a buffer to be logged by the logger thread. + * Buffer will be freed after logging completes. + * Returns -1 on error, 0 on success. + */ +int +logger_submit(logger_t *logger, logbuf_t *lb) +{ + thrqueue_enqueue(logger->queue, lb); + return 0; +} + +/* + * Logger thread main function. + */ +static void * +logger_thread(void *arg) +{ + logger_t *logger = arg; + logbuf_t *lb; + + while ((lb = thrqueue_dequeue(logger->queue))) { + logbuf_write_free(lb, logger->write); + } + + return NULL; +} + +/* + * Start the logger's write thread. + */ +int +logger_start(logger_t *logger) { + int rv; + + rv = pthread_create(&logger->thr, NULL, logger_thread, logger); + if (rv) + return -1; + return 0; +} + +/* + * Tell the logger's write thread to write all pending write requests + * and then exit. Don't wait for the logger to exit. + */ +void +logger_leave(logger_t *logger) { + thrqueue_unblock_dequeue(logger->queue); +} + +/* + * Wait for the logger to exit. + */ +int +logger_join(logger_t *logger) { + int rv; + + rv = pthread_join(logger->thr, NULL); + if (rv) + return -1; + return 0; +} + +/* + * Tell the logger's write thread to write all pending write requests + * and then exit; wait for the logger to exit. + */ +int +logger_stop(logger_t *logger) { + logger_leave(logger); + return logger_join(logger); +} + +/* + * Generic print to a logger. These functions should be called by the + * actual worker thread(s) doing network I/O. + * + * _printf(), _print() and _write() copy the input buffers. + * _ncprint() and _ncwrite() will free() the input buffers. + * + * The file descriptor argument is a virtual or real system file descriptor + * used for multiplexing write requests to several files over the same + * logger. This argument is passed to the write handler as-is and is not + * interpreted or used by the logger itself in any way. + * + * All of the functions return 0 on succes, -1 on failure. + */ +int +logger_printf(logger_t *logger, int fd, const char *fmt, ...) +{ + va_list ap; + logbuf_t *lb; + + lb = logbuf_new(NULL, 0, fd, NULL); + if (!lb) + return -1; + va_start(ap, fmt); + lb->sz = vasprintf((char**)&lb->buf, fmt, ap); + va_end(ap); + if (lb->sz == -1) { + logbuf_free(lb); + return -1; + } + return logger_submit(logger, lb); +} +int +logger_write(logger_t *logger, int fd, const void *buf, size_t sz) +{ + logbuf_t *lb; + + if (!(lb = logbuf_new_copy(buf, sz, fd, NULL))) + return -1; + return logger_submit(logger, lb); +} +int +logger_print(logger_t *logger, int fd, const char *s) +{ + logbuf_t *lb; + + if (!(lb = logbuf_new_copy(s, strlen(s), fd, NULL))) + return -1; + return logger_submit(logger, lb); +} +int +logger_write_freebuf(logger_t *logger, int fd, void *buf, size_t sz) +{ + logbuf_t *lb; + + if (!(lb = logbuf_new(buf, sz, fd, NULL))) + return -1; + return logger_submit(logger, lb); +} +int +logger_print_freebuf(logger_t *logger, int fd, char *s) +{ + logbuf_t *lb; + + if (!(lb = logbuf_new(s, strlen(s), fd, NULL))) + return -1; + return logger_submit(logger, lb); +} + +/* vim: set noet ft=c: */ diff --git a/logger.h b/logger.h new file mode 100644 index 0000000..956ddea --- /dev/null +++ b/logger.h @@ -0,0 +1,56 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include "logbuf.h" +#include "attrib.h" + +#include +#include + +typedef ssize_t (*logger_write_func_t)(int, const void *, size_t); +typedef struct logger logger_t; + +logger_t * logger_new(logger_write_func_t) NONNULL(); +void logger_free(logger_t *) NONNULL(); +int logger_start(logger_t *) NONNULL(); +void logger_leave(logger_t *) NONNULL(); +int logger_join(logger_t *) NONNULL(); +int logger_stop(logger_t *) NONNULL(); +int logger_submit(logger_t *, logbuf_t *) NONNULL(); +int logger_printf(logger_t *, int, const char *, ...) PRINTF(3,4) NONNULL(1,3); +int logger_print(logger_t *, int, const char *) NONNULL(); +int logger_write(logger_t *, int, const void *, size_t) NONNULL(); +int logger_print_freebuf(logger_t *, int, char *) NONNULL(); +int logger_write_freebuf(logger_t *, int, void *, size_t) NONNULL(1); + +#endif /* !LOGGER_H */ + +/* vim: set noet ft=c: */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..2fcc4f2 --- /dev/null +++ b/main.c @@ -0,0 +1,634 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* silence daemon(3) deprecation warning on Mac OS X */ +#if __APPLE__ +#define daemon xdaemon +#endif /* __APPLE__ */ + +#include "opts.h" +#include "proxy.h" +#include "ssl.h" +#include "nat.h" +#include "cachemgr.h" +#include "sys.h" +#include "log.h" + +#include +#include +#include +#include +#include + +#ifndef __BSD__ +#include +#endif /* !__BSD__ */ + +#include + +#include +#include + +#if __APPLE__ +#undef daemon +extern int daemon(int, int); +#endif /* __APPLE__ */ + +/* + * Print version information to stderr. + */ +static void +main_version(void) +{ + fprintf(stderr, "%s %s (built %s)\n", PNAME, VERSION, BUILD_DATE); + fprintf(stderr, "Copyright (c) 2009-2012, " + "Daniel Roethlisberger \n"); + fprintf(stderr, "http://www.roe.ch/SSLsplit\n"); + if (FEATURES[0]) { + fprintf(stderr, "Features: %s\n", FEATURES); + } + nat_version(); + ssl_openssl_version(); + fprintf(stderr, "compiled against libevent %s\n", LIBEVENT_VERSION); + fprintf(stderr, "rtlinked against libevent %s\n", event_get_version()); + fprintf(stderr, "%d CPU cores detected\n", sys_get_cpu_cores()); +} + +/* + * Print usage to stderr. + */ +static void +main_usage(void) +{ + const char *dflt, *warn; + + if (!(dflt = nat_getdefaultname())) { + dflt = "n/a"; + warn = "\nWarning: no supported NAT engine on this platform!\n" + "Only static and SNI proxyspecs are supported.\n"; + } else { + warn = ""; + } + + fprintf(stderr, +"Usage: %s [options...] [proxyspecs...]\n" +" -k pemfile use CA key from pemfile to sign forged certs\n" +" -c pemfile use CA cert from pemfile to sign forged certs\n" +" -C pemfile use CA chain from pemfile (intermediate and root CA certs)\n" +" -K pemfile use key from pemfile for leaf certs (default: generate)\n" +" -t certdir use cert+chain+key PEM files from certdir to target all sites\n" +" matching the common names (non-matching: generate if CA)\n" +" -P passthrough SSL connections if they cannot be split because of\n" +" client cert auth or no matching cert and no CA (default: drop)\n" +#ifndef OPENSSL_NO_DH +" -g pemfile use DH group params from pemfile (default: keyfiles or auto)\n" +#define OPT_g "g:" +#else /* OPENSSL_NO_DH */ +#define OPT_g +#endif /* !OPENSSL_NO_DH */ +#ifndef OPENSSL_NO_ECDH +" -G curve use ECDH named curve (default: %s for non-RSA leafkey)\n" +#define OPT_G "G:" +#else /* OPENSSL_NO_ECDH */ +#define OPT_G +#endif /* OPENSSL_NO_ECDH */ +#ifdef SSL_OP_NO_COMPRESSION +" -Z disable SSL/TLS compression on all connections\n" +#define OPT_Z "Z" +#else /* !SSL_OP_NO_COMPRESSION */ +#define OPT_Z +#endif /* !SSL_OP_NO_COMPRESSION */ +" -s ciphers use the given OpenSSL cipher suite spec (default: ALL)\n" +" -e engine specify default NAT engine to use (default: %s)\n" +" -E list available NAT engines and exit\n" +" -u user drop privileges to user (default if run as root: nobody)\n" +" -j jaildir chroot() to jaildir (default if run as root: /var/empty)\n" +" -p pidfile write pid to pidfile (default: no pid file)\n" +" -l logfile connect log: log one line summary per connection to logfile\n" +" -L logfile content log: full data to file or named pipe (excludes -S)\n" +" -S logdir content log: full data to separate files in dir (excludes -L)\n" +" -d daemon mode: run in background, log error messages to syslog\n" +" -D debug mode: run in foreground, log debug messages on stderr\n" +" -V print version information and exit\n" +" -h print usage information and exit\n" +" proxyspec = type listenaddr+port [natengine|targetaddr+port|\"sni\"+port]\n" +" e.g. http 0.0.0.0 8080 www.roe.ch 80 # http/4; static hostname dst\n" +" https ::1 8443 2001:db8::1 443 # https/6; static address dst\n" +" https 127.0.0.1 9443 sni 443 # https/4; SNI DNS lookups\n" +" tcp 127.0.0.1 10025 # tcp/4; default NAT engine\n" +" ssl 2001:db8::2 9999 pf # ssl/6; NAT engine 'pf'\n" +"Example:\n" +" %s -k ca.key -c ca.pem -P https 127.0.0.1 8443 https ::1 8443\n" + "%s", BNAME, +#ifndef OPENSSL_NO_ECDH + SSL_EC_KEY_CURVE_DEFAULT, +#endif /* !OPENSSL_NO_ECDH */ + dflt, BNAME, warn); +} + +/* + * Callback to load a cert/chain/key combo from a single PEM file. + */ +static void +main_loadtgcrt(const char *filename, void *arg) +{ + void **args = arg; + const char *argv0 = args[0]; + opts_t *opts = args[1]; + cert_t *cert; + char **names; + + cert = cert_new_load(filename); + if (!cert) { + fprintf(stderr, "%s: error loading cert and key from PEM file " + "'%s'\n", argv0, filename); + exit(EXIT_FAILURE); + } + if (X509_check_private_key(cert->crt, cert->key) != 1) { + fprintf(stderr, "%s: cert does not match key in PEM file " + "'%s':\n", argv0, filename); + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + +#ifdef DEBUG_CERTIFICATE + log_dbg_printf("Loaded '%s':\n", filename); + log_dbg_print_free(ssl_x509_to_str(cert->crt)); + log_dbg_print_free(ssl_x509_to_pem(cert->crt)); +#endif /* DEBUG_CERTIFICATE */ + + if (opts->debug) { + log_dbg_printf("Targets for '%s':", filename); + } + names = ssl_x509_names(cert->crt); + for (char **p = names; *p; p++) { + /* be deliberately vulnerable to NULL prefix attacks */ + char *sep; + if ((sep = strchr(*p, '!'))) { + *sep = '\0'; + } + if (opts->debug) { + log_dbg_printf(" '%s'", *p); + } + cachemgr_tgcrt_set(*p, cert); + free(*p); + } + if (opts->debug) { + log_dbg_printf("\n"); + } + free(names); + cert_free(cert); +} + +/* + * Main entry point. + */ +int +main(int argc, char *argv[]) +{ + const char *argv0; + int ch; + opts_t *opts; + char *natengine; + int pidfd = -1; + + argv0 = argv[0]; + opts = opts_new(); + natengine = strdup(nat_getdefaultname()); + + while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z + "k:c:C:K:t:Ps:e:Eu:j:p:l:L:S:dDVh")) != -1) { + switch (ch) { + case 'k': + if (opts->cakey) + EVP_PKEY_free(opts->cakey); + opts->cakey = ssl_key_load(optarg); + if (!opts->cakey) { + fprintf(stderr, "%s: error loading CA " + "key from '%s':\n", + argv0, optarg); + if (errno) { + fprintf(stderr, "%s\n", + strerror(errno)); + } else { + ERR_print_errors_fp(stderr); + } + exit(EXIT_FAILURE); + } + if (!opts->cacrt) { + opts->cacrt = ssl_x509_load(optarg); + } +#ifndef OPENSSL_NO_DH + if (!opts->dh) { + opts->dh = ssl_dh_load(optarg); + } +#endif /* !OPENSSL_NO_DH */ + break; + case 'c': + if (opts->cacrt) + X509_free(opts->cacrt); + opts->cacrt = ssl_x509_load(optarg); + if (!opts->cacrt) { + fprintf(stderr, "%s: error loading CA " + "cert from '%s':\n", + argv0, optarg); + if (errno) { + fprintf(stderr, "%s\n", + strerror(errno)); + } else { + ERR_print_errors_fp(stderr); + } + exit(EXIT_FAILURE); + } + if (!opts->cakey) { + opts->cakey = ssl_key_load(optarg); + } + ssl_x509_refcount_inc(opts->cacrt); + sk_X509_insert(opts->chain, opts->cacrt, 0); + break; + case 'C': + if (ssl_x509chain_load(NULL, &opts->chain, + optarg) == -1) { + fprintf(stderr, "%s: error loading " + "chain from '%s':\n", + argv0, optarg); + if (errno) { + fprintf(stderr, "%s\n", + strerror(errno)); + } else { + ERR_print_errors_fp(stderr); + } + exit(EXIT_FAILURE); + } + break; + case 'K': + if (opts->key) + EVP_PKEY_free(opts->key); + opts->key = ssl_key_load(optarg); + if (!opts->key) { + fprintf(stderr, "%s: error loading lea" + "f key from '%s':\n", + argv0, optarg); + if (errno) { + fprintf(stderr, "%s\n", + strerror(errno)); + } else { + ERR_print_errors_fp(stderr); + } + exit(EXIT_FAILURE); + } +#ifndef OPENSSL_NO_DH + if (!opts->dh) { + opts->dh = ssl_dh_load(optarg); + } +#endif /* !OPENSSL_NO_DH */ + break; + case 't': + if (!sys_isdir(optarg)) { + fprintf(stderr, "%s: '%s' is not a " + "directory\n", + argv0, optarg); + exit(EXIT_FAILURE); + } + if (opts->tgcrtdir) + free(opts->tgcrtdir); + opts->tgcrtdir = strdup(optarg); + break; + case 'P': + opts->passthrough = 1; + break; +#ifndef OPENSSL_NO_DH + case 'g': + if (opts->dh) + DH_free(opts->dh); + opts->dh = ssl_dh_load(optarg); + if (!opts->dh) { + fprintf(stderr, "%s: error loading DH " + "params from '%s':\n", + argv0, optarg); + if (errno) { + fprintf(stderr, "%s\n", + strerror(errno)); + } else { + ERR_print_errors_fp(stderr); + } + exit(EXIT_FAILURE); + } + break; +#endif /* !OPENSSL_NO_DH */ +#ifndef OPENSSL_NO_ECDH + case 'G': + { + EC_KEY *ecdh; + if (opts->ecdhcurve) + free(opts->ecdhcurve); + if (!(ecdh = ssl_ecdh_by_name(optarg))) { + fprintf(stderr, "%s: unknown curve " + "'%s'\n", + argv0, optarg); + exit(EXIT_FAILURE); + } + EC_KEY_free(ecdh); + opts->ecdhcurve = strdup(optarg); + break; + } +#endif /* !OPENSSL_NO_ECDH */ +#ifdef SSL_OP_NO_COMPRESSION + case 'Z': + opts->sslcomp = 0; + break; +#endif /* SSL_OP_NO_COMPRESSION */ + case 's': + if (opts->ciphers) + free(opts->ciphers); + opts->ciphers = strdup(optarg); + break; + case 'e': + free(natengine); + natengine = strdup(optarg); + break; + case 'E': + nat_list_engines(); + exit(EXIT_SUCCESS); + break; + case 'u': + if (opts->dropuser) + free(opts->dropuser); + opts->dropuser = strdup(optarg); + break; + case 'p': + if (opts->pidfile) + free(opts->pidfile); + opts->pidfile = strdup(optarg); + break; + case 'j': + if (opts->jaildir) + free(opts->jaildir); + opts->jaildir = strdup(optarg); + break; + case 'l': + if (opts->connectlog) + free(opts->connectlog); + opts->connectlog = strdup(optarg); + break; + case 'L': + if (opts->contentlog) + free(opts->contentlog); + opts->contentlog = strdup(optarg); + opts->contentlogdir = 0; + break; + case 'S': + if (opts->contentlog) + free(opts->contentlog); + opts->contentlog = strdup(optarg); + opts->contentlogdir = 1; + break; + case 'd': + opts->detach = 1; + break; + case 'D': + log_dbg_mode(LOG_DBG_MODE_ERRLOG); + opts->debug = 1; + break; + case 'V': + main_version(); + exit(EXIT_SUCCESS); + case 'h': + main_usage(); + exit(EXIT_SUCCESS); + case '?': + exit(EXIT_FAILURE); + default: + main_usage(); + exit(EXIT_FAILURE); + } + } + argc -= optind; + argv += optind; + opts->spec = proxyspec_parse(&argc, &argv, natengine); + + /* usage checks */ + if (opts->detach && opts->debug) { + fprintf(stderr, "%s: -d and -D are mutually exclusive.\n", + argv0); + exit(EXIT_FAILURE); + } + if (!opts->spec) { + fprintf(stderr, "%s: no proxyspec specified.\n", argv0); + exit(EXIT_FAILURE); + } + for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) { + if (spec->connect_addrlen || spec->sni_port) + continue; + if (!spec->natengine) { + fprintf(stderr, "%s: no supported NAT engines " + "on this platform.\n" + "Only static addr and SNI proxyspecs " + "supported.\n", argv0); + exit(EXIT_FAILURE); + } + if (spec->listen_addr.ss_family == AF_INET6 && + !nat_ipv6ready(spec->natengine)) { + fprintf(stderr, "%s: IPv6 not supported by '%s'\n", + argv0, spec->natengine); + exit(EXIT_FAILURE); + } + spec->natlookup = nat_getlookupcb(spec->natengine); + spec->natsocket = nat_getsocketcb(spec->natengine); + } + if (opts_has_ssl_spec(opts)) { + if ((opts->cacrt || !opts->tgcrtdir) && !opts->cakey) { + fprintf(stderr, "%s: no CA key specified (-k).\n", + argv0); + exit(EXIT_FAILURE); + } + if (opts->cakey && !opts->cacrt) { + fprintf(stderr, "%s: no CA cert specified (-c).\n", + argv0); + exit(EXIT_FAILURE); + } + if (opts->cakey && opts->cacrt && + (X509_check_private_key(opts->cacrt, opts->cakey) != 1)) { + fprintf(stderr, "%s: CA cert does not match key.\n", + argv0); + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + } + + /* dynamic defaults */ + if (!opts->ciphers) { + opts->ciphers = strdup("ALL"); + if (!opts->ciphers) { + fprintf(stderr, "%s: out of memory.\n", argv0); + exit(EXIT_FAILURE); + } + } + if (!opts->jaildir && (geteuid() == 0) && !opts->contentlogdir) { + opts->jaildir = strdup("/var/empty"); + } + if (!opts->dropuser && !geteuid() && !getuid() && + !opts->contentlogdir) { + opts->dropuser = strdup("nobody"); + } + + /* debugging */ + if (opts->debug) { + main_version(); + log_dbg_printf("proxyspecs:\n"); + for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) { + char *lbuf, *cbuf = NULL; + lbuf = sys_sockaddr_str((struct sockaddr *) + &spec->listen_addr, + spec->listen_addrlen); + if (spec->connect_addrlen) { + cbuf = sys_sockaddr_str((struct sockaddr *) + &spec->connect_addr, + spec->connect_addrlen); + } + if (spec->sni_port) { + asprintf(&cbuf, "sni %i", spec->sni_port); + } + log_dbg_printf("- %s %s %s %s\n", lbuf, + (spec->ssl ? "ssl" : "tcp"), + (spec->http ? "http" : "plain"), + (spec->natengine ? spec->natengine + : cbuf)); + if (lbuf) + free(lbuf); + if (cbuf) + free(cbuf); + } + } + + /* prevent multiple instances running */ + if (opts->pidfile) { + pidfd = sys_pidf_open(opts->pidfile); + if (pidfd == -1) { + fprintf(stderr, "%s: cannot open PID file '%s' " + "- process already running?\n", + argv0, opts->pidfile); + exit(EXIT_FAILURE); + } + } + + /* + * Initialize as much as possible before daemon() in order to be + * able to provide direct feedback to the user when failing. + */ + if (cachemgr_init() == -1) { + fprintf(stderr, "%s: failed to init cachemgr.\n", argv0); + exit(EXIT_FAILURE); + } + if (opts->debug) { + if (opts->cacrt) { + char *subj = ssl_x509_subject(opts->cacrt); + log_dbg_printf("Loaded CA: '%s'\n", subj); + free(subj); +#ifdef DEBUG_CERTIFICATE + log_dbg_print_free(ssl_x509_to_str(opts->cacrt)); + log_dbg_print_free(ssl_x509_to_pem(opts->cacrt)); +#endif /* DEBUG_CERTIFICATE */ + } else { + log_dbg_printf("No CA loaded.\n"); + } + } + if (opts_has_ssl_spec(opts) && !opts->key) { + opts->key = ssl_key_genrsa(1024); + if (!opts->key) { + fprintf(stderr, "%s: error generating RSA key:\n", + argv0); + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + if (opts->debug) { + log_dbg_printf("Generated RSA key for leaf certs.\n"); + } + } + if (opts->tgcrtdir) { + const void *arg[2]; + arg[0] = argv0; + arg[1] = opts; + sys_dir_eachfile(opts->tgcrtdir, main_loadtgcrt, arg); + } + if (log_preinit(opts) == -1) { + fprintf(stderr, "%s: failed to preinit logging.\n", argv0); + exit(EXIT_FAILURE); + } + if (nat_preinit() == -1) { + fprintf(stderr, "%s: failed to preinit NAT lookup.\n", argv0); + exit(EXIT_FAILURE); + } + proxy_ctx_t *proxy = proxy_new(opts); + if (!proxy) { + fprintf(stderr, "%s: error initializing proxy.\n", argv0); + exit(EXIT_FAILURE); + } + + /* Drop privs, chroot, detach from TTY */ + if (sys_privdrop(opts->dropuser, opts->jaildir) == -1) { + fprintf(stderr, "%s: failed to drop privileges: %s\n", + argv0, strerror(errno)); + exit(EXIT_FAILURE); + } + if (opts->detach) { + if (daemon(1, 0) == -1) { + fprintf(stderr, "%s: failed to detach from TTY: %s\n", + argv0, strerror(errno)); + exit(EXIT_FAILURE); + } + log_err_mode(LOG_ERR_MODE_SYSLOG); + } + if (opts->pidfile) { + sys_pidf_write(pidfd); + } + + /* Post-privdrop/chroot/detach initialization, thread spawning */ + if (log_init(opts) == -1) { + log_err_printf("Failed to init log facility.\n"); + exit(EXIT_FAILURE); + } + if (nat_init() == -1) { + log_err_printf("Failed to init NAT state table lookup.\n"); + exit(EXIT_FAILURE); + } + + proxy_run(proxy); + proxy_free(proxy); + cachemgr_fini(); + nat_fini(); + log_fini(); + if (opts->pidfile) { + sys_pidf_close(pidfd, opts->pidfile); + } + opts_free(opts); + ssl_fini(); + return EXIT_SUCCESS; +} + +/* vim: set noet ft=c: */ diff --git a/main.t b/main.t new file mode 100644 index 0000000..db23529 --- /dev/null +++ b/main.t @@ -0,0 +1,102 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "attrib.h" +#include "opts.h" + +#include +#include +#include + +Suite * +blank_suite(void) +{ + Suite *s; + s = suite_create(""); + return s; +} + +START_TEST(build_date_01) +{ + char *bd = BUILD_DATE; + + fail_unless(strlen(bd) == 10, "length mismatch"); + fail_unless(bd[4] == '-', "year/month separator not dash"); + fail_unless(bd[7] == '-', "month/day separator not dash"); +} +END_TEST + +Suite * +main_suite(void) +{ + Suite *s; + TCase *tc; + s = suite_create("main"); + + tc = tcase_create("build_date"); + tcase_add_test(tc, build_date_01); + suite_add_tcase(s, tc); + + return s; +} + +Suite * opts_suite(void); +Suite * cert_suite(void); +Suite * cachemgr_suite(void); +Suite * cachefkcrt_suite(void); +Suite * cachetgcrt_suite(void); +Suite * cachedsess_suite(void); +Suite * cachessess_suite(void); +Suite * ssl_suite(void); +Suite * sys_suite(void); + +int +main(UNUSED int argc, UNUSED char *argv[]) +{ + int nfail; + SRunner *sr; + + sr = srunner_create(blank_suite()); + srunner_add_suite(sr, main_suite()); + srunner_add_suite(sr, opts_suite()); + srunner_add_suite(sr, cert_suite()); + srunner_add_suite(sr, cachemgr_suite()); + srunner_add_suite(sr, cachefkcrt_suite()); + srunner_add_suite(sr, cachetgcrt_suite()); + srunner_add_suite(sr, cachedsess_suite()); + srunner_add_suite(sr, cachessess_suite()); + srunner_add_suite(sr, ssl_suite()); + srunner_add_suite(sr, sys_suite()); + srunner_run_all(sr, CK_NORMAL); + nfail = srunner_ntests_failed(sr); + srunner_free(sr); + + return !nfail ? EXIT_SUCCESS : EXIT_FAILURE; +} + +/* vim: set noet ft=c: */ diff --git a/nat.c b/nat.c new file mode 100644 index 0000000..1be6665 --- /dev/null +++ b/nat.c @@ -0,0 +1,629 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "nat.h" + +#include "log.h" +#include "attrib.h" + +#include +#include +#include +#include + +#ifdef HAVE_PF +#include +#include +#include +#include +#include +#include +#include +#include +#endif /* HAVE_PF */ + +#ifdef HAVE_IPFILTER +#include +#include +#include +#include +#include +#include +#include +#endif /* HAVE_IPFILTER */ + +#ifdef HAVE_NETFILTER +#include +#include +#include +#endif /* HAVE_NETFILTER */ + +/* + * Access NAT state tables in a NAT engine independant way. + * Adding support for additional NAT engines should require only + * changes in this file. + */ + + +/* + * pf + */ + +#ifdef HAVE_PF +static int nat_pf_fd = -1; + +static int +nat_pf_preinit(void) +{ + nat_pf_fd = open("/dev/pf", O_RDONLY); + if (nat_pf_fd < 0) { + log_err_printf("Error opening '/dev/pf': %s\n", + strerror(errno)); + return -1; + } + return 0; +} + +static int +nat_pf_init(void) +{ + int rv; + + rv = fcntl(nat_pf_fd, F_SETFD, fcntl(nat_pf_fd, F_GETFD) | FD_CLOEXEC); + if (rv == -1) { + log_err_printf("Error setting FD_CLOEXEC on '/dev/pf': %s\n", + strerror(errno)); + return -1; + } + return 0; +} + +static void +nat_pf_fini(void) +{ + close(nat_pf_fd); +} + +static int +nat_pf_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen, + evutil_socket_t s, + struct sockaddr *src_addr, UNUSED socklen_t src_addrlen) +{ + struct sockaddr_storage our_addr; + socklen_t our_addrlen; + struct pfioc_natlook nl; + + our_addrlen = sizeof(struct sockaddr_storage); + if (getsockname(s, (struct sockaddr *)&our_addr, &our_addrlen) == -1) { + log_err_printf("Error from getsockname(): %s\n", + strerror(errno)); + return -1; + } + + memset(&nl, 0, sizeof(struct pfioc_natlook)); + nl.af = src_addr->sa_family; + if (nl.af == AF_INET) { + struct sockaddr_in *src_sai = (struct sockaddr_in *)src_addr; + struct sockaddr_in *our_sai = (struct sockaddr_in *)&our_addr; + nl.saddr.v4.s_addr = src_sai->sin_addr.s_addr; + nl.sport = src_sai->sin_port; + nl.daddr.v4.s_addr = our_sai->sin_addr.s_addr; + nl.dport = our_sai->sin_port; + } + if (nl.af == AF_INET6) { + struct sockaddr_in6 *src_sai = (struct sockaddr_in6 *)src_addr; + struct sockaddr_in6 *our_sai = (struct sockaddr_in6 *)&our_addr; + memcpy(&nl.saddr.v6.s6_addr, &src_sai->sin6_addr.s6_addr, 16); + nl.sport = src_sai->sin6_port; + memcpy(&nl.daddr.v6.s6_addr, &our_sai->sin6_addr.s6_addr, 16); + nl.dport = our_sai->sin6_port; + } + nl.proto = IPPROTO_TCP; + nl.direction = PF_OUT; + + if (ioctl(nat_pf_fd, DIOCNATLOOK, &nl)) { + if (errno != ENOENT) { + log_err_printf("Error from ioctl(DIOCNATLOOK): %s\n", + strerror(errno)); + } + return -1; + } + + if ((nl.dport == nl.rdport) && + ((nl.af == AF_INET && nl.daddr.v4.s_addr == nl.rdaddr.v4.s_addr) || + (nl.af == AF_INET6 && + !memcmp(nl.daddr.v6.s6_addr, nl.rdaddr.v6.s6_addr, 16)))) { + /* no destination address/port translation in place */ + return -1; + } + + /* copy original destination address */ + if (nl.af == AF_INET) { + struct sockaddr_in *dst_sai = (struct sockaddr_in *)dst_addr; + memset(dst_sai, 0, sizeof(struct sockaddr_in)); + dst_sai->sin_addr.s_addr = nl.rdaddr.v4.s_addr; + dst_sai->sin_port = nl.rdport; + dst_sai->sin_family = nl.af; + *dst_addrlen = sizeof(struct sockaddr_in); + } + if (nl.af == AF_INET6) { + struct sockaddr_in6 *dst_sai = (struct sockaddr_in6 *)dst_addr; + memset(dst_sai, 0, sizeof(struct sockaddr_in6)); + memcpy(dst_sai->sin6_addr.s6_addr, nl.rdaddr.v6.s6_addr, 16); + dst_sai->sin6_port = nl.rdport; + dst_sai->sin6_family = nl.af; + *dst_addrlen = sizeof(struct sockaddr_in6); + } + return 0; +} +#endif /* HAVE_PF */ + + +/* + * ipfilter + */ + +#ifdef HAVE_IPFILTER +static int nat_ipfilter_fd = -1; + +static int +nat_ipfilter_preinit(void) +{ + nat_ipfilter_fd = open(IPNAT_NAME, O_RDONLY); + if (nat_ipfilter_fd < 0) { + log_err_printf("Error opening '%s': %s\n", + IPNAT_NAME, strerror(errno)); + return -1; + } + return 0; +} + +static int +nat_ipfilter_init(void) +{ + int rv; + + rv = fcntl(nat_ipfilter_fd, F_SETFD, + fcntl(nat_ipfilter_fd, F_GETFD) | FD_CLOEXEC); + if (rv == -1) { + log_err_printf("Error setting FD_CLOEXEC on '%s': %s\n", + IPNAT_NAME, strerror(errno)); + return -1; + } + return 0; +} + +static void +nat_ipfilter_fini(void) +{ + close(nat_ipfilter_fd); +} + +static int +nat_ipfilter_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen, + evutil_socket_t s, + struct sockaddr *src_addr, UNUSED socklen_t src_addrlen) +{ + struct sockaddr_storage our_addr; + socklen_t our_addrlen; + struct natlookup nl; + struct ipfobj ipfo; + + our_addrlen = sizeof(struct sockaddr_storage); + if (getsockname(s, (struct sockaddr *)&our_addr, &our_addrlen) == -1) { + log_err_printf("Error from getsockname(): %s\n", + strerror(errno)); + return -1; + } + + memset(&nl, 0, sizeof(struct natlookup)); + if (src_addr->sa_family == AF_INET) { + struct sockaddr_in *src_sai = (struct sockaddr_in *)src_addr; + struct sockaddr_in *our_sai = (struct sockaddr_in *)&our_addr; + nl.nl_outip.s_addr = src_sai->sin_addr.s_addr; + nl.nl_outport = src_sai->sin_port; + nl.nl_inip.s_addr = our_sai->sin_addr.s_addr; + nl.nl_inport = our_sai->sin_port; + } else { + log_err_printf("The ipfilter NAT engine does not " + "support IPv6 state lookups\n"); + return -1; + } + nl.nl_flags = IPN_TCP; + + /* assuming IPv4 from here */ + + memset(&ipfo, 0, sizeof(struct ipfobj)); + ipfo.ipfo_rev = IPFILTER_VERSION; + ipfo.ipfo_size = sizeof(struct natlookup); + ipfo.ipfo_ptr = &nl; + ipfo.ipfo_type = IPFOBJ_NATLOOKUP; + + if (ioctl(nat_ipfilter_fd, SIOCGNATL, &ipfo) == -1) { + if (errno != ESRCH) { + log_err_printf("Error from ioctl(SIOCGNATL): %s\n", + strerror(errno)); + } + return -1; + } + + if ((nl.nl_inport == nl.nl_realport) && + (nl.nl_inip.s_addr == nl.nl_realip.s_addr)) { + /* no destination address/port translation in place */ + return -1; + } + + /* copy original destination address */ + struct sockaddr_in *dst_sai = (struct sockaddr_in *)dst_addr; + memset(dst_sai, 0, sizeof(struct sockaddr_in)); + dst_sai->sin_addr.s_addr = nl.nl_realip.s_addr; + dst_sai->sin_port = nl.nl_realport; + dst_sai->sin_family = AF_INET; + *dst_addrlen = sizeof(struct sockaddr_in); + return 0; +} +#endif /* HAVE_IPFILTER */ + + +/* + * netfilter, tproxy + */ + +#ifdef HAVE_NETFILTER +/* + * It seems that SO_ORIGINAL_DST only works for IPv4 and that there + * is no IPv6 equivalent yet. Someone please port pf to Linux... + * We try to do something sensible for IPv6 anyway, expect it to fail. + * + * http://lists.netfilter.org/pipermail/netfilter/2007-July/069259.html + * + * It looks like TPROXY is the only way to go on Linux with IPv6. + */ +#ifndef IPV6_ORIGINAL_DST +#define IPV6_ORIGINAL_DST SO_ORIGINAL_DST +#endif /* !IPV6_ORIGINAL_DST */ +#ifndef SOL_IPV6 +#define SOL_IPV6 SOL_IP +#endif /* !SOL_IPV6 */ +static int +nat_netfilter_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen, + evutil_socket_t s, + struct sockaddr *src_addr, + UNUSED socklen_t src_addrlen) +{ + int rv; + + if (src_addr->sa_family == AF_INET6) { + rv = getsockopt(s, SOL_IPV6, IPV6_ORIGINAL_DST, + dst_addr, dst_addrlen); + } else { + rv = getsockopt(s, SOL_IP, SO_ORIGINAL_DST, + dst_addr, dst_addrlen); + } + if (rv == -1) { + log_err_printf("Error from getsockopt(SO_ORIGINAL_DST): %s\n", + strerror(errno)); + if (errno == ENOPROTOOPT) { + log_err_printf("Looks like your Linux kernel doesn't " + "support SO_ORIGINAL_DST for IPv6.\n"); + } + } + return rv; +} + +#ifdef IP_TRANSPARENT +/* + * Set the listening socket IP_TRANSPARENT. This makes the Linux IP routing + * stack omit the source address checks on output, which is needed for + * Linux TPROXY transparent proxying support. + */ +static int +nat_iptransparent_socket_cb(evutil_socket_t s) +{ + int on = 1; + int rv; + + rv = setsockopt(s, SOL_IP, IP_TRANSPARENT, (void*)&on, sizeof(on)); + if (rv == -1) { + log_err_printf("Error from setsockopt(IP_TRANSPARENT): %s\n", + strerror(errno)); + } + return rv; +} +#endif /* IP_TRANSPARENT */ +#endif /* HAVE_NETFILTER */ + + +/* + * generic + */ + +/* + * Generic getsockname based implementation. This assumes that getsockname, + * by kernel magic, gives us the original destination. + */ +static int +nat_getsockname_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen, + evutil_socket_t s, + UNUSED struct sockaddr *src_addr, + UNUSED socklen_t src_addrlen) +{ + if (getsockname(s, dst_addr, dst_addrlen) == -1) { + log_err_printf("Error from getsockname(): %s\n", + strerror(errno)); + return -1; + } + return 0; +} + + +/* + * NAT engine glue code and API. + */ + +typedef int (*nat_init_cb_t)(void); +typedef void (*nat_fini_cb_t)(void); + +struct engine { + const char *name; + unsigned int ipv6 : 1; + unsigned int used : 1; + nat_init_cb_t preinitcb; + nat_init_cb_t initcb; + nat_fini_cb_t finicb; + nat_lookup_cb_t lookupcb; + nat_socket_cb_t socketcb; +}; + +struct engine engines[] = { +#ifdef HAVE_PF + { + "pf", 1, 0, + nat_pf_preinit, nat_pf_init, nat_pf_fini, + nat_pf_lookup_cb, NULL + }, +#endif /* HAVE_PF */ +#ifdef HAVE_IPFW + { + "ipfw", 1, 0, + NULL, NULL, NULL, + nat_getsockname_lookup_cb, NULL + }, +#endif /* HAVE_IPFW */ +#ifdef HAVE_IPFILTER + { + "ipfilter", 0, 0, + nat_ipfilter_preinit, nat_ipfilter_init, nat_ipfilter_fini, + nat_ipfilter_lookup_cb, NULL + }, +#endif /* HAVE_IPFILTER */ +#ifdef HAVE_NETFILTER + { + "netfilter", 0, 0, + NULL, NULL, NULL, + nat_netfilter_lookup_cb, NULL + }, +#ifdef IP_TRANSPARENT + { + "tproxy", 1, 0, + NULL, NULL, NULL, + nat_getsockname_lookup_cb, nat_iptransparent_socket_cb + }, +#endif /* IP_TRANSPARENT */ +#endif /* HAVE_NETFILTER */ + { + NULL, 0, 0, + NULL, NULL, NULL, + NULL, NULL + } +}; + + +/* + * Return the name of the default NAT engine. + */ +const char * +nat_getdefaultname(void) +{ + return engines[0].name; +} + +/* + * Look for a NAT engine in the table and return the index if found. + * If there is no NAT engine with the given name, then the index of the + * sentinel table entry is returned. + */ +static int +nat_index(const char *name) +{ + if (name) + for (int i = 0; engines[i].name; i++) + if (!strcmp(name, engines[i].name)) + return i; + return ((sizeof(engines) / sizeof(struct engine)) - 1); +} + +/* + * Returns 1 if the named NAT engine exists, 0 if it does not exist. + * NULL refers to the default NAT engine. + */ +int +nat_exist(const char *name) +{ + if (!name) + name = engines[0].name; + return !!engines[nat_index(name)].name; +} + +/* + * Returns the lookup callback of the named NAT engine and marks the NAT + * engine as used. + * NULL refers to the default NAT engine. + */ +nat_lookup_cb_t +nat_getlookupcb(const char *name) +{ + int i; + + if (!name) + name = engines[0].name; + i = nat_index(name); + engines[i].used = 1; + return engines[i].lookupcb; +} + +/* + * Returns the socket callback of the named NAT engine. + * NULL refers to the default NAT engine. + */ +nat_socket_cb_t +nat_getsocketcb(const char *name) +{ + if (!name) + name = engines[0].name; + return engines[nat_index(name)].socketcb; +} + +/* + * Returns 1 if name is a NAT engine which supports IPv6. + * NULL refers to the default NAT engine. + */ +int +nat_ipv6ready(const char *name) +{ + if (!name) + name = engines[0].name; + return engines[nat_index(name)].ipv6; +} + +/* + * List all available NAT engines to standard output and flush. + */ +void +nat_list_engines(void) +{ + for (int i = 0; engines[i].name; i++) { + fprintf(stdout, "%s%s\n", engines[i].name, + i ? "" : " (default)"); + } + fflush(stdout); +} + +/* + * Pre-initialize all NAT engines which were marked as used by previous calls + * to nat_getlookupcb(). + * + * Privileged initialization under root privs, before dropping privs, + * before calling daemon(). Here should be initialization which needs + * to provide the user feedback on errors. This includes opening + * special device files, for which the user may not have sufficient privs. + * + * Returns -1 on failure, 0 on success. + */ +int +nat_preinit(void) +{ + for (int i = 0; engines[i].preinitcb && engines[i].used; i++) { + log_dbg_printf("NAT engine preinit '%s'\n", engines[i].name); + if (engines[i].preinitcb() == -1) + return -1; + } + return 0; +} + +/* + * Initialize all NAT engines which were marked as used by previous calls to + * nat_getlookupcb(). + * + * Unprivileged initialization, possibly root, possibly nobody or service user. + * + * Returns -1 on failure, 0 on success. + */ +int +nat_init(void) +{ + for (int i = 0; engines[i].initcb && engines[i].used; i++) { + log_dbg_printf("NAT engine init '%s'\n", engines[i].name); + if (engines[i].initcb() == -1) + return -1; + } + return 0; +} + +/* + * Cleanup all NAT engines which were marked as used by previous calls to + * nat_getlookupcb(). + */ +void +nat_fini(void) +{ + for (int i = 0; engines[i].finicb && engines[i].used; i++) { + log_dbg_printf("NAT engine fini '%s'\n", engines[i].name); + engines[i].finicb(); + } +} + +/* + * Print version and option availability to standard error. + */ +void +nat_version(void) +{ + fprintf(stderr, "NAT engines:"); + for (int i = 0; engines[i].name; i++) { + fprintf(stderr, " %s%s", engines[i].name, + i ? "" : "*"); + } + if (!engines[0].name) + fprintf(stderr, " -"); + fprintf(stderr, "\n"); +#ifdef HAVE_IPFILTER + fprintf(stderr, "ipfilter: version %d\n", IPFILTER_VERSION); +#endif /* HAVE_IPFILTER */ +#ifdef HAVE_NETFILTER + fprintf(stderr, "netfilter: "); +#ifdef IP_TRANSPARENT + fprintf(stderr, " IP_TRANSPARENT"); +#else /* !IP_TRANSPARENT */ + fprintf(stderr, " !IP_TRANSPARENT"); +#endif /* !IP_TRANSPARENT */ +#ifdef SOL_IPV6 + fprintf(stderr, " SOL_IPV6"); +#else /* !SOL_IPV6 */ + fprintf(stderr, " !SOL_IPV6"); +#endif /* !SOL_IPV6 */ +#ifdef IPV6_ORIGINAL_DST + fprintf(stderr, " IPV6_ORIGINAL_DST"); +#else /* !IPV6_ORIGINAL_DST */ + fprintf(stderr, " !IPV6_ORIGINAL_DST"); +#endif /* !IPV6_ORIGINAL_DST */ + fprintf(stderr, "\n"); +#endif /* HAVE_NETFILTER */ +} + +/* vim: set noet ft=c: */ diff --git a/nat.h b/nat.h new file mode 100644 index 0000000..c111c98 --- /dev/null +++ b/nat.h @@ -0,0 +1,55 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NAT_H +#define NAT_H + +#include +#include + +#include + +typedef int (*nat_lookup_cb_t)(struct sockaddr *, socklen_t *, evutil_socket_t, + struct sockaddr *, socklen_t); +typedef int (*nat_socket_cb_t)(evutil_socket_t); + +int nat_exist(const char *); +nat_lookup_cb_t nat_getlookupcb(const char *); +nat_socket_cb_t nat_getsocketcb(const char *); +int nat_ipv6ready(const char *); + +const char *nat_getdefaultname(void); +void nat_list_engines(void); +int nat_preinit(void); +int nat_init(void); +void nat_fini(void); +void nat_version(void); + +#endif /* !NAT_H */ + +/* vim: set noet ft=c: */ diff --git a/opts.c b/opts.c new file mode 100644 index 0000000..cdc75b3 --- /dev/null +++ b/opts.c @@ -0,0 +1,275 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "opts.h" + +#include "sys.h" + +#include +#include +#include + +#ifndef OPENSSL_NO_DH +#include +#endif /* !OPENSSL_NO_DH */ +#include + +opts_t * +opts_new(void) +{ + opts_t *opts; + + opts = malloc(sizeof(opts_t)); + memset(opts, 0, sizeof(opts_t)); + + opts->sslcomp = 1; + opts->chain = sk_X509_new_null(); + + return opts; +} + +void +opts_free(opts_t *opts) +{ + sk_X509_pop_free(opts->chain, X509_free); + if (opts->cacrt) { + X509_free(opts->cacrt); + } + if (opts->cakey) { + EVP_PKEY_free(opts->cakey); + } + if (opts->key) { + EVP_PKEY_free(opts->key); + } +#ifndef OPENSSL_NO_DH + if (opts->dh) { + DH_free(opts->dh); + } +#endif /* !OPENSSL_NO_DH */ +#ifndef OPENSSL_NO_ECDH + if (opts->ecdhcurve) { + free(opts->ecdhcurve); + } +#endif /* !OPENSSL_NO_ECDH */ + if (opts->spec) { + proxyspec_free(opts->spec); + } + if (opts->ciphers) { + free(opts->ciphers); + } + if (opts->tgcrtdir) { + free(opts->tgcrtdir); + } + if (opts->dropuser) { + free(opts->dropuser); + } + if (opts->jaildir) { + free(opts->jaildir); + } + if (opts->pidfile) { + free(opts->pidfile); + } + if (opts->connectlog) { + free(opts->connectlog); + } + if (opts->contentlog) { + free(opts->contentlog); + } + memset(opts, 0, sizeof(opts_t)); + free(opts); +} + +/* + * Return 1 if opts_t contains a proxyspec with ssl, 0 otherwise. + */ +int +opts_has_ssl_spec(opts_t *opts) +{ + proxyspec_t *p = opts->spec; + + while (p) { + if (p->ssl) + return 1; + p = p->next; + } + + return 0; +} + +/* + * Parse proxyspecs using a simple state machine. + * Returns NULL if parsing failed. + */ +proxyspec_t * +proxyspec_parse(int *argc, char **argv[], const char *natengine) +{ + proxyspec_t *curspec, *spec = NULL; + char *addr; + int af; + int state = 0; + + while ((*argc)--) { + switch (state) { + default: + case 0: + /* tcp | ssl | http | https */ + curspec = malloc(sizeof(proxyspec_t)); + memset(curspec, 0, sizeof(proxyspec_t)); + curspec->next = spec; + spec = curspec; + if (!strcmp(**argv, "tcp")) { + spec->ssl = 0; + spec->http = 0; + } else + if (!strcmp(**argv, "ssl")) { + spec->ssl = 1; + spec->http = 0; + } else + if (!strcmp(**argv, "http")) { + spec->ssl = 0; + spec->http = 1; + } else + if (!strcmp(**argv, "https")) { + spec->ssl = 1; + spec->http = 1; + } else { + fprintf(stderr, "Unknown connection " + "type '%s'\n", **argv); + exit(EXIT_FAILURE); + } + state++; + break; + case 1: + /* listenaddr */ + addr = **argv; + state++; + break; + case 2: + /* listenport */ + if (strstr(addr, ":")) + af = AF_INET6; + else if (!strpbrk(addr, "abcdefghijklmnopqrstu" + "vwxyzABCDEFGHIJKLMNOP" + "QRSTUVWXYZ-")) + af = AF_INET; + else + af = AF_UNSPEC; + af = sys_sockaddr_parse(&spec->listen_addr, + &spec->listen_addrlen, + addr, **argv, af, + EVUTIL_AI_PASSIVE); + if (af == -1) { + exit(EXIT_FAILURE); + } + spec->natengine = strdup(natengine); + state++; + break; + case 3: + /* [ natengine | dstaddr ] */ + if (!strcmp(**argv, "tcp") || + !strcmp(**argv, "ssl") || + !strcmp(**argv, "http") || + !strcmp(**argv, "https")) { + /* implicit default natengine */ + (*argv)--; (*argc)++; /* rewind */ + state = 0; + } else + if (!strcmp(**argv, "sni")) { + free(spec->natengine); + spec->natengine = NULL; + if (!spec->ssl) { + fprintf(stderr, + "SNI hostname lookup " + "only works for ssl " + "and https proxyspecs" + "\n"); + exit(EXIT_FAILURE); + } + state = 5; + } else + if (nat_exist(**argv)) { + /* natengine */ + free(spec->natengine); + spec->natengine = strdup(**argv); + state = 0; + } else { + /* explicit target address */ + free(spec->natengine); + spec->natengine = NULL; + addr = **argv; + state++; + } + break; + case 4: + /* dstport */ + af = sys_sockaddr_parse(&spec->connect_addr, + &spec->connect_addrlen, + addr, **argv, af, 0); + if (af == -1) { + exit(EXIT_FAILURE); + } + state = 0; + break; + case 5: + /* SNI dstport */ + spec->sni_port = atoi(**argv); + if (!spec->sni_port) { + fprintf(stderr, "Invalid port '%s'\n", + **argv); + exit(EXIT_FAILURE); + } + state = 0; + break; + } + (*argv)++; + } + if (state != 0 && state != 3) { + fprintf(stderr, "Incomplete proxyspec!\n"); + exit(EXIT_FAILURE); + } + + return spec; +} + +/* + * Clear and free a proxy spec. + */ +void +proxyspec_free(proxyspec_t *spec) +{ + while (spec) { + proxyspec_t *next = spec->next; + if (spec->natengine) + free(spec->natengine); + memset(spec, 0, sizeof(proxyspec_t)); + free(spec); + spec = next; + } +} + +/* vim: set noet ft=c: */ diff --git a/opts.h b/opts.h new file mode 100644 index 0000000..e78d7ac --- /dev/null +++ b/opts.h @@ -0,0 +1,91 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OPTS_H +#define OPTS_H + +#include "nat.h" +#include "ssl.h" +#include "attrib.h" + +#include +#include + +typedef struct proxyspec { + unsigned int ssl : 1; + unsigned int http : 1; + struct sockaddr_storage listen_addr; + socklen_t listen_addrlen; + /* connect_addr and connect_addrlen are set: static mode; + * natlookup is set: NAT mode; natsocket /may/ be set too; + * sni_port is set, in which case we use SNI lookups */ + struct sockaddr_storage connect_addr; + socklen_t connect_addrlen; + unsigned short sni_port; + char *natengine; + nat_lookup_cb_t natlookup; + nat_socket_cb_t natsocket; + struct proxyspec *next; +} proxyspec_t; + +typedef struct opts { + unsigned int debug : 1; + unsigned int detach : 1; + unsigned int sslcomp : 1; + unsigned int passthrough : 1; + unsigned int contentlogdir : 1; + char *ciphers; + char *tgcrtdir; + char *dropuser; + char *jaildir; + char *pidfile; + char *connectlog; + char *contentlog; + X509 *cacrt; + EVP_PKEY *cakey; + EVP_PKEY *key; + STACK_OF(X509) *chain; +#ifndef OPENSSL_NO_DH + DH *dh; +#endif /* !OPENSSL_NO_DH */ +#ifndef OPENSSL_NO_ECDH + char *ecdhcurve; +#endif /* !OPENSSL_NO_ECDH */ + proxyspec_t *spec; +} opts_t; + +opts_t *opts_new(void) MALLOC; +void opts_free(opts_t *) NONNULL(); +int opts_has_ssl_spec(opts_t *) NONNULL(); + +proxyspec_t * proxyspec_parse(int *, char **[], const char *) MALLOC; +void proxyspec_free(proxyspec_t *) NONNULL(); + +#endif /* !OPTS_H */ + +/* vim: set noet ft=c: */ diff --git a/opts.t b/opts.t new file mode 100644 index 0000000..f546cd8 --- /dev/null +++ b/opts.t @@ -0,0 +1,382 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "attrib.h" +#include "opts.h" + +#include +#include +#include +#include +#include + +static char *argv01[] = { + "https", "127.0.0.1", "10443", "127.0.0.2", "443" +}; +static char *argv02[] = { + "https", "::1", "10443", "::2", "443" +}; +static char *argv03[] = { + "http", "127.0.0.1", "10443", "127.0.0.2", "443" +}; +static char *argv04[] = { + "ssl", "127.0.0.1", "10443", "127.0.0.2", "443" +}; +static char *argv05[] = { + "tcp", "127.0.0.1", "10443", "127.0.0.2", "443" +}; +static char *argv06[] = { + "https", "127.0.0.1", "10443", "sni", "443" +}; +static char *argv07[] = { + "http", "127.0.0.1", "10443", "sni", "443" +}; +static char *argv08[] = { + "https", "127.0.0.1", "10443", "no_such_engine" +}; +static char *argv09[] = { + "https", "127.0.0.1", "10443", "127.0.0.2", "443", + "https", "::1", "10443", "::2", "443" +}; +static char *argv10[] = { + "https", "127.0.0.1", "10443", + "https", "::1", "10443" +}; + +#define NATENGINE "pf" + +START_TEST(proxyspec_parse_01) +{ + proxyspec_t *spec; + int argc = 5; + char **argv = argv01; + + spec = proxyspec_parse(&argc, &argv, NATENGINE); + fail_unless(!!spec, "failed to parse spec"); + fail_unless(spec->ssl, "not SSL"); + fail_unless(spec->http, "not HTTP"); + fail_unless(spec->listen_addrlen == sizeof(struct sockaddr_in), + "not IPv4 listen addr"); + fail_unless(spec->connect_addrlen == sizeof(struct sockaddr_in), + "not IPv4 connect addr"); + fail_unless(!spec->sni_port, "SNI port is set"); + fail_unless(!spec->natengine, "natengine is set"); + fail_unless(!spec->natlookup, "natlookup() is set"); + fail_unless(!spec->natsocket, "natsocket() is set"); + fail_unless(!spec->next, "next is set"); + proxyspec_free(spec); +} +END_TEST + +START_TEST(proxyspec_parse_02) +{ + proxyspec_t *spec; + int argc = 5; + char **argv = argv02; + + spec = proxyspec_parse(&argc, &argv, NATENGINE); + fail_unless(!!spec, "failed to parse spec"); + fail_unless(spec->ssl, "not SSL"); + fail_unless(spec->http, "not HTTP"); + fail_unless(spec->listen_addrlen == sizeof(struct sockaddr_in6), + "not IPv6 listen addr"); + fail_unless(spec->connect_addrlen == sizeof(struct sockaddr_in6), + "not IPv6 connect addr"); + fail_unless(!spec->sni_port, "SNI port is set"); + fail_unless(!spec->natengine, "natengine is set"); + fail_unless(!spec->natlookup, "natlookup() is set"); + fail_unless(!spec->natsocket, "natsocket() is set"); + fail_unless(!spec->next, "next is set"); + proxyspec_free(spec); +} +END_TEST + +START_TEST(proxyspec_parse_03) +{ + proxyspec_t *spec; + int argc = 2; + char **argv = argv01; + + close(2); + spec = proxyspec_parse(&argc, &argv, NATENGINE); +} +END_TEST + +START_TEST(proxyspec_parse_04) +{ + proxyspec_t *spec; + int argc = 4; + char **argv = argv01; + + close(2); + spec = proxyspec_parse(&argc, &argv, NATENGINE); +} +END_TEST + +START_TEST(proxyspec_parse_05) +{ + proxyspec_t *spec; + int argc = 5; + char **argv = argv03; + + spec = proxyspec_parse(&argc, &argv, NATENGINE); + fail_unless(!!spec, "failed to parse spec"); + fail_unless(!spec->ssl, "SSL"); + fail_unless(spec->http, "not HTTP"); + fail_unless(spec->listen_addrlen == sizeof(struct sockaddr_in), + "not IPv4 listen addr"); + fail_unless(spec->connect_addrlen == sizeof(struct sockaddr_in), + "not IPv4 connect addr"); + fail_unless(!spec->sni_port, "SNI port is set"); + fail_unless(!spec->natengine, "natengine is set"); + fail_unless(!spec->natlookup, "natlookup() is set"); + fail_unless(!spec->natsocket, "natsocket() is set"); + fail_unless(!spec->next, "next is set"); + proxyspec_free(spec); +} +END_TEST + +START_TEST(proxyspec_parse_06) +{ + proxyspec_t *spec; + int argc = 5; + char **argv = argv04; + + spec = proxyspec_parse(&argc, &argv, NATENGINE); + fail_unless(!!spec, "failed to parse spec"); + fail_unless(spec->ssl, "not SSL"); + fail_unless(!spec->http, "HTTP"); + fail_unless(spec->listen_addrlen == sizeof(struct sockaddr_in), + "not IPv4 listen addr"); + fail_unless(spec->connect_addrlen == sizeof(struct sockaddr_in), + "not IPv4 connect addr"); + fail_unless(!spec->sni_port, "SNI port is set"); + fail_unless(!spec->natengine, "natengine is set"); + fail_unless(!spec->natlookup, "natlookup() is set"); + fail_unless(!spec->natsocket, "natsocket() is set"); + fail_unless(!spec->next, "next is set"); + proxyspec_free(spec); +} +END_TEST + +START_TEST(proxyspec_parse_07) +{ + proxyspec_t *spec; + int argc = 5; + char **argv = argv05; + + spec = proxyspec_parse(&argc, &argv, NATENGINE); + fail_unless(!!spec, "failed to parse spec"); + fail_unless(!spec->ssl, "SSL"); + fail_unless(!spec->http, "HTTP"); + fail_unless(spec->listen_addrlen == sizeof(struct sockaddr_in), + "not IPv4 listen addr"); + fail_unless(spec->connect_addrlen == sizeof(struct sockaddr_in), + "not IPv4 connect addr"); + fail_unless(!spec->sni_port, "SNI port is set"); + fail_unless(!spec->natengine, "natengine is set"); + fail_unless(!spec->natlookup, "natlookup() is set"); + fail_unless(!spec->natsocket, "natsocket() is set"); + fail_unless(!spec->next, "next is set"); + proxyspec_free(spec); +} +END_TEST + +START_TEST(proxyspec_parse_08) +{ + proxyspec_t *spec; + int argc = 5; + char **argv = argv06; + + spec = proxyspec_parse(&argc, &argv, NATENGINE); + fail_unless(!!spec, "failed to parse spec"); + fail_unless(spec->ssl, "not SSL"); + fail_unless(spec->http, "not HTTP"); + fail_unless(spec->listen_addrlen == sizeof(struct sockaddr_in), + "not IPv4 listen addr"); + fail_unless(!spec->connect_addrlen, "connect addr set"); + fail_unless(spec->sni_port == 443, "SNI port is not set"); + fail_unless(!spec->natengine, "natengine is set"); + fail_unless(!spec->natlookup, "natlookup() is set"); + fail_unless(!spec->natsocket, "natsocket() is set"); + fail_unless(!spec->next, "next is set"); + proxyspec_free(spec); +} +END_TEST + +START_TEST(proxyspec_parse_09) +{ + proxyspec_t *spec; + int argc = 5; + char **argv = argv07; + + close(2); + spec = proxyspec_parse(&argc, &argv, NATENGINE); +} +END_TEST + +START_TEST(proxyspec_parse_10) +{ + proxyspec_t *spec; + int argc = 4; + char **argv = argv06; + + close(2); + spec = proxyspec_parse(&argc, &argv, NATENGINE); +} +END_TEST + +START_TEST(proxyspec_parse_11) +{ + proxyspec_t *spec; + int argc = 3; + char **argv = argv08; + + spec = proxyspec_parse(&argc, &argv, NATENGINE); + fail_unless(!!spec, "failed to parse spec"); + fail_unless(spec->ssl, "not SSL"); + fail_unless(spec->http, "not HTTP"); + fail_unless(spec->listen_addrlen == sizeof(struct sockaddr_in), + "not IPv4 listen addr"); + fail_unless(!spec->connect_addrlen, "connect addr set"); + fail_unless(!spec->sni_port, "SNI port is set"); + fail_unless(!!spec->natengine, "natengine not set"); + fail_unless(!strcmp(spec->natengine, NATENGINE), "natengine mismatch"); + fail_unless(!spec->natlookup, "natlookup() is set"); + fail_unless(!spec->natsocket, "natsocket() is set"); + fail_unless(!spec->next, "next is set"); + proxyspec_free(spec); +} +END_TEST + +START_TEST(proxyspec_parse_12) +{ + proxyspec_t *spec; + int argc = 4; + char **argv = argv08; + + close(2); + spec = proxyspec_parse(&argc, &argv, NATENGINE); +} +END_TEST + +START_TEST(proxyspec_parse_13) +{ + proxyspec_t *spec; + int argc = 10; + char **argv = argv09; + + spec = proxyspec_parse(&argc, &argv, NATENGINE); + fail_unless(!!spec, "failed to parse spec"); + fail_unless(spec->ssl, "not SSL"); + fail_unless(spec->http, "not HTTP"); + fail_unless(spec->listen_addrlen == sizeof(struct sockaddr_in6), + "not IPv6 listen addr"); + fail_unless(spec->connect_addrlen == sizeof(struct sockaddr_in6), + "not IPv6 connect addr"); + fail_unless(!spec->sni_port, "SNI port is set"); + fail_unless(!spec->natengine, "natengine is set"); + fail_unless(!spec->natlookup, "natlookup() is set"); + fail_unless(!spec->natsocket, "natsocket() is set"); + fail_unless(!!spec->next, "next is not set"); + fail_unless(spec->next->ssl, "not SSL"); + fail_unless(spec->next->http, "not HTTP"); + fail_unless(spec->next->listen_addrlen == sizeof(struct sockaddr_in), + "not IPv4 listen addr"); + fail_unless(spec->next->connect_addrlen == sizeof(struct sockaddr_in), + "not IPv4 connect addr"); + fail_unless(!spec->next->sni_port, "SNI port is set"); + fail_unless(!spec->next->natengine, "natengine is set"); + fail_unless(!spec->next->natlookup, "natlookup() is set"); + fail_unless(!spec->next->natsocket, "natsocket() is set"); + proxyspec_free(spec); +} +END_TEST + +START_TEST(proxyspec_parse_14) +{ + proxyspec_t *spec; + int argc = 6; + char **argv = argv10; + + spec = proxyspec_parse(&argc, &argv, NATENGINE); + fail_unless(!!spec, "failed to parse spec"); + fail_unless(spec->ssl, "not SSL"); + fail_unless(spec->http, "not HTTP"); + fail_unless(spec->listen_addrlen == sizeof(struct sockaddr_in6), + "not IPv6 listen addr"); + fail_unless(!spec->connect_addrlen, "connect addr set"); + fail_unless(!spec->sni_port, "SNI port is set"); + fail_unless(!!spec->natengine, "natengine not set"); + fail_unless(!strcmp(spec->natengine, NATENGINE), "natengine mismatch"); + fail_unless(!spec->natlookup, "natlookup() is set"); + fail_unless(!spec->natsocket, "natsocket() is set"); + fail_unless(!!spec->next, "next is not set"); + fail_unless(spec->next->ssl, "not SSL"); + fail_unless(spec->next->http, "not HTTP"); + fail_unless(spec->next->listen_addrlen == sizeof(struct sockaddr_in), + "not IPv4 listen addr"); + fail_unless(!spec->next->connect_addrlen, "connect addr set"); + fail_unless(!spec->next->sni_port, "SNI port is set"); + fail_unless(!!spec->next->natengine, "natengine not set"); + fail_unless(!strcmp(spec->next->natengine, NATENGINE), + "natengine mismatch"); + fail_unless(!spec->next->natlookup, "natlookup() is set"); + fail_unless(!spec->next->natsocket, "natsocket() is set"); + proxyspec_free(spec); +} +END_TEST + +Suite * +opts_suite(void) +{ + Suite *s; + TCase *tc; + s = suite_create("opts"); + + tc = tcase_create("proxyspec_parse"); + tcase_add_test(tc, proxyspec_parse_01); + tcase_add_test(tc, proxyspec_parse_02); + tcase_add_exit_test(tc, proxyspec_parse_03, EXIT_FAILURE); + tcase_add_exit_test(tc, proxyspec_parse_04, EXIT_FAILURE); + tcase_add_test(tc, proxyspec_parse_05); + tcase_add_test(tc, proxyspec_parse_06); + tcase_add_test(tc, proxyspec_parse_07); + tcase_add_test(tc, proxyspec_parse_08); + tcase_add_exit_test(tc, proxyspec_parse_09, EXIT_FAILURE); + tcase_add_exit_test(tc, proxyspec_parse_10, EXIT_FAILURE); + tcase_add_test(tc, proxyspec_parse_11); + tcase_add_exit_test(tc, proxyspec_parse_12, EXIT_FAILURE); + tcase_add_test(tc, proxyspec_parse_13); + tcase_add_test(tc, proxyspec_parse_14); + suite_add_tcase(s, tc); + + return s; +} + +/* vim: set noet ft=c: */ diff --git a/proxy.c b/proxy.c new file mode 100644 index 0000000..70bdadd --- /dev/null +++ b/proxy.c @@ -0,0 +1,410 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "proxy.h" + +#include "pxythrmgr.h" +#include "pxyconn.h" +#include "cachemgr.h" +#include "opts.h" +#include "log.h" +#include "attrib.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +/* + * Proxy engine, built around libevent 2.x. + */ + +static int signals[] = { SIGQUIT, SIGHUP, SIGINT, SIGPIPE }; + +struct proxy_ctx { + pxy_thrmgr_ctx_t *thrmgr; + struct event_base *evbase; + struct event *sev[sizeof(signals)/sizeof(int)]; + struct event *gcev; + struct proxy_listener_ctx *lctx; + opts_t *opts; +}; + + +/* + * Listener context. + */ +typedef struct proxy_listener_ctx { + pxy_thrmgr_ctx_t *thrmgr; + proxyspec_t *spec; + opts_t *opts; + struct evconnlistener *evcl; + struct proxy_listener_ctx *next; +} proxy_listener_ctx_t; + +static proxy_listener_ctx_t * +proxy_listener_ctx_new(pxy_thrmgr_ctx_t *thrmgr, proxyspec_t *spec, + opts_t *opts) MALLOC; +static proxy_listener_ctx_t * +proxy_listener_ctx_new(pxy_thrmgr_ctx_t *thrmgr, proxyspec_t *spec, + opts_t *opts) +{ + proxy_listener_ctx_t *ctx = malloc(sizeof(proxy_listener_ctx_t)); + if (!ctx) + return NULL; + memset(ctx, 0, sizeof(proxy_listener_ctx_t)); + ctx->thrmgr = thrmgr; + ctx->spec = spec; + ctx->opts = opts; + return ctx; +} + +static void +proxy_listener_ctx_free(proxy_listener_ctx_t *ctx) NONNULL(); +static void +proxy_listener_ctx_free(proxy_listener_ctx_t *ctx) +{ + if (ctx->evcl) { + evconnlistener_free(ctx->evcl); + } + if (ctx->next) { + proxy_listener_ctx_free(ctx->next); + } + free(ctx); +} + +/* + * Callback for accept events on the socket listener bufferevent. + */ +static void +proxy_listener_acceptcb(UNUSED struct evconnlistener *listener, + evutil_socket_t fd, + struct sockaddr *peeraddr, int peeraddrlen, + void *arg) +{ + proxy_listener_ctx_t *cfg = arg; + + pxy_conn_setup(fd, peeraddr, peeraddrlen, cfg->thrmgr, + cfg->spec, cfg->opts); +} + +/* + * Callback for error events on the socket listener bufferevent. + */ +static void +proxy_listener_errorcb(struct evconnlistener *listener, UNUSED void *ctx) +{ + struct event_base *evbase = evconnlistener_get_base(listener); + int err = EVUTIL_SOCKET_ERROR(); + log_err_printf("Error %d on listener: %s\n", err, + evutil_socket_error_to_string(err)); + event_base_loopbreak(evbase); +} + +/* + * Dump a description of an evbase to debugging code. + */ +static void +proxy_debug_base(const struct event_base *ev_base) +{ + log_dbg_printf("Using libevent backend '%s'\n", + event_base_get_method(ev_base)); + + enum event_method_feature f; + f = event_base_get_features(ev_base); + log_dbg_printf("Event base supports: edge %s, O(1) %s, anyfd %s\n", + ((f & EV_FEATURE_ET) ? "yes" : "no"), + ((f & EV_FEATURE_O1) ? "yes" : "no"), + ((f & EV_FEATURE_FDS) ? "yes" : "no")); +} + +/* + * Set up the listener for a single proxyspec and add it to evbase. + * Returns the proxy_listener_ctx_t pointer if successful, NULL otherwise. + */ +static proxy_listener_ctx_t * +proxy_listener_setup(struct event_base *evbase, pxy_thrmgr_ctx_t *thrmgr, + proxyspec_t *spec, opts_t *opts) +{ + proxy_listener_ctx_t *plc; + + evutil_socket_t fd; + int on = 1; + int rv; + + fd = socket(spec->listen_addr.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (fd == -1) { + log_err_printf("Error from socket(): %s\n", + strerror(errno)); + evutil_closesocket(fd); + return NULL; + } + + rv = evutil_make_socket_nonblocking(fd); + if (rv == -1) { + log_err_printf("Error making socket nonblocking: %s\n", + strerror(errno)); + evutil_closesocket(fd); + return NULL; + } + + rv = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on)); + if (rv == -1) { + log_err_printf("Error from setsockopt(SO_KEEPALIVE): %s\n", + strerror(errno)); + evutil_closesocket(fd); + return NULL; + } + + rv = evutil_make_listen_socket_reuseable(fd); + if (rv == -1) { + log_err_printf("Error from setsockopt(SO_REUSABLE): %s\n", + strerror(errno)); + evutil_closesocket(fd); + return NULL; + } + + if (spec->natsocket && (spec->natsocket(fd) == -1)) { + log_err_printf("Error from spec->natsocket()\n"); + evutil_closesocket(fd); + return NULL; + } + + rv = bind(fd, (struct sockaddr *)&spec->listen_addr, + spec->listen_addrlen); + if (rv == -1) { + log_err_printf("Error from bind(): %s\n", strerror(errno)); + evutil_closesocket(fd); + return NULL; + } + + plc = proxy_listener_ctx_new(thrmgr, spec, opts); + if (!plc) { + log_err_printf("Error creating listener context\n"); + evutil_closesocket(fd); + return NULL; + } + + plc->evcl = evconnlistener_new(evbase, proxy_listener_acceptcb, + plc, LEV_OPT_CLOSE_ON_FREE, 1024, fd); + if (!plc->evcl) { + log_err_printf("Error creating evconnlistener: %s\n", + strerror(errno)); + proxy_listener_ctx_free(plc); + evutil_closesocket(fd); + return NULL; + } + evconnlistener_set_error_cb(plc->evcl, proxy_listener_errorcb); + return plc; +} + +/* + * Signal handler for SIGQUIT, SIGINT, SIGHUP and SIGPIPE. + */ +static void +proxy_signal_cb(evutil_socket_t fd, UNUSED short what, void *arg) +{ + proxy_ctx_t *ctx = arg; + + if (ctx->opts->debug) { + log_dbg_printf("Received signal %i\n", fd); + } + + if (fd == SIGPIPE) { + log_err_printf("Warning: Received SIGPIPE; ignoring.\n"); + } else { + event_base_loopbreak(ctx->evbase); + } +} + +/* + * Garbace collection handler. + */ +static void +proxy_gc_cb(UNUSED evutil_socket_t fd, UNUSED short what, void *arg) +{ + proxy_ctx_t *ctx = arg; + + if (ctx->opts->debug) + log_dbg_printf("Garbage collecting caches started.\n"); + + cachemgr_gc(); + + if (ctx->opts->debug) + log_dbg_printf("Garbage collecting caches done.\n"); +} + +/* + * Set up the core event loop. + * Returns ctx on success, or NULL on error. + */ +proxy_ctx_t * +proxy_new(opts_t *opts) +{ + proxy_listener_ctx_t *head; + proxy_ctx_t *ctx; + + /* adds locking, only required if accessed from separate threads */ + evthread_use_pthreads(); + +#ifndef PURIFY + if (opts->debug) { + event_enable_debug_mode(); + } +#endif /* PURIFY */ + + ctx = malloc(sizeof(proxy_ctx_t)); + if (!ctx) { + log_err_printf("Error allocating memory\n"); + goto leave0; + } + memset(ctx, 0, sizeof(proxy_ctx_t)); + + ctx->opts = opts; + ctx->evbase = event_base_new(); + if (!ctx->evbase) { + log_err_printf("Error getting event base\n"); + goto leave1; + } + + if (opts->debug) { + proxy_debug_base(ctx->evbase); + } + + ctx->thrmgr = pxy_thrmgr_new(opts); + if (!ctx->thrmgr) { + log_err_printf("Error creating thread manager\n"); + goto leave1b; + } + + head = ctx->lctx = NULL; + for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) { + head = proxy_listener_setup(ctx->evbase, ctx->thrmgr, + spec, opts); + if (!head) + goto leave2; + head->next = ctx->lctx; + ctx->lctx = head; + } + + for (size_t i = 0; i < (sizeof(signals) / sizeof(int)); i++) { + ctx->sev[i] = evsignal_new(ctx->evbase, signals[i], + proxy_signal_cb, ctx); + if (!ctx->sev[i]) + goto leave3; + evsignal_add(ctx->sev[i], NULL); + } + + struct timeval gc_delay = {60, 0}; + ctx->gcev = event_new(ctx->evbase, -1, EV_PERSIST, proxy_gc_cb, ctx); + if (!ctx->gcev) + goto leave4; + evtimer_add(ctx->gcev, &gc_delay); + + return ctx; + +leave4: + if (ctx->gcev) { + event_free(ctx->gcev); + } + +leave3: + for (size_t i = 0; i < (sizeof(ctx->sev) / sizeof(ctx->sev[0])); i++) { + if (ctx->sev[i]) { + event_free(ctx->sev[i]); + } + } +leave2: + if (ctx->lctx) { + proxy_listener_ctx_free(ctx->lctx); + } + pxy_thrmgr_free(ctx->thrmgr); +leave1b: + event_base_free(ctx->evbase); +leave1: + free(ctx); +leave0: + return NULL; +} + +/* + * Run the event loop. Returns when the event loop is cancelled by a signal. + */ +void +proxy_run(proxy_ctx_t *ctx) +{ + if (ctx->opts->detach) { + event_reinit(ctx->evbase); + } +#ifndef PURIFY + if (ctx->opts->debug) { + event_base_dump_events(ctx->evbase, stderr); + } +#endif /* PURIFY */ + if (ctx->opts->debug) { + log_dbg_printf("Starting main event loop.\n"); + } + event_base_dispatch(ctx->evbase); + if (ctx->opts->debug) { + log_dbg_printf("Main event loop stopped.\n"); + } +} + +/* + * Free the proxy data structures. + */ +void +proxy_free(proxy_ctx_t *ctx) +{ + if (ctx->gcev) { + event_free(ctx->gcev); + } + if (ctx->lctx) { + proxy_listener_ctx_free(ctx->lctx); + } + for (size_t i = 0; i < (sizeof(ctx->sev) / sizeof(ctx->sev[0])); i++) { + event_free(ctx->sev[i]); + } + pxy_thrmgr_free(ctx->thrmgr); + event_base_free(ctx->evbase); + free(ctx); +} + +/* vim: set noet ft=c: */ diff --git a/proxy.h b/proxy.h new file mode 100644 index 0000000..6ff6fab --- /dev/null +++ b/proxy.h @@ -0,0 +1,43 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PROXY_H +#define PROXY_H + +#include "opts.h" +#include "attrib.h" + +typedef struct proxy_ctx proxy_ctx_t; + +proxy_ctx_t * proxy_new(opts_t *) NONNULL(); +void proxy_run(proxy_ctx_t *) NONNULL(); +void proxy_free(proxy_ctx_t *) NONNULL(); + +#endif /* !PROXY_H */ + +/* vim: set noet ft=c: */ diff --git a/pxyconn.c b/pxyconn.c new file mode 100644 index 0000000..8313a71 --- /dev/null +++ b/pxyconn.c @@ -0,0 +1,1466 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pxyconn.h" + +#include "pxysslshut.h" +#include "cachemgr.h" +#include "ssl.h" +#include "opts.h" +#include "sys.h" +#include "log.h" +#include "attrib.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +/* + * Maximum size of data to buffer per connection direction before + * temporarily stopping to read data from the other end. + */ +#define OUTBUF_LIMIT (128*1024) + +/* + * Print helper for logging code. + */ +#define STRORDASH(x) ((x)?(x):"-") + +/* + * Context used for all server sessions. + */ +#ifdef USE_SSL_SESSION_ID_CONTEXT +static unsigned long ssl_session_context = 0x31415926; +#endif /* USE_SSL_SESSION_ID_CONTEXT */ + + +/* + * Proxy connection context state, describes a proxy connection + * with source and destination socket bufferevents, SSL context and + * other session state. One of these exists per handled proxy + * connection. + */ + +/* single dst or src socket bufferevent descriptor */ +typedef struct pxy_conn_desc { + struct bufferevent *bev; + SSL *ssl; + unsigned int closed : 1; +} pxy_conn_desc_t; + +/* actual proxy connection state consisting of two connection descriptors, + * connection-wide state and the specs and options */ +typedef struct pxy_conn_ctx { + /* per-connection state */ + struct pxy_conn_desc src; + struct pxy_conn_desc dst; + + /* status flags */ + unsigned int immutable_cert : 1; /* 1 if the cert cannot be changed */ + unsigned int connected : 1; /* 0 until both ends are connected */ + unsigned int seen_req_header : 1; /* 0 until HTTP header is complete */ + unsigned int sent_http_conn_close : 1; /* 0 until Conn: close sent */ + unsigned int passthrough : 1; /* 1 if SSL passthrough is active */ + unsigned int sni_peek_retries : 6; /* max 64 SNI parse retries */ + + /* server name indicated by client in SNI TLS extension */ + char *sni; + + /* strings for logging */ + char *src_str; + char *dst_str; + char *http_method; + char *http_uri; + char *http_host; + char *ssl_names; + char *ssl_orignames; + + /* content log context */ + log_content_ctx_t logctx; + + /* store fd and fd event while connected is 0 */ + evutil_socket_t fd; + struct event *ev; + + /* original destination address, family and certificate */ + struct sockaddr_storage addr; + socklen_t addrlen; + int af; + X509 *origcrt; + + /* references to event base and configuration */ + struct event_base *evbase; + struct evdns_base *dnsbase; + int thridx; + pxy_thrmgr_ctx_t *thrmgr; + proxyspec_t *spec; + opts_t *opts; +} pxy_conn_ctx_t; + +#define WANT_CONNECT_LOG(ctx) ((ctx)->opts->connectlog||!(ctx)->opts->detach) +#define WANT_CONTENT_LOG(ctx) ((ctx)->opts->contentlog&&!(ctx)->passthrough) + +static pxy_conn_ctx_t * +pxy_conn_ctx_new(proxyspec_t *spec, opts_t *opts, + pxy_thrmgr_ctx_t *thrmgr, evutil_socket_t fd) + MALLOC NONNULL(); +static pxy_conn_ctx_t * +pxy_conn_ctx_new(proxyspec_t *spec, opts_t *opts, + pxy_thrmgr_ctx_t *thrmgr, evutil_socket_t fd) +{ + pxy_conn_ctx_t *ctx = malloc(sizeof(pxy_conn_ctx_t)); + if (!ctx) + return NULL; + memset(ctx, 0, sizeof(pxy_conn_ctx_t)); + ctx->spec = spec; + ctx->opts = opts; + ctx->fd = fd; + ctx->thridx = pxy_thrmgr_attach(thrmgr, &ctx->evbase, &ctx->dnsbase); + ctx->thrmgr = thrmgr; +#ifdef DEBUG_PROXY + if (opts->debug) { + log_dbg_printf("%p pxy_conn_ctx_new\n", + (void*)ctx); + } +#endif /* DEBUG_PROXY */ + return ctx; +} + +static void +pxy_conn_ctx_free(pxy_conn_ctx_t *ctx) NONNULL(); +static void +pxy_conn_ctx_free(pxy_conn_ctx_t *ctx) +{ +#ifdef DEBUG_PROXY + if (ctx->opts->debug) { + log_dbg_printf("%p pxy_conn_ctx_free\n", + (void*)ctx); + } +#endif /* DEBUG_PROXY */ + pxy_thrmgr_detach(ctx->thrmgr, ctx->thridx); + if (ctx->src_str) { + free(ctx->src_str); + } + if (ctx->dst_str) { + free(ctx->dst_str); + } + if (ctx->http_method) { + free(ctx->http_method); + } + if (ctx->http_uri) { + free(ctx->http_uri); + } + if (ctx->http_host) { + free(ctx->http_host); + } + if (ctx->ssl_names) { + free(ctx->ssl_names); + } + if (ctx->ssl_orignames) { + free(ctx->ssl_orignames); + } + if (ctx->origcrt) { + X509_free(ctx->origcrt); + } + if (ctx->ev) { + event_free(ctx->ev); + } + if (ctx->sni) { + free(ctx->sni); + } + if (WANT_CONTENT_LOG(ctx)) { + log_content_close(&ctx->logctx); + } + free(ctx); +} + + +/* forward declaration of libevent callbacks */ +static void pxy_bev_readcb(struct bufferevent *, void *); +static void pxy_bev_writecb(struct bufferevent *, void *); +static void pxy_bev_eventcb(struct bufferevent *, short, void *); +static void pxy_fd_readcb(evutil_socket_t, short, void *); + +/* forward declaration of OpenSSL callbacks */ +#ifndef OPENSSL_NO_TLSEXT +static int pxy_ossl_servername_cb(SSL *ssl, int *al, void *arg); +#endif /* !OPENSSL_NO_TLSEXT */ +static int pxy_ossl_sessnew_cb(SSL *, SSL_SESSION *); +static void pxy_ossl_sessremove_cb(SSL_CTX *, SSL_SESSION *); +static SSL_SESSION * pxy_ossl_sessget_cb(SSL *, unsigned char *, int, int *); + +/* + * Dump information on a certificate to the debug log. + */ +static void +pxy_debug_crt(X509 *crt) +{ + char *sj = ssl_x509_subject(crt); + if (sj) { + log_dbg_printf("Subject DN: %s\n", sj); + free(sj); + } + + char *names = ssl_x509_names_to_str(crt, 60); + if (names) { + log_dbg_printf("Common Names: %s\n", names); + free(names); + } + + unsigned char fpr[SSL_X509_FPRSZ]; + if (ssl_x509_fingerprint_sha1(crt, fpr) == -1) { + log_err_printf("Warning: Error generating X509 fingerprint\n"); + } else { + log_dbg_printf("Fingerprint: " "%02x:%02x:%02x:%02x:" + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:" + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + fpr[0], fpr[1], fpr[2], fpr[3], fpr[4], + fpr[5], fpr[6], fpr[7], fpr[8], fpr[9], + fpr[10], fpr[11], fpr[12], fpr[13], fpr[14], + fpr[15], fpr[16], fpr[17], fpr[18], fpr[19]); + } + +#ifdef DEBUG_CERTIFICATE + /* dump certificate */ + log_dbg_print_free(ssl_x509_to_str(crt)); + log_dbg_print_free(ssl_x509_to_pem(crt)); +#endif /* DEBUG_CERTIFICATE */ +} + +static void +pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx) +{ + char *msg; + int rv; + + if (!ctx->spec->ssl || ctx->passthrough) { + rv = asprintf(&msg, "%s %s %s\n", + ctx->passthrough ? "passthrough" : "tcp", + STRORDASH(ctx->src_str), + STRORDASH(ctx->dst_str)); + } else { + rv = asprintf(&msg, "ssl %s %s " + "sni:%s crt:%s origcrt:%s\n", + STRORDASH(ctx->src_str), + STRORDASH(ctx->dst_str), + STRORDASH(ctx->sni), + STRORDASH(ctx->ssl_names), + STRORDASH(ctx->ssl_orignames)); + } + if ((rv == -1) || !msg) + return; + if (!ctx->opts->detach) { + log_err_printf("%s", msg); + } + if (ctx->opts->connectlog) { + log_connect_print_free(msg); + } else { + free(msg); + } +} + +static void +pxy_log_connect_http(pxy_conn_ctx_t *ctx) +{ + char *msg; + int rv; + +#ifdef DEBUG_PROXY + if (ctx->passthrough) { + log_err_printf("Warning: pxy_log_connect_http called while in " + "passthrough mode\n"); + return; + } +#endif + + if (!ctx->spec->ssl) { + rv = asprintf(&msg, "http %s %s %s %s %s\n", + STRORDASH(ctx->src_str), + STRORDASH(ctx->dst_str), + STRORDASH(ctx->http_host), + STRORDASH(ctx->http_method), + STRORDASH(ctx->http_uri)); + } else { + rv = asprintf(&msg, "https %s %s %s %s %s " + "sni:%s crt:%s origcrt:%s\n", + STRORDASH(ctx->src_str), + STRORDASH(ctx->dst_str), + STRORDASH(ctx->http_host), + STRORDASH(ctx->http_method), + STRORDASH(ctx->http_uri), + STRORDASH(ctx->sni), + STRORDASH(ctx->ssl_names), + STRORDASH(ctx->ssl_orignames)); + } + if ((rv == -1) || !msg) + return; + if (!ctx->opts->detach) { + log_err_printf("%s", msg); + } + if (ctx->opts->connectlog) { + log_connect_print_free(msg); + } else { + free(msg); + } +} + +/* + * Called by OpenSSL when a new src SSL session is created. + * Return 0 means remove session from internal session cache. + */ +static int +pxy_ossl_sessnew_cb(SSL *ssl, SSL_SESSION *sess) +{ +#ifdef DEBUG_SESSION_CACHE + log_dbg_printf("===> OpenSSL new session callback:\n"); + if (sess) { + log_dbg_print_free(ssl_session_to_str(sess)); + } +#endif /* DEBUG_SESSION_CACHE */ +#ifdef DISABLE_SSLV2_SESSION_CACHE + /* Session resumption seems to fail for SSLv2 with protocol + * parsing errors, so we disable caching for SSLv2. */ + if (SSL_version(ssl) == SSL2_VERSION) { + log_err_printf("Warning: Session resumption denied to SSLv2" + "client.\n"); + return 0; + } +#endif /* DISABLE_SSLV2_SESSION_CACHE */ + cachemgr_ssess_set(sess); + return 0; +} + +/* + * Called by OpenSSL when a src SSL session should be removed. + */ +static void +pxy_ossl_sessremove_cb(UNUSED SSL_CTX *sslctx, SSL_SESSION *sess) +{ +#ifdef DEBUG_SESSION_CACHE + log_dbg_printf("===> OpenSSL remove session callback:\n"); + if (sess) { + log_dbg_print_free(ssl_session_to_str(sess)); + } +#endif /* DEBUG_SESSION_CACHE */ + + cachemgr_ssess_del(sess); +} + +/* + * Called by OpenSSL when a src SSL session is requested by the client. + */ +static SSL_SESSION * +pxy_ossl_sessget_cb(UNUSED SSL *ssl, unsigned char *id, int idlen, int *copy) +{ + SSL_SESSION *sess; + +#ifdef DEBUG_SESSION_CACHE + log_dbg_printf("===> OpenSSL get session callback:\n"); +#endif /* DEBUG_SESSION_CACHE */ + + *copy = 0; /* SSL should not increment reference count of session */ + sess = cachemgr_ssess_get(id, idlen); + +#ifdef DEBUG_SESSION_CACHE + if (sess) { + log_dbg_print_free(ssl_session_to_str(sess)); + } +#endif /* DEBUG_SESSION_CACHE */ + + log_dbg_printf("SSL session cache: %s\n", sess ? "HIT" : "MISS"); + return sess; +} + +/* + * Create and set up a new SSL_CTX instance for terminating SSL. + * Set up all the necessary callbacks, the certificate, the cert chain and key. + */ +static SSL_CTX * +pxy_srcsslctx_create(pxy_conn_ctx_t *ctx, X509 *crt, STACK_OF(X509) *chain, + EVP_PKEY *key) +{ + SSL_CTX *sslctx = SSL_CTX_new(SSLv23_method()); + SSL_CTX_set_options(sslctx, SSL_OP_ALL); +#ifdef SSL_OP_TLS_ROLLBACK_BUG + SSL_CTX_set_options(sslctx, SSL_OP_TLS_ROLLBACK_BUG); +#endif /* SSL_OP_TLS_ROLLBACK_BUG */ +#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION + SSL_CTX_set_options(sslctx, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); +#endif /* SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION */ +#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS + SSL_CTX_set_options(sslctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); +#endif /* SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS */ +#ifdef SSL_OP_NO_TICKET + SSL_CTX_set_options(sslctx, SSL_OP_NO_TICKET); +#endif /* SSL_OP_NO_TICKET */ +#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION + SSL_CTX_set_options(sslctx, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#endif /* SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION */ +#ifdef SSL_OP_NO_COMPRESSION + if (!ctx->opts->sslcomp) { + SSL_CTX_set_options(sslctx, SSL_OP_NO_COMPRESSION); + } +#endif /* SSL_OP_NO_COMPRESSION */ +#if DISABLE_SSLV2_SERVER + SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2); +#endif /* DISABLE_SSLV2_SERVER */ + SSL_CTX_set_cipher_list(sslctx, ctx->opts->ciphers); + SSL_CTX_sess_set_new_cb(sslctx, pxy_ossl_sessnew_cb); + SSL_CTX_sess_set_remove_cb(sslctx, pxy_ossl_sessremove_cb); + SSL_CTX_sess_set_get_cb(sslctx, pxy_ossl_sessget_cb); + SSL_CTX_set_session_cache_mode(sslctx, SSL_SESS_CACHE_SERVER | + SSL_SESS_CACHE_NO_INTERNAL); +#ifdef USE_SSL_SESSION_ID_CONTEXT + SSL_CTX_set_session_id_context(sslctx, (void *)(&ssl_session_context), + sizeof(ssl_session_context)); +#endif /* USE_SSL_SESSION_ID_CONTEXT */ +#ifndef OPENSSL_NO_TLSEXT + SSL_CTX_set_tlsext_servername_callback(sslctx, pxy_ossl_servername_cb); + SSL_CTX_set_tlsext_servername_arg(sslctx, ctx); +#endif /* !OPENSSL_NO_TLSEXT */ +#ifndef OPENSSL_NO_DH + if (ctx->opts->dh) { + SSL_CTX_set_tmp_dh(sslctx, ctx->opts->dh); + } else if (EVP_PKEY_type(ctx->opts->key->type) != EVP_PKEY_RSA) { + SSL_CTX_set_tmp_dh_callback(sslctx, ssl_tmp_dh_callback); + } +#endif /* !OPENSSL_NO_DH */ +#ifndef OPENSSL_NO_ECDH + if (ctx->opts->ecdhcurve) { + EC_KEY *ecdh = ssl_ecdh_by_name(ctx->opts->ecdhcurve); + SSL_CTX_set_tmp_ecdh(sslctx, ecdh); + EC_KEY_free(ecdh); + } else if (EVP_PKEY_type(ctx->opts->key->type) != EVP_PKEY_RSA) { + EC_KEY *ecdh = ssl_ecdh_by_name(NULL); + SSL_CTX_set_tmp_ecdh(sslctx, ecdh); + EC_KEY_free(ecdh); + } +#endif /* !OPENSSL_NO_ECDH */ + SSL_CTX_use_certificate(sslctx, crt); + SSL_CTX_use_PrivateKey(sslctx, key); + for (int i = 0; i < sk_X509_num(chain); i++) { + X509 *c = sk_X509_value(chain, i); + ssl_x509_refcount_inc(c); /* next call consumes a reference */ + SSL_CTX_add_extra_chain_cert(sslctx, c); + } + +#ifdef DEBUG_SESSION_CACHE + if (ctx->opts->debug) { + int mode = SSL_CTX_get_session_cache_mode(sslctx); + log_dbg_printf("SSL session cache mode: %08x\n", mode); + if (mode == SSL_SESS_CACHE_OFF) + log_dbg_printf("SSL_SESS_CACHE_OFF\n"); + if (mode & SSL_SESS_CACHE_CLIENT) + log_dbg_printf("SSL_SESS_CACHE_CLIENT\n"); + if (mode & SSL_SESS_CACHE_SERVER) + log_dbg_printf("SSL_SESS_CACHE_SERVER\n"); + if (mode & SSL_SESS_CACHE_NO_AUTO_CLEAR) + log_dbg_printf("SSL_SESS_CACHE_NO_AUTO_CLEAR\n"); + if (mode & SSL_SESS_CACHE_NO_INTERNAL_LOOKUP) + log_dbg_printf("SSL_SESS_CACHE_NO_INTERNAL_LOOKUP\n"); + if (mode & SSL_SESS_CACHE_NO_INTERNAL_STORE) + log_dbg_printf("SSL_SESS_CACHE_NO_INTERNAL_STORE\n"); + } +#endif /* DEBUG_SESSION_CACHE */ + + return sslctx; +} + +static cert_t * +pxy_srccert_create(pxy_conn_ctx_t *ctx) +{ + cert_t *cert = NULL; + char *wildcarded; + + if (ctx->opts->tgcrtdir) { + if (ctx->sni) { + cert = cachemgr_tgcrt_get(ctx->sni); + if (!cert) { + wildcarded = ssl_wildcardify(ctx->sni); + cert = cachemgr_tgcrt_get(wildcarded); + free(wildcarded); + } + } else { + char **names = ssl_x509_names(ctx->origcrt); + for (char **p = names; *p; p++) { + if (!cert) { + cert = cachemgr_tgcrt_get(*p); + } + if (!cert) { + wildcarded = ssl_wildcardify(*p); + cert = cachemgr_tgcrt_get(wildcarded); + free(wildcarded); + } + free(*p); + } + free(names); + } + + if (cert) { + ctx->immutable_cert = 1; + } + } + + if (!cert && ctx->opts->key) { + cert = cert_new(); + + cert->crt = cachemgr_fkcrt_get(ctx->origcrt); + if (cert->crt) { + if (ctx->opts->debug) + log_dbg_printf("Certificate cache: HIT\n"); + } else { + if (ctx->opts->debug) + log_dbg_printf("Certificate cache: MISS\n"); + cert->crt = ssl_x509_forge(ctx->opts->cacrt, + ctx->opts->cakey, + ctx->origcrt, NULL, + ctx->opts->key); + cachemgr_fkcrt_set(ctx->origcrt, cert->crt); + } + cert_set_key(cert, ctx->opts->key); + cert_set_chain(cert, ctx->opts->chain); + } + + return cert; +} + +/* + * Create new SSL context for the incoming connection, based on the original + * destination SSL certificate. + * Returns NULL if no suitable certificate could be found. + */ +static SSL * +pxy_srcssl_create(pxy_conn_ctx_t *ctx, SSL *origssl) +{ + cert_t *cert; + + cachemgr_dsess_set((struct sockaddr*)&ctx->addr, + ctx->addrlen, ctx->sni, + SSL_get0_session(origssl)); + + ctx->origcrt = SSL_get_peer_certificate(origssl); + + if (ctx->opts->debug) { + log_dbg_printf("===> Original server certificate:\n"); + pxy_debug_crt(ctx->origcrt); + } + + cert = pxy_srccert_create(ctx); + if (!cert) + return NULL; + + if (ctx->opts->debug) { + log_dbg_printf("===> Forged server certificate:\n"); + pxy_debug_crt(cert->crt); + } + + if (WANT_CONNECT_LOG(ctx)) { + ctx->ssl_names = ssl_x509_names_to_str(cert->crt, 128); + ctx->ssl_orignames = ssl_x509_names_to_str(ctx->origcrt, 128); + } + + SSL_CTX *sslctx = pxy_srcsslctx_create(ctx, cert->crt, cert->chain, + cert->key); + cert_free(cert); + SSL *ssl = SSL_new(sslctx); +#ifdef USE_FOOTPRINT_HACKS + /* lower memory footprint for idle connections */ + SSL_set_mode(ssl, SSL_get_mode(ssl) | SSL_MODE_RELEASE_BUFFERS); +#endif /* USE_FOOTPRINT_HACKS */ + return ssl; +} + +#ifndef OPENSSL_NO_TLSEXT +/* + * OpenSSL servername callback, called when OpenSSL receives a servername + * TLS extension in the clientHello. Must switch to a new SSL_CTX with + * a different certificate if we want to replace the server cert here. + * We generate a new certificate if the current one does not match the + * supplied servername. This should only happen if the original destination + * server supplies a certificate which does not match the server name we + * indicate to it. + */ +static int +pxy_ossl_servername_cb(SSL *ssl, UNUSED int *al, void *arg) +{ + pxy_conn_ctx_t *ctx = arg; + const char *sn; + X509 *sslcrt, *newcrt; + + if (!(sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) + return SSL_TLSEXT_ERR_NOACK; + + if (ctx->opts->debug) { + if (!!strcmp(sn, ctx->sni)) { + /* + * This may happen if the client resumes a session, but + * uses a different SNI hostname when resuming than it + * used when the session was created. OpenSSL + * correctly ignores the SNI in the ClientHello in this + * case, but since we have already sent the SNI onwards + * to the original destination, there is no way back. + * We log an error and hope this never happens. + */ + log_err_printf("Warning: SNI parser yielded different " + "hostname than OpenSSL callback for " + "the same ClientHello message: " + "[%s] != [%s]\n", ctx->sni, sn); + } + } + + /* generate a new certificate with sn as additional altSubjectName + * and replace it both in the current SSL ctx and in the cert cache */ + if (!ctx->immutable_cert && + !ssl_x509_names_match((sslcrt = SSL_get_certificate(ssl)), sn)) { + if (ctx->opts->debug) + log_dbg_printf("Certificate cache: UPDATE " + "(SNI mismatch)\n"); + newcrt = ssl_x509_forge(ctx->opts->cacrt, ctx->opts->cakey, + sslcrt, sn, ctx->opts->key); + cachemgr_fkcrt_set(ctx->origcrt, newcrt); + if (ctx->opts->debug) { + log_dbg_printf("===> Updated forged server " + "certificate:\n"); + pxy_debug_crt(newcrt); + } + if (WANT_CONNECT_LOG(ctx)) { + if (ctx->ssl_names) { + free(ctx->ssl_names); + } + ctx->ssl_names = ssl_x509_names_to_str(newcrt, 128); + } + SSL_CTX *sslctx, *newsslctx; + newsslctx = pxy_srcsslctx_create(ctx, newcrt, ctx->opts->chain, + ctx->opts->key); + sslctx = SSL_get_SSL_CTX(ssl); + SSL_set_SSL_CTX(ssl, newsslctx); + SSL_CTX_free(sslctx); + X509_free(newcrt); + } else if (ctx->opts->debug) { + log_dbg_printf("Certificate cache: KEEP (SNI match or " + "target mode)\n"); + } + + return SSL_TLSEXT_ERR_OK; +} +#endif /* !OPENSSL_NO_TLSEXT */ + +/* + * Create new SSL context for outgoing connections to the original destination. + * If hostname sni is provided, use it for Server Name Indication. + */ +static SSL * +pxy_dstssl_create(pxy_conn_ctx_t *ctx) +{ + SSL_CTX *sslctx; + SSL *ssl; + SSL_SESSION *sess; + + sslctx = SSL_CTX_new(SSLv23_method()); + SSL_CTX_set_options(sslctx, SSL_OP_ALL); +#ifdef SSL_OP_TLS_ROLLBACK_BUG + SSL_CTX_set_options(sslctx, SSL_OP_TLS_ROLLBACK_BUG); +#endif /* SSL_OP_TLS_ROLLBACK_BUG */ +#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION + SSL_CTX_set_options(sslctx, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); +#endif /* SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION */ +#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS + SSL_CTX_set_options(sslctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); +#endif /* SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS */ +#ifdef SSL_OP_NO_TICKET + SSL_CTX_set_options(sslctx, SSL_OP_NO_TICKET); +#endif /* SSL_OP_NO_TICKET */ +#ifdef SSL_OP_NO_COMPRESSION + if (!ctx->opts->sslcomp) { + SSL_CTX_set_options(sslctx, SSL_OP_NO_COMPRESSION); + } +#endif /* SSL_OP_NO_COMPRESSION */ + SSL_CTX_set_cipher_list(sslctx, ctx->opts->ciphers); + SSL_CTX_set_verify(sslctx, SSL_VERIFY_NONE, NULL); + + ssl = SSL_new(sslctx); +#ifndef OPENSSL_NO_TLSEXT + if (ctx->sni) { + SSL_set_tlsext_host_name(ssl, ctx->sni); + } +#endif /* !OPENSSL_NO_TLSEXT */ + +#if USE_FOOTPRINT_HACKS + /* lower memory footprint for idle connections */ + SSL_set_mode(ssl, SSL_get_mode(ssl) | SSL_MODE_RELEASE_BUFFERS); +#endif /* USE_FOOTPRINT_HACKS */ + + /* session resuming based on remote endpoint address and port */ + sess = cachemgr_dsess_get((struct sockaddr *)&ctx->addr, + ctx->addrlen, ctx->sni); + if (sess) { + if (ctx->opts->debug) { + log_dbg_printf("Attempt reuse dst SSL session\n"); + } + SSL_set_session(ssl, sess); + SSL_SESSION_free(sess); + } + + return ssl; +} + +/* + * Free bufferenvent and close underlying socket properly. + * For OpenSSL bufferevents, this will shutdown the SSL connection. + */ +static void +bufferevent_free_and_close_fd(struct bufferevent *bev, pxy_conn_ctx_t *ctx) +{ + evutil_socket_t fd = bufferevent_getfd(bev); + SSL *ssl = NULL; + + if (ctx->spec->ssl && !ctx->passthrough) { + ssl = bufferevent_openssl_get_ssl(bev); + } + +#ifdef DEBUG_PROXY + if (ctx->opts->debug) { + log_dbg_printf(" %p free_and_close_fd\n", + (void*)bev); + } +#endif /* DEBUG_PROXY */ + + bufferevent_free(bev); + if (ssl) { + pxy_ssl_shutdown(ctx->evbase, ssl, fd); + } else { + evutil_closesocket(fd); + } +} + +/* + * Set up a bufferevent structure for either a dst or src connection, + * optionally with or without SSL. Sets all callbacks, enables read + * and write events, but does not call bufferevent_socket_connect(). + * + * For dst connections, pass -1 as fd. Pass a pointer to an initialized + * SSL struct as ssl if the connection should use SSL. + * + * Returns pointer to initialized bufferevent structure, as returned + * by bufferevent_socket_new() or bufferevent_openssl_socket_new(). + */ +static struct bufferevent * +pxy_bufferevent_setup(pxy_conn_ctx_t *ctx, evutil_socket_t fd, SSL *ssl) +{ + struct bufferevent *bev; + + if (ssl) { + bev = bufferevent_openssl_socket_new(ctx->evbase, fd, ssl, + ((fd == -1) ? BUFFEREVENT_SSL_CONNECTING + : BUFFEREVENT_SSL_ACCEPTING), + BEV_OPT_DEFER_CALLBACKS); + } else { + bev = bufferevent_socket_new(ctx->evbase, fd, + BEV_OPT_DEFER_CALLBACKS); + } + if (!bev) { + log_err_printf("Error creating bufferevent socket\n"); + return NULL; + } +#if LIBEVENT_VERSION_NUMBER >= 0x02010000 + if (ssl) { + /* Prevent unclean (dirty) shutdowns to cause error + * events on the SSL socket bufferevent. */ + bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); + } +#endif /* LIBEVENT_VERSION_NUMBER >= 0x02010000 */ + bufferevent_setcb(bev, pxy_bev_readcb, pxy_bev_writecb, + pxy_bev_eventcb, ctx); + bufferevent_enable(bev, EV_READ|EV_WRITE); +#ifdef DEBUG_PROXY + if (ctx->opts->debug) { + log_dbg_printf(" %p pxy_bufferevent_setup\n", + (void*)bev); + } +#endif /* DEBUG_PROXY */ + return bev; +} + +static char * +pxy_http_header_filter_line(const char *line, pxy_conn_ctx_t *ctx) +{ + char *space1, *space2; + + /* parse information for connect log */ + if (!ctx->http_method) { + /* first line */ + space1 = strchr(line, ' '); + space2 = space1 ? strchr(space1 + 1, ' ') : NULL; + if (!space1) { + /* not HTTP */ + ctx->seen_req_header = 1; + } else { + ctx->http_method = malloc(space1 - line + 1); + if (ctx->http_method) { + memcpy(ctx->http_method, line, space1 - line); + ctx->http_method[space1 - line] = '\0'; + } else { + log_err_printf("Warning: Out of memory\n"); + } + space1++; + if (!space2) { + /* HTTP/0.9 */ + ctx->seen_req_header = 1; + space2 = space1 + strlen(space1); + } + ctx->http_uri = malloc(space2 - space1 + 1); + if (ctx->http_uri) { + memcpy(ctx->http_uri, space1, space2 - space1); + ctx->http_uri[space2 - space1] = '\0'; + } else { + log_err_printf("Warning: Out of memory\n"); + } + } + } else { + /* not first line */ + if (!ctx->http_host && + !strncasecmp(line, "Host: ", 6)) { + ctx->http_host = strdup(line + 6); + } else if (!strncasecmp(line, "Connection: ", 12)) { + ctx->sent_http_conn_close = 1; + return strdup("Connection: close"); + } else if (!strncasecmp(line, "Accept-Encoding: ", 17) || + !strncasecmp(line, "Keep-Alive: ", 12)) { + return NULL; + } else if (line[0] == '\0') { + ctx->seen_req_header = 1; + if (!ctx->sent_http_conn_close) { + return strdup("Connection: close\r\n"); + } + } + } + + return (char*)line; +} + +/* + * Callback for read events on the up- and downstram connection bufferevents. + * Called when there is data ready in the input evbuffer. + */ +static void +pxy_bev_readcb(struct bufferevent *bev, void *arg) +{ + pxy_conn_ctx_t *ctx = arg; + pxy_conn_desc_t *other = (bev==ctx->src.bev) ? &ctx->dst : &ctx->src; + +#ifdef DEBUG_PROXY + if (ctx->opts->debug) { + log_dbg_printf("%p %p %s readcb\n", arg, (void*)bev, + (bev == ctx->src.bev) ? "src" : "dst"); + } +#endif /* DEBUG_PROXY */ + + if (!ctx->connected) { + log_err_printf("readcb called when other end not connected - " + "aborting.\n"); + exit(EXIT_FAILURE); + } + + struct evbuffer *inbuf = bufferevent_get_input(bev); + if (other->closed) { + evbuffer_drain(inbuf, evbuffer_get_length(inbuf)); + return; + } + + struct evbuffer *outbuf = bufferevent_get_output(other->bev); + + if (ctx->spec->http && !ctx->seen_req_header && (bev == ctx->src.bev) + && !ctx->passthrough) { + logbuf_t *lb = NULL, *tail = NULL; + char *line, *replace; + while ((line = evbuffer_readln(inbuf, NULL, + EVBUFFER_EOL_CRLF))) { + if (WANT_CONTENT_LOG(ctx)) { + logbuf_t *tmp; + tmp = logbuf_new_printf(-1, NULL, + "%s\r\n", line); + if (tail) { + if (tmp) { + tail->next = tmp; + tail = tail->next; + } + } else { + lb = tail = tmp; + } + } + replace = pxy_http_header_filter_line(line, ctx); + if (replace == line) { + evbuffer_add_printf(outbuf, "%s\r\n", line); + } else if (replace) { + evbuffer_add_printf(outbuf, "%s\r\n", replace); + free(replace); + } + free(line); + if (ctx->seen_req_header) { + if (WANT_CONNECT_LOG(ctx)) { + pxy_log_connect_http(ctx); + } + break; + } + } + if (lb && WANT_CONTENT_LOG(ctx)) { + log_content_submit(&ctx->logctx, lb, 0); + } + if (!ctx->seen_req_header) + return; + } + + /* no data left after parsing headers? */ + if (evbuffer_get_length(inbuf) == 0) + return; + + if (WANT_CONTENT_LOG(ctx)) { + logbuf_t *lb; + lb = logbuf_new_alloc(evbuffer_get_length(inbuf), -1, NULL); + if (lb) { + if (evbuffer_copyout(inbuf, lb->buf, lb->sz) != -1) { + log_content_submit(&ctx->logctx, lb, + (bev != ctx->src.bev)); + } + } + } + evbuffer_add_buffer(outbuf, inbuf); + if (evbuffer_get_length(outbuf) >= OUTBUF_LIMIT) { + /* temporarily disable data source; + * set an appropriate watermark. */ + bufferevent_setwatermark(other->bev, EV_WRITE, + OUTBUF_LIMIT/2, OUTBUF_LIMIT); + bufferevent_disable(bev, EV_READ); + } +} + +/* + * Callback for write events on the up- and downstream connection bufferevents. + * Called when either all data from the output evbuffer has been written, + * or if the outbuf is only half full again after having been full. + */ +static void +pxy_bev_writecb(struct bufferevent *bev, void *arg) +{ + pxy_conn_ctx_t *ctx = arg; + pxy_conn_desc_t *other = (bev==ctx->src.bev) ? &ctx->dst : &ctx->src; + +#ifdef DEBUG_PROXY + if (ctx->opts->debug) { + log_dbg_printf("%p %p %s writecb\n", arg, (void*)bev, + (bev == ctx->src.bev) ? "src" : "dst"); + } +#endif /* DEBUG_PROXY */ + + struct evbuffer *outbuf = bufferevent_get_output(bev); + if (evbuffer_get_length(outbuf) > 0) { + /* data source temporarily disabled; + * re-enable and reset watermark to 0. */ + bufferevent_setwatermark(bev, EV_WRITE, 0, 0); + if (!other->closed) { + bufferevent_enable(other->bev, EV_READ); + } + } else if (other->closed) { + /* finished writing and other end is closed; + * close this end too and clean up memory */ + bufferevent_free_and_close_fd(bev, ctx); + pxy_conn_ctx_free(ctx); + } +} + +/* + * Callback for meta events on the up- and downstream connection bufferevents. + * Called when EOF has been reached, a connection has been made, and on errors. + */ +static void +pxy_bev_eventcb(struct bufferevent *bev, short events, void *arg) +{ + pxy_conn_ctx_t *ctx = arg; + pxy_conn_desc_t *this = (bev==ctx->src.bev) ? &ctx->src : &ctx->dst; + pxy_conn_desc_t *other = (bev==ctx->src.bev) ? &ctx->dst : &ctx->src; + +#ifdef DEBUG_PROXY + if (ctx->opts->debug) { + log_dbg_printf("%p %p eventcb %s %s%s%s\n", arg, (void*)bev, + (bev == ctx->src.bev) ? "src" : "dst", + events & BEV_EVENT_CONNECTED ? "connected" : "", + events & BEV_EVENT_ERROR ? "error" : "", + events & BEV_EVENT_EOF ? "eof" : ""); + } +#endif /* DEBUG_PROXY */ + + if ((events & BEV_EVENT_CONNECTED) && (bev == ctx->dst.bev)) { + ctx->connected = 1; + + /* wrap client-side socket in an eventbuffer */ + if (ctx->spec->ssl && !ctx->passthrough) { + ctx->src.ssl = pxy_srcssl_create(ctx, this->ssl); + if (!ctx->src.ssl) { + bufferevent_free_and_close_fd(bev, ctx); + if (ctx->opts->passthrough) { + ctx->passthrough = 1; + log_dbg_printf("No cert found; " + "falling back " + "to passthrough\n"); + pxy_fd_readcb(ctx->fd, 0, ctx); + return; + } + evutil_closesocket(ctx->fd); + pxy_conn_ctx_free(ctx); + return; + } + } + 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); + } + bufferevent_free_and_close_fd(bev, ctx); + evutil_closesocket(ctx->fd); + pxy_conn_ctx_free(ctx); + return; + } + + /* prepare logging, part 2 */ + if (WANT_CONNECT_LOG(ctx) || WANT_CONTENT_LOG(ctx)) { + ctx->dst_str = sys_sockaddr_str((struct sockaddr *) + &ctx->addr, + ctx->addrlen); + } + if (WANT_CONTENT_LOG(ctx)) { + log_content_open(&ctx->logctx, ctx->src_str, + ctx->dst_str); + } + + /* log connection */ + if (!ctx->spec->http || ctx->passthrough) { + if (WANT_CONNECT_LOG(ctx)) { + pxy_log_connect_nonhttp(ctx); + } + } + } + + if (events & BEV_EVENT_ERROR) { + unsigned long sslerr; + int have_sslerr = 0; + + /* Can happen for socket errs, ssl errs; + * may happen for unclean ssl socket shutdowns. */ + sslerr = bufferevent_get_openssl_error(bev); + if (sslerr) + have_sslerr = 1; + if (!errno && !sslerr) { +#if LIBEVENT_VERSION_NUMBER >= 0x02010000 + /* We have disabled notification for unclean shutdowns + * so this should not happen; log a warning. */ + log_err_printf("Warning: Spurious error from " + "bufferevent (errno=0,sslerr=0)\n"); +#else /* LIBEVENT_VERSION_NUMBER < 0x02010000 */ + /* Older versions of libevent will report these. */ + if (ctx->opts->debug) { + log_dbg_printf("Unclean SSL shutdown.\n"); + } +#endif /* LIBEVENT_VERSION_NUMBER < 0x02010000 */ + } else if (ERR_GET_REASON(sslerr) == + SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE) { + /* these can happen due to client cert auth, + * only log error if debugging is activated */ + log_dbg_printf("Error from bufferevent: " + "%i:%s %lu:%i:%s:%i:%s:%i:%s\n", + errno, + errno ? strerror(errno) : "-", + sslerr, + ERR_GET_REASON(sslerr), + sslerr ? + ERR_reason_error_string(sslerr) : "-", + ERR_GET_LIB(sslerr), + sslerr ? + ERR_lib_error_string(sslerr) : "-", + ERR_GET_FUNC(sslerr), + sslerr ? + ERR_func_error_string(sslerr) : "-"); + while ((sslerr = bufferevent_get_openssl_error(bev))) { + log_dbg_printf("Additional SSL error: " + "%lu:%i:%s:%i:%s:%i:%s\n", + sslerr, + ERR_GET_REASON(sslerr), + ERR_reason_error_string(sslerr), + ERR_GET_LIB(sslerr), + ERR_lib_error_string(sslerr), + ERR_GET_FUNC(sslerr), + ERR_func_error_string(sslerr)); + } + } else { + /* real errors */ + log_err_printf("Error from bufferevent: " + "%i:%s %lu:%i:%s:%i:%s:%i:%s\n", + errno, + errno ? strerror(errno) : "-", + sslerr, + ERR_GET_REASON(sslerr), + sslerr ? + ERR_reason_error_string(sslerr) : "-", + ERR_GET_LIB(sslerr), + sslerr ? + ERR_lib_error_string(sslerr) : "-", + ERR_GET_FUNC(sslerr), + sslerr ? + ERR_func_error_string(sslerr) : "-"); + while ((sslerr = bufferevent_get_openssl_error(bev))) { + log_err_printf("Additional SSL error: " + "%lu:%i:%s:%i:%s:%i:%s\n", + sslerr, + ERR_GET_REASON(sslerr), + ERR_reason_error_string(sslerr), + ERR_GET_LIB(sslerr), + ERR_lib_error_string(sslerr), + ERR_GET_FUNC(sslerr), + ERR_func_error_string(sslerr)); + } + } + + if (!ctx->connected) { + /* the callout to the original destination failed, + * e.g. because it asked for client cert auth, so + * close the accepted socket and clean up */ + if (bev == ctx->dst.bev && ctx->dst.ssl && + ctx->opts->passthrough && have_sslerr) { + /* ssl callout failed, fall back to plain + * TCP passthrough of SSL connection */ + SSL_free(ctx->dst.ssl); + ctx->dst.ssl = NULL; + ctx->passthrough = 1; + log_dbg_printf("SSL dst connection failed; fal" + "ling back to passthrough\n"); + pxy_fd_readcb(ctx->fd, 0, ctx); + return; + } + evutil_closesocket(ctx->fd); + other->closed = 1; + } else if (!other->closed) { + /* if the other end is still open and doesn't have data + * to send, close it, otherwise it's writecb will close + * it after writing what's left in the output buffer */ + struct evbuffer *outbuf; + outbuf = bufferevent_get_output(other->bev); + if (evbuffer_get_length(outbuf) == 0) { + bufferevent_free_and_close_fd(other->bev, ctx); + other->closed = 1; + } + } + goto leave; + } + + if (events & BEV_EVENT_EOF) { + if (!other->closed) { + struct evbuffer *inbuf, *outbuf; + inbuf = bufferevent_get_input(bev); + outbuf = bufferevent_get_output(other->bev); + if (evbuffer_get_length(inbuf) > 0) { + evbuffer_add_buffer(outbuf, inbuf); + } else { + /* if the other end is still open and doesn't + * have data to send, close it, otherwise it's + * writecb will close it after writing what's + * left in the output buffer. */ + if (evbuffer_get_length(outbuf) == 0) { + bufferevent_free_and_close_fd( + other->bev, ctx); + other->closed = 1; + } + } + } + goto leave; + } + + if (events & !BEV_EVENT_CONNECTED) { + log_err_printf("Unknown bufferevent 0x%02X\n", (int)events); + } + return; + +leave: + this->closed = 1; + bufferevent_free_and_close_fd(bev, ctx); + if (other->closed) { + pxy_conn_ctx_free(ctx); + } +} + +/* + * Complete the connection. This gets called after finding out where to + * connect to. + */ +static void +pxy_conn_connect(pxy_conn_ctx_t *ctx) +{ + if (!ctx->addrlen) { + log_err_printf("No target address; aborting connection\n"); + evutil_closesocket(ctx->fd); + pxy_conn_ctx_free(ctx); + return; + } + + /* TODO determine if we should terminate or redirect this connection, + * for example for OCSP denial, redirection to evilgrade, etc. */ + + /* create server-side socket and eventbuffer */ + if (ctx->spec->ssl && !ctx->passthrough) { + ctx->dst.ssl = pxy_dstssl_create(ctx); + if (!ctx->dst.ssl) { + log_err_printf("Error creating SSL\n"); + evutil_closesocket(ctx->fd); + pxy_conn_ctx_free(ctx); + return; + } + } + ctx->dst.bev = pxy_bufferevent_setup(ctx, -1, ctx->dst.ssl); + if (!ctx->dst.bev) { + if (ctx->dst.ssl) { + SSL_free(ctx->dst.ssl); + } + evutil_closesocket(ctx->fd); + pxy_conn_ctx_free(ctx); + return; + } + + /* initiate connection */ + bufferevent_socket_connect(ctx->dst.bev, + (struct sockaddr *)&ctx->addr, + ctx->addrlen); +} + +#ifndef OPENSSL_NO_TLSEXT +/* + * The SNI hostname has been resolved. Fill the first resolved address into + * the context and continue connecting. + */ +static void +pxy_sni_resolve_cb(int errcode, struct evutil_addrinfo *ai, void *arg) +{ + pxy_conn_ctx_t *ctx = arg; + + if (errcode) { + log_err_printf("Cannot resolve SNI hostname '%s': %s\n", + ctx->sni, evutil_gai_strerror(errcode)); + evutil_closesocket(ctx->fd); + pxy_conn_ctx_free(ctx); + return; + } + + memcpy(&ctx->addr, ai->ai_addr, ai->ai_addrlen); + ctx->addrlen = ai->ai_addrlen; + evutil_freeaddrinfo(ai); + pxy_conn_connect(ctx); +} +#endif /* !OPENSSL_NO_TLSEXT */ + +/* + * The src fd is readable. This is used to sneak-preview the SNI on SSL + * connections. If ctx->ev is NULL, it was called manually for a non-SSL + * connection. If ctx->passthrough is set, it was called a second time + * after the first ssl callout failed because of client cert auth. + */ +#ifndef OPENSSL_NO_TLSEXT +#define MAYBE_UNUSED +#else /* OPENSSL_NO_TLSEXT */ +#define MAYBE_UNUSED UNUSED +#endif /* OPENSSL_NO_TLSEXT */ +static void +pxy_fd_readcb(MAYBE_UNUSED evutil_socket_t fd, UNUSED short what, void *arg) +#undef MAYBE_UNUSED +{ + pxy_conn_ctx_t *ctx = arg; + +#ifndef OPENSSL_NO_TLSEXT + /* for SSL, peek clientHello and parse SNI from it */ + if (ctx->spec->ssl && !ctx->passthrough /*&& ctx->ev*/) { + unsigned char buf[1024]; + ssize_t n; + + n = recv(fd, buf, sizeof(buf), MSG_PEEK); + if (n == -1) { + log_err_printf("Error peeking on fd, aborting " + "connection\n"); + evutil_closesocket(fd); + pxy_conn_ctx_free(ctx); + return; + } + if (n == 0) { + /* socket got closed while we were waiting */ + evutil_closesocket(fd); + pxy_conn_ctx_free(ctx); + return; + } + + ctx->sni = ssl_tls_clienthello_parse_sni(buf, &n); + if (ctx->opts->debug) { + log_dbg_printf("SNI peek: [%s]\n", + ctx->sni ? ctx->sni : "n/a"); + } + if (!ctx->sni && (n == -1) && (ctx->sni_peek_retries++ < 50)) { + /* ssl_tls_clienthello_parse_sni indicates that we + * should retry later when we have more data, and we + * haven't reached the maximum retry count yet. + * Reschedule this event as timeout-only event in + * order to prevent busy looping over the read event. + * Because we only peeked at the pending bytes and + * never actually read them, fd is still ready for + * reading now. We use 25 * 0.2 s = 5 s timeout. */ + struct timeval retry_delay = {0, 100}; + + event_free(ctx->ev); + ctx->ev = event_new(ctx->evbase, fd, 0, + pxy_fd_readcb, ctx); + if (!ctx->ev) { + log_err_printf("Error creating retry " + "event, aborting " + "connection\n"); + evutil_closesocket(fd); + pxy_conn_ctx_free(ctx); + return; + } + event_add(ctx->ev, &retry_delay); + return; + } + event_free(ctx->ev); + ctx->ev = NULL; + } + + if (ctx->sni && !ctx->addrlen && ctx->spec->sni_port) { + char sniport[6]; + struct evutil_addrinfo hints; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = ctx->af; + hints.ai_flags = EVUTIL_AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + snprintf(sniport, sizeof(sniport), "%i", ctx->spec->sni_port); + evdns_getaddrinfo(ctx->dnsbase, ctx->sni, sniport, &hints, + pxy_sni_resolve_cb, ctx); + return; + } +#endif /* !OPENSSL_NO_TLSEXT */ + + pxy_conn_connect(ctx); +} + +/* + * Callback for accept events on the socket listener bufferevent. + * Called when a new incoming connection has been accepted. + * Initiates the connection to the server. The incoming connection + * from the client is not being activated until we have a successful + * connection to the server, because we need the server's certificate + * in order to set up the SSL session to the client. + * For consistency, plain TCP works the same way, even if we could + * start reading from the client while waiting on the connection to + * the server to connect. + */ +void +pxy_conn_setup(evutil_socket_t fd, + struct sockaddr *peeraddr, int peeraddrlen, + pxy_thrmgr_ctx_t *thrmgr, + proxyspec_t *spec, opts_t *opts) +{ + pxy_conn_ctx_t *ctx; + + /* create per connection pair state and attach to thread */ + ctx = pxy_conn_ctx_new(spec, opts, thrmgr, fd); + if (!ctx) { + log_err_printf("Error allocating memory\n"); + evutil_closesocket(fd); + return; + } + + ctx->af = peeraddr->sa_family; + + /* determine original destination of connection */ + if (spec->natlookup) { + /* NAT engine lookup */ + ctx->addrlen = sizeof(struct sockaddr_storage); + if (spec->natlookup((struct sockaddr *)&ctx->addr, + &ctx->addrlen, fd, + peeraddr, peeraddrlen) == -1) { + log_err_printf("Connection not found in NAT " + "state table, aborting connection\n"); + evutil_closesocket(fd); + pxy_conn_ctx_free(ctx); + return; + } + } else if (spec->connect_addrlen > 0) { + /* static forwarding */ + ctx->addrlen = spec->connect_addrlen; + memcpy(&ctx->addr, &spec->connect_addr, ctx->addrlen); + } else { + /* SNI mode */ + if (!ctx->spec->ssl) { + /* if this happens, the proxyspec parser is broken */ + log_err_printf("SNI mode used for non-SSL connection; " + "aborting connection\n"); + evutil_closesocket(fd); + pxy_conn_ctx_free(ctx); + return; + } + } + + /* prepare logging, part 1 */ + if (WANT_CONNECT_LOG(ctx) || WANT_CONTENT_LOG(ctx)) { + ctx->src_str = sys_sockaddr_str(peeraddr, peeraddrlen); + } + + /* for SSL, defer dst connection setup to initial_readcb */ + if (ctx->spec->ssl) { + ctx->ev = event_new(ctx->evbase, fd, EV_READ, pxy_fd_readcb, + ctx); + event_add(ctx->ev, NULL); + } else { + pxy_fd_readcb(fd, 0, ctx); + } +} + +/* vim: set noet ft=c: */ diff --git a/pxyconn.h b/pxyconn.h new file mode 100644 index 0000000..48d979c --- /dev/null +++ b/pxyconn.h @@ -0,0 +1,47 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PXYCONN_H +#define PXYCONN_H + +#include "opts.h" +#include "attrib.h" +#include "pxythrmgr.h" + +#include +#include + +#include +#include + +void pxy_conn_setup(evutil_socket_t, struct sockaddr *, int, + pxy_thrmgr_ctx_t *, proxyspec_t *, opts_t *) NONNULL(); + +#endif /* !PXYCONN_H */ + +/* vim: set noet ft=c: */ diff --git a/pxysslshut.c b/pxysslshut.c new file mode 100644 index 0000000..8941c10 --- /dev/null +++ b/pxysslshut.c @@ -0,0 +1,178 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pxysslshut.h" + +#include "log.h" +#include "attrib.h" + +#include +#include + +#include +#include + + +/* + * Cleanly shut down an SSL socket. Libevent currently has no support for + * cleanly shutting down an SSL socket so we work around that by using a + * low-level event. This works for recent versions of OpenSSL. OpenSSL + * with the older SSL_shutdown() semantics, not exposing WANT_READ/WRITE + * may or may not work. + */ + +/* + * SSL shutdown context. + */ + +typedef struct pxy_ssl_shutdown_ctx { + struct event_base *evbase; + struct event *ev; + SSL *ssl; + unsigned int retries; +} pxy_ssl_shutdown_ctx_t; + +static pxy_ssl_shutdown_ctx_t * +pxy_ssl_shutdown_ctx_new(struct event_base *evbase, SSL *ssl) +{ + pxy_ssl_shutdown_ctx_t *ctx; + + ctx = malloc(sizeof(pxy_ssl_shutdown_ctx_t)); + if (!ctx) + return NULL; + ctx->evbase = evbase; + ctx->ssl = ssl; + ctx->ev = NULL; + ctx->retries = 0; + return ctx; +} + +static void +pxy_ssl_shutdown_ctx_free(pxy_ssl_shutdown_ctx_t *ctx) +{ + free(ctx); +} + +/* + * The shutdown socket event handler. This is either + * scheduled as a timeout-only event, or as a fd read or + * fd write event, depending on whether SSL_shutdown() + * indicates it needs read or write on the socket. + */ +static void +pxy_ssl_shutdown_cb(evutil_socket_t fd, UNUSED short what, void *arg) +{ + pxy_ssl_shutdown_ctx_t *ctx = arg; + struct timeval retry_delay = {0, 100}; + SSL_CTX *sslctx; + short want = 0; + int rv, sslerr; + + if (ctx->ev) { + event_free(ctx->ev); + ctx->ev = NULL; + } + + /* + * Use the new (post-2008) semantics for SSL_shutdown() on a + * non-blocking socket. SSL_shutdown() returns -1 and WANT_READ + * if the other end's close notify was not received yet, and + * WANT_WRITE it could not write our own close notify. + * + * This is a good collection of recent and relevant documents: + * http://bugs.python.org/issue8108 + */ + rv = SSL_shutdown(ctx->ssl); + if (rv == 1) + goto complete; + if (rv != -1) { + goto retry; + } + switch ((sslerr = SSL_get_error(ctx->ssl, rv))) { + case SSL_ERROR_WANT_READ: + want = EV_READ; + goto retry; + case SSL_ERROR_WANT_WRITE: + want = EV_WRITE; + goto retry; + case SSL_ERROR_ZERO_RETURN: + goto retry; + case SSL_ERROR_SYSCALL: + goto complete; + default: + log_err_printf("Unhandled SSL_shutdown() " + "error %i. Closing fd.\n", sslerr); + goto complete; + } + goto complete; + +retry: + if (ctx->retries++ >= 50) { + log_err_printf("Failed to shutdown SSL connection cleanly: " + "Max retries reached. Closing fd.\n"); + goto complete; + } + ctx->ev = event_new(ctx->evbase, fd, want, pxy_ssl_shutdown_cb, ctx); + if (ctx->ev) { + event_add(ctx->ev, want ? NULL : &retry_delay); + return; + } + log_err_printf("Failed to shutdown SSL connection cleanly: " + "Cannot create event. Closing fd.\n"); + +complete: + sslctx = SSL_get_SSL_CTX(ctx->ssl); + SSL_free(ctx->ssl); + SSL_CTX_free(sslctx); + evutil_closesocket(fd); + pxy_ssl_shutdown_ctx_free(ctx); +} + +/* + * Cleanly shutdown an SSL session on file descriptor fd using low-level + * file descriptor readiness events on event base evbase. + * Guarantees that SSL and the corresponding SSL_CTX are freed and the + * socket is closed, eventually, or in the case of fatal errors, immediately. + */ +void +pxy_ssl_shutdown(struct event_base *evbase, SSL *ssl, evutil_socket_t fd) +{ + pxy_ssl_shutdown_ctx_t *sslshutctx; + + sslshutctx = pxy_ssl_shutdown_ctx_new(evbase, ssl); + if (!sslshutctx) { + SSL_CTX *sslctx = SSL_get_SSL_CTX(ssl); + SSL_free(ssl); + SSL_CTX_free(sslctx); + evutil_closesocket(fd); + return; + } + pxy_ssl_shutdown_cb(fd, 0, sslshutctx); +} + +/* vim: set noet ft=c: */ diff --git a/pxysslshut.h b/pxysslshut.h new file mode 100644 index 0000000..e5dd25d --- /dev/null +++ b/pxysslshut.h @@ -0,0 +1,42 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PXYSSLSHUT_H +#define PXYSSLSHUT_H + +#include "attrib.h" + +#include +#include +#include + +void pxy_ssl_shutdown(struct event_base *, SSL *, evutil_socket_t) NONNULL(); + +#endif /* !PXYSSLSHUT_H */ + +/* vim: set noet ft=c: */ diff --git a/pxythrmgr.c b/pxythrmgr.c new file mode 100644 index 0000000..7b02bdb --- /dev/null +++ b/pxythrmgr.c @@ -0,0 +1,236 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pxythrmgr.h" + +#include "sys.h" +#include "log.h" + +#include + +/* + * Proxy thread manager: manages the connection handling worker threads + * and the per-thread resources (i.e. event bases). The load is shared + * across num_cpu * 2 connection handling threads, using the number of + * currently assigned connections as the sole metric. + * + * The attach and detach functions are thread-safe. + */ + +typedef struct pxy_thr_ctx { + pthread_t thr; + size_t load; + struct event_base *evbase; + struct evdns_base *dnsbase; +} pxy_thr_ctx_t; + +struct pxy_thrmgr_ctx { + int num_thr; + pxy_thr_ctx_t **thr; + pthread_mutex_t mutex; +}; + +/* + * Dummy recurring timer event to prevent the event loops from exiting when + * they run out of events. + */ +static void +pxy_thrmgr_timer_cb(UNUSED evutil_socket_t fd, UNUSED short what, + UNUSED void *arg) +{ + /* do nothing */ +} + +/* + * Thread entry point; runs the event loop of the event base. + * Does not exit until the libevent loop is broken explicitly. + */ +static void * +pxy_thrmgr_thr(void *arg) +{ + pxy_thr_ctx_t *ctx = arg; + struct timeval timer_delay = {60, 0}; + struct event *ev; + + ev = event_new(ctx->evbase, -1, EV_PERSIST, pxy_thrmgr_timer_cb, NULL); + if (!ev) + return NULL; + evtimer_add(ev, &timer_delay); + event_base_dispatch(ctx->evbase); + event_free(ev); + + return NULL; +} + +/* + * Create new thread manager and start threads. + */ +pxy_thrmgr_ctx_t * +pxy_thrmgr_new(UNUSED opts_t *opts) +{ + pxy_thrmgr_ctx_t *ctx; + int idx = -1; + + if (!(ctx = malloc(sizeof(pxy_thrmgr_ctx_t)))) + return NULL; + memset(ctx, 0, sizeof(pxy_thrmgr_ctx_t)); + + ctx->num_thr = 2 * sys_get_cpu_cores(); + + if (!(ctx->thr = malloc(ctx->num_thr * sizeof(void*)))) + goto leave; + + for (idx = 0; idx < ctx->num_thr; idx++) { + if (!(ctx->thr[idx] = malloc(sizeof(pxy_thr_ctx_t)))) + goto leave; + ctx->thr[idx]->evbase = event_base_new(); + if (!ctx->thr[idx]->evbase) + goto leave; + ctx->thr[idx]->dnsbase = evdns_base_new( + ctx->thr[idx]->evbase, 1); + if (!ctx->thr[idx]->dnsbase) + goto leave; + ctx->thr[idx]->load = 0; + } + + for (idx = 0; idx < ctx->num_thr; idx++) { + if (pthread_create(&ctx->thr[idx]->thr, NULL, + pxy_thrmgr_thr, ctx->thr[idx])) + goto leave_thr; + } + + log_dbg_printf("Started %d connection handling threads\n", + ctx->num_thr); + + pthread_mutex_init(&ctx->mutex, NULL); + return ctx; + +leave_thr: + idx--; + while (idx >= 0) { + pthread_cancel(ctx->thr[idx]->thr); + pthread_join(ctx->thr[idx]->thr, NULL); + idx--; + } + idx = ctx->num_thr; + +leave: + while (idx >= 0) { + if (ctx->thr[idx]) { + if (ctx->thr[idx]->dnsbase) { + evdns_base_free(ctx->thr[idx]->dnsbase, 0); + } + if (ctx->thr[idx]->evbase) { + event_base_free(ctx->thr[idx]->evbase); + } + free(ctx->thr[idx]); + } + idx--; + } + if (ctx->thr) + free(ctx->thr); + free(ctx); + return NULL; +} + +/* + * Destroy the event manager and stop all threads. + */ +void +pxy_thrmgr_free(pxy_thrmgr_ctx_t *ctx) +{ + pthread_mutex_destroy(&ctx->mutex); + for (int idx = 0; idx < ctx->num_thr; idx++) { + event_base_loopbreak(ctx->thr[idx]->evbase); + } + for (int idx = 0; idx < ctx->num_thr; idx++) { + pthread_join(ctx->thr[idx]->thr, NULL); + } + for (int idx = 0; idx < ctx->num_thr; idx++) { + evdns_base_free(ctx->thr[idx]->dnsbase, 0); + event_base_free(ctx->thr[idx]->evbase); + free(ctx->thr[idx]); + } + free(ctx->thr); + free(ctx); +} + +/* + * Attach a new connection to a thread. Chooses the thread with the fewest + * currently active connections, returns the appropriate event bases. + * Returns the index of the chosen thread (for passing to _detach later). + * This function cannot fail. + */ +int +pxy_thrmgr_attach(pxy_thrmgr_ctx_t *ctx, struct event_base **evbase, + struct evdns_base **dnsbase) +{ + int thridx; + size_t minload; + + thridx = 0; + pthread_mutex_lock(&ctx->mutex); + minload = ctx->thr[thridx]->load; +#ifdef DEBUG_THREAD + log_dbg_printf("===> Proxy connection handler thread status:\n" + "thr[%d]: %zu\n", thridx, minload); +#endif /* DEBUG_THREAD */ + for (int idx = 1; idx < ctx->num_thr; idx++) { +#ifdef DEBUG_THREAD + log_dbg_printf("thr[%d]: %zu\n", idx, ctx->thr[idx]->load); +#endif /* DEBUG_THREAD */ + if (minload > ctx->thr[idx]->load) { + minload = ctx->thr[idx]->load; + thridx = idx; + } + } + *evbase = ctx->thr[thridx]->evbase; + *dnsbase = ctx->thr[thridx]->dnsbase; + ctx->thr[thridx]->load++; + pthread_mutex_unlock(&ctx->mutex); + +#ifdef DEBUG_THREAD + log_dbg_printf("thridx: %d\n", thridx); +#endif /* DEBUG_THREAD */ + + return thridx; +} + +/* + * Detach a connection from a thread by index. + * This function cannot fail. + */ +void +pxy_thrmgr_detach(pxy_thrmgr_ctx_t *ctx, int thridx) +{ + pthread_mutex_lock(&ctx->mutex); + ctx->thr[thridx]->load--; + pthread_mutex_unlock(&ctx->mutex); +} + +/* vim: set noet ft=c: */ diff --git a/pxythrmgr.h b/pxythrmgr.h new file mode 100644 index 0000000..5b1a573 --- /dev/null +++ b/pxythrmgr.h @@ -0,0 +1,52 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PXYTHRMGR_H +#define PXYTHRMGR_H + +#include "opts.h" +#include "attrib.h" + +#include +#include + +#include +#include + +typedef struct pxy_thrmgr_ctx pxy_thrmgr_ctx_t; + +pxy_thrmgr_ctx_t * pxy_thrmgr_new(opts_t *); +void pxy_thrmgr_free(pxy_thrmgr_ctx_t *); + +int pxy_thrmgr_attach(pxy_thrmgr_ctx_t *, struct event_base **, + struct evdns_base **); +void pxy_thrmgr_detach(pxy_thrmgr_ctx_t *, int); + +#endif /* !PXYTHRMGR_H */ + +/* vim: set noet ft=c: */ diff --git a/ssl.c b/ssl.c new file mode 100644 index 0000000..777d018 --- /dev/null +++ b/ssl.c @@ -0,0 +1,1565 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ssl.h" + +#include "log.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#ifndef OPENSSL_NO_DH +#include +#endif /* !OPENSSL_NO_DH */ +#include +#include + +/* + * Collection of helper functions on top of the OpenSSL API. + */ + +/* + * Print OpenSSL version and build-time configuration to standard error and + * return. + */ +void +ssl_openssl_version(void) +{ + fprintf(stderr, "compiled against %s (%lx)\n", + OPENSSL_VERSION_TEXT, + (long unsigned int)OPENSSL_VERSION_NUMBER); + fprintf(stderr, "rtlinked against %s (%lx)\n", + SSLeay_version(SSLEAY_VERSION), + SSLeay()); +#ifndef OPENSSL_NO_TLSEXT + fprintf(stderr, "TLS Server Name Indication (SNI) supported\n"); +#else /* OPENSSL_NO_TLSEXT */ + fprintf(stderr, "TLS Server Name Indication (SNI) not supported\n"); +#endif /* OPENSSL_NO_TLSEXT */ +#ifdef OPENSSL_THREADS +#ifndef OPENSSL_NO_THREADID + fprintf(stderr, "OpenSSL is thread-safe with THREADID\n"); +#else /* OPENSSL_NO_THREADID */ + fprintf(stderr, "OpenSSL is thread-safe without THREADID\n"); +#endif /* OPENSSL_NO_THREADID */ +#else /* !OPENSSL_THREADS */ + fprintf(stderr, "OpenSSL is not thread-safe\n"); +#endif /* !OPENSSL_THREADS */ + + fprintf(stderr, "SSL/TLS algorithm availability:"); +#ifndef OPENSSL_NO_RSA + fprintf(stderr, " RSA"); +#else /* OPENSSL_NO_RSA */ + fprintf(stderr, " !RSA"); +#endif /* OPENSSL_NO_RSA */ +#ifndef OPENSSL_NO_DSA + fprintf(stderr, " DSA"); +#else /* OPENSSL_NO_DSA */ + fprintf(stderr, " !DSA"); +#endif /* OPENSSL_NO_DSA */ +#ifndef OPENSSL_NO_ECDSA + fprintf(stderr, " ECDSA"); +#else /* OPENSSL_NO_ECDSA */ + fprintf(stderr, " !ECDSA"); +#endif /* OPENSSL_NO_ECDSA */ +#ifndef OPENSSL_NO_DH + fprintf(stderr, " DH"); +#else /* OPENSSL_NO_DH */ + fprintf(stderr, " !DH"); +#endif /* OPENSSL_NO_DH */ +#ifndef OPENSSL_NO_ECDH + fprintf(stderr, " ECDH"); +#else /* OPENSSL_NO_ECDH */ + fprintf(stderr, " !ECDH"); +#endif /* OPENSSL_NO_ECDH */ + fprintf(stderr, "\n"); + + fprintf(stderr, "OpenSSL option availability:"); +#ifdef SSL_OP_NO_COMPRESSION + fprintf(stderr, " SSL_OP_NO_COMPRESSION"); +#else /* !SSL_OP_NO_COMPRESSION */ + fprintf(stderr, " !SSL_OP_NO_COMPRESSION"); +#endif /* SSL_OP_NO_COMPRESSION */ +#ifdef SSL_OP_NO_TICKET + fprintf(stderr, " SSL_OP_NO_TICKET"); +#else /* !SSL_OP_NO_TICKET */ + fprintf(stderr, " !SSL_OP_NO_TICKET"); +#endif /* SSL_OP_NO_TICKET */ +#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION + fprintf(stderr, " SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION"); +#else /* !SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION */ + fprintf(stderr, " !SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION"); +#endif /* !SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION */ +#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS + fprintf(stderr, " SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS"); +#else /* !SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS */ + fprintf(stderr, " !SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS"); +#endif /* !SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS */ +#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION + fprintf(stderr, " SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION"); +#else /* !SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION */ + fprintf(stderr, " !SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION"); +#endif /* !SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION */ +#ifdef SSL_OP_TLS_ROLLBACK_BUG + fprintf(stderr, " SSL_OP_TLS_ROLLBACK_BUG"); +#else /* !SSL_OP_TLS_ROLLBACK_BUG */ + fprintf(stderr, " !SSL_OP_TLS_ROLLBACK_BUG"); +#endif /* !SSL_OP_TLS_ROLLBACK_BUG */ + fprintf(stderr, "\n"); +} + +/* + * 1 if OpenSSL has been initialized, 0 if not. When calling a _load() + * function the first time, OpenSSL will automatically be initialized. + * Not protected by a mutex and thus not thread-safe. + */ +static int ssl_initialized = 0; + +#ifdef OPENSSL_THREADS +struct CRYPTO_dynlock_value { + pthread_mutex_t mutex; +}; +static pthread_mutex_t *ssl_mutex; +static int ssl_mutex_num; + +/* + * OpenSSL thread-safety locking callback, #1. + */ +static void +ssl_thr_locking_cb(int mode, int type, UNUSED const char *file, + UNUSED int line) { + if (type < ssl_mutex_num) { + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&ssl_mutex[type]); + else + pthread_mutex_unlock(&ssl_mutex[type]); + } +} + +/* + * OpenSSL thread-safety locking callback, #2. + */ +static struct CRYPTO_dynlock_value * +ssl_thr_dyn_create_cb(UNUSED const char *file, UNUSED int line) +{ + struct CRYPTO_dynlock_value *dl; + + if ((dl = malloc(sizeof(struct CRYPTO_dynlock_value)))) { + pthread_mutex_init(&dl->mutex, NULL); + } + return dl; +} + +/* + * OpenSSL thread-safety locking callback, #3. + */ +static void +ssl_thr_dyn_lock_cb(int mode, struct CRYPTO_dynlock_value *dl, + UNUSED const char *file, UNUSED int line) +{ + if (mode & CRYPTO_LOCK) { + pthread_mutex_lock(&dl->mutex); + } else { + pthread_mutex_unlock(&dl->mutex); + } +} + +/* + * OpenSSL thread-safety locking callback, #4. + */ +static void +ssl_thr_dyn_destroy_cb(struct CRYPTO_dynlock_value *dl, + UNUSED const char *file, UNUSED int line) +{ + pthread_mutex_destroy(&dl->mutex); + free(dl); +} + +#ifdef OPENSSL_NO_THREADID +/* + * OpenSSL thread-safety thread ID callback, legacy version. + */ +static unsigned long +ssl_thr_id_cb(void) { + return (unsigned long) pthread_self(); +} +#else /* !OPENSSL_NO_THREADID */ +/* + * OpenSSL thread-safety thread ID callback, up-to-date version. + */ +static void +ssl_thr_id_cb(CRYPTO_THREADID *id) +{ + CRYPTO_THREADID_set_numeric(id, (unsigned long) pthread_self()); +} +#endif /* !OPENSSL_NO_THREADID */ +#endif /* OPENSSL_THREADS */ + +/* + * Initialize OpenSSL and verify the random number generator works. + * Returns -1 on failure, 0 on success. + */ +int +ssl_init(void) +{ +#ifndef PURIFY + int fd; + char buf[256]; +#endif /* !PURIFY */ + + if (ssl_initialized) + return 0; + + /* general initialization */ + SSL_library_init(); +#ifdef PURIFY + CRYPTO_malloc_init(); + CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); +#endif /* PURIFY */ + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + + /* thread-safety */ +#ifdef OPENSSL_THREADS + ssl_mutex_num = CRYPTO_num_locks(); + ssl_mutex = malloc(ssl_mutex_num * sizeof(*ssl_mutex)); + for (int i = 0; i < ssl_mutex_num; i++) { + pthread_mutex_init(&ssl_mutex[i], NULL); + } + CRYPTO_set_locking_callback(ssl_thr_locking_cb); + CRYPTO_set_dynlock_create_callback(ssl_thr_dyn_create_cb); + CRYPTO_set_dynlock_lock_callback(ssl_thr_dyn_lock_cb); + CRYPTO_set_dynlock_destroy_callback(ssl_thr_dyn_destroy_cb); +#ifdef OPENSSL_NO_THREADID + CRYPTO_set_id_callback(ssl_thr_id_cb); +#else /* !OPENSSL_NO_THREADID */ + CRYPTO_THREADID_set_callback(ssl_thr_id_cb); +#endif /* !OPENSSL_NO_THREADID */ +#endif /* OPENSSL_THREADS */ + + /* randomness */ +#ifndef PURIFY + if ((fd = open("/dev/urandom", O_RDONLY)) == -1) { + log_err_printf("Error opening /dev/urandom for reading: %s\n", + strerror(errno)); + return -1; + } + while (!RAND_status()) { + if (read(fd, buf, sizeof(buf)) == -1) { + log_err_printf("Error reading from /dev/urandom: %s\n", + strerror(errno)); + close(fd); + return -1; + } + RAND_seed(buf, sizeof(buf)); + } + close(fd); + if (!RAND_poll()) { + log_err_printf("RAND_poll() failed.\n"); + return -1; + } +#else /* PURIFY */ + memset(buf, 0, sizeof(buf)); + while (!RAND_status()) { + RAND_seed(buf, sizeof(buf)); + } +#endif /* PURIFY */ + +#ifdef USE_FOOTPRINT_HACKS + /* HACK: disable compression by zeroing the global comp algo stack. + * This lowers the per-connection memory footprint by ~500k. */ + STACK_OF(SSL_COMP)* comp_methods = SSL_COMP_get_compression_methods(); + sk_SSL_COMP_zero(comp_methods); +#endif /* USE_FOOTPRINT_HACKS */ + + ssl_initialized = 1; + return 0; +} + +/* + * Deinitialize OpenSSL and free as much memory as possible. + * Some 10k-100k will still remain resident no matter what. + */ +void +ssl_fini(void) +{ + if (!ssl_initialized) + return; + + ERR_remove_state(0); /* current thread */ + +#ifdef OPENSSL_THREADS + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_dynlock_create_callback(NULL); + CRYPTO_set_dynlock_lock_callback(NULL); + CRYPTO_set_dynlock_destroy_callback(NULL); +#ifdef OPENSSL_NO_THREADID + CRYPTO_set_id_callback(NULL); +#else /* !OPENSSL_NO_THREADID */ + CRYPTO_THREADID_set_callback(NULL); +#endif /* !OPENSSL_NO_THREADID */ + for (int i = 0; i < ssl_mutex_num; i++) { + pthread_mutex_destroy(&ssl_mutex[i]); + } + free(ssl_mutex); +#endif + + ENGINE_cleanup(); + CONF_modules_finish(); + CONF_modules_unload(1); + CONF_modules_free(); + + EVP_cleanup(); + ERR_free_strings(); + CRYPTO_cleanup_all_ex_data(); +} + +#ifndef OPENSSL_NO_DH +static unsigned char dh_g[] = { 0x02 }; +static unsigned char dh512_p[] = { + 0xAB, 0xC0, 0x34, 0x16, 0x95, 0x8B, 0x57, 0xE5, 0x5C, 0xB3, 0x4E, 0x6E, + 0x16, 0x0B, 0x35, 0xC5, 0x6A, 0xCC, 0x4F, 0xD3, 0xE5, 0x46, 0xE2, 0x23, + 0x6A, 0x5B, 0xBB, 0x5D, 0x3D, 0x52, 0xEA, 0xCE, 0x4F, 0x7D, 0xCA, 0xFF, + 0xB4, 0x8B, 0xC9, 0x78, 0xDC, 0xA0, 0xFC, 0xBE, 0xF3, 0xB5, 0xE6, 0x61, + 0xA6, 0x6D, 0x58, 0xFC, 0xA0, 0x0F, 0xF7, 0x9B, 0x97, 0xE6, 0xC7, 0xE8, + 0x1F, 0xCD, 0x16, 0x73 }; +static unsigned char dh1024_p[] = { + 0x99, 0x28, 0x34, 0x48, 0x9E, 0xB7, 0xD1, 0x4F, 0x0D, 0x17, 0x09, 0x97, + 0xB9, 0x9B, 0x20, 0xFE, 0xE5, 0x65, 0xE0, 0xE2, 0x56, 0x37, 0x80, 0xA2, + 0x9F, 0x2C, 0x2D, 0x87, 0x10, 0x58, 0x39, 0xAD, 0xF3, 0xC5, 0xA9, 0x08, + 0x24, 0xC7, 0xAA, 0xA9, 0x29, 0x3A, 0x13, 0xDF, 0x4E, 0x0A, 0x6D, 0x11, + 0x39, 0xB1, 0x1C, 0x3F, 0xFE, 0xFE, 0x0A, 0x5E, 0xAD, 0x2E, 0x5C, 0x10, + 0x97, 0x38, 0xAC, 0xE8, 0xEB, 0xAA, 0x4A, 0xA1, 0xC0, 0x5C, 0x1D, 0x27, + 0x65, 0x9C, 0xC8, 0x53, 0xAC, 0x35, 0xDD, 0x84, 0x1F, 0x47, 0x0E, 0x04, + 0xF1, 0x90, 0x61, 0x62, 0x2E, 0x29, 0x2C, 0xC6, 0x28, 0x91, 0x6D, 0xF0, + 0xE2, 0x5E, 0xCE, 0x60, 0x3E, 0xF7, 0xF8, 0x37, 0x99, 0x4D, 0x9F, 0xFB, + 0x68, 0xEC, 0x7F, 0x9D, 0x32, 0x74, 0xD1, 0xAA, 0xD4, 0x4C, 0xF5, 0xCD, + 0xC2, 0xD7, 0xD7, 0xAC, 0xDA, 0x69, 0xF5, 0x2B }; +#if 0 +static unsigned char dh2048_p[] = { + 0xAB, 0x88, 0x97, 0xCA, 0xF1, 0xE1, 0x60, 0x39, 0xFA, 0xB1, 0xA8, 0x7D, + 0xB3, 0x7A, 0x38, 0x08, 0xF0, 0x7A, 0x3D, 0x21, 0xC4, 0xE6, 0xB8, 0x32, + 0x3D, 0xAB, 0x0F, 0xE7, 0x8C, 0xA1, 0x59, 0x47, 0xB2, 0x0A, 0x7A, 0x3A, + 0x20, 0x2A, 0x1B, 0xD4, 0xBA, 0xFC, 0x4C, 0xC5, 0xEE, 0xA2, 0xB9, 0xB9, + 0x65, 0x47, 0xCC, 0x13, 0x99, 0xD7, 0xA6, 0xCA, 0xFF, 0x23, 0x05, 0x91, + 0xAB, 0x5C, 0x82, 0xB8, 0xB4, 0xFD, 0xB1, 0x2E, 0x5B, 0x0F, 0x8E, 0x03, + 0x3C, 0x23, 0xD6, 0x6A, 0xE2, 0x83, 0x95, 0xD2, 0x8E, 0xEB, 0xDF, 0x3A, + 0xAF, 0x89, 0xF0, 0xA0, 0x14, 0x09, 0x12, 0xF6, 0x54, 0x54, 0x93, 0xF4, + 0xD4, 0x41, 0x56, 0x7A, 0x0E, 0x56, 0x20, 0x1F, 0x1D, 0xBA, 0x3F, 0x07, + 0xD2, 0x89, 0x1B, 0x40, 0xD0, 0x1C, 0x08, 0xDF, 0x00, 0x7F, 0x34, 0xF4, + 0x28, 0x4E, 0xF7, 0x53, 0x8D, 0x4A, 0x00, 0xC3, 0xC0, 0x89, 0x9E, 0x63, + 0x96, 0xE9, 0x52, 0xDF, 0xA5, 0x2C, 0x00, 0x4E, 0xB0, 0x82, 0x6A, 0x10, + 0x28, 0x8D, 0xB9, 0xE7, 0x7A, 0xCB, 0xC3, 0xD6, 0xC1, 0xC0, 0x4D, 0x91, + 0xC4, 0x6F, 0xD3, 0x99, 0xD1, 0x86, 0x71, 0x67, 0x0A, 0xA1, 0xFC, 0xF4, + 0x7D, 0x40, 0x88, 0x8D, 0xAC, 0xCB, 0xBC, 0xEA, 0x17, 0x85, 0x0B, 0xC6, + 0x12, 0x3E, 0x4A, 0xB9, 0x60, 0x74, 0x93, 0x54, 0x14, 0x39, 0x10, 0xBF, + 0x21, 0xB0, 0x8B, 0xB1, 0x55, 0x3F, 0xBB, 0x6A, 0x1F, 0x42, 0x82, 0x0A, + 0x40, 0x3A, 0x15, 0xCD, 0xD3, 0x79, 0xD0, 0x02, 0xA4, 0xF5, 0x79, 0x78, + 0x03, 0xBD, 0x47, 0xCC, 0xD5, 0x08, 0x6A, 0x46, 0xAE, 0x36, 0xE4, 0xCD, + 0xB1, 0x17, 0x48, 0x30, 0xB4, 0x02, 0xBC, 0x50, 0x68, 0xE3, 0xA2, 0x76, + 0xD0, 0x5C, 0xB9, 0xE6, 0xBE, 0x4C, 0xFD, 0x50, 0xEF, 0xD0, 0x3F, 0x39, + 0x4F, 0x53, 0x16, 0x3B }; +#endif + +/* + * OpenSSL temporary DH callback which loads DH parameters from static memory. + */ +DH * +ssl_tmp_dh_callback(UNUSED SSL *s, UNUSED int is_export, int keylength) +{ + DH *dh; + + if (!(dh = DH_new())) + return NULL; + if (keylength <= 512) { + dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL); + } else /* if (keylength <= 1024) */ { + dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL); + } /* else { + dh->p = BN_bin2bn(dh2048_p, sizeof(dh2048_p), NULL); + } */ + dh->g = BN_bin2bn(dh_g, sizeof(dh_g), NULL); + if (!dh->p || !dh->g) { + DH_free(dh); + return NULL; + } + + return(dh); +} + +/* + * Load DH parameters from a PEM file. + * Not thread-safe. + */ +DH * +ssl_dh_load(const char *filename) +{ + DH *dh; + FILE *fh; + + if (ssl_init() == -1) + return NULL; + + if (!(fh = fopen(filename, "r"))) { + return NULL; + } + dh = PEM_read_DHparams(fh, NULL, NULL, NULL); + fclose(fh); + return dh; +} +#endif /* !OPENSSL_NO_DH */ + +#ifndef OPENSSL_NO_ECDH +/* + * Load an Elliptic Curve by name. If curvename is NULL, a default curve is + * loaded. + */ +EC_KEY * +ssl_ecdh_by_name(const char *curvename) +{ + int nid; + + if (!curvename) + curvename = SSL_EC_KEY_CURVE_DEFAULT; + + if ((nid = OBJ_sn2nid(curvename)) == NID_undef) { + return NULL; + } + return EC_KEY_new_by_curve_name(nid); +} +#endif /* !OPENSSL_NO_ECDH */ + +/* + * Add a X509v3 extension to a certificate and handle errors. + * Returns -1 on errors, 0 on success. + */ +int +ssl_x509_v3ext_add(X509V3_CTX *ctx, X509 *crt, char *k, char *v) +{ + X509_EXTENSION *ext; + + if (!(ext = X509V3_EXT_conf(NULL, ctx, k, v))) { + return -1; + } + if (X509_add_ext(crt, ext, -1) != 1) { + X509_EXTENSION_free(ext); + return -1; + } + X509_EXTENSION_free(ext); + return 0; +} + +/* + * Copy a X509v3 extension from one certificate to another. + * If the extension is not present in the original certificate, + * the extension will not be added to the destination certificate. + * Returns 1 if ext was copied, 0 if not present in origcrt, -1 on error. + */ +int +ssl_x509_v3ext_copy_by_nid(X509 *crt, X509 *origcrt, int nid) +{ + X509_EXTENSION *ext; + int pos; + + pos = X509_get_ext_by_NID(origcrt, nid, -1); + if (pos == -1) + return 0; + ext = X509_get_ext(origcrt, pos); + if (!ext) + return -1; + if (X509_add_ext(crt, ext, -1) != 1) + return -1; + return 1; +} + +/* + * Best effort randomness generator. + * Not for real life cryptography applications. + * Returns 0 on success, -1 on failure. + */ +int +ssl_rand(void *p, size_t sz) +{ + int rv; + + rv = RAND_pseudo_bytes((unsigned char*)p, sz); + if (rv == -1) { + rv = RAND_bytes((unsigned char*)p, sz); + if (rv != 1) + return -1; + } + return 0; +} + +/* + * Copy the serial number from src certificate to dst certificate + * and modify it by a random offset. + * If reading the serial fails for some reason, generate a new + * random serial and store it in the dst certificate. + * Using the same serial is not a good idea since some SSL stacks + * check for duplicate certificate serials. + * Returns 0 on success, -1 on error. + */ +int +ssl_x509_serial_copyrand(X509 *dstcrt, X509 *srccrt) +{ + ASN1_INTEGER *srcptr, *dstptr; + BIGNUM *bnserial; + unsigned int rand; + int rv; + +#ifndef PURIFY + rv = ssl_rand(&rand, sizeof(rand)); +#else /* PURIFY */ + rand = 0xF001; + rv = 0; +#endif /* PURIFY */ + dstptr = X509_get_serialNumber(dstcrt); + srcptr = X509_get_serialNumber(srccrt); + if ((rv == -1) || !dstptr || !srcptr) + return -1; + bnserial = ASN1_INTEGER_to_BN(srcptr, NULL); + if (!bnserial) { + /* random 32-bit serial */ + ASN1_INTEGER_set(dstptr, rand); + } else { + /* original serial plus random 32-bit offset */ + BN_add_word(bnserial, rand); + BN_to_ASN1_INTEGER(bnserial, dstptr); + BN_free(bnserial); + } + return 0; +} + +/* + * Create a fake X509v3 certificate, signed by the provided CA, + * based on the original certificate retrieved from the real server. + * The returned certificate is created using X509_new() and thus must + * be freed by the caller using X509_free(). + * The optional argument extraname is added to subjectAltNames if provided. + */ +X509 * +ssl_x509_forge(X509 *cacrt, EVP_PKEY *cakey, X509 *origcrt, + const char *extraname, EVP_PKEY *key) +{ + X509_NAME *subject, *issuer; + GENERAL_NAMES *names; + GENERAL_NAME *gn; + X509 *crt; + + subject = X509_get_subject_name(origcrt); + issuer = X509_get_subject_name(cacrt); + if (!subject || !issuer) + return NULL; + + crt = X509_new(); + if (!crt) + return NULL; + + if (!X509_set_version(crt, 0x02) || + !X509_set_subject_name(crt, subject) || + !X509_set_issuer_name(crt, issuer) || + ssl_x509_serial_copyrand(crt, origcrt) == -1 || + !X509_gmtime_adj(X509_get_notBefore(crt), (long)-60*60*24) || + !X509_gmtime_adj(X509_get_notAfter(crt), (long)60*60*24*364) || + !X509_set_pubkey(crt, key)) + goto errout; + + /* add standard v3 extensions; cf. RFC 2459 */ + X509V3_CTX ctx; + X509V3_set_ctx(&ctx, cacrt, crt, NULL, NULL, 0); + if (ssl_x509_v3ext_add(&ctx, crt, "basicConstraints", + "CA:FALSE") == -1 || + ssl_x509_v3ext_add(&ctx, crt, "keyUsage", + "digitalSignature," + "keyEncipherment") == -1 || + ssl_x509_v3ext_add(&ctx, crt, "extendedKeyUsage", + "serverAuth") == -1 || + ssl_x509_v3ext_add(&ctx, crt, "subjectKeyIdentifier", + "hash") == -1 || + ssl_x509_v3ext_add(&ctx, crt, "authorityKeyIdentifier", + "keyid,issuer:always") == -1) + goto errout; + + if (!extraname) { + /* no extraname provided: copy original subjectAltName ext */ + if (ssl_x509_v3ext_copy_by_nid(crt, origcrt, + NID_subject_alt_name) == -1) + goto errout; + } else { + names = X509_get_ext_d2i(origcrt, NID_subject_alt_name, 0, 0); + if (!names) { + /* no subjectAltName present: add new one */ + char *cfval; + if (asprintf(&cfval, "DNS:%s", extraname) == -1) + goto errout; + if (ssl_x509_v3ext_add(&ctx, crt, "subjectAltName", + cfval) == -1) { + free(cfval); + goto errout; + } + free(cfval); + } else { + /* add extraname to original subjectAltName + * and add it to the new certificate */ + gn = GENERAL_NAME_new(); + if (!gn) + goto errout2; + gn->type = GEN_DNS; + gn->d.dNSName = M_ASN1_IA5STRING_new(); + if (!gn->d.dNSName) + goto errout3; + ASN1_STRING_set(gn->d.dNSName, + (unsigned char *)extraname, + strlen(extraname)); + sk_GENERAL_NAME_push(names, gn); + X509_EXTENSION *ext = X509V3_EXT_i2d( + NID_subject_alt_name, 0, names); + if (!X509_add_ext(crt, ext, -1)) { + if (ext) { + X509_EXTENSION_free(ext); + } + goto errout3; + } + X509_EXTENSION_free(ext); + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + } + } +#ifdef DEBUG_CERTIFICATE + ssl_x509_v3ext_add(&ctx, crt, "nsComment", "Generated by " PNAME); +#endif /* DEBUG_CERTIFICATE */ + + const EVP_MD *md; + switch (EVP_PKEY_type(cakey->type)) { +#ifndef OPENSSL_NO_RSA + case EVP_PKEY_RSA: + md = EVP_sha1(); + break; +#endif /* !OPENSSL_NO_RSA */ +#ifndef OPENSSL_NO_DSA + case EVP_PKEY_DSA: + md = EVP_dss1(); + break; +#endif /* !OPENSSL_NO_DSA */ +#ifndef OPENSSL_NO_ECDSA + case EVP_PKEY_EC: + md = EVP_ecdsa(); + break; +#endif /* !OPENSSL_NO_ECDSA */ + default: + goto errout; + } + if (!X509_sign(crt, cakey, md)) + goto errout; + + return crt; + +errout3: + GENERAL_NAME_free(gn); +errout2: + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); +errout: + X509_free(crt); + return NULL; +} + +/* + * Load a X509 certificate chain from a PEM file. + * Returns the first certificate in *crt and all subsequent certificates in + * *chain. If crt is NULL, the first certificate is prepended to *chain + * instead of returned separately. If *chain is NULL, a new stack of X509* + * is created in *chain, else the certs are pushed onto an existing stack. + * Returns -1 on error. + * Not thread-safe. + * + * By accessing (SSLCTX*)->extra_certs directly, we depend on OpenSSL + * internals in this function. + * + * XXX try to reimplement this with exposed BIO/ASN.1 functionality + * in order to get rid of the ->extra_certs direct access. + */ +int +ssl_x509chain_load(X509 **crt, STACK_OF(X509) **chain, const char *filename) +{ + X509 *tmpcrt; + SSL_CTX *tmpctx; + SSL *tmpssl; + int rv; + + if (ssl_init() == -1) + return -1; + + tmpctx = SSL_CTX_new(SSLv23_server_method()); + if (!tmpctx) + goto leave1; + + rv = SSL_CTX_use_certificate_chain_file(tmpctx, filename); + if (rv != 1) + goto leave2; + tmpssl = SSL_new(tmpctx); + if (rv != 1 || !tmpssl) + goto leave2; + + tmpcrt = SSL_get_certificate(tmpssl); + if (!tmpcrt) + goto leave3; + + if (!*chain) { + *chain = sk_X509_new_null(); + if (!*chain) + goto leave3; + } + + if (crt) { + *crt = tmpcrt; + } else { + sk_X509_push(*chain, tmpcrt); + } + ssl_x509_refcount_inc(tmpcrt); + + for (int i = 0; i < sk_X509_num(tmpctx->extra_certs); i++) { + tmpcrt = sk_X509_value(tmpctx->extra_certs, i); + ssl_x509_refcount_inc(tmpcrt); + sk_X509_push(*chain, tmpcrt); + } + SSL_free(tmpssl); + SSL_CTX_free(tmpctx); + return 0; + +leave3: + SSL_free(tmpssl); +leave2: + SSL_CTX_free(tmpctx); +leave1: + return -1; +} + +/* + * Use a X509 certificate chain for an SSL context. + * Copies the certificate stack to the SSL_CTX internal data structures + * and increases reference counts accordingly. + */ +void +ssl_x509chain_use(SSL_CTX *sslctx, X509 *crt, STACK_OF(X509) *chain) +{ + SSL_CTX_use_certificate(sslctx, crt); + + for (int i = 0; i < sk_X509_num(chain); i++) { + X509 *tmpcrt; + + tmpcrt = sk_X509_value(chain, i); + ssl_x509_refcount_inc(tmpcrt); + sk_X509_push(sslctx->extra_certs, tmpcrt); + SSL_CTX_add_extra_chain_cert(sslctx, tmpcrt); + } +} + +/* + * Load a X509 certificate from a PEM file. + * Returned X509 must be freed using X509_free() by the caller. + * Not thread-safe. + */ +X509 * +ssl_x509_load(const char *filename) +{ + X509 *crt = NULL; + SSL_CTX *tmpctx; + SSL *tmpssl; + int rv; + + if (ssl_init() == -1) + return NULL; + + tmpctx = SSL_CTX_new(SSLv23_server_method()); + if (!tmpctx) + goto leave1; + rv = SSL_CTX_use_certificate_file(tmpctx, filename, SSL_FILETYPE_PEM); + tmpssl = SSL_new(tmpctx); + if (rv != 1 || !tmpssl) + goto leave2; + crt = SSL_get_certificate(tmpssl); + if (crt) + ssl_x509_refcount_inc(crt); + SSL_free(tmpssl); +leave2: + SSL_CTX_free(tmpctx); +leave1: + return crt; +} + +/* + * Load a private key from a PEM file. + * Returned EVP_PKEY must be freed using EVP_PKEY_free() by the caller. + * Not thread-safe. + */ +EVP_PKEY * +ssl_key_load(const char *filename) +{ + EVP_PKEY *key = NULL; + SSL_CTX *tmpctx; + SSL *tmpssl; + int rv; + + if (ssl_init() == -1) + return NULL; + + tmpctx = SSL_CTX_new(SSLv23_server_method()); + if (!tmpctx) + goto leave1; + rv = SSL_CTX_use_PrivateKey_file(tmpctx, filename, SSL_FILETYPE_PEM); + tmpssl = SSL_new(tmpctx); + if (rv != 1 || !tmpssl) + goto leave2; + key = SSL_get_privatekey(tmpssl); + if (key) + ssl_key_refcount_inc(key); + SSL_free(tmpssl); +leave2: + SSL_CTX_free(tmpctx); +leave1: + return key; +} + +/* + * Generate a new RSA key. + * Returned EVP_PKEY must be freed using EVP_PKEY_free() by the caller. + */ +EVP_PKEY * +ssl_key_genrsa(const int keysize) +{ + EVP_PKEY * pkey; + RSA * rsa; + + rsa = RSA_generate_key(keysize, 3, NULL, NULL); + if (!rsa) + return NULL; + pkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(pkey, rsa); + return pkey; +} + +/* + * Returns the one-line representation of the subject DN in a newly allocated + * string which must be freed by the caller. + */ +char * +ssl_x509_subject(X509 *crt) +{ + return X509_NAME_oneline(X509_get_subject_name(crt), NULL, 0); +} + +/* + * Parse the common name from the subject distinguished name. + * Returns string allocated using malloc(), caller must free(). + * Returns NULL on errors. + */ +char * +ssl_x509_subject_cn(X509 *crt, size_t *sz) +{ + X509_NAME *ptr; + char *cn; + + ptr = X509_get_subject_name(crt); + if (!ptr) + return NULL; + *sz = X509_NAME_get_text_by_NID(ptr, NID_commonName, NULL, 0) + 1; + if ((sz == 0) || !(cn = malloc(*sz))) + return NULL; + X509_NAME_get_text_by_NID(ptr, NID_commonName, cn, *sz); + return cn; +} + +/* + * Write the SHA1 fingerprint of certificate to fpr as SSL_X509_FPRSZ (20) + * bytes long binary buffer. + * Returns -1 on error, 0 on success. + */ +int +ssl_x509_fingerprint_sha1(X509 *crt, unsigned char *fpr) +{ + unsigned int sz = SSL_X509_FPRSZ; + + return X509_digest(crt, EVP_sha1(), fpr, &sz) ? 0 : -1; +} + +#ifndef OPENSSL_NO_DH +/* + * Increment the reference count of DH parameters in a thread-safe + * manner. + */ +void +ssl_dh_refcount_inc(DH *dh) +{ +#ifdef OPENSSL_THREADS + CRYPTO_add(&dh->references, 1, CRYPTO_LOCK_DH); +#else /* !OPENSSL_THREADS */ + dh->references++; +#endif /* !OPENSSL_THREADS */ +} +#endif /* !OPENSSL_NO_DH */ + +/* + * Increment the reference count of an X509 certificate in a thread-safe + * manner. + */ +void +ssl_key_refcount_inc(EVP_PKEY *key) +{ +#ifdef OPENSSL_THREADS + CRYPTO_add(&key->references, 1, CRYPTO_LOCK_EVP_PKEY); +#else /* !OPENSSL_THREADS */ + key->references++; +#endif /* !OPENSSL_THREADS */ +} + +/* + * Increment the reference count of an X509 certificate in a thread-safe + * manner. This differs from X509_dup() in that it does not create new, + * full copy of the certificate, but only increases the reference count. + */ +void +ssl_x509_refcount_inc(X509 *crt) +{ +#ifdef OPENSSL_THREADS + CRYPTO_add(&crt->references, 1, CRYPTO_LOCK_X509); +#else /* !OPENSSL_THREADS */ + crt->references++; +#endif /* !OPENSSL_THREADS */ +} + +/* + * Match a URL/URI hostname against a single certificate DNS name + * using RFC 6125 rules (6.4.3 Checking of Wildcard Certificates): + * + * 1. The client SHOULD NOT attempt to match a presented identifier in + * which the wildcard character comprises a label other than the + * left-most label (e.g., do not match bar.*.example.net). + * + * 2. If the wildcard character is the only character of the left-most + * label in the presented identifier, the client SHOULD NOT compare + * against anything but the left-most label of the reference + * identifier (e.g., *.example.com would match foo.example.com but + * not bar.foo.example.com or example.com). + * + * 3. The client MAY match a presented identifier in which the wildcard + * character is not the only character of the label (e.g., + * baz*.example.net and *baz.example.net and b*z.example.net would + * be taken to match baz1.example.net and foobaz.example.net and + * buzz.example.net, respectively). However, the client SHOULD NOT + * attempt to match a presented identifier where the wildcard + * character is embedded within an A-label or U-label [IDNA-DEFS] of + * an internationalized domain name [IDNA-PROTO]. + * + * The optional partial matching in rule 3 is not implemented. + * Returns 1 on match, 0 on no match. + */ +int +ssl_dnsname_match(const char *certname, size_t certnamesz, + const char *hostname, size_t hostnamesz) +{ + if (hostnamesz < certnamesz) + return 0; + if ((hostnamesz == certnamesz) && + !memcmp(certname, hostname, certnamesz)) + return 1; + if (!memcmp(certname, "xn--", 4)) + return 0; + if ((certnamesz == 1) && (certname[0] == '*') && + !memchr(hostname, '.', hostnamesz)) + return 1; + if ((certnamesz > 2) && (certname[0] == '*') && (certname[1] == '.') && + !memcmp(&certname[1], + &hostname[hostnamesz - (certnamesz - 1)], + certnamesz - 1) && + (memchr(hostname, '.', hostnamesz) == + &hostname[hostnamesz - (certnamesz - 1)])) + return 1; + return 0; +} + +/* + * Transform a NULL-terminated hostname into a matching wildcard hostname, + * e.g. "test.example.org" -> "*.example.org". + * Returns string which must be free()'d by the caller, or NULL on error. + */ +char * +ssl_wildcardify(const char *hostname) +{ + char *dot, *wildcarded; + size_t dotsz; + + if (!(dot = strchr(hostname, '.'))) + return strdup("*"); + dotsz = strlen(dot); + if (!(wildcarded = malloc(dotsz + 2))) + return NULL; + wildcarded[0] = '*'; + strncpy(wildcarded + 1, dot, dotsz); + wildcarded[dotsz + 1] = '\0'; + return wildcarded; +} + +/* + * Match DNS name against certificate subject CN and subjectAltNames DNS names. + * Returns 1 if any name matches, 0 if none matches. + */ +int +ssl_x509_names_match(X509 *crt, const char *dnsname) +{ + GENERAL_NAMES *altnames; + char *cn; + size_t cnsz, dnsnamesz; + + dnsnamesz = strlen(dnsname); + + cn = ssl_x509_subject_cn(crt, &cnsz); + if (cn && ssl_dnsname_match(cn, cnsz, dnsname, dnsnamesz)) { + free(cn); + return 1; + } + if (cn) { + free(cn); + } + + altnames = X509_get_ext_d2i(crt, NID_subject_alt_name, 0, 0); + if (!altnames) + return 0; + for (int i = 0; i < sk_GENERAL_NAME_num(altnames); i++) { + GENERAL_NAME *gn = sk_GENERAL_NAME_value(altnames, i); + if (gn->type == GEN_DNS) { + unsigned char *altname; + int altnamesz; + ASN1_STRING_to_UTF8(&altname, gn->d.dNSName); + altnamesz = ASN1_STRING_length(gn->d.dNSName); + if (altnamesz < 0) + break; + if (ssl_dnsname_match((char *)altname, + (size_t)altnamesz, + dnsname, dnsnamesz)) { + OPENSSL_free((char*)altname); + GENERAL_NAMES_free(altnames); + return 1; + } + OPENSSL_free((char*)altname); + } + } + GENERAL_NAMES_free(altnames); + return 0; +} + +/* + * Returns a printable representation of a certificate's common names found + * in the Subject DN CN and subjectAltNames extension. + * If the length of the common names exceeds limit characters, the rest is + * truncated. + * Caller must free returned buffer. + * Embedded NULL characters in hostnames are replaced with '!'. + */ +char * +ssl_x509_names_to_str(X509 *crt, size_t limit) +{ + GENERAL_NAMES *altnames; + char *cn, *buf; + size_t cnsz; + + if ((limit == 0) || (limit > 1023)) + limit = 1023; + buf = malloc(limit+1); + if (!buf) + return NULL; + buf[0] = '\0'; + + cn = ssl_x509_subject_cn(crt, &cnsz); + if (cn) { + strncat(buf, cn, limit); + free(cn); + } + + altnames = X509_get_ext_d2i(crt, NID_subject_alt_name, 0, 0); + if (!altnames) + return buf; + for (int i = 0; i < sk_GENERAL_NAME_num(altnames); i++) { + GENERAL_NAME *gn = sk_GENERAL_NAME_value(altnames, i); + if (gn->type == GEN_DNS) { + unsigned char *altname; + int altnamesz; + size_t written; + ASN1_STRING_to_UTF8(&altname, gn->d.dNSName); + altnamesz = ASN1_STRING_length(gn->d.dNSName); + if (altnamesz < 0) + break; + if (strlen(buf) + 1 < limit) + strncat(buf, "/", 1); + written = strlen(buf); + for (int j = 0; j < altnamesz; j++) { + if (written < limit) { + buf[written] = altname[j] ? + altname[j] : '!'; + written++; + } + } + OPENSSL_free((char*)altname); + buf[written] = '\0'; + if (written == limit) { + buf[limit-3] = '.'; + buf[limit-2] = '.'; + buf[limit-1] = '.'; + GENERAL_NAMES_free(altnames); + return buf; + } + } + } + GENERAL_NAMES_free(altnames); + return buf; +} + +/* + * Returns a NULL terminated array of pointers to all common names found + * in the Subject DN CN and subjectAltNames extension. + * Caller must free returned buffer and all pointers within. + * Embedded NULL characters in hostnames are replaced with '!'. + */ +char ** +ssl_x509_names(X509 *crt) +{ + GENERAL_NAMES *altnames; + char *cn; + size_t cnsz; + char **res, **p; + size_t count; + + cn = ssl_x509_subject_cn(crt, &cnsz); + altnames = X509_get_ext_d2i(crt, NID_subject_alt_name, 0, 0); + + count = (altnames ? sk_GENERAL_NAME_num(altnames) : 0) + (cn ? 2 : 1); + res = malloc(count * sizeof(char*)); + if (!res) + return NULL; + p = res; + if (cn) + *(p++) = cn; + if (!altnames) { + *p = NULL; + return res; + } + for (int i = 0; i < sk_GENERAL_NAME_num(altnames); i++) { + GENERAL_NAME *gn = sk_GENERAL_NAME_value(altnames, i); + if (gn->type == GEN_DNS) { + unsigned char *altname; + int altnamesz; + + ASN1_STRING_to_UTF8(&altname, gn->d.dNSName); + if (!altname) + break; + altnamesz = ASN1_STRING_length(gn->d.dNSName); + if (altnamesz < 0) { + OPENSSL_free((char*)altname); + break; + } + *p = malloc(altnamesz + 1); + if (!*p) { + OPENSSL_free((char*)altname); + GENERAL_NAMES_free(altnames); + for (p = res; *p; p++) + free(*p); + free(res); + return NULL; + } + for (int j = 0; j < altnamesz; j++) { + *p[j] = altname[j] ? altname[j] : '!'; + } + *p[altnamesz] = '\0'; + OPENSSL_free((char*)altname); + p++; + } + } + GENERAL_NAMES_free(altnames); + return res; +} + +/* + * Check whether the certificate is valid based on current time. + * Return 1 if valid, 0 otherwise. + */ +int +ssl_x509_is_valid(X509 *crt) +{ + if (X509_cmp_current_time(X509_get_notAfter(crt)) <= 0) + return 0; + if (X509_cmp_current_time(X509_get_notBefore(crt)) > 0) + return 0; + return 1; +} + +/* + * Print X509 certificate data to a newly allocated string. + * Caller must free returned string. + * Returns NULL on errors. + */ +char * +ssl_x509_to_str(X509 *crt) +{ + BIO *bio; + char *p, *ret; + size_t sz; + + bio = BIO_new(BIO_s_mem()); + if (!bio) + return NULL; + X509_print(bio, crt); + sz = BIO_get_mem_data(bio, &p); + if (!(ret = malloc(sz + 1))) { + BIO_free(bio); + return NULL; + } + memcpy(ret, p, sz); + ret[sz] = '\0'; + BIO_free(bio); + return ret; +} + +/* + * Convert X509 certificate to PEM format in a newly allocated string. + * Caller must free returned string. + * Returns NULL on errors. + */ +char * +ssl_x509_to_pem(X509 *crt) +{ + BIO *bio; + char *p, *ret; + size_t sz; + + bio = BIO_new(BIO_s_mem()); + if (!bio) + return NULL; + PEM_write_bio_X509(bio, crt); + sz = BIO_get_mem_data(bio, &p); + if (!(ret = malloc(sz + 1))) { + BIO_free(bio); + return NULL; + } + memcpy(ret, p, sz); + ret[sz] = '\0'; + BIO_free(bio); + return ret; +} + +/* + * Print SSL_SESSION data to a newly allocated string. + * Caller must free returned string. + * Returns NULL on errors. + */ +char * +ssl_session_to_str(SSL_SESSION *sess) +{ + BIO *bio; + char *p, *ret; + size_t sz; + + bio = BIO_new(BIO_s_mem()); + if (!bio) + return NULL; + SSL_SESSION_print(bio, sess); + sz = BIO_get_mem_data(bio, &p); + if (!(ret = malloc(sz + 1))) { + BIO_free(bio); + return NULL; + } + memcpy(ret, p, sz); + ret[sz] = '\0'; + BIO_free(bio); + return ret; +} + +/* + * Returns non-zero if the session timeout has not expired yet, + * zero if the session has expired or an error occured. + */ +int +ssl_session_is_valid(SSL_SESSION *sess) +{ + time_t curtimet; + long curtime, timeout; + + curtimet = time(NULL); + if (curtimet == (time_t)-1) + return 0; + curtime = curtimet; + if ((curtime < 0) || ((time_t)curtime != curtimet)) + return 0; + timeout = SSL_SESSION_get_timeout(sess); + if (curtime > LONG_MAX - timeout) + return 0; + return (SSL_SESSION_get_time(sess) < curtime + timeout); +} + +#ifndef OPENSSL_NO_TLSEXT +/* + * Ugly hack to manually parse the SNI TLS extension from a clientHello buf. + * This is needed because of limitations in the OpenSSL SNI API which only + * allows to read the indicated server name at the time when we have to + * provide the server certificate. It is not possible to asynchroniously + * read the indicated server name, wait for some event to happen, and then + * later to provide the server certificate to use and continue the handshake. + * + * This function takes a buffer containing (part of) a clientHello message as + * seen on the network. + * + * If server name extension was found and parsed, returns server name buffer + * that must be free'd by the caller. + * If parsing failed for inconsistency reasons or if SNI TLS extension was + * not present in the clientHello, returns NULL. + * If not enough data was provided in buf, returns NULL and *sz is set to -1 + * to indicate that a call to ssl_tls_clienthello_parse_sni() with more data + * in buf might succeed. + * + * References: + * RFC 2246: The TLS Protocol Version 1.0 + * RFC 3546: Transport Layer Security (TLS) Extensions + * RFC 4346: The Transport Layer Security (TLS) Protocol Version 1.1 + * RFC 4366: Transport Layer Security (TLS) Extensions + * RFC 5246: The Transport Layer Security (TLS) Protocol Version 1.2 + * RFC 6066: Transport Layer Security (TLS) Extensions: Extension Definitions + */ +char * +ssl_tls_clienthello_parse_sni(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; + char *servername = NULL; + + DBG_printf("buffer length %zd\n", n); + + if (n < 1) { + *sz = -1; + goto out; + } + 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 out; + p++; n--; + + if (n < 2) { + *sz = -1; + goto out; + } + DBG_printf("version: %02x %02x\n", p[0], p[1]); + if (p[0] != 3) + goto out; + p += 2; n -= 2; + + if (n < 2) { + *sz = -1; + goto out; + } + 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 out; + } + DBG_printf("message type: %i\n", *p); + if (*p != 1) /* message type: ClientHello */ + goto out; + p++; n--; + + if (n < 3) { + *sz = -1; + goto out; + } + 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 out; + p += 3; n -= 3; + + if (n < msglen) { + *sz = -1; + goto out; + } + n = msglen; /* only parse first message */ + + if (n < 2) + goto out; + DBG_printf("clienthello version %02x %02x\n", p[0], p[1]); + if (p[0] != 3) + goto out; + p += 2; n -= 2; + + if (n < 32) + goto out; + DBG_printf("clienthello random %02x %02x %02x %02x ...\n", + p[0], p[1], p[2], p[3]); + DBG_printf("compare localtime: %08x\n", (unsigned int)time(NULL)); + p += 32; n -= 32; + + if (n < 1) + goto out; + DBG_printf("clienthello sidlen %02x\n", *p); + ssize_t sidlen = *p; /* session id length, 0..32 */ + p += 1; n -= 1; + if (n < sidlen) + goto out; + p += sidlen; n -= sidlen; + + if (n < 2) + goto out; + DBG_printf("clienthello cipher suites length %02x %02x\n", p[0], p[1]); + ssize_t suiteslen = p[1] + (p[0] << 8); + p += 2; n -= 2; + if (n < suiteslen) { + DBG_printf("n < suiteslen (%zd, %zd)\n", n, suiteslen); + goto out; + } + p += suiteslen; + n -= suiteslen; + + if (n < 1) + goto out; + DBG_printf("clienthello compress methods length %02x\n", *p); + ssize_t compslen = *p; + p++; n--; + if (n < compslen) + goto out; + p += compslen; + n -= compslen; + + /* begin of extensions */ + + if (n < 2) + goto out; + DBG_printf("tlsexts length %02x %02x\n", p[0], p[1]); + ssize_t tlsextslen = p[1] + (p[0] << 8); + DBG_printf("tlsextslen %zd\n", tlsextslen); + p += 2; + n -= 2; + + if (n < tlsextslen) + goto out; + n = tlsextslen; /* only parse extensions, ignore trailing bits */ + + while (n > 0) { + if (n < 4) + goto out; + DBG_printf("tlsext type %02x %02x len %02x %02x\n", + p[0], p[1], p[2], p[3]); + unsigned short exttype = p[1] + (p[0] << 8); + ssize_t extlen = p[3] + (p[2] << 8); + p += 4; + n -= 4; + if (n < extlen) + goto out; + switch (exttype) { + case 0: + { + ssize_t extn = extlen; + const unsigned char *extp = p; + + if (extn < 2) + goto out; + DBG_printf("list length %02x %02x\n", + extp[0], extp[1]); + ssize_t namelistlen = extp[1] + (extp[0] << 8); + DBG_printf("namelistlen = %zd\n", namelistlen); + extp += 2; + extn -= 2; + + if (namelistlen != extn) + goto out; + + while (extn > 0) { + if (extn < 3) + goto out; + DBG_printf("ServerName type %02x" + " len %02x %02x\n", + extp[0], extp[1], extp[2]); + unsigned char sntype = extp[0]; + ssize_t snlen = extp[2] + (extp[1]<<8); + extp += 3; + extn -= 3; + if (snlen > extn) + goto out; + if (snlen > TLSEXT_MAXLEN_host_name) + goto out; + if (sntype == 0) { + servername = malloc(snlen + 1); + memcpy(servername, extp, snlen); + servername[snlen] = '\0'; + /* deliberately not checking + * for malformed hostnames + * containing invalid chars */ + goto out; + } + extp += snlen; + extn -= snlen; + } + break; + } + default: + DBG_printf("skipped\n"); + break; + } + p += extlen; + n -= extlen; + } + +#ifdef DEBUG_SNI_PARSER + if (n > 0) { + DBG_printf("unparsed next bytes %02x %02x %02x %02x\n", + p[0], p[1], p[2], p[3]); + } +#endif /* DEBUG_SNI_PARSER */ +out: + DBG_printf("%zd bytes unparsed\n", n); + return servername; +} +#endif /* !OPENSSL_NO_TLSEXT */ + +/* vim: set noet ft=c: */ diff --git a/ssl.h b/ssl.h new file mode 100644 index 0000000..ba58023 --- /dev/null +++ b/ssl.h @@ -0,0 +1,111 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SSL_H +#define SSL_H + +#include "attrib.h" + +#include +#include +#include +#include +#include +#include + +#if (OPENSSL_VERSION_NUMBER < 0x10000000L) && !defined(OPENSSL_NO_THREADID) +#define OPENSSL_NO_THREADID +#endif +#if (OPENSSL_VERSION_NUMBER < 0x0090806FL) && !defined(OPENSSL_NO_TLSEXT) +#define OPENSSL_NO_TLSEXT +#endif +#if (OPENSSL_VERSION_NUMBER < 0x1000005FL) && !defined(OPENSSL_NO_ECDH) +#define OPENSSL_NO_ECDH +#endif +#if (OPENSSL_VERSION_NUMBER < 0x0090802FL) && !defined(OPENSSL_NO_ECDSA) +#define OPENSSL_NO_ECDSA +#endif +#if (OPENSSL_VERSION_NUMBER < 0x0090802FL) && !defined(OPENSSL_NO_EC) +#define OPENSSL_NO_EC +#endif + +void ssl_openssl_version(void); +int ssl_init(void); +void ssl_fini(void); + +#ifndef OPENSSL_NO_DH +DH * ssl_tmp_dh_callback(SSL *, int, int) NONNULL(); +DH * ssl_dh_load(const char *) NONNULL(); +void ssl_dh_refcount_inc(DH *) NONNULL(); +#endif /* !OPENSSL_NO_DH */ + +#ifndef OPENSSL_NO_ECDH +#define SSL_EC_KEY_CURVE_DEFAULT "prime256v1" +EC_KEY * ssl_ecdh_by_name(const char *); +#endif /* !OPENSSL_NO_ECDH */ + +EVP_PKEY * ssl_key_load(const char *) NONNULL(); +EVP_PKEY * ssl_key_genrsa(const int); +void ssl_key_refcount_inc(EVP_PKEY *) NONNULL(); + +#ifndef OPENSSL_NO_TLSEXT +int ssl_x509_v3ext_add(X509V3_CTX *, X509 *, char *, char *) NONNULL(); +int ssl_x509_v3ext_copy_by_nid(X509 *, X509 *, int) NONNULL(); +#endif /* !OPENSSL_NO_TLSEXT */ +int ssl_x509_serial_copyrand(X509 *, X509 *) NONNULL(); +X509 * ssl_x509_forge(X509 *, EVP_PKEY *, X509 *, const char *, EVP_PKEY *) \ + NONNULL(1,2,3,5) MALLOC; +X509 * ssl_x509_load(const char *) NONNULL() MALLOC; +char * ssl_x509_subject(X509 *) NONNULL() MALLOC; +char * ssl_x509_subject_cn(X509 *, size_t *) NONNULL() MALLOC; +#define SSL_X509_FPRSZ 20 +int ssl_x509_fingerprint_sha1(X509 *, unsigned char *) NONNULL(); +int ssl_x509_names_match(X509 *, const char *) NONNULL(); +char * ssl_x509_names_to_str(X509 *, size_t) NONNULL() MALLOC; +char ** ssl_x509_names(X509 *) NONNULL() MALLOC; +int ssl_x509_is_valid(X509 *) NONNULL(); +char * ssl_x509_to_str(X509 *) NONNULL() MALLOC; +char * ssl_x509_to_pem(X509 *) NONNULL() MALLOC; +void ssl_x509_refcount_inc(X509 *) NONNULL(); + +int ssl_x509chain_load(X509 **, STACK_OF(X509) **, const char *) NONNULL(2,3); +void ssl_x509chain_use(SSL_CTX *, X509 *, STACK_OF(X509) *) NONNULL(); + +char * ssl_session_to_str(SSL_SESSION *) NONNULL() MALLOC; +int ssl_session_is_valid(SSL_SESSION *) NONNULL(); + +#ifndef OPENSSL_NO_TLSEXT +char * ssl_tls_clienthello_parse_sni(const unsigned char *, ssize_t *) \ + NONNULL() MALLOC; +#endif /* !OPENSSL_NO_TLSEXT */ +int ssl_dnsname_match(const char *, size_t, const char *, size_t) NONNULL(); +char * ssl_wildcardify(const char *) NONNULL() MALLOC; + +#endif /* !SSL_H */ + +/* vim: set noet ft=c: */ diff --git a/ssl.t b/ssl.t new file mode 100644 index 0000000..bacf96c --- /dev/null +++ b/ssl.t @@ -0,0 +1,450 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "ssl.h" + +static char wildcard1[] = "*.example.org"; +static char wildcard2[] = "www.*.example.org"; +static char wildcard3[] = "*.*.org"; +static char wildcard4[] = "www*.example.org"; +static char wildcard5[] = "*"; +static char wildcard6[] = "*.xn--r-1ga.ch"; +static char wildcard7[] = "xn--r-1ga*.xn--r-1ga.ch"; +static char wildcard8[] = "xn--r-1ga.*.xn--r-1ga.ch"; +static char name1[] = "www.example.org"; +static char name2[] = "www.example.com"; +static char name3[] = "example.org"; +static char name4[] = "www.example.org.co.uk"; +static char name5[] = "test.www.example.org"; +static char name6[] = "www.test.example.org"; +static char name7[] = "wwwtest.example.org"; +static char name8[] = "ch"; +static char name9[] = "www.xn--r-1ga.ch"; +static char name10[] = "xn--r-1ga.xn--r-1ga.ch"; +static char name11[] = ""; + +START_TEST(ssl_wildcardify_01) +{ + char *wc = ssl_wildcardify(name1); + fail_unless(!strcmp(wc, wildcard1), "mismatch for 'www.example.org'"); + free(wc); +} +END_TEST + +START_TEST(ssl_wildcardify_02) +{ + char *wc = ssl_wildcardify(name8); + fail_unless(!strcmp(wc, wildcard5), "mismatch for 'ch'"); + free(wc); +} +END_TEST + +START_TEST(ssl_wildcardify_03) +{ + char *wc = ssl_wildcardify(name11); + fail_unless(!strcmp(wc, wildcard5), "mismatch for ''"); + free(wc); +} +END_TEST + +START_TEST(ssl_dnsname_match_01) +{ + fail_unless( + ssl_dnsname_match(name1, sizeof(name1) - 1, + name1, sizeof(name1) - 1), + "Hostname does not match itself"); +} +END_TEST + +START_TEST(ssl_dnsname_match_02) +{ + fail_unless( + !ssl_dnsname_match(name1, sizeof(name1) - 1, + name2, sizeof(name2) - 1), + "Hostname matches hostname with different TLD"); +} +END_TEST + +START_TEST(ssl_dnsname_match_03) +{ + fail_unless( + ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1, + name1, sizeof(name1) - 1), + "Regular wildcard does not match"); +} +END_TEST + +START_TEST(ssl_dnsname_match_04) +{ + fail_unless( + !ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1, + name2, sizeof(name2) - 1), + "Regular wildcard matches other TLD"); +} +END_TEST + +START_TEST(ssl_dnsname_match_05) +{ + fail_unless( + !ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1, + name3, sizeof(name3) - 1), + "Regular wildcard matches upper level domain"); +} +END_TEST + +START_TEST(ssl_dnsname_match_06) +{ + fail_unless( + !ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1, + name4, sizeof(name4) - 1), + "Regular wildcard matches despite added suffix"); +} +END_TEST + +START_TEST(ssl_dnsname_match_07) +{ + fail_unless( + !ssl_dnsname_match(wildcard1, sizeof(wildcard1) - 1, + name5, sizeof(name5) - 1), + "Regular wildcard matches two elements"); +} +END_TEST + +START_TEST(ssl_dnsname_match_08) +{ + fail_unless( + !ssl_dnsname_match(wildcard2, sizeof(wildcard2) - 1, + name6, sizeof(name6) - 1), + "Wildcard matches in non-leftmost element"); +} +END_TEST + +START_TEST(ssl_dnsname_match_09) +{ + fail_unless( + !ssl_dnsname_match(wildcard3, sizeof(wildcard3) - 1, + name5, sizeof(name5) - 1), + "Multiple wildcard matches"); +} +END_TEST + +START_TEST(ssl_dnsname_match_10) +{ + fail_unless( + !ssl_dnsname_match(wildcard4, sizeof(wildcard4) - 1, + name7, sizeof(name7) - 1), + "Partial label wildcard matches"); +} +END_TEST + +START_TEST(ssl_dnsname_match_11) +{ + fail_unless( + !ssl_dnsname_match(wildcard5, sizeof(wildcard5) - 1, + name1, sizeof(name1) - 1), + "Global wildcard * matches fqdn"); +} +END_TEST + +START_TEST(ssl_dnsname_match_12) +{ + fail_unless( + ssl_dnsname_match(wildcard5, sizeof(wildcard5) - 1, + name8, sizeof(name8) - 1), + "Global wildcard * does not match TLD"); +} +END_TEST + +START_TEST(ssl_dnsname_match_13) +{ + fail_unless( + ssl_dnsname_match(wildcard6, sizeof(wildcard6) - 1, + name9, sizeof(name9) - 1), + "IDN wildcard does not match"); +} +END_TEST + +START_TEST(ssl_dnsname_match_14) +{ + fail_unless( + ssl_dnsname_match(wildcard6, sizeof(wildcard6) - 1, + name10, sizeof(name10) - 1), + "IDN wildcard does not match IDN element"); +} +END_TEST + +START_TEST(ssl_dnsname_match_15) +{ + fail_unless( + !ssl_dnsname_match(wildcard7, sizeof(wildcard7) - 1, + name10, sizeof(name10) - 1), + "Illegal IDN wildcard matches"); +} +END_TEST + +START_TEST(ssl_dnsname_match_16) +{ + fail_unless( + !ssl_dnsname_match(wildcard8, sizeof(wildcard8) - 1, + name10, sizeof(name10) - 1), + "Illegal IDN wildcard matches IDN element"); +} +END_TEST + +#ifndef OPENSSL_NO_TLSEXT +static unsigned char clienthello01[] = + "\x80\x67\x01\x03\x00\x00\x4e\x00\x00\x00\x10\x01\x00\x80\x03\x00" + "\x80\x07\x00\xc0\x06\x00\x40\x02\x00\x80\x04\x00\x80\x00\x00\x39" + "\x00\x00\x38\x00\x00\x35\x00\x00\x33\x00\x00\x32\x00\x00\x04\x00" + "\x00\x05\x00\x00\x2f\x00\x00\x16\x00\x00\x13\x00\xfe\xff\x00\x00" + "\x0a\x00\x00\x15\x00\x00\x12\x00\xfe\xfe\x00\x00\x09\x00\x00\x64" + "\x00\x00\x62\x00\x00\x03\x00\x00\x06\xa8\xb8\x93\xbb\x90\xe9\x2a" + "\xa2\x4d\x6d\xcc\x1c\xe7\x2a\x80\x21"; + /* SSL 2.0, no TLS extensions */ + +static unsigned char clienthello02[] = + "\x16\x03\x00\x00\x73\x01\x00\x00\x6f\x03\x00\x00\x34\x01\x1e\x67" + "\x3a\xfa\xce\xd9\x51\xba\xe4\xfc\x64\x95\x03\x82\x63\x0f\xe3\x39" + "\x6b\xc7\xbd\x2b\xe5\x51\x37\x23\x48\x5b\xfb\x20\xa3\xca\xad\x46" + "\x95\x5d\x64\xbb\x33\xec\xb5\x12\x91\x21\xa3\x50\xd2\xc0\xc5\xf6" + "\x67\xc3\xcc\x9e\xc0\x4a\x71\x1b\x92\xdc\x58\x55\x00\x28\x00\x39" + "\x00\x38\x00\x35\x00\x33\x00\x32\x00\x04\x00\x05\x00\x2f\x00\x16" + "\x00\x13\xfe\xff\x00\x0a\x00\x15\x00\x12\xfe\xfe\x00\x09\x00\x64" + "\x00\x62\x00\x03\x00\x06\x01\x00"; + /* SSL 3.0, no TLS extensions */ + +static unsigned char clienthello03[] = + "\x16\x03\x01\x00\x9b\x01\x00\x00\x97\x03\x01\x4b\x99\x46\xac\x38" + "\x08\xbb\xa7\x1c\x9b\xea\x79\xc5\xd6\x70\x3d\xed\x20\x80\x60\xb4" + "\x7e\xb5\x07\x13\xcf\x9a\x1c\xec\x6f\x64\xe5\x00\x00\x46\xc0\x0a" + "\xc0\x09\xc0\x07\xc0\x08\xc0\x13\xc0\x14\xc0\x11\xc0\x12\xc0\x04" + "\xc0\x05\xc0\x02\xc0\x03\xc0\x0e\xc0\x0f\xc0\x0c\xc0\x0d\x00\x2f" + "\x00\x05\x00\x04\x00\x35\x00\x0a\x00\x09\x00\x03\x00\x08\x00\x06" + "\x00\x32\x00\x33\x00\x38\x00\x39\x00\x16\x00\x15\x00\x14\x00\x13" + "\x00\x12\x00\x11\x01\x00\x00\x28\x00\x00\x00\x12\x00\x10\x00\x00" + "\x0d\x31\x39\x32\x2e\x31\x36\x38\x2e\x31\x30\x30\x2e\x34\x00\x0a" + "\x00\x08\x00\x06\x00\x17\x00\x18\x00\x19\x00\x0b\x00\x02\x01\x00"; + /* TLS 1.0, SNI extension with hostname "192.168.100.4"; + * Note: IP addresses are not legal values */ + +static unsigned char clienthello04[] = + "\x16\x03\x01\x00\x6c\x01\x00\x00\x68\x03\x01\x4a\x9d\x49\x75\xb2" + "\x7e\xf9\xbc\xc3\x76\xac\x19\x78\xfb\x6a\xee\x50\x55\x5e\x35\x4c" + "\xca\xf2\x21\x15\xf3\x8a\x2a\xfc\xb5\x35\xed\x00\x00\x28\x00\x39" + "\x00\x38\x00\x35\x00\x16\x00\x13\x00\x0a\x00\x33\x00\x32\x00\x2f" + "\x00\x07\x00\x05\x00\x04\x00\x15\x00\x12\x00\x09\x00\x14\x00\x11" + "\x00\x08\x00\x06\x00\x03\x01\x00\x00\x17\x00\x00\x00\x0f\x00\x0d" + "\x00\x00\x0a\x6b\x61\x6d\x65\x73\x68\x2e\x63\x6f\x6d\x00\x23\x00" + "\x00"; + /* TLS 1.0, SNI extension with hostname "kamesh.com" */ + +static unsigned char clienthello05[] = + "\x16\x03\x03\x01\x7d\x01\x00\x01\x79\x03\x03\x4f\x7f\x27\xd0\x76" + "\x5f\xc1\x3b\xba\x73\xd5\x07\x8b\xd9\x79\xf9\x51\xd4\xce\x7d\x9a" + "\xdb\xdf\xf8\x4e\x95\x86\x38\x61\xdd\x84\x2a\x00\x00\xca\xc0\x30" + "\xc0\x2c\xc0\x28\xc0\x24\xc0\x14\xc0\x0a\xc0\x22\xc0\x21\x00\xa3" + "\x00\x9f\x00\x6b\x00\x6a\x00\x39\x00\x38\x00\x88\x00\x87\xc0\x19" + "\xc0\x20\x00\xa7\x00\x6d\x00\x3a\x00\x89\xc0\x32\xc0\x2e\xc0\x2a" + "\xc0\x26\xc0\x0f\xc0\x05\x00\x9d\x00\x3d\x00\x35\x00\x84\xc0\x12" + "\xc0\x08\xc0\x1c\xc0\x1b\x00\x16\x00\x13\xc0\x17\xc0\x1a\x00\x1b" + "\xc0\x0d\xc0\x03\x00\x0a\xc0\x2f\xc0\x2b\xc0\x27\xc0\x23\xc0\x13" + "\xc0\x09\xc0\x1f\xc0\x1e\x00\xa2\x00\x9e\x00\x67\x00\x40\x00\x33" + "\x00\x32\x00\x9a\x00\x99\x00\x45\x00\x44\xc0\x18\xc0\x1d\x00\xa6" + "\x00\x6c\x00\x34\x00\x9b\x00\x46\xc0\x31\xc0\x2d\xc0\x29\xc0\x25" + "\xc0\x0e\xc0\x04\x00\x9c\x00\x3c\x00\x2f\x00\x96\x00\x41\x00\x07" + "\xc0\x11\xc0\x07\xc0\x16\x00\x18\xc0\x0c\xc0\x02\x00\x05\x00\x04" + "\x00\x15\x00\x12\x00\x1a\x00\x09\x00\x14\x00\x11\x00\x19\x00\x08" + "\x00\x06\x00\x17\x00\x03\x00\xff\x02\x01\x00\x00\x85\x00\x00\x00" + "\x12\x00\x10\x00\x00\x0d\x64\x61\x6e\x69\x65\x6c\x2e\x72\x6f\x65" + "\x2e\x63\x68\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x34\x00" + "\x32\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\x09\x00" + "\x0a\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00" + "\x04\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00" + "\x10\x00\x11\x00\x23\x00\x00\x00\x0d\x00\x22\x00\x20\x06\x01\x06" + "\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03" + "\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x01\x01\x00\x0f\x00" + "\x01\x01"; + /* TLS 1.2, SNI extension with hostname "daniel.roe.ch" */ + +START_TEST(ssl_tls_clienthello_parse_sni_01) +{ + ssize_t sz; + char *sni; + + sz = sizeof(clienthello01) - 1; + sni = ssl_tls_clienthello_parse_sni(clienthello01, &sz); + fail_unless(sni == NULL, "sni not null but should be"); + fail_unless(sz != -1, "size is -1 but should not"); +} +END_TEST + +START_TEST(ssl_tls_clienthello_parse_sni_02) +{ + ssize_t sz; + char *sni; + + sz = sizeof(clienthello02) - 1; + sni = ssl_tls_clienthello_parse_sni(clienthello02, &sz); + fail_unless(sni == NULL, "sni not null but should be"); + fail_unless(sz != -1, "size is -1 but should not"); +} +END_TEST + +START_TEST(ssl_tls_clienthello_parse_sni_03) +{ + ssize_t sz; + char *sni; + + sz = sizeof(clienthello03) - 1; + sni = ssl_tls_clienthello_parse_sni(clienthello03, &sz); + fail_unless(sni && !strcmp(sni, "192.168.100.4"), + "sni not '192.168.100.4' but should be"); + fail_unless(sz != -1, "size is -1 but should not"); +} +END_TEST + +START_TEST(ssl_tls_clienthello_parse_sni_04) +{ + ssize_t sz; + char *sni; + + sz = sizeof(clienthello04) - 1; + sni = ssl_tls_clienthello_parse_sni(clienthello04, &sz); + fail_unless(sni && !strcmp(sni, "kamesh.com"), + "sni not 'kamesh.com' but should be"); + fail_unless(sz != -1, "size is -1 but should not"); +} +END_TEST + +START_TEST(ssl_tls_clienthello_parse_sni_05) +{ + ssize_t sz; + char *sni; + + for (size_t i = 0; i < sizeof(clienthello04) - 1; i++) { + sz = (ssize_t)i; + sni = ssl_tls_clienthello_parse_sni(clienthello04, &sz); + fail_unless(sni == NULL, "sni not null but should be"); + fail_unless(sz == -1, "size is not -1 but should be"); + } +} +END_TEST + +START_TEST(ssl_tls_clienthello_parse_sni_06) +{ + ssize_t sz; + char *sni; + + sz = sizeof(clienthello05) - 1; + sni = ssl_tls_clienthello_parse_sni(clienthello05, &sz); + fail_unless(sni && !strcmp(sni, "daniel.roe.ch"), + "sni not 'daniel.roe.ch' but should be"); + fail_unless(sz != -1, "size is -1 but should not"); +} +END_TEST + +START_TEST(ssl_tls_clienthello_parse_sni_07) +{ + ssize_t sz; + char *sni; + + for (size_t i = 0; i < sizeof(clienthello05) - 1; i++) { + sz = (ssize_t)i; + sni = ssl_tls_clienthello_parse_sni(clienthello05, &sz); + fail_unless(sni == NULL, "sni not null but should be"); + fail_unless(sz == -1, "size is not -1 but should be"); + } +} +END_TEST +#endif /* !OPENSSL_NO_TLSEXT */ + +START_TEST(ssl_features_01) +{ + int have_threads = 0; +#ifdef OPENSSL_THREADS + have_threads = 1; +#endif /* OPENSSL_THREADS */ + fail_unless(have_threads, "!OPENSSL_THREADS: no threading support"); +} +END_TEST + +Suite * +ssl_suite(void) +{ + Suite *s; + TCase *tc; + + s = suite_create("ssl"); + + tc = tcase_create("ssl_wildcardify"); + tcase_add_test(tc, ssl_wildcardify_01); + tcase_add_test(tc, ssl_wildcardify_02); + tcase_add_test(tc, ssl_wildcardify_03); + suite_add_tcase(s, tc); + + tc = tcase_create("ssl_dnsname_match"); + tcase_add_test(tc, ssl_dnsname_match_01); + tcase_add_test(tc, ssl_dnsname_match_02); + tcase_add_test(tc, ssl_dnsname_match_03); + tcase_add_test(tc, ssl_dnsname_match_04); + tcase_add_test(tc, ssl_dnsname_match_05); + tcase_add_test(tc, ssl_dnsname_match_06); + tcase_add_test(tc, ssl_dnsname_match_07); + tcase_add_test(tc, ssl_dnsname_match_08); + tcase_add_test(tc, ssl_dnsname_match_09); + tcase_add_test(tc, ssl_dnsname_match_10); + tcase_add_test(tc, ssl_dnsname_match_11); + tcase_add_test(tc, ssl_dnsname_match_12); + tcase_add_test(tc, ssl_dnsname_match_13); + tcase_add_test(tc, ssl_dnsname_match_14); + tcase_add_test(tc, ssl_dnsname_match_15); + tcase_add_test(tc, ssl_dnsname_match_16); + suite_add_tcase(s, tc); + +#ifndef OPENSSL_NO_TLSEXT + tc = tcase_create("ssl_tls_clienthello_parse_sni"); + tcase_add_test(tc, ssl_tls_clienthello_parse_sni_01); + tcase_add_test(tc, ssl_tls_clienthello_parse_sni_02); + tcase_add_test(tc, ssl_tls_clienthello_parse_sni_03); + tcase_add_test(tc, ssl_tls_clienthello_parse_sni_04); + tcase_add_test(tc, ssl_tls_clienthello_parse_sni_05); + tcase_add_test(tc, ssl_tls_clienthello_parse_sni_06); + tcase_add_test(tc, ssl_tls_clienthello_parse_sni_07); + suite_add_tcase(s, tc); +#endif /* !OPENSSL_NO_TLSEXT */ + + tc = tcase_create("ssl_features"); + tcase_add_test(tc, ssl_features_01); + suite_add_tcase(s, tc); + + return s; +} + +/* vim: set noet ft=c: */ diff --git a/sslsplit.1 b/sslsplit.1 new file mode 100644 index 0000000..869f475 --- /dev/null +++ b/sslsplit.1 @@ -0,0 +1,487 @@ +.\" SSLsplit - transparent and scalable SSL/TLS interception +.\" Copyright (c) 2009-2012, Daniel Roethlisberger +.\" All rights reserved. +.\" http://www.roe.ch/SSLsplit +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice unmodified, this list of conditions, and the following +.\" disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.TH SSLSPLIT 1 "1 April 2012" +.SH NAME +sslsplit \-\- transparent and scalable SSL/TLS interception +.SH SYNOPSIS +.na +.B sslsplit +[\fB-PZdDgGseujplLS\fP] +\fB-k\fP \fIpem\fP \fB-c\fP \fIpem\fP [\fB-C\fP \fIpem\fP] \ +[\fB-K\fP \fIpem\fP] +\fIspecs\fP +.br +.B sslsplit +[\fB-PZdDgGseujplLS\fP] +\fB-t\fP \fIdir\fP +\fIspecs\fP +.br +.B sslsplit +[\fB-PZdDgGseujplLS\fP] +\fB-t\fP \fIdir\fP +\fB-k\fP \fIpem\fP \fB-c\fP \fIpem\fP [\fB-C\fP \fIpem\fP] \ +[\fB-K\fP \fIpem\fP] +\fIspecs\fP +.br +.B sslsplit -E +.br +.B sslsplit -V +.br +.B sslsplit -h +.br +.ad +.SH DESCRIPTION +SSLsplit is a tool for man-in-the-middle attacks against SSL/TLS encrypted +network connections. Connections are transparently intercepted through a +network address translation engine and redirected to SSLsplit. SSLsplit +terminates SSL/TLS and initiates a new SSL/TLS connection to the original +destination address, while logging all data transmitted. +.LP +SSLsplit supports plain TCP, plain SSL, HTTP and HTTPS connections over both +IPv4 and IPv6. For SSL and HTTPS connections, SSLsplit generates and signs +forged X509v3 certificates on-the-fly, based on the original server certificate +subject DN and subjectAltName extension. SSLsplit fully supports Server Name +Indication (SNI) and is able to work with RSA, DSA and ECDSA keys and DHE and +ECDHE cipher suites. SSLsplit can also use existing certificates of which the +private key is available, instead of generating forged ones. SSLsplit supports +NULL-prefix CN certificates. +.LP +SSLsplit supports a number of NAT engines, static forwarding and SNI DNS +lookups to determine the original destination of redirected connections +(see NAT ENGINES and PROXY SPECIFICATIONS below). +.LP +To actually implement an attack, you also need to redirect the traffic to the +system running \fBsslsplit\fP. Your options include running \fBsslsplit\fP on +a legitimate router, ARP spoofing, ND spoofing, DNS poisoning, deploying a +rogue access point (e.g. using hostap mode), physical recabling, malicious VLAN +reconfiguration or route injection, /etc/hosts modification and so on. +SSLsplit does not implement the actual traffic redirection. +.SH OPTIONS +.TP +.B \-c \fIpemfile\fP +Use CA certificate from \fIpemfile\fP to sign certificates forged on-the-fly. +If \fIpemfile\fP also contains the matching CA private key, it is also loaded, +otherwise it must be provided with \fB-k\fP. +If \fB-t\fP is also given, SSLsplit will only forge a certificate if there is +no matching certificate in the provided certificate directory. +.TP +.B \-C \fIpemfile\fP +Use CA certificates from \fIpemfile\fP as extra certificates in the certificate +chain. This is needed if the CA given with \fB-k\fP and \fB-c\fP is a sub-CA, +in which case any intermediate CA certificates and the root CA certificate must +be included in the certificate chain. +.TP +.B \-d +Detach from TTY and run as a daemon, logging error messages to syslog instead +of standard error. +.TP +.B \-D +Run in debug mode, log lots of debugging information to standard error. This +also forces foreground mode and cannot be used with \fB-d\fP. +.TP +.B \-e \fIengine\fP +Use \fIengine\fP as the default NAT engine for \fIspecs\fP without +explicit NAT engine, static destination address or SNI mode. +\fIengine\fP can be any of the NAT engines supported by the system, as +returned by \fB-E\fP. +.TP +.B \-E +List all supported NAT engines available on the system and exit. See +NAT ENGINES for a list of NAT engines currently supported by SSLsplit. +.TP +.B \-g \fIpemfile\fP +Use Diffie-Hellman group parameters from \fIpemfile\fP for Ephemereal +Diffie-Hellman (EDH/DHE) cipher suites. If \fB-g\fP is not given, SSLsplit +first tries to load DH parameters from the key files given by \fB-K\fP and +\fB-k\fP. If no DH parameters are found in the key files, built-in 512 or 1024 +bit group parameters are automatically used iff a non-RSA private key is given +with \fB-K\fP. +This is because DSA/DSS private keys can by themselves only be used for signing +and thus require DH to exchange an SSL/TLS session key. +If \fB-g\fP is given, the parameters from the given \fIpemfile\fP will always +be used, even with RSA private keys (within the cipher suites available in +OpenSSL). +The \fB-g\fP option is only available if SSLsplit was built against a version +of OpenSSL which supports Diffie-Hellman cipher suites. +.TP +.B \-G \fIcurve\fP +Use the named \fIcurve\fP for Ephemereal Elliptic Curve Diffie-Hellman (EECDH) +cipher suites. If \fB-G\fP is not given, the curve \fBprime256v1\fP is used +automatically iff a non-RSA private key is given with \fB-K\fP. +This is because ECDSA/ECDSS private keys can by themselves only be used for +signing and thus require ECDH to exchange an SSL/TLS session key. +If \fB-G\fP is given, the named \fIcurve\fP will always be used, even with RSA +private keys (within the cipher suites available in OpenSSL). +The \fB-G\fP option is only available if SSLsplit was built against a version +of OpenSSL which supports Elliptic Curve Diffie-Hellman cipher suites. +.TP +.B \-h +Display help on usage and exit. +.TP +.B \-j \fIjaildir\fP +Change the root directory to \fIjaildir\fP using chroot(2) after opening files. +If \fB-j\fP is not given, SSLsplit will automatically change root directory to +\fI/var/empty\fP if run as root and \fB-S\fP is not used. +.TP +.B \-k \fIpemfile\fP +Use CA private key from \fIpemfile\fP to sign certificates forged on-the-fly. +If \fIpemfile\fP also contains the matching CA certificate, it is also loaded, +otherwise it must be provided with \fB-c\fP. +If \fB-t\fP is also given, SSLsplit will only forge a certificate if there is +no matching certificate in the provided certificate directory. +.TP +.B \-K \fIpemfile\fP +Use private key from \fIpemfile\fP for certificates forged on-the-fly. +If \fB-K\fP is not given, SSLsplit will generate a random 1024-bit RSA key. +.TP +.B \-l \fIlogfile\fP +Log connections to \fIlogfile\fP in a single line per connection format, +including addresses and ports and some HTTP and SSL information, if available. +.TP +.B \-L \fIlogfile\fP +Log connection content to \fIlogfile\fP. The content log will contain a +parsable log format with transmitted data, prepended with headers identifying +the connection and the data length of each logged segment. +.TP +.B \-p \fIpidfile\fP +Write the process ID to \fIpidfile\fP and refuse to run if the \fIpidfile\fP +is already in use by another process. +.TP +.B \-P +Passthrough SSL/TLS connections which cannot be split instead of dropping them. +Connections cannot be split if \fB-c\fP and \fB-k\fP are not given and the +site does not match any certificate loaded using \fB-t\fP, or if the connection +to the original server gives SSL/TLS errors. Specifically, this happens if the +site requests a client certificate. Passthrough with \fB-P\fP results in +uninterrupted service for the clients, while dropping is the more secure +alternative if unmonitored connections must be prevented. +.TP +.B \-s \fIciphers\fP +Use OpenSSL \fIciphers\fP specification for both server and client SSL/TLS +connections. If \fB-s\fP is not given, a cipher list of \fBALL\fP is used. +Normally, SSL/TLS implementations choose the most secure cipher suites, not the +fastest ones. By specifying an appropriate OpenSSL cipher list, the set of +cipher suites can be limited to fast algorithms, or \fBNULL\fP cipher suites +can be added. Not that for connections to be successful, the SSLsplit cipher +suites must include at least one cipher suite supported by both the client and +the server of each connection. +See ciphers(1) for details on how to construct OpenSSL cipher lists. +.TP +.B \-S \fIlogdir\fP +Log connection content to separate log files under \fIlogdir\fP. For each +connection, a log file will be written, which will contain both directions of +data as transmitted. Information about the connection will be contained in +the filename only. +If \fB-S\fP is used with \fB-j\fP, \fIlogdir\fP is relative to \fIjaildir\fP. +If \fB-S\fP is used with \fB-u\fP, \fIlogdir\fP must be writable by \fIuser\fP. +.TP +.B \-t \fIcertdir\fP +Use private key, certificate and certificate chain from PEM files in +\fIcertdir\fP for sites matching the respective common names, instead of +using certificates forged on-the-fly. A single PEM file must contain a +single private key, a single certificate and optionally intermediate and +root CA certificates to use as certificate chain. +If \fB-c\fP and \fB-k\fP are also given, certificates will be forged +on-the-fly for sites matching none of the certificates loaded from +\fIcertdir\fP. +Otherwise, connections matching no certificate will be dropped, or if +\fB-P\fP is given, passed through without splitting SSL/TLS. +.TP +.B \-u +Drop privileges after opening sockets and files by setting the real, +effective and stored user IDs to \fIuser\fP and loading the appropriate +primary and ancillary groups. If \fB-u\fP is not given, SSLsplit will drop +privileges to the stored UID if EUID != UID (setuid bit scenario), or to +\fBnobody\fP if running with full \fBroot\fP privileges (EUID == UID == 0) +and \fB-S\fP is not used. +.TP +.B \-V +Display version and compiled features information and exit. +.TP +.B \-Z +Disable SSL/TLS compression on all connections. This is useful if your +limiting factor is CPU, not network bandwidth. +The \fB-Z\fP option is only available if SSLsplit was built against a version +of OpenSSL which supports disabling compression. +.SH "PROXY SPECIFICATIONS" +Proxy specifications (\fIspecs\fP) consist of the connection type, listen +address and static forward address or address resolution mechanism (NAT engine, +SNI DNS lookup): +.LP +.na +\fBhttps\fP \fIlistenaddr port\fP +[\fInat-engine\fP|\fIfwdaddr port\fP|\fBsni\fP \fIport\fP] +.br +\fBssl\fP \fIlistenaddr port\fP +[\fInat-engine\fP|\fIfwdaddr port\fP|\fBsni\fP \fIport\fP] +.br +\fBhttp\fP \fIlistenaddr port\fP +[\fInat-engine\fP|\fIfwdaddr port\fP] +.br +\fBtcp\fP \fIlistenaddr port\fP +[\fInat-engine\fP|\fIfwdaddr port\fP] +.ad +.TP +.I listenaddr port +IPv4 or IPv6 address and port or service name to listen on. This is the +address and port where the NAT engine should redirect connections to. +.TP +.I nat-engine +NAT engine to query for determining the original destination address and port +of transparently redirected connections. +If no engine is given, the default engine is used, unless overridden with +\fB-e\fP. When using a NAT engine, \fBsslsplit\fP needs to run on the same +system as the NAT rules redirecting the traffic to \fBsslsplit\fP. +See NAT ENGINES for a list of supported NAT engines. +.TP +.I fwdaddr port +Static destination address, IPv4 or IPv6, with port or service name. When this +is used, connections are forwarded to the given server address and port. +.TP +\fBsni\fP \fIport\fP +Use the Server Name Indication (SNI) hostname sent by the client in the +ClientHello SSL/TLS message to determine the IP address of the server to +connect to. This only works for \fBssl\fP and \fBhttps\fP \fIspecs\fP and +needs a port or service name as an argument. +This is the only way to redirect traffic transparently using NAT rules and run +\fBsslsplit\fP on a different system than the NAT engine. +.LP +.SH "NAT ENGINES" +SSLsplit currently supports the following NAT engines: +.TP +.B pf +OpenBSD packet filter (pf), also available on FreeBSD and NetBSD. +Fully supported, including IPv6. +Assuming inbound interface \fBem0\fP: +.LP +.RS +.nf +\fBrdr pass on em0 proto tcp from 2001:db8::/64 to any port 80 \\ + -> ::1 port 10080\fP +\fBrdr pass on em0 proto tcp from 2001:db8::/64 to any port 443 \\ + -> ::1 port 10443\fP +\fBrdr pass on em0 proto tcp from 192.0.2.0/24 to any port 80 \\ + -> 127.0.0.1 port 10080\fP +\fBrdr pass on em0 proto tcp from 192.0.2.0/24 to any port 443 \\ + -> 127.0.0.1 port 10443\fP +.fi +.RE +.TP +.B ipfw +FreeBSD IP firewall (IPFW), also available on Mac OS X. +Fully supported on FreeBSD, including IPv6. +Only supports IPv4 on Mac OS X due to the ancient version of IPFW included. +.LP +.RS +.nf +\fBipfw add fwd ::1,10080 tcp from 2001:db8::/64 to any 80\fP +\fBipfw add fwd ::1,10443 tcp from 2001:db8::/64 to any 443\fP +\fBipfw add fwd 127.0.0.1,10080 tcp from 192.0.2.0/24 to any 80\fP +\fBipfw add fwd 127.0.0.1,10443 tcp from 192.0.2.0/24 to any 443\fP +.fi +.RE +.TP +.B ipfilter +IPFilter (ipfilter, ipf), available on many systems, including FreeBSD, NetBSD, +Linux and Solaris. +Only supports IPv4 due to limitations in the SIOCGNATL ioctl(2) interface. +Assuming inbound interface \fBbge0\fP: +.LP +.RS +.nf +\fBrdr bge0 0.0.0.0/0 port 80 -> 127.0.0.1 port 10080\fP +\fBrdr bge0 0.0.0.0/0 port 443 -> 127.0.0.1 port 10443\fP +.fi +.RE +.TP +.B netfilter +Linux netfilter using the iptables REDIRECT target. +Only supports IPv4 due to limitations in the SO_ORIGINAL_DST getsockopt(2) +interface. +.LP +.RS +.nf +\fBiptables -t nat -A PREROUTING -s 192.0.2.0/24 \\ + -p tcp --dport 80 \\ + -j REDIRECT --to-ports 10080\fP +\fBiptables -t nat -A PREROUTING -s 192.0.2.0/24 \\ + -p tcp --dport 443 \\ + -j REDIRECT --to-ports 10443\fP +.fi +.RE +.TP +.B tproxy +Linux netfilter using the iptables TPROXY target together with routing +table magic to allow non-local traffic to originate on local sockets. +Fully supported, including IPv6. +.LP +.RS +.nf +\fBip -f inet6 rule add fwmark 1 lookup 100\fP +\fBip -f inet6 route add local default dev lo table 100\fP +\fBip6tables -t mangle -N DIVERT\fP +\fBip6tables -t mangle -A DIVERT -j MARK --set-mark 1\fP +\fBip6tables -t mangle -A DIVERT -j ACCEPT\fP +\fBip6tables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT\fP +\fBip6tables -t mangle -A PREROUTING -s 2001:db8::/64 \\ + -p tcp --dport 80 \\ + -j TPROXY --tproxy-mark 0x1/0x1 --on-port 10080\fP +\fBip6tables -t mangle -A PREROUTING -s 2001:db8::/64 \\ + -p tcp --dport 443 \\ + -j TPROXY --tproxy-mark 0x1/0x1 --on-port 10443\fP +\fBip -f inet rule add fwmark 1 lookup 100\fP +\fBip -f inet route add local default dev lo table 100\fP +\fBiptables -t mangle -N DIVERT\fP +\fBiptables -t mangle -A DIVERT -j MARK --set-mark 1\fP +\fBiptables -t mangle -A DIVERT -j ACCEPT\fP +\fBiptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT\fP +\fBiptables -t mangle -A PREROUTING -s 192.0.2.0/24 \\ + -p tcp --dport 80 \\ + -j TPROXY --tproxy-mark 0x1/0x1 --on-port 10080\fP +\fBiptables -t mangle -A PREROUTING -s 192.0.2.0/24 \\ + -p tcp --dport 443 \\ + -j TPROXY --tproxy-mark 0x1/0x1 --on-port 10443\fP +.fi +.LP +Note that return path filtering (rp_filter) also needs to be disabled on +interfaces which handle TPROXY redirected traffic. +.RE +.SH EXAMPLES +Matching the above NAT engine configuration samples, intercept HTTP and HTTPS +over IPv4 and IPv6 using forged certificates with CA private key \fBca.key\fP +and certificate \fBca.crt\fP, logging connections to \fBconnect.log\fP and +connection data into separate files under \fB/tmp\fP (add \fB-e\fP +\fInat-engine\fP to select the appropriate engine if multiple engines are +available on your system): +.LP +.HS +.nf +\fBsslsplit -k ca.key -c ca.crt -l connect.log -L /tmp \\ + https ::1 10443 https 127.0.0.1 10443 \\ + http ::1 10080 http 127.0.0.1 10080\fP +.fi +.RE +.LP +Intercepting IMAP/IMAPS using the same settings: +.LP +.HS +.nf +\fBsslsplit -k ca.key -c ca.crt -l connect.log -L /tmp \\ + ssl ::1 10993 ssl 127.0.0.1 10993 \\ + tcp ::1 10143 tcp 127.0.0.1 10143\fP +.fi +.RE +.LP +A more targetted setup, HTTPS only, using certificate/chain/key files from +\fB/path/to/cert.d\fP and statically redirecting to \fBwww.example.org\fP +instead of querying a NAT engine: +.LP +.HS +.nf +\fBsslsplit -t /path/to/cert.d -l connect.log -L /tmp \\ + https ::1 10443 www.example.org 443 \\ + https 127.0.0.1 10443 www.example.org 443\fP +.fi +.RE +.LP +The original example, but using SSL options optimized for speed by disabling +compression and selecting only fast block cipher cipher suites and using a +precomputed private key \fBleaf.key\fP for the forged certificates +(most significant speed increase is gained by choosing fast algorithms and +small keysizes for the CA and leaf private keys; check \fBopenssl speed\fP for +algorithm performance on your system): +.LP +.HS +.nf +\fBsslsplit -Z -s NULL:RC4:AES128 -K leaf.key \\ + -k ca.key -c ca.crt -l connect.log -L /tmp \\ + https ::1 10443 https 127.0.0.1 10443 \\ + http ::1 10080 http 127.0.0.1 10080\fP +.fi +.RE +.LP +The original example, but running as a daemon under user \fBsslsplit\fP and +writing a PID file: +.LP +.HS +.nf +\fBsslsplit -d -p /var/run/sslsplit.pid -u sslsplit \\ + -k ca.key -c ca.crt -l connect.log -L /tmp \\ + https ::1 10443 https 127.0.0.1 10443 \\ + http ::1 10080 http 127.0.0.1 10080\fP +.fi +.RE +.LP +To generate a CA private key \fBca.key\fP and certificate \fBca.crt\fP using +OpenSSL: +.LP +.HS +.nf +\fBcat >x509v3ca.cnf <<'EOF'\fP +[ req ] +distinguished_name = reqdn + +[ reqdn ] + +[ v3_ca ] +basicConstraints = CA:TRUE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +\fBEOF\fP + +\fBopenssl genrsa -out ca.key 1024\fP +\fBopenssl req -new -nodes -x509 -sha1 -out ca.crt -key ca.key \\ + -config x509v3ca.cnf -extensions v3_ca \\ + -subj '/O=SSLsplit Root CA/CN=SSLsplit Root CA/' \\ + -set_serial 0 -days 3650\fP +.fi +.SH SCALABILITY +SSLsplit is scalable to a relatively high number of listeners and connections +due to a multithreaded, event based architecture based on libevent, taking +advantage of platform specific select() replacements such as kqueue. The main +thread handles the listeners and signalling, while a number of worker threads +equal to twice the number of CPU cores is used for handling the actual +connections in separate event bases, including the CPU-intensive SSL/TLS +handling. +.LP +Care has been taken to choose scalable data structures for caching certificates +and SSL sessions. Logging is implemented in separate disk writer threads to +ensure that socket event handling threads don't have to block on disk I/O. +SSLsplit uses SSL session caching on both ends to minimize the amount of full +SSL handshakes, but even then, the limiting factor in handling SSL connections +are the actual bignum computations. +.SH "SEE ALSO" +openssl(1), ciphers(1), speed(1), +pf(4), ipfw(8), iptables(8), ip6tables(8), ip(8), +hostapd(8), arpspoof(8), parasite6(8), yersinia(8) +.SH AUTHORS +Daniel Roethlisberger +.SH BUGS +Session resumption does not work for SSLv2-only clients. As a workaround, +clients attepmting to resume a session will always be given a new one and thus +require a full handshake on every connection, resulting in degraded performance +with SSLv2 clients. However, SSLv2-only clients should be rare these days. diff --git a/sys.c b/sys.c new file mode 100644 index 0000000..8e029b8 --- /dev/null +++ b/sys.c @@ -0,0 +1,327 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sys.h" + +#include "log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _SC_NPROCESSORS_ONLN +#include +#endif /* !_SC_NPROCESSORS_ONLN */ + +#include + +/* + * Permanently drop from root privileges to an unprivileged user account. + * Sets the real, effective and stored user and group ID and the list of + * ancillary groups. This is only safe if the effective user ID is 0. + * If username is unset and the effective uid != uid, drop privs to uid. + * This is to support setuid bit configurations. + * If jaildir is set, also chroot to jaildir after reading system files + * but before dropping privileges. + * Returns 0 on success, -1 on failure. + */ +int +sys_privdrop(const char *username, const char *jaildir) +{ + struct passwd *pw = NULL; + int ret = -1; + + if (username) { + if (!(pw = getpwnam(username))) { + log_err_printf("Failed to getpwnam user '%s': %s\n", + username, strerror(errno)); + goto error; + } + if (initgroups(username, pw->pw_gid) == -1) { + log_err_printf("Failed to initgroups user '%s': %s\n", + username, strerror(errno)); + goto error; + } + } + + if (jaildir) { + if (chroot(jaildir) == -1) { + log_err_printf("Failed to chroot to '%s': %s\n", + jaildir, strerror(errno)); + goto error; + } + if (chdir("/") == -1) { + log_err_printf("Failed to chdir to '/': %s\n", + strerror(errno)); + goto error; + } + } + + if (username) { + if (setgid(pw->pw_gid) == -1) { + log_err_printf("Failed to setgid to %i: %s\n", + pw->pw_gid, strerror(errno)); + goto error; + } + if (setuid(pw->pw_uid) == -1) { + log_err_printf("Failed to setuid to %i: %s\n", + pw->pw_uid, strerror(errno)); + goto error; + } + } else if (getuid() != geteuid()) { + if (setuid(getuid()) == -1) { + log_err_printf("Failed to setuid(getuid()): %s\n", + strerror(errno)); + goto error; + } + } + + ret = 0; +error: + if (pw) { + endpwent(); + } + return ret; +} + +/* + * Open and lock process ID file fn. + * Returns open file descriptor on success or -1 on errors. + */ +int +sys_pidf_open(const char *fn) +{ + int fd; + + if ((fd = open(fn, O_RDWR|O_CREAT, 0640)) == -1) { + log_err_printf("Failed to open '%s': %s\n", fn, + strerror(errno)); + return -1; + } + if (flock(fd, LOCK_EX|LOCK_NB) == -1) { + log_err_printf("Failed to lock '%s': %s\n", fn, + strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +/* + * Write process ID to open process ID file descriptor fd. + * Returns 0 on success, -1 on errors. + */ +int +sys_pidf_write(int fd) +{ + char pidbuf[4*sizeof(pid_t)]; + int rv; + + rv = snprintf(pidbuf, sizeof(pidbuf), "%d\n", getpid()); + if (rv == -1 || rv >= (int)sizeof(pidbuf)) + return -1; + + write(fd, pidbuf, strlen(pidbuf)); + fsync(fd); + + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + + return 0; +} + +/* + * Close and remove open process ID file before quitting. + */ +void +sys_pidf_close(int fd, const char *fn) +{ + unlink(fn); + close(fd); +} + +/* + * Parse an ascii host/IP and port tuple into a sockaddr_storage. + * On success, returns address family and fills in addr, addrlen. + * Exit and don't return on errors. + */ +int +sys_sockaddr_parse(struct sockaddr_storage *addr, socklen_t *addrlen, + char *naddr, char *nport, int af, int flags) +{ + struct evutil_addrinfo hints; + struct evutil_addrinfo *ai; + int rv; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = af; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = EVUTIL_AI_ADDRCONFIG | flags; + rv = evutil_getaddrinfo(naddr, nport, &hints, &ai); + if (rv != 0) { + log_err_printf("Cannot resolve address '%s' port '%s': %s\n", + naddr, nport, gai_strerror(rv)); + return -1; + } + memcpy(addr, ai->ai_addr, ai->ai_addrlen); + *addrlen = ai->ai_addrlen; + af = ai->ai_family; + freeaddrinfo(ai); + return af; +} + +/* + * Converts an IPv4/IPv6 sockaddr into a printable string representation. + * Returns an allocated buffer which must be freed by caller, or NULL on error. + */ +char * +sys_sockaddr_str(struct sockaddr *addr, socklen_t addrlen) +{ + char host[INET6_ADDRSTRLEN], serv[6]; + char *buf; + int rv; + size_t bufsz; + + bufsz = sizeof(host) + sizeof(serv) + 3; + buf = malloc(bufsz); + if (!buf) { + log_err_printf("Cannot allocate memory\n"); + return NULL; + } + rv = getnameinfo(addr, addrlen, host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST | NI_NUMERICSERV); + if (rv != 0) { + log_err_printf("Cannot get nameinfo for socket address: %s\n", + gai_strerror(rv)); + free(buf); + return NULL; + } + snprintf(buf, bufsz, "[%s]:%s", host, serv); + return buf; +} + +/* + * Returns 1 if path points to an existing directory node in the filesystem. + * Returns 0 if path is NULL, does not exist, or points to a file of some kind. + */ +int +sys_isdir(const char *path) +{ + struct stat s; + + if (stat(path, &s) == -1) + return 0; + if (s.st_mode & S_IFDIR) + return 1; + return 0; +} + +/* + * Iterate over all files in a directory hierarchy, calling the callback + * cb for each file, passing the filename and arg as arguments. Files and + * directories beginning with a dot are skipped, symlinks are followed. + */ +int +sys_dir_eachfile(const char *dirname, sys_dir_eachfile_cb_t cb, void *arg) +{ + FTS *tree; + FTSENT *node; + char * paths[2]; + + paths[1] = NULL; + paths[0] = strdup(dirname); + if (!paths[0]) + return -1; + + tree = fts_open(paths, FTS_NOCHDIR | FTS_LOGICAL, NULL); + if (!tree) { + log_err_printf("Cannot open directory '%s': %s\n", + dirname, strerror(errno)); + return -1; + } + + while ((node = fts_read(tree))) { + if (node->fts_level > 0 && node->fts_name[0] == '.') + fts_set(tree, node, FTS_SKIP); + else if (node->fts_info & FTS_F) { + cb(node->fts_path, arg); + } + } + if (errno) { + log_err_printf("Error reading directory entry: %s\n", + strerror(errno)); + return -1; + } + fts_close(tree); + + free(paths[0]); + return 0; +} + +/* + * Portably get the number of CPU cores online in the system. + */ +uint32_t +sys_get_cpu_cores(void) +{ +#ifdef _SC_NPROCESSORS_ONLN + return sysconf(_SC_NPROCESSORS_ONLN); +#else /* !_SC_NPROCESSORS_ONLN */ + int mib[2]; + uint32_t n; + size_t len = sizeof(n); + + mib[0] = CTL_HW; + mib[1] = HW_AVAILCPU; + sysctl(mib, sizeof(mib)/sizeof(int), &n, &len, NULL, 0); + + if (n < 1) { + mib[1] = HW_NCPU; + sysctl(mib, sizeof(mib)/sizeof(int), &n, &len, NULL, 0); + if (n < 1) { + n = 1; + } + } + return n; +#endif /* !_SC_NPROCESSORS_ONLN */ +} + +/* vim: set noet ft=c: */ diff --git a/sys.h b/sys.h new file mode 100644 index 0000000..d344aa4 --- /dev/null +++ b/sys.h @@ -0,0 +1,57 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SYS_H +#define SYS_H + +#include "attrib.h" + +#include +#include +#include + +int sys_privdrop(const char *, const char *); + +int sys_pidf_open(const char *) NONNULL(); +int sys_pidf_write(int); +void sys_pidf_close(int, const char *) NONNULL(2); + +int sys_sockaddr_parse(struct sockaddr_storage *, socklen_t *, + char *, char *, int, int) NONNULL(1,2,3,4); +char * sys_sockaddr_str(struct sockaddr *, socklen_t) NONNULL(1); + +int sys_isdir(const char *) NONNULL(); + +typedef void (*sys_dir_eachfile_cb_t)(const char *, void *) NONNULL(1); +int sys_dir_eachfile(const char *, sys_dir_eachfile_cb_t, void *) NONNULL(1,2); + +uint32_t sys_get_cpu_cores(void); + +#endif /* !SYS_H */ + +/* vim: set noet ft=c: */ diff --git a/sys.t b/sys.t new file mode 100644 index 0000000..454d314 --- /dev/null +++ b/sys.t @@ -0,0 +1,140 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys.h" + +static char basedir[] = "/tmp/" BNAME ".test.XXXXXX"; +static char *file, *lfile, *dir, *ldir, *notexist; + +static void +sys_isdir_setup(void) +{ + if (!mkdtemp(basedir)) { + perror("mkdtemp"); + exit(EXIT_FAILURE); + } + asprintf(&file, "%s/file", basedir); + asprintf(&lfile, "%s/lfile", basedir); + asprintf(&dir, "%s/dir", basedir); + asprintf(&ldir, "%s/ldir", basedir); + asprintf(¬exist, "%s/DOES_NOT_EXIST", basedir); + if (!file || !lfile || !dir || !ldir || !notexist) { + perror("asprintf"); + exit(EXIT_FAILURE); + } + close(open(file, O_CREAT|O_WRONLY|O_APPEND, 0600)); + symlink(file, lfile); + mkdir(dir, 0700); + symlink(dir, ldir); +} + +static void +sys_isdir_teardown(void) +{ + unlink(lfile); + unlink(file); + unlink(ldir); + rmdir(dir); + rmdir(basedir); + free(lfile); + free(file); + free(ldir); + free(dir); + free(notexist); +} + +START_TEST(sys_isdir_01) +{ + fail_unless(sys_isdir(dir), "Directory !isdir"); +} +END_TEST + +START_TEST(sys_isdir_02) +{ + fail_unless(sys_isdir(ldir), "Symlink dir !isdir"); +} +END_TEST + +START_TEST(sys_isdir_03) +{ + fail_unless(!sys_isdir(notexist), "Not-exist isdir"); +} +END_TEST + +START_TEST(sys_isdir_04) +{ + fail_unless(!sys_isdir(file), "File isdir"); +} +END_TEST + +START_TEST(sys_isdir_05) +{ + fail_unless(!sys_isdir(lfile), "Symlink file isdir"); +} +END_TEST + +START_TEST(sys_get_cpu_cores_01) +{ + fail_unless(sys_get_cpu_cores() >= 1, "Number of CPU cores < 1"); +} +END_TEST + +Suite * +sys_suite(void) +{ + Suite *s; + TCase *tc; + + s = suite_create("sys"); + + tc = tcase_create("sys_isdir"); + tcase_add_unchecked_fixture(tc, sys_isdir_setup, sys_isdir_teardown); + tcase_add_test(tc, sys_isdir_01); + tcase_add_test(tc, sys_isdir_02); + tcase_add_test(tc, sys_isdir_03); + tcase_add_test(tc, sys_isdir_04); + tcase_add_test(tc, sys_isdir_05); + suite_add_tcase(s, tc); + + tc = tcase_create("sys_get_cpu_cores"); + tcase_add_test(tc, sys_get_cpu_cores_01); + suite_add_tcase(s, tc); + + return s; +} + +/* vim: set noet ft=c: */ diff --git a/thrqueue.c b/thrqueue.c new file mode 100644 index 0000000..efa3865 --- /dev/null +++ b/thrqueue.c @@ -0,0 +1,215 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "thrqueue.h" + +#include +#include +#include + +/* + * Threadsafe, bounded-size queue based on pthreads mutex and conds. + * Both enqueue and dequeue are available in a blocking and non-blocking + * version. + */ + +struct thrqueue { + void **data; + size_t sz, n; + size_t in, out; + unsigned int block_enqueue : 1; + unsigned int block_dequeue : 1; + pthread_mutex_t mutex; + pthread_cond_t notempty; + pthread_cond_t notfull; +}; + +/* + * Create a new thread-safe queue of size sz. + */ +thrqueue_t * +thrqueue_new(size_t sz) +{ + thrqueue_t *queue; + + if (!(queue = malloc(sizeof(thrqueue_t)))) + return NULL; + if (!(queue->data = malloc(sz * sizeof(void*)))) { + free(queue); + return NULL; + } + queue->sz = sz; + queue->n = 0; + queue->in = 0; + queue->out = 0; + queue->block_enqueue = 1; + queue->block_dequeue = 1; + pthread_mutex_init(&queue->mutex, NULL); + pthread_cond_init(&queue->notempty, NULL); + pthread_cond_init(&queue->notfull, NULL); + return queue; +} + +/* + * Free all resources associated with queue. + * The caller must ensure that there are no threads still + * using the queue when it is free'd. + */ +void +thrqueue_free(thrqueue_t *queue) +{ + free(queue->data); + pthread_mutex_destroy(&queue->mutex); + pthread_cond_destroy(&queue->notempty); + pthread_cond_destroy(&queue->notfull); + free(queue); +} + +/* + * Enqueue an item into the queue. Will block if the queue is full. + * If enqueue has been switched to non-blocking mode, never blocks + * but instead returns NULL if queue is full. + * Returns enqueued item on success. + */ +void * +thrqueue_enqueue(thrqueue_t *queue, void *item) +{ + pthread_mutex_lock(&queue->mutex); + while (queue->n == queue->sz) { + if (!queue->block_enqueue) { + pthread_mutex_unlock(&queue->mutex); + return NULL; + } + pthread_cond_wait(&queue->notfull, &queue->mutex); + } + queue->data[queue->in++] = item; + queue->in %= queue->sz; + queue->n++; + pthread_mutex_unlock(&queue->mutex); + pthread_cond_broadcast(&queue->notempty); + return item; +} + +/* + * Non-blocking enqueue. Never blocks. + * Returns NULL if the queue is full. + * Returns the enqueued item on success. + */ +void * +thrqueue_enqueue_nb(thrqueue_t *queue, void *item) +{ + pthread_mutex_lock(&queue->mutex); + if (queue->n == queue->sz) { + pthread_mutex_unlock(&queue->mutex); + return NULL; + } + queue->data[queue->in++] = item; + queue->in %= queue->sz; + queue->n++; + pthread_mutex_unlock(&queue->mutex); + pthread_cond_signal(&queue->notempty); + return item; +} + +/* + * Dequeue an item from the queue. Will block if the queue is empty. + * If dequeue has been switched to non-blocking mode, never blocks + * but instead returns NULL if queue is empty. + * Returns dequeued item on success. + */ +void * +thrqueue_dequeue(thrqueue_t *queue) +{ + void *item; + + pthread_mutex_lock(&queue->mutex); + while (queue->n == 0) { + if (!queue->block_dequeue) { + pthread_mutex_unlock(&queue->mutex); + return NULL; + } + pthread_cond_wait(&queue->notempty, &queue->mutex); + } + item = queue->data[queue->out++]; + queue->out %= queue->sz; + queue->n--; + pthread_mutex_unlock(&queue->mutex); + pthread_cond_signal(&queue->notfull); + return item; +} + +/* + * Non-blocking dequeue. Never blocks. + * Returns NULL if the queue is empty. + * Returns the dequeued item on success. + */ +void * +thrqueue_dequeue_nb(thrqueue_t *queue) +{ + void *item; + + pthread_mutex_lock(&queue->mutex); + if (queue->n == 0) { + pthread_mutex_unlock(&queue->mutex); + return NULL; + } + item = queue->data[queue->out++]; + queue->out %= queue->sz; + queue->n--; + pthread_mutex_unlock(&queue->mutex); + pthread_cond_signal(&queue->notfull); + return item; +} + +/* + * Permanently make all enqueue operations on queue non-blocking and wake + * up all threads currently waiting for the queue to become not full. + * This is to allow threads to finish their work on the queue on application + * shutdown, but not be blocked forever. + */ +void +thrqueue_unblock_enqueue(thrqueue_t *queue) +{ + queue->block_enqueue = 0; + pthread_cond_broadcast(&queue->notfull); +} + +/* + * Permanently make all dequeue operations on queue non-blocking and wake + * up all threads currently waiting for the queue to become not empty. + * This is to allow threads to finish their work on the queue on application + * shutdown, but not be blocked forever. + */ +void +thrqueue_unblock_dequeue(thrqueue_t *queue) +{ + queue->block_dequeue = 0; + pthread_cond_broadcast(&queue->notempty); +} + +/* vim: set noet ft=c: */ diff --git a/thrqueue.h b/thrqueue.h new file mode 100644 index 0000000..c6cab7f --- /dev/null +++ b/thrqueue.h @@ -0,0 +1,50 @@ +/* + * SSLsplit - transparent and scalable SSL/TLS interception + * Copyright (c) 2009-2012, Daniel Roethlisberger + * All rights reserved. + * http://www.roe.ch/SSLsplit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THRQUEUE_H +#define THRQUEUE_H + +#include "attrib.h" + +#include + +typedef struct thrqueue thrqueue_t; + +thrqueue_t * thrqueue_new(size_t) MALLOC; +void thrqueue_free(thrqueue_t *) NONNULL(); + +void * thrqueue_enqueue(thrqueue_t *, void *) NONNULL(1); +void * thrqueue_enqueue_nb(thrqueue_t *, void *) NONNULL(1); +void * thrqueue_dequeue(thrqueue_t *) NONNULL(1); +void * thrqueue_dequeue_nb(thrqueue_t *) NONNULL(1); +void thrqueue_unblock_enqueue(thrqueue_t *) NONNULL(1); +void thrqueue_unblock_dequeue(thrqueue_t *) NONNULL(1); + +#endif /* !THRQUEUE_H */ + +/* vim: set noet ft=c: */