summaryrefslogtreecommitdiffstats
path: root/src/tls
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:06:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:06:34 +0000
commit5e61585d76ae77fd5e9e96ebabb57afa4d74880d (patch)
tree2b467823aaeebc7ef8bc9e3cabe8074eaef1666d /src/tls
parentInitial commit. (diff)
downloadpostfix-5e61585d76ae77fd5e9e96ebabb57afa4d74880d.tar.xz
postfix-5e61585d76ae77fd5e9e96ebabb57afa4d74880d.zip
Adding upstream version 3.5.24.upstream/3.5.24upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tls')
l---------src/tls/.indent.pro1
-rw-r--r--src/tls/Makefile.in618
-rw-r--r--src/tls/bad-back-to-back-keys.pem64
-rw-r--r--src/tls/bad-back-to-back-keys.pem.ref2
-rw-r--r--src/tls/bad-ec-cert-before-key.pem36
-rw-r--r--src/tls/bad-ec-cert-before-key.pem.ref2
-rw-r--r--src/tls/bad-key-cert-mismatch.pem36
-rw-r--r--src/tls/bad-key-cert-mismatch.pem.ref2
-rw-r--r--src/tls/bad-rsa-key-last.pem64
-rw-r--r--src/tls/bad-rsa-key-last.pem.ref2
-rw-r--r--src/tls/ecca-cert.pem10
-rw-r--r--src/tls/ecca-pkey.pem5
-rw-r--r--src/tls/ecee-cert.pem11
-rw-r--r--src/tls/ecee-pkey.pem5
-rw-r--r--src/tls/ecroot-cert.pem10
-rw-r--r--src/tls/ecroot-pkey.pem5
-rw-r--r--src/tls/good-mixed-keyfirst.pem45
-rw-r--r--src/tls/good-mixed-keyfirst.pem.ref12
-rw-r--r--src/tls/good-mixed-keylast.pem45
-rw-r--r--src/tls/good-mixed-keylast.pem.ref12
-rw-r--r--src/tls/good-mixed-keymiddle.pem45
-rw-r--r--src/tls/good-mixed-keymiddle.pem.ref12
-rw-r--r--src/tls/goodchains.pem121
-rw-r--r--src/tls/goodchains.pem.ref24
-rw-r--r--src/tls/mkcert.sh264
-rw-r--r--src/tls/rsaca-cert.pem19
-rw-r--r--src/tls/rsaca-pkey.pem28
-rw-r--r--src/tls/rsaee-cert.pem20
-rw-r--r--src/tls/rsaee-pkey.pem28
-rw-r--r--src/tls/rsaroot-cert.pem18
-rw-r--r--src/tls/rsaroot-pkey.pem28
-rw-r--r--src/tls/tls.h765
-rw-r--r--src/tls/tls_bio_ops.c296
-rw-r--r--src/tls/tls_certkey.c781
-rw-r--r--src/tls/tls_client.c1248
-rw-r--r--src/tls/tls_dane.c2080
-rw-r--r--src/tls/tls_dane.sh211
-rw-r--r--src/tls/tls_dh.c387
-rw-r--r--src/tls/tls_fprint.c413
-rw-r--r--src/tls/tls_level.c95
-rw-r--r--src/tls/tls_mgr.c471
-rw-r--r--src/tls/tls_mgr.h71
-rw-r--r--src/tls/tls_misc.c1678
-rw-r--r--src/tls/tls_prng.h50
-rw-r--r--src/tls/tls_prng_dev.c155
-rw-r--r--src/tls/tls_prng_egd.c166
-rw-r--r--src/tls/tls_prng_exch.c142
-rw-r--r--src/tls/tls_prng_file.c155
-rw-r--r--src/tls/tls_proxy.h303
-rw-r--r--src/tls/tls_proxy_client_misc.c192
-rw-r--r--src/tls/tls_proxy_client_print.c398
-rw-r--r--src/tls/tls_proxy_client_scan.c692
-rw-r--r--src/tls/tls_proxy_clnt.c292
-rw-r--r--src/tls/tls_proxy_context_print.c112
-rw-r--r--src/tls/tls_proxy_context_scan.c188
-rw-r--r--src/tls/tls_proxy_server_print.c143
-rw-r--r--src/tls/tls_proxy_server_scan.c245
-rw-r--r--src/tls/tls_rsa.c127
-rw-r--r--src/tls/tls_scache.c595
-rw-r--r--src/tls/tls_scache.h73
-rw-r--r--src/tls/tls_seed.c88
-rw-r--r--src/tls/tls_server.c1032
-rw-r--r--src/tls/tls_session.c185
-rw-r--r--src/tls/tls_stream.c160
-rw-r--r--src/tls/tls_verify.c507
-rw-r--r--src/tls/warn-mixed-multi-key.pem51
-rw-r--r--src/tls/warn-mixed-multi-key.pem.ref13
67 files changed, 16154 insertions, 0 deletions
diff --git a/src/tls/.indent.pro b/src/tls/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/tls/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/tls/Makefile.in b/src/tls/Makefile.in
new file mode 100644
index 0000000..fec7d45
--- /dev/null
+++ b/src/tls/Makefile.in
@@ -0,0 +1,618 @@
+SHELL = /bin/sh
+SRCS = tls_prng_dev.c tls_prng_egd.c tls_prng_file.c tls_fprint.c \
+ tls_prng_exch.c tls_stream.c tls_bio_ops.c tls_misc.c tls_dh.c \
+ tls_rsa.c tls_verify.c tls_dane.c tls_certkey.c tls_session.c \
+ tls_client.c tls_server.c tls_scache.c tls_mgr.c tls_seed.c \
+ tls_level.c \
+ tls_proxy_clnt.c tls_proxy_context_print.c tls_proxy_context_scan.c \
+ tls_proxy_client_init_print.c tls_proxy_client_init_scan.c \
+ tls_proxy_server_init_print.c tls_proxy_server_init_scan.c \
+ tls_proxy_client_start_print.c tls_proxy_client_start_scan.c \
+ tls_proxy_server_start_print.c tls_proxy_server_start_scan.c \
+ tls_proxy_client_misc.c
+OBJS = tls_prng_dev.o tls_prng_egd.o tls_prng_file.o tls_fprint.o \
+ tls_prng_exch.o tls_stream.o tls_bio_ops.o tls_misc.o tls_dh.o \
+ tls_rsa.o tls_verify.o tls_dane.o tls_certkey.o tls_session.o \
+ tls_client.o tls_server.o tls_scache.o tls_mgr.o tls_seed.o \
+ tls_level.o \
+ tls_proxy_clnt.o tls_proxy_context_print.o tls_proxy_context_scan.o \
+ tls_proxy_client_print.o tls_proxy_client_scan.o \
+ tls_proxy_server_print.o tls_proxy_server_scan.o \
+ tls_proxy_client_misc.o
+HDRS = tls.h tls_prng.h tls_scache.h tls_mgr.h tls_proxy.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = lib$(LIB_PREFIX)tls$(LIB_SUFFIX)
+TESTPROG= tls_dh tls_mgr tls_rsa tls_dane tls_certkey
+
+LIBS = ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+MAKES =
+
+.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests: tls_certkey_tests
+
+tls_certkey_tests: test
+ @echo Testing loading of keys and certs
+ @for pem in goodchains.pem; do \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem: OK"; \
+ done; \
+ for pem in *-mixed-*.pem ; do \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -m $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem: OK"; \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -k $$pem $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem (with key in $$pem): OK"; \
+ case $$pem in good-*) \
+ ln -sf $$pem tmpkey.pem; \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -k tmpkey.pem $$pem > $$pem.out 2>&1 || exit 1; \
+ diff $$pem.ref $$pem.out || exit 1; \
+ echo " $$pem (with key in tmpkey.pem): OK"; \
+ rm -f tmpkey.pem;; \
+ esac; \
+ done; \
+ for pem in bad-*.pem; do \
+ $(SHLIB_ENV) $(VALGRIND) ./tls_certkey $$pem > $$pem.out 2>&1 && exit 1 || : ok; \
+ egrep -v 'TLS library problem' $$pem.out | diff $$pem.ref - || \
+ exit 1; \
+ echo " $$pem: OK"; \
+ done
+
+root_tests:
+
+$(LIB): $(OBJS)
+ $(AR) $(ARFL) $(LIB) $?
+ $(RANLIB) $(LIB)
+ $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(RANLIB) $(LIB_DIR)/$(LIB)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o $(LIB) *core $(TESTPROG) junk *.pem.out
+ rm -rf printfck
+
+tidy: clean
+
+tls_dh: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_mgr: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_rsa: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_dane: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+tls_certkey: $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+ -e 's/o: \.\//o: /' -e p -e '}' ; \
+ done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+tls_bio_ops.o: ../../include/argv.h
+tls_bio_ops.o: ../../include/check_arg.h
+tls_bio_ops.o: ../../include/dns.h
+tls_bio_ops.o: ../../include/iostuff.h
+tls_bio_ops.o: ../../include/msg.h
+tls_bio_ops.o: ../../include/myaddrinfo.h
+tls_bio_ops.o: ../../include/name_code.h
+tls_bio_ops.o: ../../include/name_mask.h
+tls_bio_ops.o: ../../include/sock_addr.h
+tls_bio_ops.o: ../../include/sys_defs.h
+tls_bio_ops.o: ../../include/vbuf.h
+tls_bio_ops.o: ../../include/vstream.h
+tls_bio_ops.o: ../../include/vstring.h
+tls_bio_ops.o: tls.h
+tls_bio_ops.o: tls_bio_ops.c
+tls_certkey.o: ../../include/argv.h
+tls_certkey.o: ../../include/check_arg.h
+tls_certkey.o: ../../include/dns.h
+tls_certkey.o: ../../include/mail_params.h
+tls_certkey.o: ../../include/msg.h
+tls_certkey.o: ../../include/myaddrinfo.h
+tls_certkey.o: ../../include/name_code.h
+tls_certkey.o: ../../include/name_mask.h
+tls_certkey.o: ../../include/sock_addr.h
+tls_certkey.o: ../../include/sys_defs.h
+tls_certkey.o: ../../include/vbuf.h
+tls_certkey.o: ../../include/vstream.h
+tls_certkey.o: ../../include/vstring.h
+tls_certkey.o: tls.h
+tls_certkey.o: tls_certkey.c
+tls_client.o: ../../include/argv.h
+tls_client.o: ../../include/check_arg.h
+tls_client.o: ../../include/dict.h
+tls_client.o: ../../include/dns.h
+tls_client.o: ../../include/iostuff.h
+tls_client.o: ../../include/mail_params.h
+tls_client.o: ../../include/midna_domain.h
+tls_client.o: ../../include/msg.h
+tls_client.o: ../../include/myaddrinfo.h
+tls_client.o: ../../include/myflock.h
+tls_client.o: ../../include/mymalloc.h
+tls_client.o: ../../include/name_code.h
+tls_client.o: ../../include/name_mask.h
+tls_client.o: ../../include/sock_addr.h
+tls_client.o: ../../include/stringops.h
+tls_client.o: ../../include/sys_defs.h
+tls_client.o: ../../include/vbuf.h
+tls_client.o: ../../include/vstream.h
+tls_client.o: ../../include/vstring.h
+tls_client.o: tls.h
+tls_client.o: tls_client.c
+tls_client.o: tls_mgr.h
+tls_client.o: tls_scache.h
+tls_dane.o: ../../include/argv.h
+tls_dane.o: ../../include/check_arg.h
+tls_dane.o: ../../include/ctable.h
+tls_dane.o: ../../include/dns.h
+tls_dane.o: ../../include/events.h
+tls_dane.o: ../../include/hex_code.h
+tls_dane.o: ../../include/mail_params.h
+tls_dane.o: ../../include/msg.h
+tls_dane.o: ../../include/myaddrinfo.h
+tls_dane.o: ../../include/mymalloc.h
+tls_dane.o: ../../include/name_code.h
+tls_dane.o: ../../include/name_mask.h
+tls_dane.o: ../../include/safe_ultostr.h
+tls_dane.o: ../../include/sock_addr.h
+tls_dane.o: ../../include/split_at.h
+tls_dane.o: ../../include/stringops.h
+tls_dane.o: ../../include/sys_defs.h
+tls_dane.o: ../../include/timecmp.h
+tls_dane.o: ../../include/vbuf.h
+tls_dane.o: ../../include/vstream.h
+tls_dane.o: ../../include/vstring.h
+tls_dane.o: tls.h
+tls_dane.o: tls_dane.c
+tls_dh.o: ../../include/argv.h
+tls_dh.o: ../../include/check_arg.h
+tls_dh.o: ../../include/dns.h
+tls_dh.o: ../../include/mail_params.h
+tls_dh.o: ../../include/msg.h
+tls_dh.o: ../../include/myaddrinfo.h
+tls_dh.o: ../../include/mymalloc.h
+tls_dh.o: ../../include/name_code.h
+tls_dh.o: ../../include/name_mask.h
+tls_dh.o: ../../include/sock_addr.h
+tls_dh.o: ../../include/stringops.h
+tls_dh.o: ../../include/sys_defs.h
+tls_dh.o: ../../include/vbuf.h
+tls_dh.o: ../../include/vstream.h
+tls_dh.o: ../../include/vstring.h
+tls_dh.o: tls.h
+tls_dh.o: tls_dh.c
+tls_fprint.o: ../../include/argv.h
+tls_fprint.o: ../../include/check_arg.h
+tls_fprint.o: ../../include/dns.h
+tls_fprint.o: ../../include/mail_params.h
+tls_fprint.o: ../../include/msg.h
+tls_fprint.o: ../../include/myaddrinfo.h
+tls_fprint.o: ../../include/mymalloc.h
+tls_fprint.o: ../../include/name_code.h
+tls_fprint.o: ../../include/name_mask.h
+tls_fprint.o: ../../include/sock_addr.h
+tls_fprint.o: ../../include/stringops.h
+tls_fprint.o: ../../include/sys_defs.h
+tls_fprint.o: ../../include/vbuf.h
+tls_fprint.o: ../../include/vstream.h
+tls_fprint.o: ../../include/vstring.h
+tls_fprint.o: tls.h
+tls_fprint.o: tls_fprint.c
+tls_level.o: ../../include/argv.h
+tls_level.o: ../../include/check_arg.h
+tls_level.o: ../../include/dns.h
+tls_level.o: ../../include/myaddrinfo.h
+tls_level.o: ../../include/name_code.h
+tls_level.o: ../../include/name_mask.h
+tls_level.o: ../../include/sock_addr.h
+tls_level.o: ../../include/sys_defs.h
+tls_level.o: ../../include/vbuf.h
+tls_level.o: ../../include/vstream.h
+tls_level.o: ../../include/vstring.h
+tls_level.o: tls.h
+tls_level.o: tls_level.c
+tls_mgr.o: ../../include/argv.h
+tls_mgr.o: ../../include/attr.h
+tls_mgr.o: ../../include/attr_clnt.h
+tls_mgr.o: ../../include/check_arg.h
+tls_mgr.o: ../../include/dict.h
+tls_mgr.o: ../../include/htable.h
+tls_mgr.o: ../../include/iostuff.h
+tls_mgr.o: ../../include/mail_params.h
+tls_mgr.o: ../../include/mail_proto.h
+tls_mgr.o: ../../include/msg.h
+tls_mgr.o: ../../include/myflock.h
+tls_mgr.o: ../../include/mymalloc.h
+tls_mgr.o: ../../include/nvtable.h
+tls_mgr.o: ../../include/stringops.h
+tls_mgr.o: ../../include/sys_defs.h
+tls_mgr.o: ../../include/vbuf.h
+tls_mgr.o: ../../include/vstream.h
+tls_mgr.o: ../../include/vstring.h
+tls_mgr.o: tls_mgr.c
+tls_mgr.o: tls_mgr.h
+tls_mgr.o: tls_scache.h
+tls_misc.o: ../../include/argv.h
+tls_misc.o: ../../include/check_arg.h
+tls_misc.o: ../../include/dict.h
+tls_misc.o: ../../include/dns.h
+tls_misc.o: ../../include/mail_conf.h
+tls_misc.o: ../../include/mail_params.h
+tls_misc.o: ../../include/maps.h
+tls_misc.o: ../../include/msg.h
+tls_misc.o: ../../include/myaddrinfo.h
+tls_misc.o: ../../include/myflock.h
+tls_misc.o: ../../include/mymalloc.h
+tls_misc.o: ../../include/name_code.h
+tls_misc.o: ../../include/name_mask.h
+tls_misc.o: ../../include/sock_addr.h
+tls_misc.o: ../../include/stringops.h
+tls_misc.o: ../../include/sys_defs.h
+tls_misc.o: ../../include/valid_hostname.h
+tls_misc.o: ../../include/vbuf.h
+tls_misc.o: ../../include/vstream.h
+tls_misc.o: ../../include/vstring.h
+tls_misc.o: tls.h
+tls_misc.o: tls_misc.c
+tls_prng_dev.o: ../../include/connect.h
+tls_prng_dev.o: ../../include/iostuff.h
+tls_prng_dev.o: ../../include/msg.h
+tls_prng_dev.o: ../../include/mymalloc.h
+tls_prng_dev.o: ../../include/sys_defs.h
+tls_prng_dev.o: tls_prng.h
+tls_prng_dev.o: tls_prng_dev.c
+tls_prng_egd.o: ../../include/connect.h
+tls_prng_egd.o: ../../include/iostuff.h
+tls_prng_egd.o: ../../include/msg.h
+tls_prng_egd.o: ../../include/mymalloc.h
+tls_prng_egd.o: ../../include/sys_defs.h
+tls_prng_egd.o: tls_prng.h
+tls_prng_egd.o: tls_prng_egd.c
+tls_prng_exch.o: ../../include/iostuff.h
+tls_prng_exch.o: ../../include/msg.h
+tls_prng_exch.o: ../../include/myflock.h
+tls_prng_exch.o: ../../include/mymalloc.h
+tls_prng_exch.o: ../../include/sys_defs.h
+tls_prng_exch.o: tls_prng.h
+tls_prng_exch.o: tls_prng_exch.c
+tls_prng_file.o: ../../include/connect.h
+tls_prng_file.o: ../../include/iostuff.h
+tls_prng_file.o: ../../include/msg.h
+tls_prng_file.o: ../../include/mymalloc.h
+tls_prng_file.o: ../../include/sys_defs.h
+tls_prng_file.o: tls_prng.h
+tls_prng_file.o: tls_prng_file.c
+tls_proxy_client_misc.o: ../../include/argv.h
+tls_proxy_client_misc.o: ../../include/attr.h
+tls_proxy_client_misc.o: ../../include/check_arg.h
+tls_proxy_client_misc.o: ../../include/dns.h
+tls_proxy_client_misc.o: ../../include/htable.h
+tls_proxy_client_misc.o: ../../include/mail_params.h
+tls_proxy_client_misc.o: ../../include/msg.h
+tls_proxy_client_misc.o: ../../include/myaddrinfo.h
+tls_proxy_client_misc.o: ../../include/mymalloc.h
+tls_proxy_client_misc.o: ../../include/name_code.h
+tls_proxy_client_misc.o: ../../include/name_mask.h
+tls_proxy_client_misc.o: ../../include/nvtable.h
+tls_proxy_client_misc.o: ../../include/sock_addr.h
+tls_proxy_client_misc.o: ../../include/sys_defs.h
+tls_proxy_client_misc.o: ../../include/vbuf.h
+tls_proxy_client_misc.o: ../../include/vstream.h
+tls_proxy_client_misc.o: ../../include/vstring.h
+tls_proxy_client_misc.o: tls.h
+tls_proxy_client_misc.o: tls_proxy.h
+tls_proxy_client_misc.o: tls_proxy_client_misc.c
+tls_proxy_client_print.o: ../../include/argv.h
+tls_proxy_client_print.o: ../../include/argv_attr.h
+tls_proxy_client_print.o: ../../include/attr.h
+tls_proxy_client_print.o: ../../include/check_arg.h
+tls_proxy_client_print.o: ../../include/dns.h
+tls_proxy_client_print.o: ../../include/htable.h
+tls_proxy_client_print.o: ../../include/mail_params.h
+tls_proxy_client_print.o: ../../include/msg.h
+tls_proxy_client_print.o: ../../include/myaddrinfo.h
+tls_proxy_client_print.o: ../../include/mymalloc.h
+tls_proxy_client_print.o: ../../include/name_code.h
+tls_proxy_client_print.o: ../../include/name_mask.h
+tls_proxy_client_print.o: ../../include/nvtable.h
+tls_proxy_client_print.o: ../../include/sock_addr.h
+tls_proxy_client_print.o: ../../include/sys_defs.h
+tls_proxy_client_print.o: ../../include/vbuf.h
+tls_proxy_client_print.o: ../../include/vstream.h
+tls_proxy_client_print.o: ../../include/vstring.h
+tls_proxy_client_print.o: tls.h
+tls_proxy_client_print.o: tls_proxy.h
+tls_proxy_client_print.o: tls_proxy_client_print.c
+tls_proxy_client_scan.o: ../../include/argv.h
+tls_proxy_client_scan.o: ../../include/argv_attr.h
+tls_proxy_client_scan.o: ../../include/attr.h
+tls_proxy_client_scan.o: ../../include/check_arg.h
+tls_proxy_client_scan.o: ../../include/dns.h
+tls_proxy_client_scan.o: ../../include/htable.h
+tls_proxy_client_scan.o: ../../include/mail_params.h
+tls_proxy_client_scan.o: ../../include/msg.h
+tls_proxy_client_scan.o: ../../include/myaddrinfo.h
+tls_proxy_client_scan.o: ../../include/mymalloc.h
+tls_proxy_client_scan.o: ../../include/name_code.h
+tls_proxy_client_scan.o: ../../include/name_mask.h
+tls_proxy_client_scan.o: ../../include/nvtable.h
+tls_proxy_client_scan.o: ../../include/sock_addr.h
+tls_proxy_client_scan.o: ../../include/sys_defs.h
+tls_proxy_client_scan.o: ../../include/vbuf.h
+tls_proxy_client_scan.o: ../../include/vstream.h
+tls_proxy_client_scan.o: ../../include/vstring.h
+tls_proxy_client_scan.o: tls.h
+tls_proxy_client_scan.o: tls_proxy.h
+tls_proxy_client_scan.o: tls_proxy_client_scan.c
+tls_proxy_clnt.o: ../../include/argv.h
+tls_proxy_clnt.o: ../../include/attr.h
+tls_proxy_clnt.o: ../../include/check_arg.h
+tls_proxy_clnt.o: ../../include/connect.h
+tls_proxy_clnt.o: ../../include/dns.h
+tls_proxy_clnt.o: ../../include/htable.h
+tls_proxy_clnt.o: ../../include/iostuff.h
+tls_proxy_clnt.o: ../../include/mail_params.h
+tls_proxy_clnt.o: ../../include/mail_proto.h
+tls_proxy_clnt.o: ../../include/msg.h
+tls_proxy_clnt.o: ../../include/myaddrinfo.h
+tls_proxy_clnt.o: ../../include/mymalloc.h
+tls_proxy_clnt.o: ../../include/name_code.h
+tls_proxy_clnt.o: ../../include/name_mask.h
+tls_proxy_clnt.o: ../../include/nvtable.h
+tls_proxy_clnt.o: ../../include/sock_addr.h
+tls_proxy_clnt.o: ../../include/stringops.h
+tls_proxy_clnt.o: ../../include/sys_defs.h
+tls_proxy_clnt.o: ../../include/vbuf.h
+tls_proxy_clnt.o: ../../include/vstream.h
+tls_proxy_clnt.o: ../../include/vstring.h
+tls_proxy_clnt.o: tls.h
+tls_proxy_clnt.o: tls_proxy.h
+tls_proxy_clnt.o: tls_proxy_clnt.c
+tls_proxy_context_print.o: ../../include/argv.h
+tls_proxy_context_print.o: ../../include/attr.h
+tls_proxy_context_print.o: ../../include/check_arg.h
+tls_proxy_context_print.o: ../../include/dns.h
+tls_proxy_context_print.o: ../../include/htable.h
+tls_proxy_context_print.o: ../../include/myaddrinfo.h
+tls_proxy_context_print.o: ../../include/mymalloc.h
+tls_proxy_context_print.o: ../../include/name_code.h
+tls_proxy_context_print.o: ../../include/name_mask.h
+tls_proxy_context_print.o: ../../include/nvtable.h
+tls_proxy_context_print.o: ../../include/sock_addr.h
+tls_proxy_context_print.o: ../../include/sys_defs.h
+tls_proxy_context_print.o: ../../include/vbuf.h
+tls_proxy_context_print.o: ../../include/vstream.h
+tls_proxy_context_print.o: ../../include/vstring.h
+tls_proxy_context_print.o: tls.h
+tls_proxy_context_print.o: tls_proxy.h
+tls_proxy_context_print.o: tls_proxy_context_print.c
+tls_proxy_context_scan.o: ../../include/argv.h
+tls_proxy_context_scan.o: ../../include/attr.h
+tls_proxy_context_scan.o: ../../include/check_arg.h
+tls_proxy_context_scan.o: ../../include/dns.h
+tls_proxy_context_scan.o: ../../include/htable.h
+tls_proxy_context_scan.o: ../../include/msg.h
+tls_proxy_context_scan.o: ../../include/myaddrinfo.h
+tls_proxy_context_scan.o: ../../include/mymalloc.h
+tls_proxy_context_scan.o: ../../include/name_code.h
+tls_proxy_context_scan.o: ../../include/name_mask.h
+tls_proxy_context_scan.o: ../../include/nvtable.h
+tls_proxy_context_scan.o: ../../include/sock_addr.h
+tls_proxy_context_scan.o: ../../include/sys_defs.h
+tls_proxy_context_scan.o: ../../include/vbuf.h
+tls_proxy_context_scan.o: ../../include/vstream.h
+tls_proxy_context_scan.o: ../../include/vstring.h
+tls_proxy_context_scan.o: tls.h
+tls_proxy_context_scan.o: tls_proxy.h
+tls_proxy_context_scan.o: tls_proxy_context_scan.c
+tls_proxy_server_print.o: ../../include/argv.h
+tls_proxy_server_print.o: ../../include/attr.h
+tls_proxy_server_print.o: ../../include/check_arg.h
+tls_proxy_server_print.o: ../../include/dns.h
+tls_proxy_server_print.o: ../../include/htable.h
+tls_proxy_server_print.o: ../../include/myaddrinfo.h
+tls_proxy_server_print.o: ../../include/mymalloc.h
+tls_proxy_server_print.o: ../../include/name_code.h
+tls_proxy_server_print.o: ../../include/name_mask.h
+tls_proxy_server_print.o: ../../include/nvtable.h
+tls_proxy_server_print.o: ../../include/sock_addr.h
+tls_proxy_server_print.o: ../../include/sys_defs.h
+tls_proxy_server_print.o: ../../include/vbuf.h
+tls_proxy_server_print.o: ../../include/vstream.h
+tls_proxy_server_print.o: ../../include/vstring.h
+tls_proxy_server_print.o: tls.h
+tls_proxy_server_print.o: tls_proxy.h
+tls_proxy_server_print.o: tls_proxy_server_print.c
+tls_proxy_server_scan.o: ../../include/argv.h
+tls_proxy_server_scan.o: ../../include/attr.h
+tls_proxy_server_scan.o: ../../include/check_arg.h
+tls_proxy_server_scan.o: ../../include/dns.h
+tls_proxy_server_scan.o: ../../include/htable.h
+tls_proxy_server_scan.o: ../../include/myaddrinfo.h
+tls_proxy_server_scan.o: ../../include/mymalloc.h
+tls_proxy_server_scan.o: ../../include/name_code.h
+tls_proxy_server_scan.o: ../../include/name_mask.h
+tls_proxy_server_scan.o: ../../include/nvtable.h
+tls_proxy_server_scan.o: ../../include/sock_addr.h
+tls_proxy_server_scan.o: ../../include/sys_defs.h
+tls_proxy_server_scan.o: ../../include/vbuf.h
+tls_proxy_server_scan.o: ../../include/vstream.h
+tls_proxy_server_scan.o: ../../include/vstring.h
+tls_proxy_server_scan.o: tls.h
+tls_proxy_server_scan.o: tls_proxy.h
+tls_proxy_server_scan.o: tls_proxy_server_scan.c
+tls_rsa.o: ../../include/argv.h
+tls_rsa.o: ../../include/check_arg.h
+tls_rsa.o: ../../include/dns.h
+tls_rsa.o: ../../include/msg.h
+tls_rsa.o: ../../include/myaddrinfo.h
+tls_rsa.o: ../../include/name_code.h
+tls_rsa.o: ../../include/name_mask.h
+tls_rsa.o: ../../include/sock_addr.h
+tls_rsa.o: ../../include/sys_defs.h
+tls_rsa.o: ../../include/vbuf.h
+tls_rsa.o: ../../include/vstream.h
+tls_rsa.o: ../../include/vstring.h
+tls_rsa.o: tls.h
+tls_rsa.o: tls_rsa.c
+tls_scache.o: ../../include/argv.h
+tls_scache.o: ../../include/check_arg.h
+tls_scache.o: ../../include/dict.h
+tls_scache.o: ../../include/hex_code.h
+tls_scache.o: ../../include/msg.h
+tls_scache.o: ../../include/myflock.h
+tls_scache.o: ../../include/mymalloc.h
+tls_scache.o: ../../include/stringops.h
+tls_scache.o: ../../include/sys_defs.h
+tls_scache.o: ../../include/timecmp.h
+tls_scache.o: ../../include/vbuf.h
+tls_scache.o: ../../include/vstream.h
+tls_scache.o: ../../include/vstring.h
+tls_scache.o: tls_scache.c
+tls_scache.o: tls_scache.h
+tls_seed.o: ../../include/argv.h
+tls_seed.o: ../../include/check_arg.h
+tls_seed.o: ../../include/dict.h
+tls_seed.o: ../../include/dns.h
+tls_seed.o: ../../include/msg.h
+tls_seed.o: ../../include/myaddrinfo.h
+tls_seed.o: ../../include/myflock.h
+tls_seed.o: ../../include/name_code.h
+tls_seed.o: ../../include/name_mask.h
+tls_seed.o: ../../include/sock_addr.h
+tls_seed.o: ../../include/sys_defs.h
+tls_seed.o: ../../include/vbuf.h
+tls_seed.o: ../../include/vstream.h
+tls_seed.o: ../../include/vstring.h
+tls_seed.o: tls.h
+tls_seed.o: tls_mgr.h
+tls_seed.o: tls_scache.h
+tls_seed.o: tls_seed.c
+tls_server.o: ../../include/argv.h
+tls_server.o: ../../include/check_arg.h
+tls_server.o: ../../include/dict.h
+tls_server.o: ../../include/dns.h
+tls_server.o: ../../include/hex_code.h
+tls_server.o: ../../include/iostuff.h
+tls_server.o: ../../include/mail_params.h
+tls_server.o: ../../include/msg.h
+tls_server.o: ../../include/myaddrinfo.h
+tls_server.o: ../../include/myflock.h
+tls_server.o: ../../include/mymalloc.h
+tls_server.o: ../../include/name_code.h
+tls_server.o: ../../include/name_mask.h
+tls_server.o: ../../include/sock_addr.h
+tls_server.o: ../../include/stringops.h
+tls_server.o: ../../include/sys_defs.h
+tls_server.o: ../../include/vbuf.h
+tls_server.o: ../../include/vstream.h
+tls_server.o: ../../include/vstring.h
+tls_server.o: tls.h
+tls_server.o: tls_mgr.h
+tls_server.o: tls_scache.h
+tls_server.o: tls_server.c
+tls_session.o: ../../include/argv.h
+tls_session.o: ../../include/check_arg.h
+tls_session.o: ../../include/dns.h
+tls_session.o: ../../include/mail_params.h
+tls_session.o: ../../include/msg.h
+tls_session.o: ../../include/myaddrinfo.h
+tls_session.o: ../../include/mymalloc.h
+tls_session.o: ../../include/name_code.h
+tls_session.o: ../../include/name_mask.h
+tls_session.o: ../../include/sock_addr.h
+tls_session.o: ../../include/sys_defs.h
+tls_session.o: ../../include/vbuf.h
+tls_session.o: ../../include/vstream.h
+tls_session.o: ../../include/vstring.h
+tls_session.o: tls.h
+tls_session.o: tls_session.c
+tls_stream.o: ../../include/argv.h
+tls_stream.o: ../../include/check_arg.h
+tls_stream.o: ../../include/dns.h
+tls_stream.o: ../../include/iostuff.h
+tls_stream.o: ../../include/msg.h
+tls_stream.o: ../../include/myaddrinfo.h
+tls_stream.o: ../../include/name_code.h
+tls_stream.o: ../../include/name_mask.h
+tls_stream.o: ../../include/sock_addr.h
+tls_stream.o: ../../include/sys_defs.h
+tls_stream.o: ../../include/vbuf.h
+tls_stream.o: ../../include/vstream.h
+tls_stream.o: ../../include/vstring.h
+tls_stream.o: tls.h
+tls_stream.o: tls_stream.c
+tls_verify.o: ../../include/argv.h
+tls_verify.o: ../../include/check_arg.h
+tls_verify.o: ../../include/dns.h
+tls_verify.o: ../../include/msg.h
+tls_verify.o: ../../include/myaddrinfo.h
+tls_verify.o: ../../include/mymalloc.h
+tls_verify.o: ../../include/name_code.h
+tls_verify.o: ../../include/name_mask.h
+tls_verify.o: ../../include/sock_addr.h
+tls_verify.o: ../../include/stringops.h
+tls_verify.o: ../../include/sys_defs.h
+tls_verify.o: ../../include/vbuf.h
+tls_verify.o: ../../include/vstream.h
+tls_verify.o: ../../include/vstring.h
+tls_verify.o: tls.h
+tls_verify.o: tls_verify.c
diff --git a/src/tls/bad-back-to-back-keys.pem b/src/tls/bad-back-to-back-keys.pem
new file mode 100644
index 0000000..b1895ec
--- /dev/null
+++ b/src/tls/bad-back-to-back-keys.pem
@@ -0,0 +1,64 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/bad-back-to-back-keys.pem.ref b/src/tls/bad-back-to-back-keys.pem.ref
new file mode 100644
index 0000000..192e9ea
--- /dev/null
+++ b/src/tls/bad-back-to-back-keys.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: error loading certificate chain: key at index 1 in bad-back-to-back-keys.pem not followed by a certificate
+unknown: warning: error loading private keys and certificates from: bad-back-to-back-keys.pem: disabling TLS support
diff --git a/src/tls/bad-ec-cert-before-key.pem b/src/tls/bad-ec-cert-before-key.pem
new file mode 100644
index 0000000..520927a
--- /dev/null
+++ b/src/tls/bad-ec-cert-before-key.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/bad-ec-cert-before-key.pem.ref b/src/tls/bad-ec-cert-before-key.pem.ref
new file mode 100644
index 0000000..71e5f78
--- /dev/null
+++ b/src/tls/bad-ec-cert-before-key.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: error loading chain from bad-ec-cert-before-key.pem: key not first
+unknown: warning: error loading private keys and certificates from: bad-ec-cert-before-key.pem: disabling TLS support
diff --git a/src/tls/bad-key-cert-mismatch.pem b/src/tls/bad-key-cert-mismatch.pem
new file mode 100644
index 0000000..1e78833
--- /dev/null
+++ b/src/tls/bad-key-cert-mismatch.pem
@@ -0,0 +1,36 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC
+rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy
+b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/bad-key-cert-mismatch.pem.ref b/src/tls/bad-key-cert-mismatch.pem.ref
new file mode 100644
index 0000000..a32b657
--- /dev/null
+++ b/src/tls/bad-key-cert-mismatch.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: key at index 1 in bad-key-cert-mismatch.pem does not match next certificate
+unknown: warning: error loading private keys and certificates from: bad-key-cert-mismatch.pem: disabling TLS support
diff --git a/src/tls/bad-rsa-key-last.pem b/src/tls/bad-rsa-key-last.pem
new file mode 100644
index 0000000..de15808
--- /dev/null
+++ b/src/tls/bad-rsa-key-last.pem
@@ -0,0 +1,64 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
diff --git a/src/tls/bad-rsa-key-last.pem.ref b/src/tls/bad-rsa-key-last.pem.ref
new file mode 100644
index 0000000..a72c0d9
--- /dev/null
+++ b/src/tls/bad-rsa-key-last.pem.ref
@@ -0,0 +1,2 @@
+unknown: warning: No certs for key at index 5 in bad-rsa-key-last.pem
+unknown: warning: error loading private keys and certificates from: bad-rsa-key-last.pem: disabling TLS support
diff --git a/src/tls/ecca-cert.pem b/src/tls/ecca-cert.pem
new file mode 100644
index 0000000..54418f1
--- /dev/null
+++ b/src/tls/ecca-cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
diff --git a/src/tls/ecca-pkey.pem b/src/tls/ecca-pkey.pem
new file mode 100644
index 0000000..2d95f45
--- /dev/null
+++ b/src/tls/ecca-pkey.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC
+rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy
+b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV
+-----END PRIVATE KEY-----
diff --git a/src/tls/ecee-cert.pem b/src/tls/ecee-cert.pem
new file mode 100644
index 0000000..1afb2f5
--- /dev/null
+++ b/src/tls/ecee-cert.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
diff --git a/src/tls/ecee-pkey.pem b/src/tls/ecee-pkey.pem
new file mode 100644
index 0000000..ffec716
--- /dev/null
+++ b/src/tls/ecee-pkey.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
diff --git a/src/tls/ecroot-cert.pem b/src/tls/ecroot-cert.pem
new file mode 100644
index 0000000..4f5041c
--- /dev/null
+++ b/src/tls/ecroot-cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/ecroot-pkey.pem b/src/tls/ecroot-pkey.pem
new file mode 100644
index 0000000..0d36dc4
--- /dev/null
+++ b/src/tls/ecroot-pkey.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg85iR6mrXjfV9O38h
+9/KivPeTKxdjXHQ6j2VCWD8K8KahRANCAAR8MXvjqDNXBKh79SxW0WpJfUBIptgx
+1xwT2k4HkP98rlo+pmzL39gpIT1P78VwKBlrq4RE7JHnzdlQzJNAWgdG
+-----END PRIVATE KEY-----
diff --git a/src/tls/good-mixed-keyfirst.pem b/src/tls/good-mixed-keyfirst.pem
new file mode 100644
index 0000000..c7ba743
--- /dev/null
+++ b/src/tls/good-mixed-keyfirst.pem
@@ -0,0 +1,45 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
+
diff --git a/src/tls/good-mixed-keyfirst.pem.ref b/src/tls/good-mixed-keyfirst.pem.ref
new file mode 100644
index 0000000..95502d8
--- /dev/null
+++ b/src/tls/good-mixed-keyfirst.pem.ref
@@ -0,0 +1,12 @@
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/good-mixed-keylast.pem b/src/tls/good-mixed-keylast.pem
new file mode 100644
index 0000000..6ce758e
--- /dev/null
+++ b/src/tls/good-mixed-keylast.pem
@@ -0,0 +1,45 @@
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
diff --git a/src/tls/good-mixed-keylast.pem.ref b/src/tls/good-mixed-keylast.pem.ref
new file mode 100644
index 0000000..95502d8
--- /dev/null
+++ b/src/tls/good-mixed-keylast.pem.ref
@@ -0,0 +1,12 @@
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/good-mixed-keymiddle.pem b/src/tls/good-mixed-keymiddle.pem
new file mode 100644
index 0000000..b3dda42
--- /dev/null
+++ b/src/tls/good-mixed-keymiddle.pem
@@ -0,0 +1,45 @@
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/good-mixed-keymiddle.pem.ref b/src/tls/good-mixed-keymiddle.pem.ref
new file mode 100644
index 0000000..95502d8
--- /dev/null
+++ b/src/tls/good-mixed-keymiddle.pem.ref
@@ -0,0 +1,12 @@
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/goodchains.pem b/src/tls/goodchains.pem
new file mode 100644
index 0000000..81b2b32
--- /dev/null
+++ b/src/tls/goodchains.pem
@@ -0,0 +1,121 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1SU0Eg
+aXNzdWVyIENBMCAXDTE5MDEwODA2NDQzMloYDzIxMTkwMTA5MDY0NDMyWjAaMRgw
+FgYDVQQDDA9teDEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCu99Z6AB3EA2tAlWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxl
+BNHTk4c8HkobGqMTRU0x0qpzMnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX
++5ZnqS9gWbLZyjkCCqVQR/nyK0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakH
+ZIB8TYI6p6hanNQyz4NWq1PEpWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJki
+Hka24JzNCLx3GD/Q0GNGFGoE9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIo
+W0/dbG0KaKTTeHf9LegFXez2tJeLl+vpAgMBAAGjfjB8MB0GA1UdDgQWBBTCrBLC
+itbyxruhJFbJEaGYdG4NRjAfBgNVHSMEGDAWgBTCbuRdkXOuqXbXqJJnH5iQPhkT
+JTAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214
+MS5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAYOgZ5NaFJhmjT6V+GI5m
+cuTsMC7WB5nQ8nbf2wxw052jgBvnZWAgnshtgWvrK0T6402VcTOzHt8VKS0KwLwk
+FESvj2rdoDGvGqUcw6DhCns98PRuts2nMTDlKYJTYWab2Q08PPYxTtRt1Y+A2sp7
+et+bxMjYr2mdO0wkqK7guUA9H2bZkGYJck5bbna67hpzx17YomBjDUjTtMiagXKs
+E5QzOXXqCQxiSunlxsMqDwt/HRxGvQy+BHuNq4epuZfeMOndIjl2ZyKdN2ABy88G
+dYqRO6nBHUO2/ZxlAZf11xpueVZZ61SueimPfzbsw0B7ZvPSi9KdbPIGBbD4cCwt
+JA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjQyMjRaGA8yMTE5MDEwOTA2NDIyNFowGDEWMBQG
+A1UEAwwNUlNBIGlzc3VlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3tLZ4hc8RmIX0Yd0uj1Z31mK94KMcVN//L0iiR/ZOFr39ma2Ys7AL0UQeV
+H9s4hUaMM91n0fkwVL0r8NUsCkuPS/MvJClWxTbN3rEzzaajD+BdyW1Y1QfdEI5t
+AI2wSSieF3GtiTmxH43lDHTeZz0qhwh8NNzh+j09DwYS+TfQnO70LdmR5Vks0SoF
+DmxTs2/DRup/iCWGd853C3T/lfixdhkdVwGNuxtuF6/xSEQirT2OUjjnVIcCPGq0
+Zbz3hFJ0xHoqiwNBv9PytfLk4BNf+VWTANFXnzNvZqWrOO8yQWS+RHIdf2+a96G2
+EFIgWz/rwo4WP9tozETlPzQc6P8CAwEAAaNTMFEwHQYDVR0OBBYEFMJu5F2Rc66p
+dteokmcfmJA+GRMlMB8GA1UdIwQYMBaAFJGpciw0EYdhbO3y0sM62cWnHTtiMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD6ngGSUb9grVkhlb8M5
+geC4SEhBiysoXoxHleFW6FFe4k1v5dg/xbCbNwysToEG+2S498f2FfyNzKWVDSM7
+5al+4jYrKlwTbX6W9krH6iGhE0Sdz/YHWYdPCDYssdOvS46/+MGj16WPiOdj5UYY
+U0+TVKlqLOcpqkPKrz/9X7sM8bXi8FQHPme8ksvnB9qMdwcru/BN62tUOw1JHnmh
+CCUbI3TU5xE8fUCl4F43vsLq73EfaHs4TO9hhk2hBW7K0QOinNcFlcNy8AF7mF/T
+kY4FWI/kMfw6cXkg+F44Oy3j3KUF3hgByhWzTQimY2oRpk7U5tGuC8Xgh6eHPbRG
+Kck=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjE5NDJaGA8yMTE5MDEwOTA2MTk0MlowFjEUMBIG
+A1UEAwwLUlNBIHJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCoZoGzTiMbZcztQVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65An
+K2vHNrE37DDRl2YTmP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6
+yuO07nut+rNFMSSjgnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0S
+oxQVSkyOIr/phPAkLWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2
+X8lMaz8ooT9OI/4ln/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu
+1ViLHIsm+55CJj5OI676Q4JJAgMBAAGjUzBRMB0GA1UdDgQWBBSRqXIsNBGHYWzt
+8tLDOtnFpx07YjAfBgNVHSMEGDAWgBSRqXIsNBGHYWzt8tLDOtnFpx07YjAPBgNV
+HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCHEQ8DRqrWz8xibaNEv3Id
+SEJvgfRRsMA7jk2HAnE84bO1ZLkXF9HX7oqXbIMRw5IZRUrBXO0fUwcxz3DwTYZj
+ojRPP1yNfylmc8uxyd2goMoG4OgTdt2/Ewu8bli0S9NapH9fdBYbSDUBw3QiH+Wy
+ze9BiDVLwM7eERHGj1XKlC3iUaTZTOFWrr4y5eRjCD8oMzCB/73CWMRwBGPyF+Wg
+uP8FfF7i05B9rrKELAzCr4/hqYPDYNX8mv8QzcwXbn93PY35VN5y/wBuccggv0af
+iK5NfoNxvmTp9SVD4wA2prlkq1lbjMuqoAxrKpCGG0I0x8Dzn3bTaTNETal61Wt/
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/goodchains.pem.ref b/src/tls/goodchains.pem.ref
new file mode 100644
index 0000000..ade7562
--- /dev/null
+++ b/src/tls/goodchains.pem.ref
@@ -0,0 +1,24 @@
+depth = 0
+issuer = /CN=RSA issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=RSA root CA
+subject = /CN=RSA issuer CA
+
+depth = 2
+issuer = /CN=RSA root CA
+subject = /CN=RSA root CA
+
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+
diff --git a/src/tls/mkcert.sh b/src/tls/mkcert.sh
new file mode 100644
index 0000000..c15f4e1
--- /dev/null
+++ b/src/tls/mkcert.sh
@@ -0,0 +1,264 @@
+#! /bin/bash
+#
+# Copyright (c) 2016 Viktor Dukhovni <openssl-users@dukhovni.org>.
+# All rights reserved.
+#
+# Licensed under the terms of the Postfix SECURE MAILER license
+# included with the Postfix source code.
+#
+# This file is dual-licensed and is also available under other terms.
+# Please contact the author.
+
+# 100 years should be enough for now
+if [ -z "$DAYS" ]; then
+ DAYS=36525
+fi
+
+if [ -z "$OPENSSL_SIGALG" ]; then
+ OPENSSL_SIGALG=sha256
+fi
+
+case ${OPENSSL_SIGALG} in
+ null) dopts=();;
+ *) dopts=(-"${OPENSSL_SIGALG}");;
+esac
+
+if [ -z "$REQMASK" ]; then
+ REQMASK=utf8only
+fi
+
+stderr_onerror() {
+ (
+ err=$("$@" >&3 2>&1) || {
+ printf "%s\n" "$err" >&2
+ exit 1
+ }
+ ) 3>&1
+}
+
+key() {
+ local key=$1; shift
+
+ local alg=rsa
+ if [ -n "$OPENSSL_KEYALG" ]; then
+ alg=$OPENSSL_KEYALG
+ fi
+
+ local bits=2048
+ if [ -n "$OPENSSL_KEYBITS" ]; then
+ bits=$OPENSSL_KEYBITS
+ fi
+
+ if [ ! -f "${key}.pem" ]; then
+ args=(-algorithm "$alg")
+ case $alg in
+ rsa) args=("${args[@]}" -pkeyopt rsa_keygen_bits:$bits );;
+ ec) args=("${args[@]}" -pkeyopt "ec_paramgen_curve:$bits")
+ args=("${args[@]}" -pkeyopt ec_param_enc:named_curve);;
+ dsa) args=(-paramfile "$bits");;
+ ed25519) ;;
+ ed448) ;;
+ *) printf "Unsupported key algorithm: %s\n" "$alg" >&2; return 1;;
+ esac
+ stderr_onerror \
+ openssl genpkey "${args[@]}" -out "${key}.pem"
+ fi
+}
+
+# Usage: $0 req keyname dn1 dn2 ...
+req() {
+ local key=$1; shift
+
+ key "$key"
+ local errs
+
+ stderr_onerror \
+ openssl req "${dopts[@]}" -new -key "${key}.pem" \
+ -config <(printf "string_mask=%s\n[req]\n%s\n%s\n[dn]\n" \
+ "$REQMASK" "prompt = no" "distinguished_name = dn"
+ for dn in "$@"; do echo "$dn"; done)
+}
+
+req_nocn() {
+ local key=$1; shift
+
+ key "$key"
+ stderr_onerror \
+ openssl req "${dopts[@]}" -new -subj / -key "${key}.pem" \
+ -config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \
+ "distinguished_name = dn")
+}
+
+cert() {
+ local cert=$1; shift
+ local exts=$1; shift
+
+ stderr_onerror \
+ openssl x509 "${dopts[@]}" -req -out "${cert}.pem" \
+ -extfile <(printf "%s\n" "$exts") "$@"
+}
+
+genroot() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local skid="subjectKeyIdentifier = hash"
+ local akid="authorityKeyIdentifier = keyid"
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = critical,CA:true")
+ for eku in "$@"
+ do
+ exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
+ done
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days "${DAYS}"
+}
+
+genca() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local cacert=$1; shift
+ local skid="subjectKeyIdentifier = hash"
+ local akid="authorityKeyIdentifier = keyid"
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = critical,CA:true")
+ for eku in "$@"
+ do
+ exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
+ done
+ if [ -n "$NC" ]; then
+ exts=$(printf "%s\nnameConstraints = %s\n" "$exts" "$NC")
+ fi
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+gen_nonbc_ca() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local cacert=$1; shift
+ local skid="subjectKeyIdentifier = hash"
+ local akid="authorityKeyIdentifier = keyid"
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid")
+ exts=$(printf "%s\nkeyUsage = %s\n" "$exts" "keyCertSign, cRLSign")
+ for eku in "$@"
+ do
+ exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
+ done
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+# Usage: $0 genpc keyname certname eekeyname eecertname pcext1 pcext2 ...
+#
+# Note: takes csr on stdin, so must be used with $0 req like this:
+#
+# $0 req keyname dn | $0 genpc keyname certname eekeyname eecertname pcext ...
+genpc() {
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local ca=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer:always" \
+ "basicConstraints = CA:false" \
+ "proxyCertInfo = critical, @pcexts";
+ echo "[pcexts]";
+ for x in "$@"; do echo $x; done)
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+# Usage: $0 geneealt keyname certname eekeyname eecertname alt1 alt2 ...
+#
+# Note: takes csr on stdin, so must be used with $0 req like this:
+#
+# $0 req keyname dn | $0 geneealt keyname certname eekeyname eecertname alt ...
+geneealt() {
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local ca=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid" \
+ "basicConstraints = CA:false" \
+ "subjectAltName = @alts";
+ echo "[alts]";
+ for x in "$@"; do echo $x; done)
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}"
+}
+
+genee() {
+ local OPTIND=1
+ local purpose=serverAuth
+
+ while getopts p: o
+ do
+ case $o in
+ p) purpose="$OPTARG";;
+ *) echo "Usage: $0 genee [-p EKU] cn keyname certname cakeyname cacertname" >&2
+ return 1;;
+ esac
+ done
+
+ shift $((OPTIND - 1))
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local cakey=$1; shift
+ local ca=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:false" \
+ "extendedKeyUsage = $purpose" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days "${DAYS}" "$@"
+}
+
+genss() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:false" \
+ "extendedKeyUsage = serverAuth" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ csr=$(req "$key" "CN = $cn") || return 1
+ echo "$csr" |
+ cert "$cert" "$exts" -signkey "${key}.pem" \
+ -set_serial 1 -days "${DAYS}" "$@"
+}
+
+gennocn() {
+ local key=$1; shift
+ local cert=$1; shift
+
+ csr=$(req_nocn "$key") || return 1
+ echo "$csr" |
+ cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days -1 "$@"
+}
+
+"$@"
diff --git a/src/tls/rsaca-cert.pem b/src/tls/rsaca-cert.pem
new file mode 100644
index 0000000..1be736a
--- /dev/null
+++ b/src/tls/rsaca-cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjQyMjRaGA8yMTE5MDEwOTA2NDIyNFowGDEWMBQG
+A1UEAwwNUlNBIGlzc3VlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3tLZ4hc8RmIX0Yd0uj1Z31mK94KMcVN//L0iiR/ZOFr39ma2Ys7AL0UQeV
+H9s4hUaMM91n0fkwVL0r8NUsCkuPS/MvJClWxTbN3rEzzaajD+BdyW1Y1QfdEI5t
+AI2wSSieF3GtiTmxH43lDHTeZz0qhwh8NNzh+j09DwYS+TfQnO70LdmR5Vks0SoF
+DmxTs2/DRup/iCWGd853C3T/lfixdhkdVwGNuxtuF6/xSEQirT2OUjjnVIcCPGq0
+Zbz3hFJ0xHoqiwNBv9PytfLk4BNf+VWTANFXnzNvZqWrOO8yQWS+RHIdf2+a96G2
+EFIgWz/rwo4WP9tozETlPzQc6P8CAwEAAaNTMFEwHQYDVR0OBBYEFMJu5F2Rc66p
+dteokmcfmJA+GRMlMB8GA1UdIwQYMBaAFJGpciw0EYdhbO3y0sM62cWnHTtiMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD6ngGSUb9grVkhlb8M5
+geC4SEhBiysoXoxHleFW6FFe4k1v5dg/xbCbNwysToEG+2S498f2FfyNzKWVDSM7
+5al+4jYrKlwTbX6W9krH6iGhE0Sdz/YHWYdPCDYssdOvS46/+MGj16WPiOdj5UYY
+U0+TVKlqLOcpqkPKrz/9X7sM8bXi8FQHPme8ksvnB9qMdwcru/BN62tUOw1JHnmh
+CCUbI3TU5xE8fUCl4F43vsLq73EfaHs4TO9hhk2hBW7K0QOinNcFlcNy8AF7mF/T
+kY4FWI/kMfw6cXkg+F44Oy3j3KUF3hgByhWzTQimY2oRpk7U5tGuC8Xgh6eHPbRG
+Kck=
+-----END CERTIFICATE-----
diff --git a/src/tls/rsaca-pkey.pem b/src/tls/rsaca-pkey.pem
new file mode 100644
index 0000000..2381ae6
--- /dev/null
+++ b/src/tls/rsaca-pkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC97S2eIXPEZiF9
+GHdLo9Wd9ZiveCjHFTf/y9Iokf2Tha9/ZmtmLOwC9FEHlR/bOIVGjDPdZ9H5MFS9
+K/DVLApLj0vzLyQpVsU2zd6xM82mow/gXcltWNUH3RCObQCNsEkonhdxrYk5sR+N
+5Qx03mc9KocIfDTc4fo9PQ8GEvk30Jzu9C3ZkeVZLNEqBQ5sU7Nvw0bqf4glhnfO
+dwt0/5X4sXYZHVcBjbsbbhev8UhEIq09jlI451SHAjxqtGW894RSdMR6KosDQb/T
+8rXy5OATX/lVkwDRV58zb2alqzjvMkFkvkRyHX9vmvehthBSIFs/68KOFj/baMxE
+5T80HOj/AgMBAAECggEBAIY9Ch4XRMzO5uKVFVRoEwcXXHjBNAkqTS9F719veEv5
+lEY2rLhGDfY0msUCOMboVwK6+7mEtNsstugSE6GIBCrNuH/ElQmG49NNhRW4KKWb
+4Q/TGhhoTgHF1PrlvhtnOv4zZxyY7EHTmBrhhoFf5JZN5a0wpOht7EG2U1UWugEg
+/fOXp9XUsmJRxwp1jukqIi71GDVtoq3XCK7clluvNdeSlBn7D8dZ8cYUnMkqqaZA
+YYamyP3qF6UdxblW3xzGC3/0sLQvmHZOlMBuSpWUgr6NDsQPG4qXq10XePeteViw
+WnUqu11ILp8Ivoc20wJkokj1dCN3MOO0MyGcCxEX+5ECgYEA/P+Y+0AXq1j9yjNM
+aaXT3EZwSSAP/5MtZRiomFVwCgYbH6/hUV+OVih20Yhsl3MA5vgQTpSThZmmcBPC
+c3bn53OXVi6jnSzNzuQW28R8pSGoNnGT6TzG+J+my7ZxA0NTCAa72qhDgbI0dqd/
+sjk1E6hRtOkEI8fD5/IipkvxCpcCgYEAwC4FA0AA6Oy2YOAi1KeACZpIKIDcOFJz
+TLfuxhwnNi5C/1i99d5W0doCzaneECyadhGKPbv0DlVS2fe9Xm06PQLD2Y/+vkag
+iQ7V31JfwvoTQNTPVqnGcRu2ZmzI8Nja4/BcEEoxpPbMVbQ1ualkqcwpYu66ZlbD
+yWpCLSGFadkCgYEAvAkoZYzkSqjwr4jjAR6L0QrVR7Q5z8VOlvX10Iqno/uXyzxI
+Zdd0jdqzPNZ9hy6lfATg8daBsmlZh7FX88NrZt3Fm/s8BYSYTm2+A4cM8RqL0DMo
+MNDIPV9Dc+LcKgWuv6dplYE78zhEv++L/CWCqmKOn7wUJJfDpi+Tyy9kLm8CgYAy
++gIKYqfbIS8fc1TJ48Rqx6nsVIIVzokXCJMlqcIc9RiAcyGwXlHZSGMF+tEUqUAv
+oWdyCLEsPCXF+5kXuxF/rYQV6cRA5Ksgr/a7TjZomb0RrWFyM4aX6inv8Vs7x8oI
+PHGvQH76qxx4f1zg6rXw9F7mBz0aeFlmy/DR19pzwQKBgQCJWwlv7zisuTDXAuG/
+KF4VV6D2Y00pEHsxVx/RGeUXm+3HmqrZbcnccgivxyNMIAfuE5+7s2wfnjG38nnw
+DbZtp+rEcKX1h+95QnOlFxiowdlfX/ueOC2J59+Dc+R7UoDxqkQrSbdE5advHIl9
+Xb64SvwMhLzJYSYacv+fogoAPw==
+-----END PRIVATE KEY-----
diff --git a/src/tls/rsaee-cert.pem b/src/tls/rsaee-cert.pem
new file mode 100644
index 0000000..9f923b4
--- /dev/null
+++ b/src/tls/rsaee-cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1SU0Eg
+aXNzdWVyIENBMCAXDTE5MDEwODA2NDQzMloYDzIxMTkwMTA5MDY0NDMyWjAaMRgw
+FgYDVQQDDA9teDEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCu99Z6AB3EA2tAlWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxl
+BNHTk4c8HkobGqMTRU0x0qpzMnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX
++5ZnqS9gWbLZyjkCCqVQR/nyK0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakH
+ZIB8TYI6p6hanNQyz4NWq1PEpWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJki
+Hka24JzNCLx3GD/Q0GNGFGoE9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIo
+W0/dbG0KaKTTeHf9LegFXez2tJeLl+vpAgMBAAGjfjB8MB0GA1UdDgQWBBTCrBLC
+itbyxruhJFbJEaGYdG4NRjAfBgNVHSMEGDAWgBTCbuRdkXOuqXbXqJJnH5iQPhkT
+JTAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214
+MS5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAYOgZ5NaFJhmjT6V+GI5m
+cuTsMC7WB5nQ8nbf2wxw052jgBvnZWAgnshtgWvrK0T6402VcTOzHt8VKS0KwLwk
+FESvj2rdoDGvGqUcw6DhCns98PRuts2nMTDlKYJTYWab2Q08PPYxTtRt1Y+A2sp7
+et+bxMjYr2mdO0wkqK7guUA9H2bZkGYJck5bbna67hpzx17YomBjDUjTtMiagXKs
+E5QzOXXqCQxiSunlxsMqDwt/HRxGvQy+BHuNq4epuZfeMOndIjl2ZyKdN2ABy88G
+dYqRO6nBHUO2/ZxlAZf11xpueVZZ61SueimPfzbsw0B7ZvPSi9KdbPIGBbD4cCwt
+JA==
+-----END CERTIFICATE-----
diff --git a/src/tls/rsaee-pkey.pem b/src/tls/rsaee-pkey.pem
new file mode 100644
index 0000000..aa4e498
--- /dev/null
+++ b/src/tls/rsaee-pkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA
+lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz
+Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny
+K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE
+pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE
+9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2
+tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/
+0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU
+TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7
+ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE
+8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU
+ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc
+5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv
+rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/
+QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz
+CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W
+2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe
+RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC
+LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc
+kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP
+9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM
+8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo
++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC
+yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9
+Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc
+Okn/aJneEclcdzv8JHfVMcdNxA==
+-----END PRIVATE KEY-----
diff --git a/src/tls/rsaroot-cert.pem b/src/tls/rsaroot-cert.pem
new file mode 100644
index 0000000..3911451
--- /dev/null
+++ b/src/tls/rsaroot-cert.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg
+cm9vdCBDQTAgFw0xOTAxMDgwNjE5NDJaGA8yMTE5MDEwOTA2MTk0MlowFjEUMBIG
+A1UEAwwLUlNBIHJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCoZoGzTiMbZcztQVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65An
+K2vHNrE37DDRl2YTmP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6
+yuO07nut+rNFMSSjgnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0S
+oxQVSkyOIr/phPAkLWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2
+X8lMaz8ooT9OI/4ln/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu
+1ViLHIsm+55CJj5OI676Q4JJAgMBAAGjUzBRMB0GA1UdDgQWBBSRqXIsNBGHYWzt
+8tLDOtnFpx07YjAfBgNVHSMEGDAWgBSRqXIsNBGHYWzt8tLDOtnFpx07YjAPBgNV
+HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCHEQ8DRqrWz8xibaNEv3Id
+SEJvgfRRsMA7jk2HAnE84bO1ZLkXF9HX7oqXbIMRw5IZRUrBXO0fUwcxz3DwTYZj
+ojRPP1yNfylmc8uxyd2goMoG4OgTdt2/Ewu8bli0S9NapH9fdBYbSDUBw3QiH+Wy
+ze9BiDVLwM7eERHGj1XKlC3iUaTZTOFWrr4y5eRjCD8oMzCB/73CWMRwBGPyF+Wg
+uP8FfF7i05B9rrKELAzCr4/hqYPDYNX8mv8QzcwXbn93PY35VN5y/wBuccggv0af
+iK5NfoNxvmTp9SVD4wA2prlkq1lbjMuqoAxrKpCGG0I0x8Dzn3bTaTNETal61Wt/
+-----END CERTIFICATE-----
diff --git a/src/tls/rsaroot-pkey.pem b/src/tls/rsaroot-pkey.pem
new file mode 100644
index 0000000..a5509b7
--- /dev/null
+++ b/src/tls/rsaroot-pkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoZoGzTiMbZczt
+QVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65AnK2vHNrE37DDRl2YT
+mP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6yuO07nut+rNFMSSj
+gnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0SoxQVSkyOIr/phPAk
+LWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2X8lMaz8ooT9OI/4l
+n/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu1ViLHIsm+55CJj5O
+I676Q4JJAgMBAAECggEBAKLmzk7KpnFpb+yPC5SnJ+65M+tWRxC4FQmyuEUz03Ky
+j5pJKPTGeFVkO3b27fLGMTN798E2LR+DCo6or+3VbmA9n0kZvItWOuiYt2G54hrM
+vD3wJB6KdIdUp0BIzeFNBStQi7oIS4UCfgPiQckV3F/t9tyGG1kKLLz5aAvlY0Qw
+iXylE9h6dDZyufLPlkq0KVVuZA2Em1oa6UaqSYgKPX5KN0Oa4cVmzqXVt0BGq1tB
+SW80CAtKIH2AhCvHQ98ZlWbt1XgzFeP/3idZdjF2H0y2cg9rBDwRmFBwVTceQSbY
+Ezrh5WW4okFpldb/qxjURuhwkPpf3vMe/jOIBUoXjRECgYEA2/Xoz4Z8RmhgPjh2
+QOXwSuCWyKVpOXbCoP9hH9stVbUaxDuU7/ywSVW0P+x7hImUXnnKDp6pJ+NzO96b
+u6nBuUH6tNP+GTyWFgwHJf//6rEQg9WGIK0srX8Epg+S0wCRlvxggIwXWQe3HY9P
+KWFJJdBJ165AMoc/yeYHEsK536UCgYEAw/3xTrrOqNnstLwRJw2uKYj8BDTMgCA4
+ZW96t6hYI/Pwweuu4MXVCaCnt4tzyrYVP6FcYXK5Oa0G8yTF32mk45Nmro9Q4ZhX
+Wyavk/P37W0Ep+vSVtbJ7/zgRLTGVh8LPn3T7dVcoXx/JSvEW8Ofp49GXHKq5l2V
+0luXyZmfVtUCgYEAw0Ijnf1TWqkTLoiuqOO1kLKYB5uWshUzpvms/Ttyng+7qBEA
+IJ3e2+rBrLE/4KLE260faiT8IlWtmKr+8fM67jqc1GMPwNVgokehHOGJC4yNDYrB
+m0Y/T/Bebw+KFdb+Ztq4y8QQgc7whcQO0Lv01CV3N4gOowwe2xpgkw3bNKUCgYAf
+a/gjAiodwgqEE17Anx3cBN06o2hh5kiEYrIO/ctbwEFKJcn8uVrlVz9sWswupLCV
+af5QlT8C8y2ZD701i09nOPuOYuW5tV3T/EjL9KI8C21iqpknWPo5IpamDUF7DzET
+TMMMb1eRgE82G2U4vQ08pOjH645groJVnl+gb6OvHQKBgCurbKUMy0kPGECptAJ9
+dgqC9fricGX63bMPIcnz0V7uqvYbalNPhlmhwvMCkPNebWgzMnLQM3zrh+VnmOR2
+43MfgCm/+hqhRIyt3PQcgKS7shzrf2xPFZRMZVJFRk+sbDaD0MhUxXQ4g34tHtFz
+ozsnCRsyRaJNG8hC49dTTrlh
+-----END PRIVATE KEY-----
diff --git a/src/tls/tls.h b/src/tls/tls.h
new file mode 100644
index 0000000..6c0c453
--- /dev/null
+++ b/src/tls/tls.h
@@ -0,0 +1,765 @@
+#ifndef _TLS_H_INCLUDED_
+#define _TLS_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls 3h
+/* SUMMARY
+/* libtls internal interfaces
+/* SYNOPSIS
+/* #include <tls.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <name_code.h>
+#include <argv.h>
+
+ /*
+ * TLS enforcement levels. Non-sentinel values may also be used to indicate
+ * the actual security level of a session.
+ *
+ * XXX TLS_LEV_NOTFOUND no longer belongs in this list. The SMTP client will
+ * have to use something else to report that policy table lookup failed.
+ *
+ * The order of levels matters, but we hide most of the details in macros.
+ *
+ * "dane" vs. "fingerprint", both must lie between "encrypt" and "verify".
+ *
+ * - With "may" and higher, TLS is enabled.
+ *
+ * - With "encrypt" and higher, TLS encryption must be applied.
+ *
+ * - Strictly above "encrypt", the peer certificate must match.
+ *
+ * - At "dane" and higher, the peer certificate must also be trusted. With
+ * "dane" the trust may be self-asserted, so we only log trust verification
+ * errors when TA associations are involved.
+ */
+#define TLS_LEV_INVALID -2 /* sentinel */
+#define TLS_LEV_NOTFOUND -1 /* XXX not in policy table */
+#define TLS_LEV_NONE 0 /* plain-text only */
+#define TLS_LEV_MAY 1 /* wildcard */
+#define TLS_LEV_ENCRYPT 2 /* encrypted connection */
+#define TLS_LEV_FPRINT 3 /* "peer" CA-less verification */
+#define TLS_LEV_HALF_DANE 4 /* DANE TLSA MX host, insecure MX RR */
+#define TLS_LEV_DANE 5 /* Opportunistic TLSA policy */
+#define TLS_LEV_DANE_ONLY 6 /* Required TLSA policy */
+#define TLS_LEV_VERIFY 7 /* certificate verified */
+#define TLS_LEV_SECURE 8 /* "secure" verification */
+
+#define TLS_REQUIRED(l) ((l) > TLS_LEV_MAY)
+#define TLS_MUST_MATCH(l) ((l) > TLS_LEV_ENCRYPT)
+#define TLS_MUST_TRUST(l) ((l) >= TLS_LEV_HALF_DANE)
+#define TLS_MUST_PKIX(l) ((l) >= TLS_LEV_VERIFY)
+#define TLS_OPPORTUNISTIC(l) ((l) == TLS_LEV_MAY || (l) == TLS_LEV_DANE)
+#define TLS_DANE_BASED(l) \
+ ((l) >= TLS_LEV_HALF_DANE && (l) <= TLS_LEV_DANE_ONLY)
+#define TLS_NEVER_SECURED(l) ((l) == TLS_LEV_HALF_DANE)
+
+extern int tls_level_lookup(const char *);
+extern const char *str_tls_level(int);
+
+#ifdef USE_TLS
+
+ /*
+ * OpenSSL library.
+ */
+#include <openssl/lhash.h>
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/rand.h>
+#include <openssl/crypto.h> /* Legacy SSLEAY_VERSION_NUMBER */
+#include <openssl/opensslv.h> /* OPENSSL_VERSION_NUMBER */
+#include <openssl/ssl.h>
+#include <openssl/conf.h>
+
+ /* Appease indent(1) */
+#define x509_stack_t STACK_OF(X509)
+#define general_name_stack_t STACK_OF(GENERAL_NAME)
+#define ssl_cipher_stack_t STACK_OF(SSL_CIPHER)
+#define ssl_comp_stack_t STACK_OF(SSL_COMP)
+
+ /*-
+ * Official way to check minimum OpenSSL API version from 3.0 onward.
+ * We simply define it false for all prior versions, where we typically also
+ * need the patch level to determine API compatibility.
+ */
+#ifndef OPENSSL_VERSION_PREREQ
+#define OPENSSL_VERSION_PREREQ(m,n) 0
+#endif
+
+
+#if (OPENSSL_VERSION_NUMBER < 0x1000200fUL)
+#error "OpenSSL releases prior to 1.0.2 are no longer supported"
+#endif
+
+ /* Backwards compatibility with OpenSSL < 1.1.0 */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#define OpenSSL_version_num SSLeay
+#define OpenSSL_version SSLeay_version
+#define OPENSSL_VERSION SSLEAY_VERSION
+#define X509_STORE_up_ref(store) \
+ CRYPTO_add(&((store)->references), 1, CRYPTO_LOCK_X509)
+#define X509_up_ref(x) \
+ CRYPTO_add(&((x)->references), 1, CRYPTO_LOCK_X509)
+#define EVP_PKEY_up_ref(k) \
+ CRYPTO_add(&((k)->references), 1, CRYPTO_LOCK_EVP_PKEY)
+#define X509_STORE_CTX_get0_cert(ctx) ((ctx)->cert)
+#define X509_STORE_CTX_get0_untrusted(ctx) ((ctx)->untrusted)
+#define X509_STORE_CTX_set0_untrusted X509_STORE_CTX_set_chain
+#define X509_STORE_CTX_set0_trusted_stack X509_STORE_CTX_trusted_stack
+#define ASN1_STRING_get0_data ASN1_STRING_data
+#define X509_getm_notBefore X509_get_notBefore
+#define X509_getm_notAfter X509_get_notAfter
+#define TLS_method SSLv23_method
+#define TLS_client_method SSLv23_client_method
+#define TLS_server_method SSLv23_server_method
+#define EVP_MD_CTX_new EVP_MD_CTX_create
+#define EVP_MD_CTX_free EVP_MD_CTX_destroy
+#endif
+
+ /* Backwards compatibility with OpenSSL < 1.1.1 */
+#if OPENSSL_VERSION_NUMBER < 0x1010100fUL
+#define SSL_CTX_set_num_tickets(ctx, num) ((void)0)
+#endif
+
+ /*-
+ * Backwards compatibility with OpenSSL < 1.1.1a.
+ *
+ * In OpenSSL 1.1.1a the client-only interface SSL_get_server_tmp_key() was
+ * updated to work on both the client and the server, and was renamed to
+ * SSL_get_peer_tmp_key(), with the original name left behind as an alias. We
+ * use the new name when available.
+ */
+#if OPENSSL_VERSION_NUMBER < 0x1010101fUL
+#undef SSL_get_signature_nid
+#define SSL_get_signature_nid(ssl, pnid) (NID_undef)
+#define tls_get_peer_dh_pubkey SSL_get_server_tmp_key
+#else
+#define tls_get_peer_dh_pubkey SSL_get_peer_tmp_key
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <name_mask.h>
+#include <name_code.h>
+
+ /*
+ * TLS library.
+ */
+#include <dns.h>
+
+ /*
+ * TLS role, presently for logging.
+ */
+typedef enum {
+ TLS_ROLE_CLIENT, TLS_ROLE_SERVER,
+} TLS_ROLE;
+
+typedef enum {
+ TLS_USAGE_NEW, TLS_USAGE_USED,
+} TLS_USAGE;
+
+ /*
+ * Names of valid tlsmgr(8) session caches.
+ */
+#define TLS_MGR_SCACHE_SMTPD "smtpd"
+#define TLS_MGR_SCACHE_SMTP "smtp"
+#define TLS_MGR_SCACHE_LMTP "lmtp"
+
+ /*
+ * RFC 6698, 7671, 7672 DANE
+ */
+#define TLS_DANE_TA 0 /* Match trust-anchor digests */
+#define TLS_DANE_EE 1 /* Match end-entity digests */
+
+#define TLS_DANE_CERT 0 /* Match the certificate digest */
+#define TLS_DANE_PKEY 1 /* Match the public key digest */
+
+#define TLS_DANE_FLAG_NORRS (1<<0) /* Nothing found in DNS */
+#define TLS_DANE_FLAG_EMPTY (1<<1) /* Nothing usable found in DNS */
+#define TLS_DANE_FLAG_ERROR (1<<2) /* TLSA record lookup error */
+
+#define tls_dane_unusable(dane) ((dane)->flags & TLS_DANE_FLAG_EMPTY)
+#define tls_dane_notfound(dane) ((dane)->flags & TLS_DANE_FLAG_NORRS)
+
+#define TLS_DANE_CACHE_TTL_MIN 1 /* A lot can happen in ~2 seconds */
+#define TLS_DANE_CACHE_TTL_MAX 100 /* Comparable to max_idle */
+
+ /*
+ * Certificate and public key digests (typically from TLSA RRs), grouped by
+ * algorithm.
+ */
+typedef struct TLS_TLSA {
+ char *mdalg; /* Algorithm for this digest list */
+ ARGV *certs; /* Complete certificate digests */
+ ARGV *pkeys; /* SubjectPublicKeyInfo digests */
+ struct TLS_TLSA *next; /* Chain to next algorithm */
+} TLS_TLSA;
+
+ /*
+ * Linked list of full X509 trust-anchor certs.
+ */
+typedef struct TLS_CERTS {
+ X509 *cert;
+ struct TLS_CERTS *next;
+} TLS_CERTS;
+
+ /*
+ * Linked list of full EVP_PKEY trust-anchor public keys.
+ */
+typedef struct TLS_PKEYS {
+ EVP_PKEY *pkey;
+ struct TLS_PKEYS *next;
+} TLS_PKEYS;
+
+typedef struct TLS_DANE {
+ TLS_TLSA *ta; /* Trust-anchor cert/pubkey digests */
+ TLS_TLSA *ee; /* End-entity cert/pubkey digests */
+ TLS_CERTS *certs; /* Full trust-anchor certificates */
+ TLS_PKEYS *pkeys; /* Full trust-anchor public keys */
+ char *base_domain; /* Base domain of TLSA RRset */
+ int flags; /* Lookup status */
+ time_t expires; /* Expiration time of this record */
+ int refs; /* Reference count */
+} TLS_DANE;
+
+#define TLS_DANE_HASTA(d) ((d) ? (d)->ta : 0)
+#define TLS_DANE_HASEE(d) ((d) ? (d)->ee : 0)
+
+ /*
+ * tls_dane.c
+ */
+extern int tls_dane_avail(void);
+extern void tls_dane_flush(void);
+extern void tls_dane_verbose(int);
+extern TLS_DANE *tls_dane_alloc(void);
+extern void tls_dane_add_ee_digests(TLS_DANE *, const char *, const char *,
+ const char *);
+extern void tls_dane_free(TLS_DANE *);
+extern TLS_DANE *tls_dane_resolve(unsigned, const char *, DNS_RR *, int);
+extern int tls_dane_load_trustfile(TLS_DANE *, const char *);
+
+ /*
+ * TLS session context, also used by the VSTREAM call-back routines for SMTP
+ * input/output, and by OpenSSL call-back routines for key verification.
+ *
+ * Only some members are (read-only) accessible by the public.
+ */
+#define CCERT_BUFSIZ 256
+
+typedef struct {
+ /* Public, read-only. */
+ char *peer_CN; /* Peer Common Name */
+ char *issuer_CN; /* Issuer Common Name */
+ char *peer_sni; /* SNI sent to or by the peer */
+ char *peer_cert_fprint; /* ASCII certificate fingerprint */
+ char *peer_pkey_fprint; /* ASCII public key fingerprint */
+ int peer_status; /* Certificate and match status */
+ const char *protocol;
+ const char *cipher_name;
+ int cipher_usebits;
+ int cipher_algbits;
+ const char *kex_name; /* shared key-exchange algorithm */
+ const char *kex_curve; /* shared key-exchange ECDHE curve */
+ int kex_bits; /* shared FFDHE key exchange bits */
+ const char *clnt_sig_name; /* client's signature key algorithm */
+ const char *clnt_sig_curve; /* client's ECDSA curve name */
+ int clnt_sig_bits; /* client's RSA signature key bits */
+ const char *clnt_sig_dgst; /* client's signature digest */
+ const char *srvr_sig_name; /* server's signature key algorithm */
+ const char *srvr_sig_curve; /* server's ECDSA curve name */
+ int srvr_sig_bits; /* server's RSA signature key bits */
+ const char *srvr_sig_dgst; /* server's signature digest */
+ /* Private. */
+ SSL *con;
+ char *cache_type; /* tlsmgr(8) cache type if enabled */
+ int ticketed; /* Session ticket issued */
+ char *serverid; /* unique server identifier */
+ char *namaddr; /* nam[addr] for logging */
+ int log_mask; /* What to log */
+ int session_reused; /* this session was reused */
+ int am_server; /* Are we an SSL server or client? */
+ const char *mdalg; /* default message digest algorithm */
+ /* Built-in vs external SSL_accept/read/write/shutdown support. */
+ VSTREAM *stream; /* Blocking-mode SMTP session */
+ /* DANE TLSA trust input and verification state */
+ const TLS_DANE *dane; /* DANE TLSA digests */
+ int errordepth; /* Chain depth of error cert */
+ int tadepth; /* Chain depth of trust anchor */
+ int errorcode; /* First error at error depth */
+ X509 *errorcert; /* Error certificate closest to leaf */
+ x509_stack_t *untrusted; /* Certificate chain fodder */
+ x509_stack_t *trusted; /* Internal root CA list */
+} TLS_SESS_STATE;
+
+ /*
+ * Peer status bits. TLS_CERT_FLAG_MATCHED implies TLS_CERT_FLAG_TRUSTED
+ * only in the case of a hostname match.
+ */
+#define TLS_CERT_FLAG_PRESENT (1<<0)
+#define TLS_CERT_FLAG_ALTNAME (1<<1)
+#define TLS_CERT_FLAG_TRUSTED (1<<2)
+#define TLS_CERT_FLAG_MATCHED (1<<3)
+#define TLS_CERT_FLAG_SECURED (1<<4)
+
+#define TLS_CERT_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_PRESENT))
+#define TLS_CERT_IS_ALTNAME(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_ALTNAME))
+#define TLS_CERT_IS_TRUSTED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_TRUSTED))
+#define TLS_CERT_IS_MATCHED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_MATCHED))
+#define TLS_CERT_IS_SECURED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_SECURED))
+
+ /*
+ * Opaque client context handle.
+ */
+typedef struct TLS_APPL_STATE TLS_APPL_STATE;
+
+#ifdef TLS_INTERNAL
+
+ /*
+ * Log mask details are internal to the library.
+ */
+extern int tls_log_mask(const char *, const char *);
+
+ /*
+ * What to log.
+ */
+#define TLS_LOG_NONE (1<<0)
+#define TLS_LOG_SUMMARY (1<<1)
+#define TLS_LOG_UNTRUSTED (1<<2)
+#define TLS_LOG_PEERCERT (1<<3)
+#define TLS_LOG_CERTMATCH (1<<4)
+#define TLS_LOG_VERBOSE (1<<5)
+#define TLS_LOG_CACHE (1<<6)
+#define TLS_LOG_DEBUG (1<<7)
+#define TLS_LOG_TLSPKTS (1<<8)
+#define TLS_LOG_ALLPKTS (1<<9)
+
+ /*
+ * Client and Server application contexts
+ */
+struct TLS_APPL_STATE {
+ SSL_CTX *ssl_ctx;
+ SSL_CTX *sni_ctx;
+ int log_mask;
+ char *cache_type;
+};
+
+ /*
+ * tls_misc.c Application-context update and disposal.
+ */
+extern void tls_update_app_logmask(TLS_APPL_STATE *, int);
+extern void tls_free_app_context(TLS_APPL_STATE *);
+
+ /*
+ * tls_misc.c
+ */
+extern void tls_param_init(void);
+extern int tls_library_init(void);
+
+ /*
+ * Protocol selection.
+ */
+#define TLS_PROTOCOL_INVALID (~0) /* All protocol bits masked */
+
+#ifdef SSL_TXT_SSLV2
+#define TLS_PROTOCOL_SSLv2 (1<<0) /* SSLv2 */
+#else
+#define SSL_TXT_SSLV2 "SSLv2"
+#define TLS_PROTOCOL_SSLv2 0 /* Unknown */
+#undef SSL_OP_NO_SSLv2
+#define SSL_OP_NO_SSLv2 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_SSLV3
+#define TLS_PROTOCOL_SSLv3 (1<<1) /* SSLv3 */
+#else
+#define SSL_TXT_SSLV3 "SSLv3"
+#define TLS_PROTOCOL_SSLv3 0 /* Unknown */
+#undef SSL_OP_NO_SSLv3
+#define SSL_OP_NO_SSLv3 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_TLSV1
+#define TLS_PROTOCOL_TLSv1 (1<<2) /* TLSv1 */
+#else
+#define SSL_TXT_TLSV1 "TLSv1"
+#define TLS_PROTOCOL_TLSv1 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1
+#define SSL_OP_NO_TLSv1 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_TLSV1_1
+#define TLS_PROTOCOL_TLSv1_1 (1<<3) /* TLSv1_1 */
+#else
+#define SSL_TXT_TLSV1_1 "TLSv1.1"
+#define TLS_PROTOCOL_TLSv1_1 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1_1
+#define SSL_OP_NO_TLSv1_1 0L /* Noop */
+#endif
+
+#ifdef SSL_TXT_TLSV1_2
+#define TLS_PROTOCOL_TLSv1_2 (1<<4) /* TLSv1_2 */
+#else
+#define SSL_TXT_TLSV1_2 "TLSv1.2"
+#define TLS_PROTOCOL_TLSv1_2 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1_2
+#define SSL_OP_NO_TLSv1_2 0L /* Noop */
+#endif
+
+ /*
+ * OpenSSL 1.1.1 does not define a TXT macro for TLS 1.3, so we roll our
+ * own.
+ */
+#define TLS_PROTOCOL_TXT_TLSV1_3 "TLSv1.3"
+
+#if defined(TLS1_3_VERSION) && defined(SSL_OP_NO_TLSv1_3)
+#define TLS_PROTOCOL_TLSv1_3 (1<<5) /* TLSv1_3 */
+#else
+#define TLS_PROTOCOL_TLSv1_3 0 /* Unknown */
+#undef SSL_OP_NO_TLSv1_3
+#define SSL_OP_NO_TLSv1_3 0L /* Noop */
+#endif
+
+/*
+ * Always used when defined, SMTP has no truncation attacks.
+ */
+#ifndef SSL_OP_IGNORE_UNEXPECTED_EOF
+#define SSL_OP_IGNORE_UNEXPECTED_EOF 0L
+#endif
+
+#define TLS_KNOWN_PROTOCOLS \
+ ( TLS_PROTOCOL_SSLv2 | TLS_PROTOCOL_SSLv3 | TLS_PROTOCOL_TLSv1 \
+ | TLS_PROTOCOL_TLSv1_1 | TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3 )
+#define TLS_SSL_OP_PROTOMASK(m) \
+ ((((m) & TLS_PROTOCOL_SSLv2) ? SSL_OP_NO_SSLv2 : 0L) \
+ | (((m) & TLS_PROTOCOL_SSLv3) ? SSL_OP_NO_SSLv3 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1) ? SSL_OP_NO_TLSv1 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1_1) ? SSL_OP_NO_TLSv1_1 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1_2) ? SSL_OP_NO_TLSv1_2 : 0L) \
+ | (((m) & TLS_PROTOCOL_TLSv1_3) ? SSL_OP_NO_TLSv1_3 : 0L))
+
+/*
+ * SSL options that are managed via dedicated Postfix features, rather than
+ * just exposed via hex codes or named elements of tls_ssl_options.
+ */
+#define TLS_SSL_OP_MANAGED_BITS \
+ (SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_IGNORE_UNEXPECTED_EOF | \
+ TLS_SSL_OP_PROTOMASK(~0))
+
+extern int tls_protocol_mask(const char *);
+
+ /*
+ * Cipher grade selection.
+ */
+#define TLS_CIPHER_NONE 0
+#define TLS_CIPHER_NULL 1
+#define TLS_CIPHER_EXPORT 2
+#define TLS_CIPHER_LOW 3
+#define TLS_CIPHER_MEDIUM 4
+#define TLS_CIPHER_HIGH 5
+
+extern const NAME_CODE tls_cipher_grade_table[];
+
+#define tls_cipher_grade(str) \
+ name_code(tls_cipher_grade_table, NAME_CODE_FLAG_NONE, (str))
+#define str_tls_cipher_grade(gr) \
+ str_name_code(tls_cipher_grade_table, (gr))
+
+ /*
+ * Cipher lists with exclusions.
+ */
+extern const char *tls_set_ciphers(TLS_SESS_STATE *, const char *,
+ const char *);
+
+ /*
+ * Populate TLS context with TLS 1.3-related signature parameters.
+ */
+extern void tls_get_signature_params(TLS_SESS_STATE *);
+
+#endif /* TLS_INTERNAL */
+
+ /*
+ * tls_client.c
+ */
+typedef struct {
+ const char *log_param;
+ const char *log_level;
+ int verifydepth;
+ const char *cache_type;
+ const char *chain_files;
+ const char *cert_file;
+ const char *key_file;
+ const char *dcert_file;
+ const char *dkey_file;
+ const char *eccert_file;
+ const char *eckey_file;
+ const char *CAfile;
+ const char *CApath;
+ const char *mdalg; /* default message digest algorithm */
+} TLS_CLIENT_INIT_PROPS;
+
+typedef struct {
+ TLS_APPL_STATE *ctx;
+ VSTREAM *stream;
+ int fd; /* Event-driven file descriptor */
+ int timeout;
+ int tls_level; /* Security level */
+ const char *nexthop; /* destination domain */
+ const char *host; /* MX hostname */
+ const char *namaddr; /* nam[addr] for logging */
+ const char *sni; /* optional SNI name when not DANE */
+ const char *serverid; /* Session cache key */
+ const char *helo; /* Server name from EHLO response */
+ const char *protocols; /* Enabled protocols */
+ const char *cipher_grade; /* Minimum cipher grade */
+ const char *cipher_exclusions; /* Ciphers to exclude */
+ const ARGV *matchargv; /* Cert match patterns */
+ const char *mdalg; /* default message digest algorithm */
+ const TLS_DANE *dane; /* DANE TLSA verification */
+} TLS_CLIENT_START_PROPS;
+
+extern TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *);
+extern TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *);
+extern TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *,
+ const TLS_CLIENT_START_PROPS *);
+
+#define tls_client_stop(ctx, stream, timeout, failure, TLScontext) \
+ tls_session_stop(ctx, (stream), (timeout), (failure), (TLScontext))
+
+#define TLS_CLIENT_INIT_ARGS(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14) \
+ (((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14), (props))
+
+#define TLS_CLIENT_INIT(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14) \
+ tls_client_init(TLS_CLIENT_INIT_ARGS(props, a1, a2, a3, a4, a5, \
+ a6, a7, a8, a9, a10, a11, a12, a13, a14))
+
+#define TLS_CLIENT_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14, a15, a16, a17) \
+ tls_client_start((((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \
+ ((props)->a16), ((props)->a17), (props)))
+
+ /*
+ * tls_server.c
+ */
+typedef struct {
+ const char *log_param;
+ const char *log_level;
+ int verifydepth;
+ const char *cache_type;
+ int set_sessid;
+ const char *chain_files;
+ const char *cert_file;
+ const char *key_file;
+ const char *dcert_file;
+ const char *dkey_file;
+ const char *eccert_file;
+ const char *eckey_file;
+ const char *CAfile;
+ const char *CApath;
+ const char *protocols;
+ const char *eecdh_grade;
+ const char *dh1024_param_file;
+ const char *dh512_param_file;
+ int ask_ccert;
+ const char *mdalg; /* default message digest algorithm */
+} TLS_SERVER_INIT_PROPS;
+
+typedef struct {
+ TLS_APPL_STATE *ctx; /* TLS application context */
+ VSTREAM *stream; /* Client stream */
+ int fd; /* Event-driven file descriptor */
+ int timeout; /* TLS handshake timeout */
+ int requirecert; /* Insist on client cert? */
+ const char *serverid; /* Server instance (salt cache key) */
+ const char *namaddr; /* Client nam[addr] for logging */
+ const char *cipher_grade;
+ const char *cipher_exclusions;
+ const char *mdalg; /* default message digest algorithm */
+} TLS_SERVER_START_PROPS;
+
+extern TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *);
+extern TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props);
+extern TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *);
+
+#define tls_server_stop(ctx, stream, timeout, failure, TLScontext) \
+ tls_session_stop(ctx, (stream), (timeout), (failure), (TLScontext))
+
+#define TLS_SERVER_INIT(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \
+ tls_server_init((((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \
+ ((props)->a16), ((props)->a17), ((props)->a18), ((props)->a19), \
+ ((props)->a20), (props)))
+
+#define TLS_SERVER_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \
+ tls_server_start((((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), (props)))
+
+ /*
+ * tls_session.c
+ */
+extern void tls_session_stop(TLS_APPL_STATE *, VSTREAM *, int, int, TLS_SESS_STATE *);
+
+ /*
+ * tls_misc.c
+ */
+extern const char *tls_compile_version(void);
+extern const char *tls_run_version(void);
+extern const char **tls_pkey_algorithms(void);
+extern void tls_log_summary(TLS_ROLE, TLS_USAGE, TLS_SESS_STATE *);
+extern void tls_pre_jail_init(TLS_ROLE);
+
+#ifdef TLS_INTERNAL
+
+#include <vstring.h>
+
+extern VSTRING *tls_session_passivate(SSL_SESSION *);
+extern SSL_SESSION *tls_session_activate(const char *, int);
+
+ /*
+ * tls_stream.c.
+ */
+extern void tls_stream_start(VSTREAM *, TLS_SESS_STATE *);
+extern void tls_stream_stop(VSTREAM *);
+
+ /*
+ * tls_bio_ops.c: a generic multi-personality driver that retries SSL
+ * operations until they are satisfied or until a hard error happens.
+ * Because of its ugly multi-personality user interface we invoke it via
+ * not-so-ugly single-personality wrappers.
+ */
+extern int tls_bio(int, int, TLS_SESS_STATE *,
+ int (*) (SSL *), /* handshake */
+ int (*) (SSL *, void *, int), /* read */
+ int (*) (SSL *, const void *, int), /* write */
+ void *, int);
+
+#define tls_bio_connect(fd, timeout, context) \
+ tls_bio((fd), (timeout), (context), SSL_connect, \
+ NULL, NULL, NULL, 0)
+#define tls_bio_accept(fd, timeout, context) \
+ tls_bio((fd), (timeout), (context), SSL_accept, \
+ NULL, NULL, NULL, 0)
+#define tls_bio_shutdown(fd, timeout, context) \
+ tls_bio((fd), (timeout), (context), SSL_shutdown, \
+ NULL, NULL, NULL, 0)
+#define tls_bio_read(fd, buf, len, timeout, context) \
+ tls_bio((fd), (timeout), (context), NULL, \
+ SSL_read, NULL, (buf), (len))
+#define tls_bio_write(fd, buf, len, timeout, context) \
+ tls_bio((fd), (timeout), (context), NULL, \
+ NULL, SSL_write, (buf), (len))
+
+ /*
+ * tls_dh.c
+ */
+extern void tls_set_dh_from_file(const char *, int);
+extern DH *tls_tmp_dh_cb(SSL *, int, int);
+extern void tls_set_eecdh_curve(SSL_CTX *, const char *);
+extern void tls_auto_eecdh_curves(SSL_CTX *, const char *);
+
+ /*
+ * tls_rsa.c
+ */
+extern RSA *tls_tmp_rsa_cb(SSL *, int, int);
+
+ /*
+ * tls_verify.c
+ */
+extern char *tls_peer_CN(X509 *, const TLS_SESS_STATE *);
+extern char *tls_issuer_CN(X509 *, const TLS_SESS_STATE *);
+extern const char *tls_dns_name(const GENERAL_NAME *, const TLS_SESS_STATE *);
+extern int tls_verify_certificate_callback(int, X509_STORE_CTX *);
+extern void tls_log_verify_error(TLS_SESS_STATE *);
+
+ /*
+ * tls_dane.c
+ */
+extern int tls_dane_match(TLS_SESS_STATE *, int, X509 *, int);
+extern void tls_dane_set_callback(SSL_CTX *, TLS_SESS_STATE *);
+
+ /*
+ * tls_fprint.c
+ */
+extern const EVP_MD *tls_digest_byname(const char *, EVP_MD_CTX **);
+extern char *tls_digest_encode(const unsigned char *, int);
+extern char *tls_data_fprint(const char *, int, const char *);
+extern char *tls_cert_fprint(X509 *, const char *);
+extern char *tls_pkey_fprint(X509 *, const char *);
+extern char *tls_serverid_digest(const TLS_CLIENT_START_PROPS *, long,
+ const char *);
+
+ /*
+ * tls_certkey.c
+ */
+extern int tls_set_ca_certificate_info(SSL_CTX *, const char *, const char *);
+extern int tls_load_pem_chain(SSL *, const char *, const char *);
+extern int tls_set_my_certificate_key_info(SSL_CTX *, /* All */ const char *,
+ /* RSA */ const char *, const char *,
+ /* DSA */ const char *, const char *,
+ /* ECDSA */ const char *, const char *);
+
+ /*
+ * tls_misc.c
+ */
+extern int TLScontext_index;
+
+extern TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *, SSL_CTX *, int);
+extern TLS_SESS_STATE *tls_alloc_sess_context(int, const char *);
+extern void tls_free_context(TLS_SESS_STATE *);
+extern void tls_check_version(void);
+extern long tls_bug_bits(void);
+extern void tls_print_errors(void);
+extern void tls_info_callback(const SSL *, int, int);
+extern long tls_bio_dump_cb(BIO *, int, const char *, int, long, long);
+extern int tls_validate_digest(const char *);
+
+ /*
+ * tls_seed.c
+ */
+extern void tls_int_seed(void);
+extern int tls_ext_seed(int);
+
+#endif /* TLS_INTERNAL */
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+#endif /* USE_TLS */
+#endif /* _TLS_H_INCLUDED_ */
diff --git a/src/tls/tls_bio_ops.c b/src/tls/tls_bio_ops.c
new file mode 100644
index 0000000..9b66195
--- /dev/null
+++ b/src/tls/tls_bio_ops.c
@@ -0,0 +1,296 @@
+/*++
+/* NAME
+/* tls_bio_ops 3
+/* SUMMARY
+/* TLS network basic I/O management
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_bio_connect(fd, timeout, context)
+/* int fd;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_accept(fd, timeout, context)
+/* int fd;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_shutdown(fd, timeout, context)
+/* int fd;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_read(fd, buf, len, timeout, context)
+/* int fd;
+/* void *buf;
+/* int len;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/*
+/* int tls_bio_write(fd, buf, len, timeout, context)
+/* int fd;
+/* void *buf;
+/* int len;
+/* int timeout;
+/* TLS_SESS_STATE *context;
+/* DESCRIPTION
+/* This module enforces VSTREAM-style timeouts on non-blocking
+/* I/O while performing TLS handshake or input/output operations.
+/*
+/* The Postfix VSTREAM read/write routines invoke the
+/* tls_bio_read/write routines to send and receive plain-text
+/* data. In addition, this module provides tls_bio_connect/accept
+/* routines that trigger the initial TLS handshake. The
+/* tls_bio_xxx routines invoke the corresponding SSL routines
+/* that translate the requests into TLS protocol messages.
+/*
+/* Whenever an SSL operation indicates that network input (or
+/* output) needs to happen, the tls_bio_xxx routines wait for
+/* the network to become readable (or writable) within the
+/* timeout limit, then retry the SSL operation. This works
+/* because the network socket is in non-blocking mode.
+/*
+/* tls_bio_connect() performs the SSL_connect() operation.
+/*
+/* tls_bio_accept() performs the SSL_accept() operation.
+/*
+/* tls_bio_shutdown() performs the SSL_shutdown() operation.
+/*
+/* tls_bio_read() performs the SSL_read() operation.
+/*
+/* tls_bio_write() performs the SSL_write() operation.
+/*
+/* Arguments:
+/* .IP fd
+/* Network socket.
+/* .IP buf
+/* Read/write buffer.
+/* .IP len
+/* Read/write request size.
+/* .IP timeout
+/* Read/write timeout.
+/* .IP TLScontext
+/* TLS session state.
+/* DIAGNOSTICS
+/* A result value > 0 means successful completion.
+/*
+/* A result value < 0 means that the requested operation did
+/* not complete due to TLS protocol failure, system call
+/* failure, or for any reason described under "in addition"
+/* below.
+/*
+/* A result value of 0 from tls_bio_shutdown() means that the
+/* operation is in progress. A result value of 0 from other
+/* tls_bio_ops(3) operations means that the remote party either
+/* closed the network connection or that it sent a TLS shutdown
+/* request.
+/*
+/* Upon return from the tls_bio_ops(3) routines the global
+/* errno value is non-zero when the requested operation did not
+/* complete due to system call failure.
+/*
+/* In addition, the result value is set to -1, and the global
+/* errno value is set to ETIMEDOUT, when some network read/write
+/* operation did not complete within the time limit.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/time.h>
+
+#ifndef timersub
+/* res = a - b */
+#define timersub(a, b, res) do { \
+ (res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+ (res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
+ if ((res)->tv_usec < 0) { \
+ (res)->tv_sec--; \
+ (res)->tv_usec += 1000000; \
+ } \
+ } while (0)
+#endif
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* tls_bio - perform SSL input/output operation with extreme prejudice */
+
+int tls_bio(int fd, int timeout, TLS_SESS_STATE *TLScontext,
+ int (*hsfunc) (SSL *),
+ int (*rfunc) (SSL *, void *, int),
+ int (*wfunc) (SSL *, const void *, int),
+ void *buf, int num)
+{
+ const char *myname = "tls_bio";
+ int status;
+ int err;
+ int enable_deadline;
+ struct timeval time_left; /* amount of time left */
+ struct timeval time_deadline; /* time of deadline */
+ struct timeval time_now; /* time after SSL_mumble() call */
+
+ /*
+ * Compensation for interface mis-match: With VSTREAMs, timeout <= 0
+ * means wait forever; with the read/write_wait() calls below, we need to
+ * specify timeout < 0 instead.
+ *
+ * Safety: no time limit means no deadline.
+ */
+ if (timeout <= 0) {
+ timeout = -1;
+ enable_deadline = 0;
+ }
+
+ /*
+ * Deadline management is simpler than with VSTREAMs, because we don't
+ * need to decrement a per-stream time limit. We just work within the
+ * budget that is available for this tls_bio() call.
+ */
+ else {
+ enable_deadline =
+ vstream_fstat(TLScontext->stream, VSTREAM_FLAG_DEADLINE);
+ if (enable_deadline) {
+ GETTIMEOFDAY(&time_deadline);
+ time_deadline.tv_sec += timeout;
+ }
+ }
+
+ /*
+ * If necessary, retry the SSL handshake or read/write operation after
+ * handling any pending network I/O.
+ */
+ for (;;) {
+
+ /*
+ * Flush the per-thread SSL error queue. Otherwise, errors from other
+ * code that also uses TLS may confuse SSL_get_error(3).
+ */
+ ERR_clear_error();
+
+ if (hsfunc)
+ status = hsfunc(TLScontext->con);
+ else if (rfunc)
+ status = rfunc(TLScontext->con, buf, num);
+ else if (wfunc)
+ status = wfunc(TLScontext->con, buf, num);
+ else
+ msg_panic("%s: nothing to do here", myname);
+ err = SSL_get_error(TLScontext->con, status);
+
+ /*
+ * Correspondence between SSL_ERROR_* error codes and tls_bio_(read,
+ * write, accept, connect, shutdown) return values (for brevity:
+ * retval).
+ *
+ * SSL_ERROR_NONE corresponds with retval > 0. With SSL_(read, write)
+ * this is the number of plaintext bytes sent or received. With
+ * SSL_(accept, connect, shutdown) this means that the operation was
+ * completed successfully.
+ *
+ * SSL_ERROR_WANT_(WRITE, READ) start a new loop iteration, or force
+ * (retval = -1, errno = ETIMEDOUT) when the time limit is exceeded.
+ *
+ * All other SSL_ERROR_* cases correspond with retval <= 0. With
+ * SSL_(read, write, accept, connect) retval == 0 means that the
+ * remote party either closed the network connection or that it
+ * requested TLS shutdown; with SSL_shutdown() retval == 0 means that
+ * our own shutdown request is in progress. With all operations
+ * retval < 0 means that there was an error. In the latter case,
+ * SSL_ERROR_SYSCALL means that error details are returned via the
+ * errno value.
+ *
+ * Find out if we must retry the operation and/or if there is pending
+ * network I/O.
+ *
+ * XXX If we're the first to invoke SSL_shutdown(), then the operation
+ * isn't really complete when the call returns. We could hide that
+ * anomaly here and repeat the call.
+ */
+ switch (err) {
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ if (enable_deadline) {
+ GETTIMEOFDAY(&time_now);
+ timersub(&time_deadline, &time_now, &time_left);
+ timeout = time_left.tv_sec + (time_left.tv_usec > 0);
+ if (timeout <= 0) {
+ errno = ETIMEDOUT;
+ return (-1);
+ }
+ }
+ if (err == SSL_ERROR_WANT_WRITE) {
+ if (write_wait(fd, timeout) < 0)
+ return (-1); /* timeout error */
+ } else {
+ if (read_wait(fd, timeout) < 0)
+ return (-1); /* timeout error */
+ }
+ break;
+
+ /*
+ * Unhandled cases: SSL_ERROR_WANT_(ACCEPT, CONNECT, X509_LOOKUP)
+ * etc. Historically, Postfix silently treated these as ordinary
+ * I/O errors so we don't really know how common they are. For
+ * now, we just log a warning.
+ */
+ default:
+ msg_warn("%s: unexpected SSL_ERROR code %d", myname, err);
+ /* FALLTHROUGH */
+
+ /*
+ * With tls_timed_read() and tls_timed_write() the caller is the
+ * VSTREAM library module which is unaware of TLS, so we log the
+ * TLS error stack here. In a better world, each VSTREAM I/O
+ * object would provide an error reporting method in addition to
+ * the timed_read and timed_write methods, so that we would not
+ * need to have ad-hoc code like this.
+ */
+ case SSL_ERROR_SSL:
+ if (rfunc || wfunc)
+ tls_print_errors();
+ /* FALLTHROUGH */
+ case SSL_ERROR_ZERO_RETURN:
+ case SSL_ERROR_NONE:
+ errno = 0; /* avoid bogus warnings */
+ /* FALLTHROUGH */
+ case SSL_ERROR_SYSCALL:
+ return (status);
+ }
+ }
+}
+
+#endif
diff --git a/src/tls/tls_certkey.c b/src/tls/tls_certkey.c
new file mode 100644
index 0000000..be8d470
--- /dev/null
+++ b/src/tls/tls_certkey.c
@@ -0,0 +1,781 @@
+/*++
+/* NAME
+/* tls_certkey 3
+/* SUMMARY
+/* public key certificate and private key loader
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_set_ca_certificate_info(ctx, CAfile, CApath)
+/* SSL_CTX *ctx;
+/* const char *CAfile;
+/* const char *CApath;
+/*
+/* int tls_set_my_certificate_key_info(ctx, chain_files,
+/* cert_file, key_file,
+/* dcert_file, dkey_file,
+/* eccert_file, eckey_file)
+/* SSL_CTX *ctx;
+/* const char *chain_files;
+/* const char *cert_file;
+/* const char *key_file;
+/* const char *dcert_file;
+/* const char *dkey_file;
+/* const char *eccert_file;
+/* const char *eckey_file;
+/*
+/* int tls_load_pem_chain(ssl, pem, origin);
+/* SSL *ssl;
+/* const char *pem;
+/* const char *origin;
+/* DESCRIPTION
+/* OpenSSL supports two options to specify CA certificates:
+/* either one file CAfile that contains all CA certificates,
+/* or a directory CApath with separate files for each
+/* individual CA, with symbolic links named after the hash
+/* values of the certificates. The second option is not
+/* convenient with a chrooted process.
+/*
+/* tls_set_ca_certificate_info() loads the CA certificate
+/* information for the specified TLS server or client context.
+/* The result is -1 on failure, 0 on success.
+/*
+/* tls_set_my_certificate_key_info() loads the public key
+/* certificates and private keys for the specified TLS server
+/* or client context. Up to 3 pairs of key pairs (RSA, DSA and
+/* ECDSA) may be specified; each certificate and key pair must
+/* match. The chain_files argument makes it possible to load
+/* keys and certificates for more than 3 algorithms, via either
+/* a single file, or a list of multiple files. The result is -1
+/* on failure, 0 on success.
+/*
+/* tls_load_pem_chain() loads one or more (key, cert, [chain])
+/* triples from an in-memory PEM blob. The "origin" argument
+/* is used for error logging, to identify the provenance of the
+/* PEM blob. "ssl" must be non-zero, and the keys and certificates
+/* will be loaded into that object.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+#define PEM_LOAD_STATE_NOGO -2 /* Unusable object or sequence */
+#define PEM_LOAD_STATE_FAIL -1 /* Error in libcrypto */
+#define PEM_LOAD_STATE_DONE 0 /* End of PEM file, return value only */
+#define PEM_LOAD_STATE_INIT 1 /* No PEM objects seen */
+#define PEM_LOAD_STATE_PKEY 2 /* Last object was a private key */
+#define PEM_LOAD_STATE_CERT 3 /* Last object was a certificate */
+#define PEM_LOAD_STATE_BOTH 4 /* Unordered, key + first cert seen */
+
+#define PEM_LOAD_READ_LAST 0 /* Reading last file */
+#define PEM_LOAD_READ_MORE 1 /* More files to be read */
+
+typedef struct pem_load_state_t {
+ const char *origin; /* PEM chain origin description */
+ const char *source; /* PEM BIO origin description */
+ const char *keysrc; /* Source of last key */
+ BIO *pembio; /* PEM input stream */
+ SSL_CTX *ctx; /* SSL connection factory */
+ SSL *ssl; /* SSL connection handle */
+ EVP_PKEY *pkey; /* current key */
+ X509 *cert; /* current certificate */
+ x509_stack_t *chain; /* current chain */
+ int keynum; /* Index of last key */
+ int objnum; /* Index in current source */
+ int state; /* Current state, never "DONE" */
+ int mixed; /* Single file with key anywhere */
+} pem_load_state_t;
+
+/* init_pem_load_state - fill in initial pem_load_state structure */
+
+static void init_pem_load_state(pem_load_state_t *st, SSL_CTX *ctx, SSL *ssl,
+ const char *origin)
+{
+ st->origin = origin;
+ st->source = origin;
+ st->keysrc = 0;
+ st->pembio = 0;
+ st->ctx = ctx;
+ st->ssl = ssl;
+ st->pkey = 0;
+ st->cert = 0;
+ st->chain = 0;
+ st->keynum = 0;
+ st->objnum = 0;
+ st->state = PEM_LOAD_STATE_INIT;
+ st->mixed = 0;
+}
+
+/* use_chain - load cert, key and chain into ctx or ssl */
+
+#if OPENSSL_VERSION_NUMBER >= 0x1010100fUL
+static int use_chain(pem_load_state_t *st)
+{
+ int ret;
+ int replace = 0;
+
+ /*
+ * With replace == 0, an error is returned if the algorithm slot is
+ * already taken, and a previous key + chain of the same type would be
+ * clobbered.
+ */
+ if (st->ctx)
+ ret = SSL_CTX_use_cert_and_key(st->ctx, st->cert, st->pkey, st->chain,
+ replace);
+ else
+ ret = SSL_use_cert_and_key(st->ssl, st->cert, st->pkey, st->chain,
+ replace);
+
+ /*
+ * SSL_[CTX_]_use_cert_key() uprefs all the objects in question, so we
+ * must free ours.
+ */
+ X509_free(st->cert);
+ st->cert = 0;
+ EVP_PKEY_free(st->pkey);
+ st->pkey = 0;
+ sk_X509_pop_free(st->chain, X509_free);
+ st->chain = 0;
+
+ return ret;
+}
+
+#else
+
+/* Legacy OpenSSL 1.0.2 and 1.1.0 interface */
+static int use_chain(pem_load_state_t *st)
+{
+ int ret = 1;
+
+#define TRY(op, o) \
+ ((st->ctx && SSL_CTX_##op(st->ctx, st->o)) || \
+ (st->ssl && SSL_##op(st->ssl, st->o)))
+
+ /*
+ * This ensures the cert and key have the same type and match. A similar
+ * check is performed in use_PrivateKey(), but only if if the key and
+ * cert are of the same type.
+ */
+ if (!X509_check_private_key(st->cert, st->pkey))
+ ret = 0;
+
+ /*
+ * XXX: With OpenSSL 1.0.2, setting the certificate clears any previous
+ * mismatched key of the same type, so we don't detect conflicting chains
+ * for the same algorithm, and silently use the last one.
+ */
+
+ /* use_certificate() increments the refcount */
+ if (ret && !TRY(use_certificate, cert))
+ ret = 0;
+ X509_free(st->cert);
+ st->cert = 0;
+
+ /* use_PrivateKey() increments the refcount */
+ if (ret && !TRY(use_PrivateKey, pkey))
+ ret = 0;
+ EVP_PKEY_free(st->pkey);
+ st->pkey = 0;
+
+ /* set0_chain() does not increment the refcount */
+ if (!ret || !(ret = TRY(set0_chain, chain)))
+ sk_X509_pop_free(st->chain, X509_free);
+ /* The chain is now owned by the SSL library or freed, zero for next use */
+ st->chain = 0;
+
+ return ret;
+}
+
+#endif
+
+/* load_cert - decode and load a DER-encoded X509 certificate */
+
+static void load_cert(pem_load_state_t *st, unsigned char *buf,
+ long buflen)
+{
+ const unsigned char *p = buf;
+ X509 *cert = d2i_X509(0, &p, buflen);
+
+ /*
+ * When expecting one or more keys, each key must precede the associated
+ * certificate (chain).
+ */
+ if (!st->mixed && st->state == PEM_LOAD_STATE_INIT) {
+ msg_warn("error loading chain from %s: key not first", st->source);
+ if (cert)
+ X509_free(cert);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+ if (!cert) {
+ msg_warn("error loading certificate (PEM object number %d) from %s",
+ st->objnum, st->source);
+ st->state = PEM_LOAD_STATE_FAIL;
+ return;
+ }
+ if (p - buf != buflen) {
+ msg_warn("error loading certificate (PEM object number %d) from %s:"
+ " excess data", st->objnum, st->source);
+ X509_free(cert);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+
+ /*
+ * The first certificate after a new key becomes the leaf certificate for
+ * that key. Subsequent certificates are added to the issuer chain.
+ *
+ * In "mixed" mode, the first certificate is either after the key, or else
+ * comes first.
+ */
+ switch (st->state) {
+ case PEM_LOAD_STATE_PKEY:
+ st->cert = cert;
+ st->state = st->mixed ? PEM_LOAD_STATE_BOTH : PEM_LOAD_STATE_CERT;
+ return;
+ case PEM_LOAD_STATE_INIT:
+ st->cert = cert;
+ st->state = PEM_LOAD_STATE_CERT;
+ return;
+ case PEM_LOAD_STATE_CERT:
+ case PEM_LOAD_STATE_BOTH:
+ if ((!st->chain && (st->chain = sk_X509_new_null()) == 0)
+ || !sk_X509_push(st->chain, cert)) {
+ X509_free(cert);
+ st->state = PEM_LOAD_STATE_FAIL;
+ }
+ return;
+ }
+}
+
+/* load_pkey - decode and load a DER-encoded private key */
+
+static void load_pkey(pem_load_state_t *st, int pkey_type,
+ unsigned char *buf, long buflen)
+{
+ const char *myname = "load_pkey";
+ const unsigned char *p = buf;
+ PKCS8_PRIV_KEY_INFO *p8;
+ EVP_PKEY *pkey = 0;
+
+ /*
+ * Keys are either algorithm-specific, or else (ideally) algorithm
+ * agnostic, in which case they are wrapped as PKCS#8 objects with an
+ * algorithm OID.
+ */
+ if (pkey_type != NID_undef) {
+ pkey = d2i_PrivateKey(pkey_type, 0, &p, buflen);
+ } else {
+ p8 = d2i_PKCS8_PRIV_KEY_INFO(NULL, &p, buflen);
+ if (p8) {
+ pkey = EVP_PKCS82PKEY(p8);
+ PKCS8_PRIV_KEY_INFO_free(p8);
+ }
+ }
+
+ /*
+ * Except in "mixed" mode, where a single key appears anywhere in a file
+ * with multiple certificates, a given key is either at the first object
+ * we process, or occurs after a previous key and one or more associated
+ * certificates. Thus, encountering a key in a state other than "INIT"
+ * or "CERT" is an error, except in "mixed" mode where a second key is
+ * ignored with a warning.
+ */
+ switch (st->state) {
+ case PEM_LOAD_STATE_CERT:
+
+ /*
+ * When processing the key of a "next" chain, we're in the "CERT"
+ * state, and first complete the processing of the previous chain.
+ */
+ if (!st->mixed && !use_chain(st)) {
+ msg_warn("error loading certificate chain: "
+ "key at index %d in %s does not match the certificate",
+ st->keynum, st->keysrc);
+ st->state = PEM_LOAD_STATE_FAIL;
+ return;
+ }
+ /* FALLTHROUGH */
+ case PEM_LOAD_STATE_INIT:
+
+ if (!pkey) {
+ msg_warn("error loading private key (PEM object number %d) from %s",
+ st->objnum, st->source);
+ st->state = PEM_LOAD_STATE_FAIL;
+ return;
+ }
+ /* Reject unexpected data beyond the end of the DER-encoded object */
+ if (p - buf != buflen) {
+ msg_warn("error loading private key (PEM object number %d) from"
+ " %s: excess data", st->objnum, st->source);
+ EVP_PKEY_free(pkey);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+ /* All's well, update the state */
+ st->pkey = pkey;
+ if (st->state == PEM_LOAD_STATE_INIT)
+ st->state = PEM_LOAD_STATE_PKEY;
+ else if (st->mixed)
+ st->state = PEM_LOAD_STATE_BOTH;
+ else
+ st->state = PEM_LOAD_STATE_PKEY;
+ return;
+
+ case PEM_LOAD_STATE_PKEY:
+ case PEM_LOAD_STATE_BOTH:
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ /* XXX: Legacy behaviour was silent, should we stay silent? */
+ if (st->mixed) {
+ msg_warn("ignoring 2nd key at index %d in %s after 1st at %d",
+ st->objnum, st->source, st->keynum);
+ return;
+ }
+ /* else back-to-back keys */
+ msg_warn("error loading certificate chain: "
+ "key at index %d in %s not followed by a certificate",
+ st->keynum, st->keysrc);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+
+ default:
+ msg_error("%s: internal error: bad state: %d", myname, st->state);
+ st->state = PEM_LOAD_STATE_NOGO;
+ return;
+ }
+}
+
+/* load_pem_object - load next pkey or cert from open BIO */
+
+static int load_pem_object(pem_load_state_t *st)
+{
+ char *name = 0;
+ char *header = 0;
+ unsigned char *buf = 0;
+ long buflen;
+ int pkey_type = NID_undef;
+
+ if (!PEM_read_bio(st->pembio, &name, &header, &buf, &buflen)) {
+ if (ERR_GET_REASON(ERR_peek_last_error()) != PEM_R_NO_START_LINE)
+ return (st->state = PEM_LOAD_STATE_FAIL);
+
+ ERR_clear_error();
+ /* Clean EOF, preserve stored state for any next input file */
+ return (PEM_LOAD_STATE_DONE);
+ }
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ load_cert(st, buf, buflen);
+ } else if (strcmp(name, PEM_STRING_PKCS8INF) == 0
+ || ((pkey_type = EVP_PKEY_RSA) != NID_undef
+ && strcmp(name, PEM_STRING_RSA) == 0)
+ || ((pkey_type = EVP_PKEY_EC) != NID_undef
+ && strcmp(name, PEM_STRING_ECPRIVATEKEY) == 0)
+ || ((pkey_type = EVP_PKEY_DSA) != NID_undef
+ && strcmp(name, PEM_STRING_DSA) == 0)) {
+ load_pkey(st, pkey_type, buf, buflen);
+ } else if (!st->mixed) {
+ msg_warn("loading %s: ignoring PEM type: %s", st->source, name);
+ }
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(buf);
+ return (st->state);
+}
+
+/* load_pem_bio - load all key/certs from bio and free the bio */
+
+static int load_pem_bio(pem_load_state_t *st, int more)
+{
+ int state = st->state;
+
+ /* Don't report old news */
+ ERR_clear_error();
+
+ /*
+ * When "more" is PEM_LOAD_READ_MORE, more files will be loaded after the
+ * current file, and final processing for the last key and chain is
+ * deferred.
+ *
+ * When "more" is PEM_LOAD_READ_LAST, this is the last file in the list, and
+ * we validate the final chain.
+ *
+ * When st->mixed is true, this is the only file, and its key can occur at
+ * any location. In this case we load at most one key.
+ */
+ for (st->objnum = 1; state > PEM_LOAD_STATE_DONE; ++st->objnum) {
+ state = load_pem_object(st);
+ if ((st->mixed && st->keynum == 0 &&
+ (state == PEM_LOAD_STATE_PKEY || state == PEM_LOAD_STATE_BOTH))
+ || (!st->mixed && state == PEM_LOAD_STATE_PKEY)) {
+ /* Squirrel-away the current key location */
+ st->keynum = st->objnum;
+ st->keysrc = st->source;
+ }
+ }
+ /* We're responsible for unconditionally freeing the BIO */
+ BIO_free(st->pembio);
+
+ /* Success with current file, go back for more? */
+ if (more == PEM_LOAD_READ_MORE && state >= PEM_LOAD_STATE_DONE)
+ return 0;
+
+ /*
+ * If all is well so far, complete processing for the final chain.
+ */
+ switch (st->state) {
+ case PEM_LOAD_STATE_FAIL:
+ tls_print_errors();
+ break;
+ default:
+ break;
+ case PEM_LOAD_STATE_INIT:
+ msg_warn("No PEM data in %s", st->origin);
+ break;
+ case PEM_LOAD_STATE_PKEY:
+ msg_warn("No certs for key at index %d in %s", st->keynum, st->keysrc);
+ break;
+ case PEM_LOAD_STATE_CERT:
+ if (st->mixed) {
+ msg_warn("No private key found in %s", st->origin);
+ break;
+ }
+ /* FALLTHROUGH */
+ case PEM_LOAD_STATE_BOTH:
+ /* use_chain() frees the key and certs, and zeroes the pointers */
+ if (use_chain(st))
+ return (0);
+ msg_warn("key at index %d in %s does not match next certificate",
+ st->keynum, st->keysrc);
+ tls_print_errors();
+ break;
+ }
+ /* Free any left-over unused keys and certs */
+ EVP_PKEY_free(st->pkey);
+ X509_free(st->cert);
+ sk_X509_pop_free(st->chain, X509_free);
+
+ msg_warn("error loading private keys and certificates from: %s: %s",
+ st->origin, st->ctx ? "disabling TLS support" :
+ "aborting TLS handshake");
+ return (-1);
+}
+
+/* load_chain_files - load sequence of (key, cert, [chain]) from files */
+
+static int load_chain_files(SSL_CTX *ctx, const char *chain_files)
+{
+ pem_load_state_t st;
+ ARGV *files = argv_split(chain_files, CHARS_COMMA_SP);
+ char **filep;
+ int ret = 0;
+ int more;
+
+ init_pem_load_state(&st, ctx, 0, chain_files);
+ for (filep = files->argv; ret == 0 && *filep; ++filep) {
+ st.source = *filep;
+ if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) {
+ msg_warn("error opening chain file: %s: %m", st.source);
+ st.state = PEM_LOAD_STATE_NOGO;
+ break;
+ }
+ more = filep[1] ? PEM_LOAD_READ_MORE : PEM_LOAD_READ_LAST;
+ /* load_pem_bio() frees the BIO */
+ ret = load_pem_bio(&st, more);
+ }
+ argv_free(files);
+ return (ret);
+}
+
+/* load_mixed_file - load certs with single key anywhere in the file */
+
+static int load_mixed_file(SSL_CTX *ctx, const char *file)
+{
+ pem_load_state_t st;
+
+ init_pem_load_state(&st, ctx, 0, file);
+ if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) {
+ msg_warn("error opening chain file: %s: %m", st.source);
+ return (-1);
+ }
+ st.mixed = 1;
+ /* load_pem_bio() frees the BIO */
+ return load_pem_bio(&st, PEM_LOAD_READ_LAST);
+}
+
+/* tls_set_ca_certificate_info - load Certification Authority certificates */
+
+int tls_set_ca_certificate_info(SSL_CTX *ctx, const char *CAfile,
+ const char *CApath)
+{
+ if (*CAfile == 0)
+ CAfile = 0;
+ if (*CApath == 0)
+ CApath = 0;
+
+#define CA_PATH_FMT "%s%s%s"
+#define CA_PATH_ARGS(var, nextvar) \
+ var ? #var "=\"" : "", \
+ var ? var : "", \
+ var ? (nextvar ? "\", " : "\"") : ""
+
+ if (CAfile || CApath) {
+ if (!SSL_CTX_load_verify_locations(ctx, CAfile, CApath)) {
+ msg_info("cannot load Certification Authority data, "
+ CA_PATH_FMT CA_PATH_FMT ": disabling TLS support",
+ CA_PATH_ARGS(CAfile, CApath),
+ CA_PATH_ARGS(CApath, 0));
+ tls_print_errors();
+ return (-1);
+ }
+ if (var_tls_append_def_CA && !SSL_CTX_set_default_verify_paths(ctx)) {
+ msg_info("cannot set default OpenSSL certificate verification "
+ "paths: disabling TLS support");
+ tls_print_errors();
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+/* set_cert_stuff - specify certificate and key information */
+
+static int set_cert_stuff(SSL_CTX *ctx, const char *cert_type,
+ const char *cert_file,
+ const char *key_file)
+{
+
+ /*
+ * When the certfile and keyfile are one and the same, load both in a
+ * single pass, avoiding potential race conditions during key rollover.
+ */
+ if (strcmp(cert_file, key_file) == 0)
+ return (load_mixed_file(ctx, cert_file) == 0);
+
+ /*
+ * We need both the private key (in key_file) and the public key
+ * certificate (in cert_file).
+ *
+ * Code adapted from OpenSSL apps/s_cb.c.
+ */
+ ERR_clear_error();
+ if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) {
+ msg_warn("cannot get %s certificate from file \"%s\": "
+ "disabling TLS support", cert_type, cert_file);
+ tls_print_errors();
+ return (0);
+ }
+ if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) {
+ msg_warn("cannot get %s private key from file \"%s\": "
+ "disabling TLS support", cert_type, key_file);
+ tls_print_errors();
+ return (0);
+ }
+
+ /*
+ * Sanity check.
+ */
+ if (!SSL_CTX_check_private_key(ctx)) {
+ msg_warn("%s private key in %s does not match public key in %s: "
+ "disabling TLS support", cert_type, key_file, cert_file);
+ return (0);
+ }
+ return (1);
+}
+
+/* tls_set_my_certificate_key_info - load client or server certificates/keys */
+
+int tls_set_my_certificate_key_info(SSL_CTX *ctx, const char *chain_files,
+ const char *cert_file,
+ const char *key_file,
+ const char *dcert_file,
+ const char *dkey_file,
+ const char *eccert_file,
+ const char *eckey_file)
+{
+
+ /* The "chain_files" parameter overrides all the legacy parameters */
+ if (chain_files && *chain_files)
+ return load_chain_files(ctx, chain_files);
+
+ /*
+ * Lack of certificates is fine so long as we are prepared to use
+ * anonymous ciphers.
+ */
+ if (*cert_file && !set_cert_stuff(ctx, "RSA", cert_file, key_file))
+ return (-1); /* logged */
+ if (*dcert_file && !set_cert_stuff(ctx, "DSA", dcert_file, dkey_file))
+ return (-1); /* logged */
+#ifndef OPENSSL_NO_ECDH
+ if (*eccert_file && !set_cert_stuff(ctx, "ECDSA", eccert_file, eckey_file))
+ return (-1); /* logged */
+#else
+ if (*eccert_file)
+ msg_warn("ECDSA not supported. Ignoring ECDSA certificate file \"%s\"",
+ eccert_file);
+#endif
+ return (0);
+}
+
+/* tls_load_pem_chain - load in-memory PEM client or server chain */
+
+int tls_load_pem_chain(SSL *ssl, const char *pem, const char *origin)
+{
+ static VSTRING *obuf;
+ pem_load_state_t st;
+
+ if (!obuf)
+ obuf = vstring_alloc(100);
+ vstring_sprintf(obuf, "SNI data for %s", origin);
+ init_pem_load_state(&st, 0, ssl, vstring_str(obuf));
+
+ if ((st.pembio = BIO_new_mem_buf(pem, -1)) == NULL) {
+ msg_warn("error opening memory BIO for %s", st.origin);
+ tls_print_errors();
+ return (-1);
+ }
+ /* load_pem_bio() frees the BIO */
+ return (load_pem_bio(&st, PEM_LOAD_READ_LAST));
+}
+
+#ifdef TEST
+
+static NORETURN usage(void)
+{
+ fprintf(stderr, "usage: tls_certkey [-m] <chainfiles>\n");
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ int ch;
+ int mixed = 0;
+ int ret;
+ char *key_file = 0;
+ SSL_CTX *ctx;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+ /*
+ * Initialize the OpenSSL library by the book! To start with, we must
+ * initialize the algorithms. We want cleartext error messages instead of
+ * just error codes, so we load the error_strings.
+ */
+ SSL_load_error_strings();
+ OpenSSL_add_ssl_algorithms();
+#endif
+
+ if (!(ctx = SSL_CTX_new(TLS_client_method()))) {
+ tls_print_errors();
+ exit(1);
+ }
+ while ((ch = GETOPT(argc, argv, "mk:")) > 0) {
+ switch (ch) {
+ case 'k':
+ key_file = optarg;
+ break;
+ case 'm':
+ mixed = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ if (key_file)
+ ret = set_cert_stuff(ctx, "any", argv[0], key_file) == 0;
+ else if (mixed)
+ ret = load_mixed_file(ctx, argv[0]);
+ else
+ ret = load_chain_files(ctx, argv[0]);
+
+ if (ret != 0)
+ exit(1);
+
+ if (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_FIRST) != 1) {
+ fprintf(stderr, "error selecting first certificate\n");
+ tls_print_errors();
+ exit(1);
+ }
+ do {
+ STACK_OF(X509) *chain;
+ int i;
+
+ if (SSL_CTX_get0_chain_certs(ctx, &chain) != 1) {
+ fprintf(stderr, "error locating certificate chain\n");
+ tls_print_errors();
+ exit(1);
+ }
+ for (i = 0; i <= sk_X509_num(chain); ++i) {
+ char buf[CCERT_BUFSIZ];
+ X509 *cert;
+
+ if (i > 0)
+ cert = sk_X509_value(chain, i - 1);
+ else
+ cert = SSL_CTX_get0_certificate(ctx);
+
+ printf("depth = %d\n", i);
+
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
+ printf("issuer = %s\n", buf);
+
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
+ printf("subject = %s\n\n", buf);
+ }
+ } while (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_NEXT) != 0);
+
+ exit(0);
+}
+
+#endif
+
+#endif
diff --git a/src/tls/tls_client.c b/src/tls/tls_client.c
new file mode 100644
index 0000000..02cb903
--- /dev/null
+++ b/src/tls/tls_client.c
@@ -0,0 +1,1248 @@
+/*++
+/* NAME
+/* tls_client
+/* SUMMARY
+/* client-side TLS engine
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* TLS_APPL_STATE *tls_client_init(init_props)
+/* const TLS_CLIENT_INIT_PROPS *init_props;
+/*
+/* TLS_SESS_STATE *tls_client_start(start_props)
+/* const TLS_CLIENT_START_PROPS *start_props;
+/*
+/* TLS_SESS_STATE *tls_client_post_connect(TLScontext, start_props)
+/* TLS_SESS_STATE *TLScontext;
+/* const TLS_CLIENT_START_PROPS *start_props;
+/*
+/* void tls_client_stop(app_ctx, stream, failure, TLScontext)
+/* TLS_APPL_STATE *app_ctx;
+/* VSTREAM *stream;
+/* int failure;
+/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* This module is the interface between Postfix TLS clients,
+/* the OpenSSL library and the TLS entropy and cache manager.
+/*
+/* The SMTP client will attempt to verify the server hostname
+/* against the names listed in the server certificate. When
+/* a hostname match is required, the verification fails
+/* on certificate verification or hostname mis-match errors.
+/* When no hostname match is required, hostname verification
+/* failures are logged but they do not affect the TLS handshake
+/* or the SMTP session.
+/*
+/* The rules for peer name wild-card matching differ between
+/* RFC 2818 (HTTP over TLS) and RFC 2830 (LDAP over TLS), while
+/* RFC RFC3207 (SMTP over TLS) does not specify a rule at all.
+/* Postfix uses a restrictive match algorithm. One asterisk
+/* ('*') is allowed as the left-most component of a wild-card
+/* certificate name; it matches the left-most component of
+/* the peer hostname.
+/*
+/* Another area where RFCs aren't always explicit is the
+/* handling of dNSNames in peer certificates. RFC 3207 (SMTP
+/* over TLS) does not mention dNSNames. Postfix follows the
+/* strict rules in RFC 2818 (HTTP over TLS), section 3.1: The
+/* Subject Alternative Name/dNSName has precedence over
+/* CommonName. If at least one dNSName is provided, Postfix
+/* verifies those against the peer hostname and ignores the
+/* CommonName, otherwise Postfix verifies the CommonName
+/* against the peer hostname.
+/*
+/* tls_client_init() is called once when the SMTP client
+/* initializes.
+/* Certificate details are also decided during this phase,
+/* so peer-specific certificate selection is not possible.
+/*
+/* tls_client_start() activates the TLS session over an established
+/* stream. We expect that network buffers are flushed and
+/* the TLS handshake can begin immediately.
+/*
+/* tls_client_stop() sends the "close notify" alert via
+/* SSL_shutdown() to the peer and resets all connection specific
+/* TLS data. As RFC2487 does not specify a separate shutdown, it
+/* is assumed that the underlying TCP connection is shut down
+/* immediately afterwards. Any further writes to the channel will
+/* be discarded, and any further reads will report end-of-file.
+/* If the failure flag is set, no SSL_shutdown() handshake is performed.
+/*
+/* Once the TLS connection is initiated, information about the TLS
+/* state is available via the TLScontext structure:
+/* .IP TLScontext->protocol
+/* the protocol name (SSLv2, SSLv3, TLSv1),
+/* .IP TLScontext->cipher_name
+/* the cipher name (e.g. RC4/MD5),
+/* .IP TLScontext->cipher_usebits
+/* the number of bits actually used (e.g. 40),
+/* .IP TLScontext->cipher_algbits
+/* the number of bits the algorithm is based on (e.g. 128).
+/* .PP
+/* The last two values may differ from each other when export-strength
+/* encryption is used.
+/*
+/* If the peer offered a certificate, part of the certificate data are
+/* available as:
+/* .IP TLScontext->peer_status
+/* A bitmask field that records the status of the peer certificate
+/* verification. This consists of one or more of
+/* TLS_CERT_FLAG_PRESENT, TLS_CERT_FLAG_ALTNAME, TLS_CERT_FLAG_TRUSTED,
+/* TLS_CERT_FLAG_MATCHED and TLS_CERT_FLAG_SECURED.
+/* .IP TLScontext->peer_CN
+/* Extracted CommonName of the peer, or zero-length string if the
+/* information could not be extracted.
+/* .IP TLScontext->issuer_CN
+/* Extracted CommonName of the issuer, or zero-length string if the
+/* information could not be extracted.
+/* .IP TLScontext->peer_cert_fprint
+/* At the fingerprint security level, if the peer presented a certificate
+/* the fingerprint of the certificate.
+/* .PP
+/* If no peer certificate is presented the peer_status is set to 0.
+/* EVENT_DRIVEN APPLICATIONS
+/* .ad
+/* .fi
+/* Event-driven programs manage multiple I/O channels. Such
+/* programs cannot use the synchronous VSTREAM-over-TLS
+/* implementation that the TLS library historically provides,
+/* including tls_client_stop() and the underlying tls_stream(3)
+/* and tls_bio_ops(3) routines.
+/*
+/* With the current TLS library implementation, this means
+/* that an event-driven application is responsible for calling
+/* and retrying SSL_connect(), SSL_read(), SSL_write() and
+/* SSL_shutdown().
+/*
+/* To maintain control over TLS I/O, an event-driven client
+/* invokes tls_client_start() with a null VSTREAM argument and
+/* with an fd argument that specifies the I/O file descriptor.
+/* Then, tls_client_start() performs all the necessary
+/* preparations before the TLS handshake and returns a partially
+/* populated TLS context. The event-driven application is then
+/* responsible for invoking SSL_connect(), and if successful,
+/* for invoking tls_client_post_connect() to finish the work
+/* that was started by tls_client_start(). In case of unrecoverable
+/* failure, tls_client_post_connect() destroys the TLS context
+/* and returns a null pointer value.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <argv.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <msg.h>
+#include <iostuff.h> /* non-blocking */
+#include <midna_domain.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* load_clnt_session - load session from client cache (non-callback) */
+
+static SSL_SESSION *load_clnt_session(TLS_SESS_STATE *TLScontext)
+{
+ const char *myname = "load_clnt_session";
+ SSL_SESSION *session = 0;
+ VSTRING *session_data = vstring_alloc(2048);
+
+ /*
+ * Prepare the query.
+ */
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("looking for session %s in %s cache",
+ TLScontext->serverid, TLScontext->cache_type);
+
+ /*
+ * We only get here if the cache_type is not empty. This code is not
+ * called unless caching is enabled and the cache_type is stored in the
+ * server SSL context.
+ */
+ if (TLScontext->cache_type == 0)
+ msg_panic("%s: null client session cache type in session lookup",
+ myname);
+
+ /*
+ * Look up and activate the SSL_SESSION object. Errors are non-fatal,
+ * since caching is only an optimization.
+ */
+ if (tls_mgr_lookup(TLScontext->cache_type, TLScontext->serverid,
+ session_data) == TLS_MGR_STAT_OK) {
+ session = tls_session_activate(STR(session_data), LEN(session_data));
+ if (session) {
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("reloaded session %s from %s cache",
+ TLScontext->serverid, TLScontext->cache_type);
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(session_data);
+
+ return (session);
+}
+
+/* new_client_session_cb - name new session and save it to client cache */
+
+static int new_client_session_cb(SSL *ssl, SSL_SESSION *session)
+{
+ const char *myname = "new_client_session_cb";
+ TLS_SESS_STATE *TLScontext;
+ VSTRING *session_data;
+
+ /*
+ * The cache name (if caching is enabled in tlsmgr(8)) and the cache ID
+ * string for this session are stored in the TLScontext. It cannot be
+ * null at this point.
+ */
+ if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
+ msg_panic("%s: null TLScontext in new session callback", myname);
+
+ /*
+ * We only get here if the cache_type is not empty. This callback is not
+ * set unless caching is enabled and the cache_type is stored in the
+ * server SSL context.
+ */
+ if (TLScontext->cache_type == 0)
+ msg_panic("%s: null session cache type in new session callback",
+ myname);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("save session %s to %s cache",
+ TLScontext->serverid, TLScontext->cache_type);
+
+ /*
+ * Passivate and save the session object. Errors are non-fatal, since
+ * caching is only an optimization.
+ */
+ if ((session_data = tls_session_passivate(session)) != 0) {
+ tls_mgr_update(TLScontext->cache_type, TLScontext->serverid,
+ STR(session_data), LEN(session_data));
+ vstring_free(session_data);
+ }
+
+ /*
+ * Clean up.
+ */
+ SSL_SESSION_free(session); /* 200502 */
+
+ return (1);
+}
+
+/* uncache_session - remove session from the external cache */
+
+static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
+{
+ SSL_SESSION *session = SSL_get_session(TLScontext->con);
+
+ SSL_CTX_remove_session(ctx, session);
+ if (TLScontext->cache_type == 0 || TLScontext->serverid == 0)
+ return;
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ /* serverid contains transport:addr:port information */
+ msg_info("remove session %s from client cache", TLScontext->serverid);
+
+ tls_mgr_delete(TLScontext->cache_type, TLScontext->serverid);
+}
+
+/* tls_client_init - initialize client-side TLS engine */
+
+TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *props)
+{
+ long off = 0;
+ int cachable;
+ int scache_timeout;
+ SSL_CTX *client_ctx;
+ TLS_APPL_STATE *app_ctx;
+ int log_mask;
+
+ /*
+ * Convert user loglevel to internal logmask.
+ */
+ log_mask = tls_log_mask(props->log_param, props->log_level);
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("initializing the client-side TLS engine");
+
+ /*
+ * Load (mostly cipher related) TLS-library internal main.cf parameters.
+ */
+ tls_param_init();
+
+ /*
+ * Detect mismatch between compile-time headers and run-time library.
+ */
+ tls_check_version();
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+ /*
+ * Initialize the OpenSSL library by the book! To start with, we must
+ * initialize the algorithms. We want cleartext error messages instead of
+ * just error codes, so we load the error_strings.
+ */
+ SSL_load_error_strings();
+ OpenSSL_add_ssl_algorithms();
+#endif
+
+ /*
+ * Initialize the OpenSSL library, possibly loading its configuration
+ * file.
+ */
+ if (tls_library_init() == 0)
+ return (0);
+
+ /*
+ * Create an application data index for SSL objects, so that we can
+ * attach TLScontext information; this information is needed inside
+ * tls_verify_certificate_callback().
+ */
+ if (TLScontext_index < 0) {
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) {
+ msg_warn("Cannot allocate SSL application data index: "
+ "disabling TLS support");
+ return (0);
+ }
+ }
+
+ /*
+ * If the administrator specifies an unsupported digest algorithm, fail
+ * now, rather than in the middle of a TLS handshake.
+ */
+ if (!tls_validate_digest(props->mdalg)) {
+ msg_warn("disabling TLS support");
+ return (0);
+ }
+
+ /*
+ * Initialize the PRNG (Pseudo Random Number Generator) with some seed
+ * from external and internal sources. Don't enable TLS without some real
+ * entropy.
+ */
+ if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) {
+ msg_warn("no entropy for TLS key generation: disabling TLS support");
+ return (0);
+ }
+ tls_int_seed();
+
+ /*
+ * The SSL/TLS specifications require the client to send a message in the
+ * oldest specification it understands with the highest level it
+ * understands in the message. RFC2487 is only specified for TLSv1, but
+ * we want to be as compatible as possible, so we will start off with a
+ * SSLv2 greeting allowing the best we can offer: TLSv1. We can restrict
+ * this with the options setting later, anyhow.
+ */
+ ERR_clear_error();
+ client_ctx = SSL_CTX_new(TLS_client_method());
+ if (client_ctx == 0) {
+ msg_warn("cannot allocate client SSL_CTX: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+#ifdef SSL_SECOP_PEER
+ /* Backwards compatible security as a base for opportunistic TLS. */
+ SSL_CTX_set_security_level(client_ctx, 0);
+#endif
+
+ /*
+ * See the verify callback in tls_verify.c
+ */
+ SSL_CTX_set_verify_depth(client_ctx, props->verifydepth + 1);
+
+ /*
+ * Presently we use TLS only with SMTP where truncation attacks are not
+ * possible as a result of application framing. If we ever use TLS in
+ * some other application protocol where truncation could be relevant,
+ * we'd need to disable truncation detection conditionally, or explicitly
+ * clear the option in that code path.
+ */
+ off |= SSL_OP_IGNORE_UNEXPECTED_EOF;
+
+ /*
+ * Protocol selection is destination dependent, so we delay the protocol
+ * selection options to the per-session SSL object.
+ */
+ off |= tls_bug_bits();
+ SSL_CTX_set_options(client_ctx, off);
+
+ /* Enable all supported protocols */
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fUL
+ SSL_CTX_set_min_proto_version(client_ctx, 0);
+#endif
+
+ /*
+ * Set the call-back routine for verbose logging.
+ */
+ if (log_mask & TLS_LOG_DEBUG)
+ SSL_CTX_set_info_callback(client_ctx, tls_info_callback);
+
+ /*
+ * Load the CA public key certificates for both the client cert and for
+ * the verification of server certificates. As provided by OpenSSL we
+ * support two types of CA certificate handling: One possibility is to
+ * add all CA certificates to one large CAfile, the other possibility is
+ * a directory pointed to by CApath, containing separate files for each
+ * CA with softlinks named after the hash values of the certificate. The
+ * first alternative has the advantage that the file is opened and read
+ * at startup time, so that you don't have the hassle to maintain another
+ * copy of the CApath directory for chroot-jail.
+ */
+ if (tls_set_ca_certificate_info(client_ctx,
+ props->CAfile, props->CApath) < 0) {
+ /* tls_set_ca_certificate_info() already logs a warning. */
+ SSL_CTX_free(client_ctx); /* 200411 */
+ return (0);
+ }
+
+ /*
+ * We do not need a client certificate, so the certificates are only
+ * loaded (and checked) if supplied. A clever client would handle
+ * multiple client certificates and decide based on the list of
+ * acceptable CAs, sent by the server, which certificate to submit.
+ * OpenSSL does however not do this and also has no call-back hooks to
+ * easily implement it.
+ *
+ * Load the client public key certificate and private key from file and
+ * check whether the cert matches the key. We can use RSA certificates
+ * ("cert") DSA certificates ("dcert") or ECDSA certificates ("eccert").
+ * All three can be made available at the same time. The CA certificates
+ * for all three are handled in the same setup already finished. Which
+ * one is used depends on the cipher negotiated (that is: the first
+ * cipher listed by the client which does match the server). The client
+ * certificate is presented after the server chooses the session cipher,
+ * so we will just present the right cert for the chosen cipher (if it
+ * uses certificates).
+ */
+ if (tls_set_my_certificate_key_info(client_ctx,
+ props->chain_files,
+ props->cert_file,
+ props->key_file,
+ props->dcert_file,
+ props->dkey_file,
+ props->eccert_file,
+ props->eckey_file) < 0) {
+ /* tls_set_my_certificate_key_info() already logs a warning. */
+ SSL_CTX_free(client_ctx); /* 200411 */
+ return (0);
+ }
+
+ /*
+ * 2015-12-05: Ephemeral RSA removed from OpenSSL 1.1.0-dev
+ */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+ /*
+ * According to the OpenSSL documentation, temporary RSA key is needed
+ * export ciphers are in use. We have to provide one, so well, we just do
+ * it.
+ */
+ SSL_CTX_set_tmp_rsa_callback(client_ctx, tls_tmp_rsa_cb);
+#endif
+
+ /*
+ * With OpenSSL 1.0.2 and later the client EECDH curve list becomes
+ * configurable with the preferred curve negotiated via the supported
+ * curves extension.
+ */
+ tls_auto_eecdh_curves(client_ctx, var_tls_eecdh_auto);
+
+ /*
+ * Finally, the setup for the server certificate checking, done "by the
+ * book".
+ */
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE,
+ tls_verify_certificate_callback);
+
+ /*
+ * Initialize the session cache.
+ *
+ * Since the client does not search an internal cache, we simply disable it.
+ * It is only useful for expiring old sessions, but we do that in the
+ * tlsmgr(8).
+ *
+ * This makes SSL_CTX_remove_session() not useful for flushing broken
+ * sessions from the external cache, so we must delete them directly (not
+ * via a callback).
+ */
+ if (tls_mgr_policy(props->cache_type, &cachable,
+ &scache_timeout) != TLS_MGR_STAT_OK)
+ scache_timeout = 0;
+ if (scache_timeout <= 0)
+ cachable = 0;
+
+ /*
+ * Allocate an application context, and populate with mandatory protocol
+ * and cipher data.
+ */
+ app_ctx = tls_alloc_app_context(client_ctx, 0, log_mask);
+
+ /*
+ * The external session cache is implemented by the tlsmgr(8) process.
+ */
+ if (cachable) {
+
+ app_ctx->cache_type = mystrdup(props->cache_type);
+
+ /*
+ * OpenSSL does not use callbacks to load sessions from a client
+ * cache, so we must invoke that function directly. Apparently,
+ * OpenSSL does not provide a way to pass session names from here to
+ * call-back routines that do session lookup.
+ *
+ * OpenSSL can, however, automatically save newly created sessions for
+ * us by callback (we create the session name in the call-back
+ * function).
+ *
+ * XXX gcc 2.95 can't compile #ifdef .. #endif in the expansion of
+ * SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE |
+ * SSL_SESS_CACHE_NO_AUTO_CLEAR.
+ */
+#ifndef SSL_SESS_CACHE_NO_INTERNAL_STORE
+#define SSL_SESS_CACHE_NO_INTERNAL_STORE 0
+#endif
+
+ SSL_CTX_set_session_cache_mode(client_ctx,
+ SSL_SESS_CACHE_CLIENT |
+ SSL_SESS_CACHE_NO_INTERNAL_STORE |
+ SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ SSL_CTX_sess_set_new_cb(client_ctx, new_client_session_cb);
+
+ /*
+ * OpenSSL ignores timed-out sessions. We need to set the internal
+ * cache timeout at least as high as the external cache timeout. This
+ * applies even if no internal cache is used. We set the session to
+ * twice the cache lifetime. This way a session always lasts longer
+ * than its lifetime in the cache.
+ */
+ SSL_CTX_set_timeout(client_ctx, 2 * scache_timeout);
+ }
+ return (app_ctx);
+}
+
+/* match_servername - match servername against pattern */
+
+static int match_servername(const char *certid,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ const ARGV *cmatch_argv;
+ const char *nexthop = props->nexthop;
+ const char *hname = props->host;
+ const char *domain;
+ const char *parent;
+ const char *aname;
+ int match_subdomain;
+ int i;
+ int idlen;
+ int domlen;
+
+ if ((cmatch_argv = props->matchargv) == 0)
+ return 0;
+
+#ifndef NO_EAI
+
+ /*
+ * DNS subjectAltNames are required to be ASCII.
+ *
+ * Per RFC 6125 Section 6.4.4 Matching the CN-ID, follows the same rules
+ * (6.4.1, 6.4.2 and 6.4.3) that apply to subjectAltNames. In
+ * particular, 6.4.2 says that the reference identifier is coerced to
+ * ASCII, but no conversion is stated or implied for the CN-ID, so it
+ * seems it only matches if it is all ASCII. Otherwise, it is some other
+ * sort of name.
+ */
+ if (!allascii(certid))
+ return (0);
+ if (!allascii(nexthop) && (aname = midna_domain_to_ascii(nexthop)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", nexthop, aname);
+ nexthop = aname;
+ }
+#endif
+
+ /*
+ * Match the certid against each pattern until we find a match.
+ */
+ for (i = 0; i < cmatch_argv->argc; ++i) {
+ match_subdomain = 0;
+ if (!strcasecmp(cmatch_argv->argv[i], "nexthop"))
+ domain = nexthop;
+ else if (!strcasecmp(cmatch_argv->argv[i], "hostname"))
+ domain = hname;
+ else if (!strcasecmp(cmatch_argv->argv[i], "dot-nexthop")) {
+ domain = nexthop;
+ match_subdomain = 1;
+ } else {
+ domain = cmatch_argv->argv[i];
+ if (*domain == '.') {
+ if (domain[1]) {
+ ++domain;
+ match_subdomain = 1;
+ }
+ }
+#ifndef NO_EAI
+
+ /*
+ * Besides U+002E (full stop) IDNA2003 allows labels to be
+ * separated by any of the Unicode variants U+3002 (ideographic
+ * full stop), U+FF0E (fullwidth full stop), and U+FF61
+ * (halfwidth ideographic full stop). Their respective UTF-8
+ * encodings are: E38082, EFBC8E and EFBDA1.
+ *
+ * IDNA2008 does not permit (upper) case and other variant
+ * differences in U-labels. The midna_domain_to_ascii() function,
+ * based on UTS46, normalizes such differences away.
+ *
+ * The IDNA to_ASCII conversion does not allow empty leading labels,
+ * so we handle these explicitly here.
+ */
+ else {
+ unsigned char *cp = (unsigned char *) domain;
+
+ if ((cp[0] == 0xe3 && cp[1] == 0x80 && cp[2] == 0x82)
+ || (cp[0] == 0xef && cp[1] == 0xbc && cp[2] == 0x8e)
+ || (cp[0] == 0xef && cp[1] == 0xbd && cp[2] == 0xa1)) {
+ if (domain[3]) {
+ domain = domain + 3;
+ match_subdomain = 1;
+ }
+ }
+ }
+ if (!allascii(domain)
+ && (aname = midna_domain_to_ascii(domain)) != 0) {
+ if (msg_verbose)
+ msg_info("%s asciified to %s", domain, aname);
+ domain = aname;
+ }
+#endif
+ }
+
+ /*
+ * Sub-domain match: certid is any sub-domain of hostname.
+ */
+ if (match_subdomain) {
+ if ((idlen = strlen(certid)) > (domlen = strlen(domain)) + 1
+ && certid[idlen - domlen - 1] == '.'
+ && !strcasecmp(certid + (idlen - domlen), domain))
+ return (1);
+ else
+ continue;
+ }
+
+ /*
+ * Exact match and initial "*" match. The initial "*" in a certid
+ * matches one (if var_tls_multi_label is false) or more hostname
+ * components under the condition that the certid contains multiple
+ * hostname components.
+ */
+ if (!strcasecmp(certid, domain)
+ || (certid[0] == '*' && certid[1] == '.' && certid[2] != 0
+ && (parent = strchr(domain, '.')) != 0
+ && (idlen = strlen(certid + 1)) <= (domlen = strlen(parent))
+ && strcasecmp(var_tls_multi_wildcard == 0 ? parent :
+ parent + domlen - idlen,
+ certid + 1) == 0))
+ return (1);
+ }
+ return (0);
+}
+
+/* verify_extract_name - verify peer name and extract peer information */
+
+static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ int i;
+ int r;
+ int matched = 0;
+ int dnsname_match;
+ int verify_peername = 0;
+ int log_certmatch;
+ int verbose;
+ const char *dnsname;
+ const GENERAL_NAME *gn;
+ general_name_stack_t *gens;
+
+ /*
+ * On exit both peer_CN and issuer_CN should be set.
+ */
+ TLScontext->issuer_CN = tls_issuer_CN(peercert, TLScontext);
+
+ /*
+ * Is the certificate trust chain valid and trusted?
+ */
+ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK)
+ TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
+
+ /*
+ * With fingerprint or dane we may already be done. Otherwise, verify the
+ * peername if using traditional PKI or DANE with trust-anchors.
+ */
+ if (!TLS_CERT_IS_MATCHED(TLScontext)
+ && TLS_CERT_IS_TRUSTED(TLScontext)
+ && TLS_MUST_TRUST(props->tls_level))
+ verify_peername = 1;
+
+ /* Force cert processing so we can log the data? */
+ log_certmatch = TLScontext->log_mask & TLS_LOG_CERTMATCH;
+
+ /* Log cert details when processing? */
+ verbose = log_certmatch || (TLScontext->log_mask & TLS_LOG_VERBOSE);
+
+ if (verify_peername || log_certmatch) {
+
+ /*
+ * Verify the dNSName(s) in the peer certificate against the nexthop
+ * and hostname.
+ *
+ * If DNS names are present, we use the first matching (or else simply
+ * the first) DNS name as the subject CN. The CommonName in the
+ * issuer DN is obsolete when SubjectAltName is available. This
+ * yields much less surprising logs, because we log the name we
+ * verified or a name we checked and failed to match.
+ *
+ * XXX: The nexthop and host name may both be the same network address
+ * rather than a DNS name. In this case we really should be looking
+ * for GEN_IPADD entries, not GEN_DNS entries.
+ *
+ * XXX: In ideal world the caller who used the address to build the
+ * connection would tell us that the nexthop is the connection
+ * address, but if that is not practical, we can parse the nexthop
+ * again here.
+ */
+ gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, 0, 0);
+ if (gens) {
+ r = sk_GENERAL_NAME_num(gens);
+ for (i = 0; i < r; ++i) {
+ gn = sk_GENERAL_NAME_value(gens, i);
+ if (gn->type != GEN_DNS)
+ continue;
+
+ /*
+ * Even if we have an invalid DNS name, we still ultimately
+ * ignore the CommonName, because subjectAltName:DNS is
+ * present (though malformed). Replace any previous peer_CN
+ * if empty or we get a match.
+ *
+ * We always set at least an empty peer_CN if the ALTNAME cert
+ * flag is set. If not, we set peer_CN from the cert
+ * CommonName below, so peer_CN is always non-null on return.
+ */
+ TLScontext->peer_status |= TLS_CERT_FLAG_ALTNAME;
+ dnsname = tls_dns_name(gn, TLScontext);
+ if (dnsname && *dnsname) {
+ if ((dnsname_match = match_servername(dnsname, props)) != 0)
+ matched++;
+ /* Keep the first matched name. */
+ if (TLScontext->peer_CN
+ && ((dnsname_match && matched == 1)
+ || *TLScontext->peer_CN == 0)) {
+ myfree(TLScontext->peer_CN);
+ TLScontext->peer_CN = 0;
+ }
+ if (verbose)
+ msg_info("%s: %ssubjectAltName: %s", props->namaddr,
+ dnsname_match ? "Matched " : "", dnsname);
+ }
+ if (TLScontext->peer_CN == 0)
+ TLScontext->peer_CN = mystrdup(dnsname ? dnsname : "");
+ if (matched && !log_certmatch)
+ break;
+ }
+ if (verify_peername && matched)
+ TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
+
+ /*
+ * (Sam Rushing, Ironport) Free stack *and* member GENERAL_NAME
+ * objects
+ */
+ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
+ }
+
+ /*
+ * No subjectAltNames, peer_CN is taken from CommonName.
+ */
+ if (TLScontext->peer_CN == 0) {
+ TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext);
+ if (*TLScontext->peer_CN)
+ matched = match_servername(TLScontext->peer_CN, props);
+ if (verify_peername && matched)
+ TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
+ if (verbose)
+ msg_info("%s %sCommonName %s", props->namaddr,
+ matched ? "Matched " : "", TLScontext->peer_CN);
+ } else if (verbose) {
+ char *tmpcn = tls_peer_CN(peercert, TLScontext);
+
+ /*
+ * Though the CommonName was superceded by a subjectAltName, log
+ * it when certificate match debugging was requested.
+ */
+ msg_info("%s CommonName %s", TLScontext->namaddr, tmpcn);
+ myfree(tmpcn);
+ }
+ } else
+ TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext);
+
+ /*
+ * Give them a clue. Problems with trust chain verification are logged
+ * when the session is first negotiated, before the session is stored
+ * into the cache. We don't want mystery failures, so log the fact the
+ * real problem is to be found in the past.
+ */
+ if (!TLS_CERT_IS_TRUSTED(TLScontext)
+ && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+ if (TLScontext->session_reused == 0)
+ tls_log_verify_error(TLScontext);
+ else
+ msg_info("%s: re-using session with untrusted certificate, "
+ "look for details earlier in the log", props->namaddr);
+ }
+}
+
+/* verify_extract_print - extract and verify peer fingerprint */
+
+static void verify_extract_print(TLS_SESS_STATE *TLScontext, X509 *peercert,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ TLScontext->peer_cert_fprint = tls_cert_fprint(peercert, props->mdalg);
+ TLScontext->peer_pkey_fprint = tls_pkey_fprint(peercert, props->mdalg);
+
+ /*
+ * Whether the level is "dane" or "fingerprint" when the peer certificate
+ * is matched without resorting to a separate CA, we set both the trusted
+ * and matched bits. This simplifies logic in smtp_proto.c where "dane"
+ * must be trusted and matched, since some "dane" TLSA RRsets do use CAs.
+ *
+ * This also suppresses spurious logging of the peer certificate as
+ * untrusted in verify_extract_name().
+ */
+ if (TLS_DANE_HASEE(props->dane)
+ && tls_dane_match(TLScontext, TLS_DANE_EE, peercert, 0))
+ TLScontext->peer_status |=
+ TLS_CERT_FLAG_TRUSTED | TLS_CERT_FLAG_MATCHED;
+}
+
+ /*
+ * This is the actual startup routine for the connection. We expect that the
+ * buffers are flushed and the "220 Ready to start TLS" was received by us,
+ * so that we can immediately start the TLS handshake process.
+ */
+TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props)
+{
+ int sts;
+ int protomask;
+ const char *cipher_list;
+ SSL_SESSION *session = 0;
+ TLS_SESS_STATE *TLScontext;
+ TLS_APPL_STATE *app_ctx = props->ctx;
+ const char *sni = 0;
+ char *myserverid;
+ int log_mask = app_ctx->log_mask;
+
+ /*
+ * When certificate verification is required, log trust chain validation
+ * errors even when disabled by default for opportunistic sessions. For
+ * DANE this only applies when using trust-anchor associations.
+ */
+ if (TLS_MUST_TRUST(props->tls_level)
+ && (!TLS_DANE_BASED(props->tls_level) || TLS_DANE_HASTA(props->dane)))
+ log_mask |= TLS_LOG_UNTRUSTED;
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("setting up TLS connection to %s", props->namaddr);
+
+ /*
+ * First make sure we have valid protocol and cipher parameters
+ *
+ * Per-session protocol restrictions must be applied to the SSL connection,
+ * as restrictions in the global context cannot be cleared.
+ */
+ protomask = tls_protocol_mask(props->protocols);
+ if (protomask == TLS_PROTOCOL_INVALID) {
+ /* tls_protocol_mask() logs no warning. */
+ msg_warn("%s: Invalid TLS protocol list \"%s\": aborting TLS session",
+ props->namaddr, props->protocols);
+ return (0);
+ }
+ /* DANE requires SSLv3 or later, not SSLv2. */
+ if (TLS_DANE_BASED(props->tls_level))
+ protomask |= TLS_PROTOCOL_SSLv2;
+
+ /*
+ * Allocate a new TLScontext for the new connection and get an SSL
+ * structure. Add the location of TLScontext to the SSL to later retrieve
+ * the information inside the tls_verify_certificate_callback().
+ *
+ * If session caching was enabled when TLS was initialized, the cache type
+ * is stored in the client SSL context.
+ */
+ TLScontext = tls_alloc_sess_context(log_mask, props->namaddr);
+ TLScontext->cache_type = app_ctx->cache_type;
+
+ if ((TLScontext->con = SSL_new(app_ctx->ssl_ctx)) == NULL) {
+ msg_warn("Could not allocate 'TLScontext->con' with SSL_new()");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * Per session cipher selection for sessions with mandatory encryption
+ *
+ * The cipherlist is applied to the global SSL context, since it is likely
+ * to stay the same between connections, so we make use of a 1-element
+ * cache to return the same result for identical inputs.
+ */
+ cipher_list = tls_set_ciphers(TLScontext, props->cipher_grade,
+ props->cipher_exclusions);
+ if (cipher_list == 0) {
+ /* already warned */
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list);
+
+ /*
+ * OpenSSL will ignore cached sessions that use the wrong protocol. So we
+ * do not need to filter out cached sessions with the "wrong" protocol,
+ * rather OpenSSL will simply negotiate a new session.
+ *
+ * We salt the session lookup key with the protocol list, so that sessions
+ * found in the cache are plausibly acceptable.
+ *
+ * By the time a TLS client is negotiating ciphers it has already offered to
+ * re-use a session, it is too late to renege on the offer. So we must
+ * not attempt to re-use sessions whose ciphers are too weak. We salt the
+ * session lookup key with the cipher list, so that sessions found in the
+ * cache are always acceptable.
+ *
+ * With DANE, (more generally any TLScontext where we specified explicit
+ * trust-anchor or end-entity certificates) the verification status of
+ * the SSL session depends on the specified list. Since we verify the
+ * certificate only during the initial handshake, we must segregate
+ * sessions with different TA lists. Note, that TA re-verification is
+ * not possible with cached sessions, since these don't hold the complete
+ * peer trust chain. Therefore, we compute a digest of the sorted TA
+ * parameters and append it to the serverid.
+ */
+ myserverid = tls_serverid_digest(props, protomask, cipher_list);
+
+ TLScontext->serverid = myserverid;
+ TLScontext->stream = props->stream;
+ TLScontext->mdalg = props->mdalg;
+
+ /* Alias DANE digest info from props */
+ TLScontext->dane = props->dane;
+
+ if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
+ msg_warn("Could not set application data for 'TLScontext->con'");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * Apply session protocol restrictions.
+ */
+ if (protomask != 0)
+ SSL_set_options(TLScontext->con, TLS_SSL_OP_PROTOMASK(protomask));
+
+#ifdef SSL_SECOP_PEER
+ /* When authenticating the peer, use 80-bit plus OpenSSL security level */
+ if (TLS_MUST_MATCH(props->tls_level))
+ SSL_set_security_level(TLScontext->con, 1);
+#endif
+
+ /*
+ * XXX To avoid memory leaks we must always call SSL_SESSION_free() after
+ * calling SSL_set_session(), regardless of whether or not the session
+ * will be reused.
+ */
+ if (TLScontext->cache_type) {
+ session = load_clnt_session(TLScontext);
+ if (session) {
+ SSL_set_session(TLScontext->con, session);
+ SSL_SESSION_free(session); /* 200411 */
+ }
+ }
+#ifdef TLSEXT_MAXLEN_host_name
+ if (TLS_DANE_BASED(props->tls_level)) {
+
+ /*
+ * With DANE sessions, send an SNI hint. We don't care whether the
+ * server reports finding a matching certificate or not, so no
+ * callback is required to process the server response. Our use of
+ * SNI is limited to giving servers that are (mis)configured to use
+ * SNI the best opportunity to find the certificate they promised via
+ * the associated TLSA RRs. (Generally, server administrators should
+ * avoid SNI, and there are no plans to support SNI in the Postfix
+ * SMTP server).
+ *
+ * Per RFC7672, the required SNI name is the TLSA "base domain" (the one
+ * used to construct the "_25._tcp.<fqdn>" TLSA record DNS query).
+ *
+ * Since the hostname is DNSSEC-validated, it must be a DNS FQDN and
+ * thererefore valid for use with SNI.
+ */
+ sni = props->dane->base_domain;
+ } else if (props->sni && *props->sni) {
+ if (strcmp(props->sni, "hostname") == 0)
+ sni = props->host;
+ else if (strcmp(props->sni, "nexthop") == 0)
+ sni = props->nexthop;
+ else
+ sni = props->sni;
+ }
+ if (sni && strlen(sni) <= TLSEXT_MAXLEN_host_name) {
+
+ /*
+ * Failure to set a valid SNI hostname is a memory allocation error,
+ * and thus transient. Since we must not cache the session if we
+ * failed to send the SNI name, we have little choice but to abort.
+ */
+ if (!SSL_set_tlsext_host_name(TLScontext->con, sni)) {
+ msg_warn("%s: error setting SNI hostname to: %s", props->namaddr,
+ sni);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * The saved value is not presently used client-side, but could later
+ * be logged if acked by the server (requires new client-side
+ * callback to detect the ack). For now this just maintains symmetry
+ * with the server code, where do record the received SNI for
+ * logging.
+ */
+ TLScontext->peer_sni = mystrdup(sni);
+ if (log_mask & TLS_LOG_DEBUG)
+ msg_info("%s: SNI hostname: %s", props->namaddr, sni);
+ }
+#endif
+
+ /*
+ * Before really starting anything, try to seed the PRNG a little bit
+ * more.
+ */
+ tls_int_seed();
+ (void) tls_ext_seed(var_tls_daemon_rand_bytes);
+
+ /*
+ * Connect the SSL connection with the network socket.
+ */
+ if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd :
+ vstream_fileno(props->stream)) != 1) {
+ msg_info("SSL_set_fd error to %s", props->namaddr);
+ tls_print_errors();
+ uncache_session(app_ctx->ssl_ctx, TLScontext);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * If the debug level selected is high enough, all of the data is dumped:
+ * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will
+ * dump everything.
+ *
+ * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
+ * Well there is a BIO below the SSL routines that is automatically
+ * created for us, so we can use it for debugging purposes.
+ */
+ if (log_mask & TLS_LOG_TLSPKTS)
+ BIO_set_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb);
+
+ tls_dane_set_callback(app_ctx->ssl_ctx, TLScontext);
+
+ /*
+ * If we don't trigger the handshake in the library, leave control over
+ * SSL_connect/read/write/etc with the application.
+ */
+ if (props->stream == 0)
+ return (TLScontext);
+
+ /*
+ * Turn on non-blocking I/O so that we can enforce timeouts on network
+ * I/O.
+ */
+ non_blocking(vstream_fileno(props->stream), NON_BLOCKING);
+
+ /*
+ * Start TLS negotiations. This process is a black box that invokes our
+ * call-backs for certificate verification.
+ *
+ * Error handling: If the SSL handshake fails, we print out an error message
+ * and remove all TLS state concerning this session.
+ */
+ sts = tls_bio_connect(vstream_fileno(props->stream), props->timeout,
+ TLScontext);
+ if (sts <= 0) {
+ if (ERR_peek_error() != 0) {
+ msg_info("SSL_connect error to %s: %d", props->namaddr, sts);
+ tls_print_errors();
+ } else if (errno != 0) {
+ msg_info("SSL_connect error to %s: %m", props->namaddr);
+ } else {
+ msg_info("SSL_connect error to %s: lost connection",
+ props->namaddr);
+ }
+ uncache_session(app_ctx->ssl_ctx, TLScontext);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ return (tls_client_post_connect(TLScontext, props));
+}
+
+/* tls_client_post_connect - post-handshake processing */
+
+TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *TLScontext,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ const SSL_CIPHER *cipher;
+ X509 *peercert;
+
+ /* Turn off packet dump if only dumping the handshake */
+ if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0)
+ BIO_set_callback(SSL_get_rbio(TLScontext->con), 0);
+
+ /*
+ * The caller may want to know if this session was reused or if a new
+ * session was negotiated.
+ */
+ TLScontext->session_reused = SSL_session_reused(TLScontext->con);
+ if ((TLScontext->log_mask & TLS_LOG_CACHE) && TLScontext->session_reused)
+ msg_info("%s: Reusing old session", TLScontext->namaddr);
+
+ /*
+ * Do peername verification if requested and extract useful information
+ * from the certificate for later use.
+ */
+ if ((peercert = SSL_get_peer_certificate(TLScontext->con)) != 0) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+
+ /*
+ * Peer name or fingerprint verification as requested.
+ * Unconditionally set peer_CN, issuer_CN and peer_cert_fprint. Check
+ * fingerprint first, and avoid logging verified as untrusted in the
+ * call to verify_extract_name().
+ */
+ verify_extract_print(TLScontext, peercert, props);
+ verify_extract_name(TLScontext, peercert, props);
+
+ if (TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ msg_info("%s: subject_CN=%s, issuer_CN=%s, "
+ "fingerprint=%s, pkey_fingerprint=%s", props->namaddr,
+ TLScontext->peer_CN, TLScontext->issuer_CN,
+ TLScontext->peer_cert_fprint,
+ TLScontext->peer_pkey_fprint);
+ X509_free(peercert);
+ } else {
+ TLScontext->issuer_CN = mystrdup("");
+ TLScontext->peer_CN = mystrdup("");
+ TLScontext->peer_cert_fprint = mystrdup("");
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ }
+
+ /*
+ * Finally, collect information about protocol and cipher for logging
+ */
+ TLScontext->protocol = SSL_get_version(TLScontext->con);
+ cipher = SSL_get_current_cipher(TLScontext->con);
+ TLScontext->cipher_name = SSL_CIPHER_get_name(cipher);
+ TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher,
+ &(TLScontext->cipher_algbits));
+
+ /*
+ * The TLS engine is active. Switch to the tls_timed_read/write()
+ * functions and make the TLScontext available to those functions.
+ */
+ if (TLScontext->stream != 0)
+ tls_stream_start(props->stream, TLScontext);
+
+ /*
+ * Fully secured only if trusted, matched and not insecure like halfdane.
+ * Should perhaps also exclude "verify" (as opposed to "secure") here,
+ * because that can be subject to insecure MX indirection, but that's
+ * rather incompatible. Users have been warned.
+ */
+ if (TLS_CERT_IS_PRESENT(TLScontext)
+ && TLS_CERT_IS_TRUSTED(TLScontext)
+ && TLS_CERT_IS_MATCHED(TLScontext)
+ && !TLS_NEVER_SECURED(props->tls_level))
+ TLScontext->peer_status |= TLS_CERT_FLAG_SECURED;
+
+ /*
+ * With the handshake done, extract TLS 1.3 signature metadata.
+ */
+ tls_get_signature_params(TLScontext);
+
+ if (TLScontext->log_mask & TLS_LOG_SUMMARY)
+ tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW, TLScontext);
+
+ tls_int_seed();
+
+ return (TLScontext);
+}
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_dane.c b/src/tls/tls_dane.c
new file mode 100644
index 0000000..3375905
--- /dev/null
+++ b/src/tls/tls_dane.c
@@ -0,0 +1,2080 @@
+/*++
+/* NAME
+/* tls_dane 3
+/* SUMMARY
+/* Support for RFC 6698, 7671, 7672 (DANE) certificate matching
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* int tls_dane_avail()
+/*
+/* void tls_dane_flush()
+/*
+/* void tls_dane_verbose(on)
+/* int on;
+/*
+/* TLS_DANE *tls_dane_alloc()
+/*
+/* void tls_dane_free(dane)
+/* TLS_DANE *dane;
+/*
+/* void tls_dane_add_ee_digests(dane, mdalg, digest, delim)
+/* TLS_DANE *dane;
+/* const char *mdalg;
+/* const char *digest;
+/* const char *delim;
+/*
+/* int tls_dane_load_trustfile(dane, tafile)
+/* TLS_DANE *dane;
+/* const char *tafile;
+/*
+/* int tls_dane_match(TLSContext, usage, cert, depth)
+/* TLS_SESS_STATE *TLScontext;
+/* int usage;
+/* X509 *cert;
+/* int depth;
+/*
+/* void tls_dane_set_callback(ssl_ctx, TLScontext)
+/* SSL_CTX *ssl_ctx;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* TLS_DANE *tls_dane_resolve(port, proto, hostrr, forcetlsa)
+/* unsigned port;
+/* const char *proto;
+/* DNS_RR *hostrr;
+/* int forcetlsa;
+/*
+/* int tls_dane_unusable(dane)
+/* const TLS_DANE *dane;
+/*
+/* int tls_dane_notfound(dane)
+/* const TLS_DANE *dane;
+/* DESCRIPTION
+/* tls_dane_avail() returns true if the features required to support DANE
+/* are present in OpenSSL's libcrypto and in libresolv. Since OpenSSL's
+/* libcrypto is not initialized until we call tls_client_init(), calls
+/* to tls_dane_avail() must be deferred until this initialization is
+/* completed successufully.
+/*
+/* tls_dane_flush() flushes all entries from the cache, and deletes
+/* the cache.
+/*
+/* tls_dane_verbose() turns on verbose logging of TLSA record lookups.
+/*
+/* tls_dane_alloc() returns a pointer to a newly allocated TLS_DANE
+/* structure with null ta and ee digest sublists.
+/*
+/* tls_dane_free() frees the structure allocated by tls_dane_alloc().
+/*
+/* tls_dane_add_ee_digests() splits "digest" using the characters in
+/* "delim" as delimiters and stores the results on the EE match list
+/* to match either a certificate or a public key. This is an incremental
+/* interface, that builds a TLS_DANE structure outside the cache by
+/* manually adding entries.
+/*
+/* tls_dane_load_trustfile() imports trust-anchor certificates and
+/* public keys from a file (rather than DNS TLSA records).
+/*
+/* tls_dane_match() matches the full and/or public key digest of
+/* "cert" against each candidate digest in TLScontext->dane. If usage
+/* is TLS_DANE_EE, the match is against end-entity digests, otherwise
+/* it is against trust-anchor digests. Returns true if a match is found,
+/* false otherwise.
+/*
+/* tls_dane_set_callback() wraps the SSL certificate verification logic
+/* in a function that modifies the input trust chain and trusted
+/* certificate store to map DANE TA validation onto the existing PKI
+/* verification model. When TLScontext is NULL the callback is
+/* cleared, otherwise it is set. This callback should only be set
+/* when out-of-band trust-anchors (via DNSSEC DANE TLSA records or
+/* per-destination local configuration) are provided. Such trust
+/* anchors always override the legacy public CA PKI. Otherwise, the
+/* callback MUST be cleared.
+/*
+/* tls_dane_resolve() maps a (port, protocol, hostrr) tuple to a
+/* corresponding TLS_DANE policy structure found in the DNS. The port
+/* argument is in network byte order. A null pointer is returned when
+/* the DNS query for the TLSA record tempfailed. In all other cases the
+/* return value is a pointer to the corresponding TLS_DANE structure.
+/* The caller must free the structure via tls_dane_free().
+/*
+/* tls_dane_unusable() checks whether a cached TLS_DANE record is
+/* the result of a validated RRset, with no usable elements. In
+/* this case, TLS is mandatory, but certificate verification is
+/* not DANE-based.
+/*
+/* tls_dane_notfound() checks whether a cached TLS_DANE record is
+/* the result of a validated DNS lookup returning NODATA. In
+/* this case, TLS is not required by RFC, though users may elect
+/* a mandatory TLS fallback policy.
+/*
+/* Arguments:
+/* .IP dane
+/* Pointer to a TLS_DANE structure that lists the valid trust-anchor
+/* and end-entity full-certificate and/or public-key digests.
+/* .IP port
+/* The TCP port in network byte order.
+/* .IP proto
+/* Almost certainly "tcp".
+/* .IP hostrr
+/* DNS_RR pointer to TLSA base domain data.
+/* .IP forcetlsa
+/* When true, TLSA lookups are performed even when the qname and rname
+/* are insecure. This is only useful in the unlikely case that DLV is
+/* used to secure the TLSA RRset in an otherwise insecure zone.
+/* .IP TLScontext
+/* Client context with TA/EE matching data and related state.
+/* .IP usage
+/* Trust anchor (TLS_DANE_TA) or end-entity (TLS_DANE_EE) digests?
+/* .IP cert
+/* Certificate from peer trust chain (CA or leaf server).
+/* .IP depth
+/* The certificate depth for logging.
+/* .IP ssl_ctx
+/* The global SSL_CTX structure used to initialize child SSL
+/* conenctions.
+/* .IP mdalg
+/* Name of a message digest algorithm suitable for computing secure
+/* (1st pre-image resistant) message digests of certificates. For now,
+/* md5, sha1, or member of SHA-2 family if supported by OpenSSL.
+/* .IP digest
+/* The digest (or list of digests concatenated with characters from
+/* "delim") to be added to the TLS_DANE record.
+/* .IP delim
+/* The set of delimiter characters used above.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <events.h> /* event_time() */
+#include <timecmp.h>
+#include <ctable.h>
+#include <hex_code.h>
+#include <safe_ultostr.h>
+#include <split_at.h>
+#include <name_code.h>
+
+#define STR(x) vstring_str(x)
+
+/* Global library */
+
+#include <mail_params.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#undef DANE_TLSA_SUPPORT
+
+#if defined(TLSEXT_MAXLEN_host_name) && RES_USE_DNSSEC && RES_USE_EDNS0
+#define DANE_TLSA_SUPPORT
+static int dane_tlsa_support = 1;
+
+#else
+static int dane_tlsa_support = 0;
+
+#endif
+
+static const char *signalg;
+static ASN1_OBJECT *serverAuth;
+
+/*
+ * https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml
+ */
+typedef struct {
+ const char *mdalg;
+ uint8_t dane_id;
+} iana_digest;
+
+static iana_digest iana_table[] = {
+ {"", DNS_TLSA_MATCHING_TYPE_NO_HASH_USED},
+ {"sha256", DNS_TLSA_MATCHING_TYPE_SHA256},
+ {"sha512", DNS_TLSA_MATCHING_TYPE_SHA512},
+ {0, 0}
+};
+
+typedef struct dane_digest {
+ struct dane_digest *next; /* linkage */
+ const char *mdalg; /* OpenSSL name */
+ const EVP_MD *md; /* OpenSSL EVP handle */
+ int len; /* digest octet length */
+ int pref; /* tls_dane_digests index or -1 */
+ uint8_t dane_id; /* IANA id */
+} dane_digest;
+
+#define MAXDIGESTS 256 /* RFC limit */
+static dane_digest *digest_list;
+
+/*
+ * This is not intended to be a long-term cache of pre-parsed TLSA data,
+ * rather we primarily want to avoid fetching and parsing the TLSA records
+ * for a single multi-homed MX host more than once per delivery. Therefore,
+ * we keep the table reasonably small.
+ */
+#define CACHE_SIZE 20
+static CTABLE *dane_cache;
+
+static int dane_initialized;
+static int dane_verbose;
+
+/* tls_dane_verbose - enable/disable verbose logging */
+
+void tls_dane_verbose(int on)
+{
+ dane_verbose = on;
+}
+
+/* add_digest - validate and append digest to digest list */
+
+static dane_digest *add_digest(char *mdalg, int pref)
+{
+ iana_digest *i;
+ dane_digest *d;
+ int dane_id = -1;
+ const char *dane_mdalg = mdalg;
+ char *value = split_at(mdalg, '=');
+ const EVP_MD *md = 0;
+ size_t mdlen = 0;
+
+ if (value && *value) {
+ unsigned long l;
+ char *endcp;
+
+ /*
+ * XXX: safe_strtoul() does not flag empty or white-space only input.
+ * Since we get idbuf by splitting white-space/comma delimited
+ * tokens, this is not a problem here. Fixed as of 210131209.
+ */
+ l = safe_strtoul(value, &endcp, 10);
+ if ((l == 0 && (errno == EINVAL || endcp == value))
+ || l >= MAXDIGESTS
+ || *endcp) {
+ msg_warn("Invalid matching type number in %s: %s=%s",
+ VAR_TLS_DANE_DIGESTS, mdalg, value);
+ return (0);
+ }
+ dane_id = l;
+ }
+
+ /*
+ * Check for known IANA conflicts
+ */
+ for (i = iana_table; i->mdalg; ++i) {
+ if (*mdalg && strcasecmp(i->mdalg, mdalg) == 0) {
+ if (dane_id >= 0 && i->dane_id != dane_id) {
+ msg_warn("Non-standard value in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return (0);
+ }
+ dane_id = i->dane_id;
+ } else if (i->dane_id == dane_id) {
+ if (*mdalg) {
+ msg_warn("Non-standard algorithm in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return (0);
+ }
+ dane_mdalg = i->mdalg;
+ }
+ }
+
+ /*
+ * Check for unknown implicit digest or value
+ */
+ if (dane_id < 0 || (dane_id > 0 && !*dane_mdalg)) {
+ msg_warn("Unknown incompletely specified element in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return 0;
+ }
+
+ /*
+ * Check for duplicate entries
+ */
+ for (d = digest_list; d; d = d->next) {
+ if (strcasecmp(d->mdalg, dane_mdalg) == 0
+ || d->dane_id == dane_id) {
+ msg_warn("Duplicate element in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return (0);
+ }
+ }
+
+ if (*dane_mdalg
+ && ((md = tls_digest_byname(dane_mdalg, NULL)) == 0
+ || (mdlen = EVP_MD_size(md)) <= 0
+ || mdlen > EVP_MAX_MD_SIZE)) {
+ msg_warn("Unimplemented digest algorithm in %s: %s%s%s",
+ VAR_TLS_DANE_DIGESTS, mdalg,
+ value ? "=" : "", value ? value : "");
+ return (0);
+ }
+ d = (dane_digest *) mymalloc(sizeof(*d));
+ d->next = digest_list;
+ d->mdalg = mystrdup(dane_mdalg);
+ d->md = md;
+ d->len = mdlen;
+ d->pref = pref;
+ d->dane_id = dane_id;
+
+ return (digest_list = d);
+}
+
+/* digest_byid - locate digest_table entry for given IANA id */
+
+static dane_digest *digest_byid(uint8_t dane_id)
+{
+ dane_digest *d;
+
+ for (d = digest_list; d; d = d->next)
+ if (d->dane_id == dane_id)
+ return (d);
+ return (0);
+}
+
+/* digest_pref_byid - digest preference by IANA id */
+
+static int digest_pref_byid(uint8_t dane_id)
+{
+ dane_digest *d = digest_byid(dane_id);
+
+ return (d ? (d->pref) : (MAXDIGESTS + dane_id));
+}
+
+/* dane_init - initialize DANE parameters */
+
+static void dane_init(void)
+{
+ int digest_pref = 0;
+ char *cp;
+ char *save;
+ char *tok;
+ static char fullmtype[] = "=0";
+ dane_digest *d;
+
+ /*
+ * Add the full matching type at highest preference and then the users
+ * configured list.
+ *
+ * The most preferred digest will be used for hashing full values for
+ * comparison.
+ */
+ if (add_digest(fullmtype, 0)) {
+ save = cp = mystrdup(var_tls_dane_digests);
+ while ((tok = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ if ((d = add_digest(tok, ++digest_pref)) == 0) {
+ signalg = 0;
+ break;
+ }
+ if (digest_pref == 1) {
+ signalg = d->mdalg;
+ }
+ }
+ myfree(save);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ /*
+ * DANE TLSA support requires working DANE digests.
+ */
+ if ((serverAuth = OBJ_nid2obj(NID_server_auth)) == 0) {
+ msg_warn("cannot designate intermediate TA certificates, "
+ "no DANE support");
+ tls_print_errors();
+ dane_tlsa_support = 0;
+ } else if (signalg == 0) {
+ msg_warn("digest algorithm initializaton failed, no DANE support");
+ tls_print_errors();
+ dane_tlsa_support = 0;
+ }
+ dane_initialized = 1;
+}
+
+/* tls_dane_avail - check for availability of dane required digests */
+
+int tls_dane_avail(void)
+{
+ if (!dane_initialized)
+ dane_init();
+ return (dane_tlsa_support);
+}
+
+/* tls_dane_flush - flush the cache */
+
+void tls_dane_flush(void)
+{
+ if (dane_cache)
+ ctable_free(dane_cache);
+ dane_cache = 0;
+}
+
+/* tls_dane_alloc - allocate a TLS_DANE structure */
+
+TLS_DANE *tls_dane_alloc(void)
+{
+ TLS_DANE *dane = (TLS_DANE *) mymalloc(sizeof(*dane));
+
+ dane->ta = 0;
+ dane->ee = 0;
+ dane->certs = 0;
+ dane->pkeys = 0;
+ dane->base_domain = 0;
+ dane->flags = 0;
+ dane->expires = 0;
+ dane->refs = 1;
+ return (dane);
+}
+
+static void ta_cert_insert(TLS_DANE *d, X509 *x)
+{
+ TLS_CERTS *new = (TLS_CERTS *) mymalloc(sizeof(*new));
+
+ X509_up_ref(x);
+ new->cert = x;
+ new->next = d->certs;
+ d->certs = new;
+}
+
+static void free_ta_certs(TLS_DANE *d)
+{
+ TLS_CERTS *head;
+ TLS_CERTS *next;
+
+ for (head = d->certs; head; head = next) {
+ next = head->next;
+ X509_free(head->cert);
+ myfree((void *) head);
+ }
+}
+
+static void ta_pkey_insert(TLS_DANE *d, EVP_PKEY *k)
+{
+ TLS_PKEYS *new = (TLS_PKEYS *) mymalloc(sizeof(*new));
+
+ EVP_PKEY_up_ref(k);
+ new->pkey = k;
+ new->next = d->pkeys;
+ d->pkeys = new;
+}
+
+static void free_ta_pkeys(TLS_DANE *d)
+{
+ TLS_PKEYS *head;
+ TLS_PKEYS *next;
+
+ for (head = d->pkeys; head; head = next) {
+ next = head->next;
+ EVP_PKEY_free(head->pkey);
+ myfree((void *) head);
+ }
+}
+
+static void tlsa_free(TLS_TLSA *tlsa)
+{
+
+ myfree(tlsa->mdalg);
+ if (tlsa->certs)
+ argv_free(tlsa->certs);
+ if (tlsa->pkeys)
+ argv_free(tlsa->pkeys);
+ myfree((void *) tlsa);
+}
+
+/* tls_dane_free - free a TLS_DANE structure */
+
+void tls_dane_free(TLS_DANE *dane)
+{
+ TLS_TLSA *tlsa;
+ TLS_TLSA *next;
+
+ if (--dane->refs > 0)
+ return;
+
+ /* De-allocate TA and EE lists */
+ for (tlsa = dane->ta; tlsa; tlsa = next) {
+ next = tlsa->next;
+ tlsa_free(tlsa);
+ }
+ for (tlsa = dane->ee; tlsa; tlsa = next) {
+ next = tlsa->next;
+ tlsa_free(tlsa);
+ }
+
+ /* De-allocate full trust-anchor certs and pkeys */
+ free_ta_certs(dane);
+ free_ta_pkeys(dane);
+ if (dane->base_domain)
+ myfree(dane->base_domain);
+
+ myfree((void *) dane);
+}
+
+/* dane_free - ctable style */
+
+static void dane_free(void *dane, void *unused_context)
+{
+ tls_dane_free((TLS_DANE *) dane);
+}
+
+/* dane_locate - list head address of TLSA sublist for given algorithm */
+
+static TLS_TLSA **dane_locate(TLS_TLSA **tlsap, const char *mdalg)
+{
+ TLS_TLSA *new;
+
+ /*
+ * Correct computation of the session cache serverid requires a TLSA
+ * digest list that is sorted by algorithm name. Below we maintain the
+ * sort order (by algorithm name canonicalized to lowercase).
+ */
+ for (; *tlsap; tlsap = &(*tlsap)->next) {
+ int cmp = strcasecmp(mdalg, (*tlsap)->mdalg);
+
+ if (cmp == 0)
+ return (tlsap);
+ if (cmp < 0)
+ break;
+ }
+
+ new = (TLS_TLSA *) mymalloc(sizeof(*new));
+ new->mdalg = lowercase(mystrdup(mdalg));
+ new->certs = 0;
+ new->pkeys = 0;
+ new->next = *tlsap;
+ *tlsap = new;
+
+ return (tlsap);
+}
+
+/* tls_dane_add_ee_digests - split and append digests */
+
+void tls_dane_add_ee_digests(TLS_DANE *dane, const char *mdalg,
+ const char *digest, const char *delim)
+{
+ TLS_TLSA **tlsap = dane_locate(&dane->ee, mdalg);
+ TLS_TLSA *tlsa = *tlsap;
+
+ /* Delimited append, may append nothing */
+ if (tlsa->pkeys == 0)
+ tlsa->pkeys = argv_split(digest, delim);
+ else
+ argv_split_append(tlsa->pkeys, digest, delim);
+
+ /* Remove empty elements from the list */
+ if (tlsa->pkeys->argc == 0) {
+ argv_free(tlsa->pkeys);
+ tlsa->pkeys = 0;
+
+ if (tlsa->certs == 0) {
+ *tlsap = tlsa->next;
+ tlsa_free(tlsa);
+ }
+ return;
+ }
+
+ /*
+ * At the "fingerprint" security level certificate digests and public key
+ * digests are interchangeable. Each leaf certificate is matched via
+ * either the public key digest or full certificate digest. The DER
+ * encoding of a certificate is not a valid public key, and conversely,
+ * the DER encoding of a public key is not a valid certificate. An
+ * attacker would need a 2nd-preimage that is feasible across types
+ * (given cert digest == some pkey digest) and yet presumably difficult
+ * within a type (e.g. given cert digest == some other cert digest). No
+ * such attacks are known at this time, and it is expected that if any
+ * are found they would work within as well as across the cert/pkey data
+ * types.
+ */
+ if (tlsa->certs == 0)
+ tlsa->certs = argv_split(digest, delim);
+ else
+ argv_split_append(tlsa->certs, digest, delim);
+}
+
+/* dane_add - add a digest entry */
+
+static void dane_add(TLS_DANE *dane, int certusage, int selector,
+ const char *mdalg, char *digest)
+{
+ TLS_TLSA **tlsap;
+ TLS_TLSA *tlsa;
+ ARGV **argvp;
+
+ switch (certusage) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ certusage = TLS_DANE_TA;
+ break;
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ certusage = TLS_DANE_EE; /* Collapse 1/3 -> 3 */
+ break;
+ default:
+ msg_panic("Unsupported DANE certificate usage: %d", certusage);
+ }
+
+ switch (selector) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ selector = TLS_DANE_CERT;
+ break;
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ selector = TLS_DANE_PKEY;
+ break;
+ default:
+ msg_panic("Unsupported DANE selector: %d", selector);
+ }
+
+ tlsap = (certusage == TLS_DANE_EE) ? &dane->ee : &dane->ta;
+ tlsa = *(tlsap = dane_locate(tlsap, mdalg));
+ argvp = (selector == TLS_DANE_PKEY) ? &tlsa->pkeys : &tlsa->certs;
+
+ if (*argvp == 0)
+ *argvp = argv_alloc(1);
+ argv_add(*argvp, digest, ARGV_END);
+}
+
+#define FILTER_CTX_AGILITY_OK (1<<0)
+#define FILTER_CTX_APPLY_AGILITY (1<<1)
+#define FILTER_CTX_PARSE_DATA (1<<2)
+
+#define FILTER_RR_DROP 0
+#define FILTER_RR_KEEP 1
+
+typedef struct filter_ctx {
+ TLS_DANE *dane; /* Parsed result */
+ int count; /* Digest mtype count */
+ int target; /* Digest mtype target count */
+ int flags; /* Action/result bitmask */
+} filter_ctx;
+
+typedef int (*tlsa_filter) (DNS_RR *, filter_ctx *);
+
+/* tlsa_apply - apply filter to each rr in turn */
+
+static DNS_RR *tlsa_apply(DNS_RR *rr, tlsa_filter filter, filter_ctx *ctx)
+{
+ DNS_RR *head = 0; /* First retained RR */
+ DNS_RR *tail = 0; /* Last retained RR */
+ DNS_RR *next;
+
+ /*
+ * XXX Code that modifies or destroys DNS_RR lists or entries belongs in
+ * the DNS library, not here.
+ */
+ for ( /* nop */ ; rr; rr = next) {
+ next = rr->next;
+
+ if (filter(rr, ctx) == FILTER_RR_KEEP) {
+ tail = rr;
+ if (!head)
+ head = rr;
+ } else {
+ if (tail)
+ tail->next = rr->next;
+ rr->next = 0;
+ dns_rr_free(rr);
+ }
+ }
+ return (head);
+}
+
+/* usmdelta - packed usage/selector/mtype bits changing in next record */
+
+static unsigned int usmdelta(uint8_t u, uint8_t s, uint8_t m, DNS_RR *next)
+{
+ uint8_t *ip = (next && next->data_len >= 3) ? (uint8_t *) next->data : 0;
+ uint8_t nu = ip ? *ip++ : ~u;
+ uint8_t ns = ip ? *ip++ : ~s;
+ uint8_t nm = ip ? *ip++ : ~m;
+
+ return (((u ^ nu) << 16) | ((s ^ ns) << 8) | (m ^ nm));
+}
+
+/* tlsa_rr_cmp - qsort TLSA rrs in case shuffled by name server */
+
+static int tlsa_rr_cmp(DNS_RR *a, DNS_RR *b)
+{
+ int cmp;
+
+ /*
+ * Sort in ascending order, by usage, selector, matching type preference
+ * and payload. The usage, selector and matching type are the first
+ * three unsigned octets of the RR data.
+ */
+ if (a->data_len > 2 && b->data_len > 2) {
+ uint8_t *ai = (uint8_t *) a->data;
+ uint8_t *bi = (uint8_t *) b->data;
+
+#define signedcmp(x, y) (((int)(x)) - ((int)(y)))
+
+ if ((cmp = signedcmp(ai[0], bi[0])) != 0
+ || (cmp = signedcmp(ai[1], bi[1])) != 0
+ || (cmp = digest_pref_byid(ai[2]) -
+ digest_pref_byid(bi[2])) != 0)
+ return (cmp);
+ }
+ if ((cmp = a->data_len - b->data_len) != 0)
+ return (cmp);
+ return (memcmp(a->data, b->data, a->data_len));
+}
+
+/* parse_tlsa_rr - parse a validated TLSA RRset */
+
+static int parse_tlsa_rr(DNS_RR *rr, filter_ctx *ctx)
+{
+ uint8_t *ip;
+ uint8_t usage;
+ uint8_t selector;
+ uint8_t mtype;
+ ssize_t dlen;
+ const unsigned char *data;
+ const unsigned char *p;
+ int iscname = strcasecmp(rr->rname, rr->qname);
+ const char *q = (iscname) ? (rr)->qname : "";
+ const char *a = (iscname) ? " -> " : "";
+ const char *r = rr->rname;
+ unsigned int change;
+
+ if (rr->type != T_TLSA)
+ msg_panic("unexpected non-TLSA RR type %u for %s%s%s", rr->type,
+ q, a, r);
+
+ /* Drop truncated records */
+ if ((dlen = rr->data_len - 3) < 0) {
+ msg_warn("truncated length %u RR: %s%s%s IN TLSA ...",
+ (unsigned) rr->data_len, q, a, r);
+ ctx->flags &= ~FILTER_CTX_AGILITY_OK;
+ return (FILTER_RR_DROP);
+ }
+ ip = (uint8_t *) rr->data;
+ usage = *ip++;
+ selector = *ip++;
+ mtype = *ip++;
+ change = usmdelta(usage, selector, mtype, rr->next);
+ p = data = (const unsigned char *) ip;
+
+ /*
+ * Handle digest agility for non-zero matching types.
+ */
+ if (mtype) {
+ if (ctx->count && (ctx->flags & FILTER_CTX_APPLY_AGILITY)) {
+ if (change & 0xffff00) /* New usage/selector, */
+ ctx->count = 0; /* disable drop */
+ return (FILTER_RR_DROP);
+ }
+ }
+ /*-
+ * Drop unsupported usages.
+ * Note: NO SUPPORT for usages 0/1 which do not apply to SMTP.
+ */
+ switch (usage) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ break;
+ default:
+ msg_warn("unsupported certificate usage %u in RR: "
+ "%s%s%s IN TLSA %u ...", usage,
+ q, a, r, usage);
+ return (FILTER_RR_DROP);
+ }
+
+ /*
+ * Drop unsupported selectors
+ */
+ switch (selector) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ break;
+ default:
+ msg_warn("unsupported selector %u in RR: "
+ "%s%s%s IN TLSA %u %u ...", selector,
+ q, a, r, usage, selector);
+ return (FILTER_RR_DROP);
+ }
+
+ if (mtype) {
+ dane_digest *d = digest_byid(mtype);
+
+ if (d == 0) {
+ msg_warn("unsupported matching type %u in RR: "
+ "%s%s%s IN TLSA %u %u %u ...", mtype,
+ q, a, r, usage, selector, mtype);
+ return (FILTER_RR_DROP);
+ }
+ if (dlen != d->len) {
+ msg_warn("malformed %s digest, length %lu, in RR: "
+ "%s%s%s IN TLSA %u %u %u ...",
+ d->mdalg, (unsigned long) dlen,
+ q, a, r, usage, selector, mtype);
+ ctx->flags &= ~FILTER_CTX_AGILITY_OK;
+ return (FILTER_RR_DROP);
+ }
+ /* New digest mtype next? Prepare to drop following RRs */
+ if (change && (change & 0xffff00) == 0
+ && (ctx->flags & FILTER_CTX_APPLY_AGILITY))
+ ++ctx->count;
+
+ if (ctx->flags & FILTER_CTX_PARSE_DATA) {
+ char *digest = tls_digest_encode(data, dlen);
+
+ dane_add(ctx->dane, usage, selector, d->mdalg, digest);
+ if (msg_verbose || dane_verbose)
+ msg_info("using DANE RR: %s%s%s IN TLSA %u %u %u %s",
+ q, a, r, usage, selector, mtype, digest);
+ myfree(digest);
+ }
+ } else {
+ X509 *x = 0; /* OpenSSL re-uses *x if x!=0 */
+ EVP_PKEY *k = 0; /* OpenSSL re-uses *k if k!=0 */
+
+ /* Validate the cert or public key via d2i_mumble() */
+ switch (selector) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ if (!d2i_X509(&x, &p, dlen) || dlen != p - data) {
+ msg_warn("malformed %s in RR: "
+ "%s%s%s IN TLSA %u %u %u ...", "certificate",
+ q, a, r, usage, selector, mtype);
+ if (x)
+ X509_free(x);
+ return (FILTER_RR_DROP);
+ }
+ /* Also unusable if public key is malformed or unsupported */
+ k = X509_get_pubkey(x);
+ EVP_PKEY_free(k);
+ if (k == 0) {
+ msg_warn("malformed %s in RR: %s%s%s IN TLSA %u %u %u ...",
+ "or unsupported certificate public key",
+ q, a, r, usage, selector, mtype);
+ X509_free(x);
+ return (FILTER_RR_DROP);
+ }
+
+ /*
+ * When a full trust-anchor certificate is published via DNS, we
+ * may need to use it to validate the server trust chain. Store
+ * it away for later use.
+ */
+ if (usage == DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION
+ && (ctx->flags & FILTER_CTX_PARSE_DATA))
+ ta_cert_insert(ctx->dane, x);
+ X509_free(x);
+ break;
+
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ if (!d2i_PUBKEY(&k, &p, dlen) || dlen != p - data) {
+ msg_warn("malformed %s in RR: %s%s%s IN TLSA %u %u %u ...",
+ "public key", q, a, r, usage, selector, mtype);
+ if (k)
+ EVP_PKEY_free(k);
+ return (FILTER_RR_DROP);
+ }
+
+ /*
+ * When a full trust-anchor public key is published via DNS, we
+ * may need to use it to validate the server trust chain. Store
+ * it away for later use.
+ */
+ if (usage == DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION
+ && (ctx->flags & FILTER_CTX_PARSE_DATA))
+ ta_pkey_insert(ctx->dane, k);
+ EVP_PKEY_free(k);
+ break;
+ }
+
+ /*
+ * The cert or key was valid, just digest the raw object, and encode
+ * the digest value.
+ */
+ if (ctx->flags & FILTER_CTX_PARSE_DATA) {
+ char *digest = tls_data_fprint((char *) data, dlen, signalg);
+
+ dane_add(ctx->dane, usage, selector, signalg, digest);
+ if (msg_verbose || dane_verbose)
+ msg_info("using DANE RR: %s%s%s IN TLSA %u %u %u <%s>; "
+ "%s digest %s", q, a, r, usage, selector, mtype,
+ (selector == DNS_TLSA_SELECTOR_FULL_CERTIFICATE) ?
+ "certificate" : "public key", signalg, digest);
+ myfree(digest);
+ }
+ }
+ return (FILTER_RR_KEEP);
+}
+
+/* process_rrs - filter and parse the TLSA RRset */
+
+static DNS_RR *process_rrs(TLS_DANE *dane, DNS_RR *rrset)
+{
+ filter_ctx ctx;
+
+ ctx.dane = dane;
+ ctx.count = ctx.target = 0;
+ ctx.flags = FILTER_CTX_APPLY_AGILITY | FILTER_CTX_PARSE_DATA;
+
+ rrset = tlsa_apply(rrset, parse_tlsa_rr, &ctx);
+
+ if (dane->ta == 0 && dane->ee == 0)
+ dane->flags |= TLS_DANE_FLAG_EMPTY;
+
+ return (rrset);
+}
+
+/* dane_lookup - TLSA record lookup, ctable style */
+
+static void *dane_lookup(const char *tlsa_fqdn, void *unused_ctx)
+{
+ static VSTRING *why = 0;
+ int ret;
+ DNS_RR *rrs = 0;
+ TLS_DANE *dane;
+
+ if (why == 0)
+ why = vstring_alloc(10);
+
+ dane = tls_dane_alloc();
+ ret = dns_lookup(tlsa_fqdn, T_TLSA, RES_USE_DNSSEC, &rrs, 0, why);
+
+ switch (ret) {
+ case DNS_OK:
+ if (TLS_DANE_CACHE_TTL_MIN && rrs->ttl < TLS_DANE_CACHE_TTL_MIN)
+ rrs->ttl = TLS_DANE_CACHE_TTL_MIN;
+ if (TLS_DANE_CACHE_TTL_MAX && rrs->ttl > TLS_DANE_CACHE_TTL_MAX)
+ rrs->ttl = TLS_DANE_CACHE_TTL_MAX;
+
+ /* One more second to account for discrete time */
+ dane->expires = 1 + event_time() + rrs->ttl;
+
+ if (rrs->dnssec_valid) {
+
+ /*
+ * Sort for deterministic digest in session cache lookup key. In
+ * addition we must arrange for more preferred matching types
+ * (full value or digest) to precede less preferred ones for the
+ * same usage and selector.
+ */
+ rrs = dns_rr_sort(rrs, tlsa_rr_cmp);
+ rrs = process_rrs(dane, rrs);
+ } else
+ dane->flags |= TLS_DANE_FLAG_NORRS;
+
+ if (rrs)
+ dns_rr_free(rrs);
+ break;
+
+ case DNS_NOTFOUND:
+ dane->flags |= TLS_DANE_FLAG_NORRS;
+ dane->expires = 1 + event_time() + TLS_DANE_CACHE_TTL_MIN;
+ break;
+
+ default:
+ msg_warn("DANE TLSA lookup problem: %s", STR(why));
+ dane->flags |= TLS_DANE_FLAG_ERROR;
+ break;
+ }
+
+ return (void *) dane;
+}
+
+/* resolve_host - resolve TLSA RRs for hostname (rname or qname) */
+
+static TLS_DANE *resolve_host(const char *host, const char *proto,
+ unsigned port)
+{
+ static VSTRING *query_domain;
+ TLS_DANE *dane;
+
+ if (query_domain == 0)
+ query_domain = vstring_alloc(64);
+
+ vstring_sprintf(query_domain, "_%u._%s.%s", ntohs(port), proto, host);
+ dane = (TLS_DANE *) ctable_locate(dane_cache, STR(query_domain));
+ if (timecmp(event_time(), dane->expires) > 0)
+ dane = (TLS_DANE *) ctable_refresh(dane_cache, STR(query_domain));
+ if (dane->base_domain == 0)
+ dane->base_domain = mystrdup(host);
+ /* Increment ref-count of cached entry */
+ ++dane->refs;
+ return (dane);
+}
+
+/* qname_secure - Lookup qname DNSSEC status */
+
+static int qname_secure(const char *qname)
+{
+ static VSTRING *why;
+ int ret = 0;
+ DNS_RR *rrs;
+
+ if (!why)
+ why = vstring_alloc(10);
+
+ /*
+ * We assume that qname is already an fqdn, and does not need any
+ * suffixes from RES_DEFNAME or RES_DNSRCH. This is typically the name
+ * of an MX host, and must be a complete DNS name. DANE initialization
+ * code in the SMTP client is responsible for checking that the default
+ * resolver flags do not include RES_DEFNAME and RES_DNSRCH.
+ */
+ ret = dns_lookup(qname, T_CNAME, RES_USE_DNSSEC, &rrs, 0, why);
+ if (ret == DNS_OK) {
+ ret = rrs->dnssec_valid;
+ dns_rr_free(rrs);
+ return (ret);
+ }
+ if (ret == DNS_NOTFOUND)
+ vstring_sprintf(why, "no longer a CNAME");
+ msg_warn("DNSSEC status lookup error for %s: %s", qname, STR(why));
+ return (-1);
+}
+
+/* tls_dane_resolve - cached map: (name, proto, port) -> TLS_DANE */
+
+TLS_DANE *tls_dane_resolve(unsigned port, const char *proto, DNS_RR *hostrr,
+ int forcetlsa)
+{
+ TLS_DANE *dane = 0;
+ int iscname = strcasecmp(hostrr->rname, hostrr->qname);
+ int isvalid = 1;
+
+ if (!tls_dane_avail())
+ return (0); /* Error */
+
+ /*
+ * By default suppress TLSA lookups for hosts in non-DNSSEC zones. If
+ * the host zone is not DNSSEC validated, the TLSA qname sub-domain is
+ * safely assumed to not be in a DNSSEC Look-aside Validation child zone.
+ */
+ if (!forcetlsa && !hostrr->dnssec_valid) {
+ isvalid = iscname ? qname_secure(hostrr->qname) : 0;
+ if (isvalid < 0)
+ return (0); /* Error */
+ }
+ if (!isvalid) {
+ dane = tls_dane_alloc();
+ dane->flags = TLS_DANE_FLAG_NORRS;
+ } else {
+ if (!dane_cache)
+ dane_cache = ctable_create(CACHE_SIZE, dane_lookup, dane_free, 0);
+
+ /*
+ * Try the rname first if secure, if nothing there, try the qname if
+ * different. Note, lookup errors are distinct from success with
+ * nothing found. If the rname lookup fails we don't try the qname.
+ */
+ if (hostrr->dnssec_valid) {
+ dane = resolve_host(hostrr->rname, proto, port);
+ if (tls_dane_notfound(dane) && iscname) {
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+ if (!dane)
+ dane = resolve_host(hostrr->qname, proto, port);
+ if (dane->flags & TLS_DANE_FLAG_ERROR) {
+ /* We don't return this object. */
+ tls_dane_free(dane);
+ dane = 0;
+ }
+ }
+
+ return (dane);
+}
+
+/* tls_dane_load_trustfile - load trust anchor certs or keys from file */
+
+int tls_dane_load_trustfile(TLS_DANE *dane, const char *tafile)
+{
+ BIO *bp;
+ char *name = 0;
+ char *header = 0;
+ unsigned char *data = 0;
+ long len;
+ int tacount;
+ char *errtype = 0; /* if error: cert or pkey? */
+ const char *mdalg;
+
+ /* nop */
+ if (tafile == 0 || *tafile == 0)
+ return (1);
+
+ if (!dane_initialized)
+ dane_init();
+
+ /* Per-destination TA support is available even when DANE is not */
+ mdalg = signalg ? signalg : "sha1";
+
+ /*
+ * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio,
+ * calls PEM_read_bio() and then frees the bio. It is just as easy to
+ * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio()
+ * directly.
+ */
+ if ((bp = BIO_new_file(tafile, "r")) == NULL) {
+ msg_warn("error opening trust anchor file: %s: %m", tafile);
+ return (0);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ for (tacount = 0;
+ errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len);
+ ++tacount) {
+ const unsigned char *p = data;
+ int usage = DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION;
+ int selector;
+ char *digest;
+
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ X509 *cert = d2i_X509(0, &p, len);
+
+ if (cert && (p - data) == len) {
+ selector = DNS_TLSA_SELECTOR_FULL_CERTIFICATE;
+ digest = tls_data_fprint((char *) data, len, mdalg);
+ dane_add(dane, usage, selector, mdalg, digest);
+ myfree(digest);
+ ta_cert_insert(dane, cert);
+ } else
+ errtype = "certificate";
+ if (cert)
+ X509_free(cert);
+ } else if (strcmp(name, PEM_STRING_PUBLIC) == 0) {
+ EVP_PKEY *pkey = d2i_PUBKEY(0, &p, len);
+
+ if (pkey && (p - data) == len) {
+ selector = DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO;
+ digest = tls_data_fprint((char *) data, len, mdalg);
+ dane_add(dane, usage, selector, mdalg, digest);
+ myfree(digest);
+ ta_pkey_insert(dane, pkey);
+ } else
+ errtype = "public key";
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ }
+
+ /*
+ * If any of these were null, PEM_read() would have failed.
+ */
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ }
+ BIO_free(bp);
+
+ if (errtype) {
+ tls_print_errors();
+ msg_warn("error reading: %s: malformed trust-anchor %s",
+ tafile, errtype);
+ return (0);
+ }
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ /* Reached end of PEM file */
+ ERR_clear_error();
+ return (tacount > 0);
+ }
+ /* Some other PEM read error */
+ tls_print_errors();
+ return (0);
+}
+
+/* tls_dane_match - match cert against given list of TA or EE digests */
+
+int tls_dane_match(TLS_SESS_STATE *TLScontext, int usage,
+ X509 *cert, int depth)
+{
+ const TLS_DANE *dane = TLScontext->dane;
+ TLS_TLSA *tlsa = (usage == TLS_DANE_EE) ? dane->ee : dane->ta;
+ const char *namaddr = TLScontext->namaddr;
+ const char *ustr = (usage == TLS_DANE_EE) ? "end entity" : "trust anchor";
+ int matched;
+
+ for (matched = 0; tlsa && !matched; tlsa = tlsa->next) {
+ char **dgst;
+
+ /*
+ * Note, set_trust() needs to know whether the match was for a pkey
+ * digest or a certificate digest. We return MATCHED_PKEY or
+ * MATCHED_CERT accordingly.
+ */
+#define MATCHED_CERT 1
+#define MATCHED_PKEY 2
+
+ if (tlsa->pkeys) {
+ char *pkey_dgst = tls_pkey_fprint(cert, tlsa->mdalg);
+
+ for (dgst = tlsa->pkeys->argv; !matched && *dgst; ++dgst)
+ if (strcasecmp(pkey_dgst, *dgst) == 0)
+ matched = MATCHED_PKEY;
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH)
+ && matched)
+ msg_info("%s: depth=%d matched %s public-key %s digest=%s",
+ namaddr, depth, ustr, tlsa->mdalg, pkey_dgst);
+ myfree(pkey_dgst);
+ }
+ if (tlsa->certs != 0 && !matched) {
+ char *cert_dgst = tls_cert_fprint(cert, tlsa->mdalg);
+
+ for (dgst = tlsa->certs->argv; !matched && *dgst; ++dgst)
+ if (strcasecmp(cert_dgst, *dgst) == 0)
+ matched = MATCHED_CERT;
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH)
+ && matched)
+ msg_info("%s: depth=%d matched %s certificate %s digest %s",
+ namaddr, depth, ustr, tlsa->mdalg, cert_dgst);
+ myfree(cert_dgst);
+ }
+ }
+
+ return (matched);
+}
+
+/* add_ext - add simple extension (no config section references) */
+
+static int add_ext(X509 *issuer, X509 *subject, int ext_nid, char *ext_val)
+{
+ int ret = 0;
+ X509V3_CTX v3ctx;
+ X509_EXTENSION *ext;
+
+ X509V3_set_ctx(&v3ctx, issuer, subject, 0, 0, 0);
+ if ((ext = X509V3_EXT_conf_nid(0, &v3ctx, ext_nid, ext_val)) != 0) {
+ ret = X509_add_ext(subject, ext, -1);
+ X509_EXTENSION_free(ext);
+ }
+ return ret;
+}
+
+/* set_serial - set serial number to match akid or use subject's plus 1 */
+
+static int set_serial(X509 *cert, AUTHORITY_KEYID *akid, X509 *subject)
+{
+ int ret = 0;
+ BIGNUM *bn;
+
+ if (akid && akid->serial)
+ return (X509_set_serialNumber(cert, akid->serial));
+
+ /*
+ * Add one to subject's serial to avoid collisions between TA serial and
+ * serial of signing root.
+ */
+ if ((bn = ASN1_INTEGER_to_BN(X509_get_serialNumber(subject), 0)) != 0
+ && BN_add_word(bn, 1)
+ && BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(cert)))
+ ret = 1;
+
+ if (bn)
+ BN_free(bn);
+ return (ret);
+}
+
+/* add_akid - add authority key identifier */
+
+static int add_akid(X509 *cert, AUTHORITY_KEYID *akid)
+{
+ ASN1_OCTET_STRING *id;
+ unsigned char c = 0;
+ int nid = NID_authority_key_identifier;
+ int ret = 0;
+
+ /*
+ * 0 will never be our subject keyid from a SHA-1 hash, but it could be
+ * our subject keyid if forced from child's akid. If so, set our
+ * authority keyid to 1. This way we are never self-signed, and thus
+ * exempt from any potential (off by default for now in OpenSSL)
+ * self-signature checks!
+ */
+ id = ((akid && akid->keyid) ? akid->keyid : 0);
+ if (id && ASN1_STRING_length(id) == 1 && *ASN1_STRING_get0_data(id) == c)
+ c = 1;
+
+ if ((akid = AUTHORITY_KEYID_new()) != 0
+ && (akid->keyid = ASN1_OCTET_STRING_new()) != 0
+ && ASN1_OCTET_STRING_set(akid->keyid, (void *) &c, 1)
+ && X509_add1_ext_i2d(cert, nid, akid, 0, X509V3_ADD_DEFAULT) > 0)
+ ret = 1;
+ if (akid)
+ AUTHORITY_KEYID_free(akid);
+ return (ret);
+}
+
+/* add_skid - add subject key identifier to match child's akid */
+
+static int add_skid(X509 *cert, AUTHORITY_KEYID *akid)
+{
+ int nid = NID_subject_key_identifier;
+
+ if (!akid || !akid->keyid)
+ return (add_ext(0, cert, nid, "hash"));
+ else
+ return (X509_add1_ext_i2d(cert, nid, akid->keyid, 0,
+ X509V3_ADD_DEFAULT) > 0);
+}
+
+/* akid_issuer_name - get akid issuer directory name */
+
+static X509_NAME *akid_issuer_name(AUTHORITY_KEYID *akid)
+{
+ if (akid && akid->issuer) {
+ int i;
+ general_name_stack_t *gens = akid->issuer;
+
+ for (i = 0; i < sk_GENERAL_NAME_num(gens); ++i) {
+ GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
+
+ if (gn->type == GEN_DIRNAME)
+ return (gn->d.dirn);
+ }
+ }
+ return (0);
+}
+
+/* set_issuer - set issuer DN to match akid if specified */
+
+static int set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid, X509_NAME *subj)
+{
+ X509_NAME *name = akid_issuer_name(akid);
+
+ /*
+ * If subject's akid specifies an authority key identifier issuer name,
+ * we must use that.
+ */
+ if (name)
+ return (X509_set_issuer_name(cert, name));
+ return (X509_set_issuer_name(cert, subj));
+}
+
+/* grow_chain - add certificate to trusted or untrusted chain */
+
+static void grow_chain(TLS_SESS_STATE *TLScontext, int trusted, X509 *cert)
+{
+ x509_stack_t **xs = trusted ? &TLScontext->trusted : &TLScontext->untrusted;
+
+#define UNTRUSTED 0
+#define TRUSTED 1
+
+ if (!*xs && (*xs = sk_X509_new_null()) == 0)
+ msg_fatal("out of memory");
+ if (cert) {
+ if (trusted && !X509_add1_trust_object(cert, serverAuth))
+ msg_fatal("out of memory");
+ X509_up_ref(cert);
+ if (!sk_X509_push(*xs, cert))
+ msg_fatal("out of memory");
+ }
+}
+
+/* wrap_key - wrap TA "key" as issuer of "subject" */
+
+static void wrap_key(TLS_SESS_STATE *TLScontext, int depth,
+ EVP_PKEY *key, X509 *subject)
+{
+ X509 *cert = 0;
+ AUTHORITY_KEYID *akid;
+ X509_NAME *name = X509_get_issuer_name(subject);
+
+ /*
+ * The subject name is never a NULL object unless we run out of memory.
+ * It may be an empty sequence, but the containing object always exists
+ * and its storage is owned by the certificate itself.
+ */
+ if (name == 0 || (cert = X509_new()) == 0)
+ msg_fatal("Out of memory");
+
+ /*
+ * Record the depth of the intermediate wrapper certificate, logged in
+ * the verify callback.
+ */
+ if (TLScontext->tadepth < 0) {
+ TLScontext->tadepth = depth + 1;
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH))
+ msg_info("%s: depth=%d chain is trust-anchor signed",
+ TLScontext->namaddr, depth);
+ }
+ akid = X509_get_ext_d2i(subject, NID_authority_key_identifier, 0, 0);
+
+ ERR_clear_error();
+
+ /* CA cert valid for +/- 30 days. */
+ if (!X509_set_version(cert, 2)
+ || !set_serial(cert, akid, subject)
+ || !set_issuer_name(cert, akid, name)
+ || !X509_gmtime_adj(X509_getm_notBefore(cert), -30 * 86400L)
+ || !X509_gmtime_adj(X509_getm_notAfter(cert), 30 * 86400L)
+ || !X509_set_subject_name(cert, name)
+ || !X509_set_pubkey(cert, key)
+ || !add_ext(0, cert, NID_basic_constraints, "CA:TRUE")
+ || (key && !add_akid(cert, akid))
+ || !add_skid(cert, akid)) {
+ tls_print_errors();
+ msg_fatal("error generating DANE wrapper certificate");
+ }
+ if (akid)
+ AUTHORITY_KEYID_free(akid);
+ grow_chain(TLScontext, TRUSTED, cert);
+ if (cert)
+ X509_free(cert);
+}
+
+/* wrap_cert - wrap "tacert" as trust-anchor. */
+
+static void wrap_cert(TLS_SESS_STATE *TLScontext, X509 *tacert, int depth)
+{
+ if (TLScontext->tadepth < 0)
+ TLScontext->tadepth = depth + 1;
+
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH))
+ msg_info("%s: depth=%d trust-anchor certificate",
+ TLScontext->namaddr, depth);
+
+ grow_chain(TLScontext, TRUSTED, tacert);
+ return;
+}
+
+/* ta_signed - is certificate signed by a TLSA cert or pkey */
+
+static int ta_signed(TLS_SESS_STATE *TLScontext, X509 *cert, int depth)
+{
+ const TLS_DANE *dane = TLScontext->dane;
+ EVP_PKEY *pk;
+ TLS_PKEYS *k;
+ TLS_CERTS *x;
+ int done = 0;
+
+ /*
+ * First check whether issued and signed by a TA cert, this is cheaper
+ * than the bare-public key checks below, since we can determine whether
+ * the candidate TA certificate issued the certificate to be checked
+ * first (name comparisons), before we bother with signature checks
+ * (public key operations).
+ */
+ for (x = dane->certs; !done && x; x = x->next) {
+ if (X509_check_issued(x->cert, cert) == X509_V_OK) {
+ if ((pk = X509_get_pubkey(x->cert)) == 0)
+ continue;
+ /* Check signature, since some other TA may work if not this. */
+ if ((done = (X509_verify(cert, pk) > 0)) != 0)
+ wrap_cert(TLScontext, x->cert, depth);
+ EVP_PKEY_free(pk);
+ }
+ }
+
+ /*
+ * With bare TA public keys, we can't check whether the trust chain is
+ * issued by the key, but we can determine whether it is signed by the
+ * key, so we go with that.
+ *
+ * Ideally, the corresponding certificate was presented in the chain, and we
+ * matched it by its public key digest one level up. This code is here
+ * to handle adverse conditions imposed by sloppy administrators of
+ * receiving systems with poorly constructed chains.
+ *
+ * We'd like to optimize out keys that should not match when the cert's
+ * authority key id does not match the key id of this key computed via
+ * the RFC keyid algorithm (SHA-1 digest of public key bit-string sans
+ * ASN1 tag and length thus also excluding the unused bits field that is
+ * logically part of the length). However, some CAs have a non-standard
+ * authority keyid, so we lose. Too bad.
+ *
+ * This may push errors onto the stack when the certificate signature is not
+ * of the right type or length, throw these away.
+ */
+ for (k = dane->pkeys; !done && k; k = k->next)
+ if ((done = (X509_verify(cert, k->pkey) > 0)) != 0)
+ wrap_key(TLScontext, depth, k->pkey, cert);
+ else
+ ERR_clear_error();
+
+ return (done);
+}
+
+/* set_trust - configure for DANE validation */
+
+static void set_trust(TLS_SESS_STATE *TLScontext, X509_STORE_CTX *ctx)
+{
+ int n;
+ int i;
+ int match;
+ int depth = 0;
+ EVP_PKEY *takey;
+ X509 *ca;
+ X509 *cert = X509_STORE_CTX_get0_cert(ctx);
+ x509_stack_t *in = X509_STORE_CTX_get0_untrusted(ctx);
+
+ /* shallow copy */
+ if ((in = sk_X509_dup(in)) == 0)
+ msg_fatal("out of memory");
+
+ /*
+ * At each iteration we consume the issuer of the current cert. This
+ * reduces the length of the "in" chain by one. If no issuer is found,
+ * we are done. We also stop when a certificate matches a TA in the
+ * peer's TLSA RRset.
+ *
+ * Caller ensures that the initial certificate is not self-signed.
+ */
+ for (n = sk_X509_num(in); n > 0; --n, ++depth) {
+ for (i = 0; i < n; ++i)
+ if (X509_check_issued(sk_X509_value(in, i), cert) == X509_V_OK)
+ break;
+
+ /*
+ * Final untrusted element with no issuer in the peer's chain, it may
+ * however be signed by a pkey or cert obtained via a TLSA RR.
+ */
+ if (i == n)
+ break;
+
+ /* Peer's chain contains an issuer ca. */
+ ca = sk_X509_delete(in, i);
+
+ /* Is it a trust anchor? */
+ match = tls_dane_match(TLScontext, TLS_DANE_TA, ca, depth + 1);
+ if (match) {
+ switch (match) {
+ case MATCHED_CERT:
+ wrap_cert(TLScontext, ca, depth);
+ break;
+ case MATCHED_PKEY:
+ if ((takey = X509_get_pubkey(ca)) == 0)
+ msg_panic("trust-anchor certificate has null pkey");
+ wrap_key(TLScontext, depth, takey, cert);
+ EVP_PKEY_free(takey);
+ break;
+ default:
+ msg_panic("unexpected tls_dane_match result: %d", match);
+ }
+ cert = 0;
+ break;
+ }
+ /* Add untrusted ca. */
+ grow_chain(TLScontext, UNTRUSTED, ca);
+
+ /* Final untrusted self-signed element? */
+ if (X509_check_issued(ca, ca) == X509_V_OK) {
+ cert = 0;
+ break;
+ }
+ /* Restart with issuer as subject */
+ cert = ca;
+ }
+
+ /*
+ * When the loop exits, if "cert" is set, it is not self-signed and has
+ * no issuer in the chain, we check for a possible signature via a DNS
+ * obtained TA cert or public key. Otherwise, we found no TAs and no
+ * issuer, so set an empty list of TAs.
+ */
+ if (!cert || !ta_signed(TLScontext, cert, depth)) {
+ /* Create empty trust list if null, else NOP */
+ grow_chain(TLScontext, TRUSTED, 0);
+ }
+ /* shallow free */
+ if (in)
+ sk_X509_free(in);
+}
+
+/* dane_cb - wrap chain verification for DANE */
+
+static int dane_cb(X509_STORE_CTX *ctx, void *app_ctx)
+{
+ const char *myname = "dane_cb";
+ TLS_SESS_STATE *TLScontext = (TLS_SESS_STATE *) app_ctx;
+ X509 *cert = X509_STORE_CTX_get0_cert(ctx);
+
+ /*
+ * Degenerate case: depth 0 self-signed cert.
+ *
+ * XXX: Should we suppress name checks, ... when the leaf certificate is a
+ * TA. After all they could sign any name they want. However, this
+ * requires a bit of additional code. For now we allow depth 0 TAs, but
+ * then the peer name has to match.
+ */
+ if (X509_check_issued(cert, cert) == X509_V_OK) {
+
+ /*
+ * Empty untrusted chain, could be NULL, but then ABI check less
+ * reliable, we may zero some other field, ...
+ */
+ grow_chain(TLScontext, UNTRUSTED, 0);
+ if (tls_dane_match(TLScontext, TLS_DANE_TA, cert, 0)) {
+ TLScontext->tadepth = 0;
+ grow_chain(TLScontext, TRUSTED, cert);
+ } else
+ grow_chain(TLScontext, TRUSTED, 0);
+ } else {
+ set_trust(TLScontext, ctx);
+ }
+
+ /*
+ * Check that setting the untrusted chain updates the expected structure
+ * member at the expected offset.
+ */
+ X509_STORE_CTX_set0_trusted_stack(ctx, TLScontext->trusted);
+ X509_STORE_CTX_set0_untrusted(ctx, TLScontext->untrusted);
+ if (X509_STORE_CTX_get0_untrusted(ctx) != TLScontext->untrusted)
+ msg_panic("%s: OpenSSL ABI change", myname);
+
+ return X509_verify_cert(ctx);
+}
+
+/* tls_dane_set_callback - set or clear verification wrapper callback */
+
+void tls_dane_set_callback(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
+{
+ if (TLS_DANE_HASTA(TLScontext->dane))
+ SSL_CTX_set_cert_verify_callback(ctx, dane_cb, (void *) TLScontext);
+ else
+ SSL_CTX_set_cert_verify_callback(ctx, 0, 0);
+}
+
+#ifdef TEST
+
+#include <unistd.h>
+#include <stdarg.h>
+
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <msg_vstream.h>
+
+static int verify_chain(SSL *ssl, x509_stack_t *chain, TLS_SESS_STATE *tctx)
+{
+ int ret;
+ X509 *cert;
+ X509_STORE_CTX *store_ctx;
+ SSL_CTX *ssl_ctx = SSL_get_SSL_CTX(ssl);
+ X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
+ int store_ctx_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+
+ cert = sk_X509_value(chain, 0);
+ if ((store_ctx = X509_STORE_CTX_new()) == NULL) {
+ SSLerr(SSL_F_SSL_VERIFY_CERT_CHAIN, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+ if (!X509_STORE_CTX_init(store_ctx, store, cert, chain)) {
+ X509_STORE_CTX_free(store_ctx);
+ return 0;
+ }
+ X509_STORE_CTX_set_ex_data(store_ctx, store_ctx_idx, ssl);
+
+ X509_STORE_CTX_set_default(store_ctx, "ssl_server");
+ X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(store_ctx),
+ SSL_get0_param(ssl));
+
+ if (SSL_get_verify_callback(ssl))
+ X509_STORE_CTX_set_verify_cb(store_ctx, SSL_get_verify_callback(ssl));
+
+ ret = dane_cb(store_ctx, tctx);
+
+ SSL_set_verify_result(ssl, X509_STORE_CTX_get_error(store_ctx));
+ X509_STORE_CTX_free(store_ctx);
+
+ return (ret);
+}
+
+static void add_tlsa(TLS_DANE *dane, char *argv[])
+{
+ char *digest;
+ X509 *cert = 0;
+ BIO *bp;
+ unsigned char *buf;
+ unsigned char *buf2;
+ int len;
+ uint8_t u = atoi(argv[1]);
+ uint8_t s = atoi(argv[2]);
+ const char *mdname = argv[3];
+ EVP_PKEY *pkey;
+
+ /* Unsupported usages are fatal */
+ switch (u) {
+ case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
+ case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
+ break;
+ default:
+ msg_fatal("unsupported certificate usage %u", u);
+ }
+
+ /* Unsupported selectors are fatal */
+ switch (s) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ break;
+ default:
+ msg_fatal("unsupported selector %u", s);
+ }
+
+ /* Unsupported digests are fatal */
+ if (*mdname && !tls_validate_digest(mdname))
+ msg_fatal("unsupported digest algorithm: %s", mdname);
+
+ if ((bp = BIO_new_file(argv[4], "r")) == NULL)
+ msg_fatal("error opening %s: %m", argv[4]);
+ if (!PEM_read_bio_X509(bp, &cert, 0, 0)) {
+ tls_print_errors();
+ msg_fatal("error loading certificate from %s: %m", argv[4]);
+ }
+ BIO_free(bp);
+
+ /*
+ * Extract ASN.1 DER form of certificate or public key.
+ */
+ switch (s) {
+ case DNS_TLSA_SELECTOR_FULL_CERTIFICATE:
+ len = i2d_X509(cert, NULL);
+ buf2 = buf = (unsigned char *) mymalloc(len);
+ i2d_X509(cert, &buf2);
+ if (!*mdname)
+ ta_cert_insert(dane, cert);
+ break;
+ case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO:
+ pkey = X509_get_pubkey(cert);
+ len = i2d_PUBKEY(pkey, NULL);
+ buf2 = buf = (unsigned char *) mymalloc(len);
+ i2d_PUBKEY(pkey, &buf2);
+ if (!*mdname)
+ ta_pkey_insert(dane, pkey);
+ EVP_PKEY_free(pkey);
+ break;
+ }
+ OPENSSL_assert(buf2 - buf == len);
+
+ digest = tls_data_fprint((char *) buf, len, *mdname ? mdname : signalg);
+ dane_add(dane, u, s, *mdname ? mdname : signalg, digest);
+ myfree((void *) digest);
+ myfree((void *) buf);
+}
+
+static x509_stack_t *load_chain(const char *chainfile)
+{
+ BIO *bp;
+ char *name = 0;
+ char *header = 0;
+ unsigned char *data = 0;
+ long len;
+ int count;
+ char *errtype = 0; /* if error: cert or pkey? */
+ x509_stack_t *chain;
+ typedef X509 *(*d2i_X509_t) (X509 **, const unsigned char **, long);
+
+ if ((chain = sk_X509_new_null()) == 0) {
+ perror("malloc");
+ exit(1);
+ }
+
+ /*
+ * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio,
+ * calls PEM_read_bio() and then frees the bio. It is just as easy to
+ * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio()
+ * directly.
+ */
+ if ((bp = BIO_new_file(chainfile, "r")) == NULL) {
+ fprintf(stderr, "error opening chainfile: %s: %m\n", chainfile);
+ exit(1);
+ }
+ /* Don't report old news */
+ ERR_clear_error();
+
+ for (count = 0;
+ errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len);
+ ++count) {
+ const unsigned char *p = data;
+
+ if (strcmp(name, PEM_STRING_X509) == 0
+ || strcmp(name, PEM_STRING_X509_TRUSTED) == 0
+ || strcmp(name, PEM_STRING_X509_OLD) == 0) {
+ d2i_X509_t d;
+ X509 *cert;
+
+ d = strcmp(name, PEM_STRING_X509_TRUSTED) ? d2i_X509_AUX : d2i_X509;
+ if ((cert = d(0, &p, len)) == 0 || (p - data) != len)
+ errtype = "certificate";
+ else if (sk_X509_push(chain, cert) == 0) {
+ perror("malloc");
+ exit(1);
+ }
+ } else {
+ fprintf(stderr, "unexpected chain file object: %s\n", name);
+ exit(1);
+ }
+
+ /*
+ * If any of these were null, PEM_read() would have failed.
+ */
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ }
+ BIO_free(bp);
+
+ if (errtype) {
+ tls_print_errors();
+ fprintf(stderr, "error reading: %s: malformed %s", chainfile, errtype);
+ exit(1);
+ }
+ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+ /* Reached end of PEM file */
+ ERR_clear_error();
+ if (count > 0)
+ return chain;
+ fprintf(stderr, "no certificates found in: %s\n", chainfile);
+ exit(1);
+ }
+ /* Some other PEM read error */
+ tls_print_errors();
+ fprintf(stderr, "error reading: %s\n", chainfile);
+ exit(1);
+}
+
+static void usage(const char *progname)
+{
+ fprintf(stderr, "Usage: %s certificate-usage selector matching-type"
+ " certfile \\\n\t\tCAfile chainfile hostname [certname ...]\n",
+ progname);
+ fprintf(stderr, " where, certificate-usage = TLSA certificate usage,\n");
+ fprintf(stderr, "\t selector = TLSA selector,\n");
+ fprintf(stderr, "\t matching-type = empty string or OpenSSL digest algorithm name,\n");
+ fprintf(stderr, "\t PEM certfile provides certificate association data,\n");
+ fprintf(stderr, "\t PEM CAfile contains any usage 0/1 trusted roots,\n");
+ fprintf(stderr, "\t PEM chainfile = server chain file to verify\n");
+ fprintf(stderr, "\t hostname = destination hostname,\n");
+ fprintf(stderr, "\t each certname augments the hostname for name checks.\n");
+ exit(1);
+}
+
+/* match_servername - match servername against pattern */
+
+static int match_servername(const char *certid, ARGV *margv)
+{
+ const char *domain;
+ const char *parent;
+ int match_subdomain;
+ int i;
+ int idlen;
+ int domlen;
+
+ /*
+ * XXX EAI support.
+ */
+
+ /*
+ * Match the certid against each pattern until we find a match.
+ */
+ for (i = 0; i < margv->argc; ++i) {
+ match_subdomain = 0;
+ domain = margv->argv[i];
+ if (*domain == '.' && domain[1] != '\0') {
+ ++domain;
+ match_subdomain = 1;
+ }
+
+ /*
+ * Sub-domain match: certid is any sub-domain of hostname.
+ */
+ if (match_subdomain) {
+ if ((idlen = strlen(certid)) > (domlen = strlen(domain)) + 1
+ && certid[idlen - domlen - 1] == '.'
+ && !strcasecmp(certid + (idlen - domlen), domain))
+ return (1);
+ else
+ continue;
+ }
+
+ /*
+ * Exact match and initial "*" match. The initial "*" in a certid
+ * matches one (if var_tls_multi_label is false) or more hostname
+ * components under the condition that the certid contains multiple
+ * hostname components.
+ */
+ if (!strcasecmp(certid, domain)
+ || (certid[0] == '*' && certid[1] == '.' && certid[2] != 0
+ && (parent = strchr(domain, '.')) != 0
+ && (idlen = strlen(certid + 1)) <= (domlen = strlen(parent))
+ && strcasecmp(var_tls_multi_wildcard == 0 ? parent :
+ parent + domlen - idlen,
+ certid + 1) == 0))
+ return (1);
+ }
+ return (0);
+}
+
+static void check_name(TLS_SESS_STATE *tctx, X509 *cert, ARGV *margs)
+{
+ char *cn;
+ int matched = 0;
+ general_name_stack_t *gens;
+
+ if (SSL_get_verify_result(tctx->con) != X509_V_OK)
+ return;
+
+ tctx->peer_status |= TLS_CERT_FLAG_TRUSTED;
+
+ gens = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
+ if (gens) {
+ int has_dnsname = 0;
+ int num_gens = sk_GENERAL_NAME_num(gens);
+ int i;
+
+ for (i = 0; !matched && i < num_gens; ++i) {
+ const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
+ const char *dnsname;
+
+ if (gn->type != GEN_DNS)
+ continue;
+ has_dnsname = 1;
+ tctx->peer_status |= TLS_CERT_FLAG_ALTNAME;
+ dnsname = tls_dns_name(gn, tctx);
+ if (dnsname && *dnsname
+ && (matched = match_servername(dnsname, margs)) != 0)
+ tctx->peer_status |= TLS_CERT_FLAG_MATCHED;
+ }
+ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
+ if (has_dnsname)
+ return;
+ }
+ cn = tls_peer_CN(cert, tctx);
+ if (match_servername(cn, margs))
+ tctx->peer_status |= TLS_CERT_FLAG_MATCHED;
+ myfree(cn);
+}
+
+static void check_print(TLS_SESS_STATE *tctx, X509 *cert)
+{
+ if (TLS_DANE_HASEE(tctx->dane)
+ && tls_dane_match(tctx, TLS_DANE_EE, cert, 0))
+ tctx->peer_status |= TLS_CERT_FLAG_TRUSTED | TLS_CERT_FLAG_MATCHED;
+}
+
+static void check_peer(TLS_SESS_STATE *tctx, X509 *cert, int argc, char **argv)
+{
+ ARGV match;
+
+ tctx->peer_status |= TLS_CERT_FLAG_PRESENT;
+ check_print(tctx, cert);
+ if (!TLS_CERT_IS_MATCHED(tctx)) {
+ match.argc = argc;
+ match.argv = argv;
+ check_name(tctx, cert, &match);
+ }
+}
+
+static SSL_CTX *ctx_init(const char *CAfile)
+{
+ SSL_CTX *client_ctx;
+
+ tls_param_init();
+ tls_check_version();
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ SSL_load_error_strings();
+ SSL_library_init();
+#endif
+
+ if (!tls_validate_digest(LN_sha1))
+ msg_fatal("%s digest algorithm not available", LN_sha1);
+
+ if (TLScontext_index < 0)
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0)
+ msg_fatal("Cannot allocate SSL application data index");
+
+ ERR_clear_error();
+ if ((client_ctx = SSL_CTX_new(TLS_client_method())) == 0)
+ msg_fatal("cannot allocate client SSL_CTX");
+ SSL_CTX_set_verify_depth(client_ctx, 5);
+
+ if (tls_set_ca_certificate_info(client_ctx, CAfile, "") < 0) {
+ tls_print_errors();
+ msg_fatal("cannot load CAfile: %s", CAfile);
+ }
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE,
+ tls_verify_certificate_callback);
+ return (client_ctx);
+}
+
+int main(int argc, char *argv[])
+{
+ SSL_CTX *ssl_ctx;
+ TLS_SESS_STATE *tctx;
+ x509_stack_t *chain;
+
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+ msg_vstream_init(var_procname, VSTREAM_OUT);
+
+ if (argc < 8)
+ usage(argv[0]);
+
+ ssl_ctx = ctx_init(argv[5]);
+ if (!tls_dane_avail())
+ msg_fatal("DANE TLSA support not available");
+
+ tctx = tls_alloc_sess_context(TLS_LOG_NONE, argv[7]);
+ tctx->namaddr = argv[7];
+ tctx->mdalg = LN_sha1;
+ tctx->dane = tls_dane_alloc();
+
+ if ((tctx->con = SSL_new(ssl_ctx)) == 0
+ || !SSL_set_ex_data(tctx->con, TLScontext_index, tctx)) {
+ tls_print_errors();
+ msg_fatal("Error allocating SSL connection");
+ }
+ SSL_set_connect_state(tctx->con);
+ add_tlsa((TLS_DANE *) tctx->dane, argv);
+ tls_dane_set_callback(ssl_ctx, tctx);
+
+ /* Verify saved server chain */
+ chain = load_chain(argv[6]);
+ verify_chain(tctx->con, chain, tctx);
+ check_peer(tctx, sk_X509_value(chain, 0), argc - 7, argv + 7);
+ tls_print_errors();
+
+ msg_info("%s %s", TLS_CERT_IS_MATCHED(tctx) ? "Verified" :
+ TLS_CERT_IS_TRUSTED(tctx) ? "Trusted" : "Untrusted", argv[7]);
+
+ return (TLS_CERT_IS_MATCHED(tctx) ? 0 : 1);
+}
+
+#endif /* TEST */
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_dane.sh b/src/tls/tls_dane.sh
new file mode 100644
index 0000000..a950857
--- /dev/null
+++ b/src/tls/tls_dane.sh
@@ -0,0 +1,211 @@
+#! /bin/bash
+
+set -e
+
+DOMAIN=example.com
+HOST=mail.${DOMAIN}
+TEST=./tls_dane
+
+key() {
+ local key=$1; shift
+
+ if [ ! -f "${key}.pem" ]; then
+ openssl genpkey 2>/dev/null \
+ -paramfile <(openssl ecparam -name prime256v1) \
+ -out "${key}.pem"
+ fi
+}
+
+req() {
+ local key=$1; shift
+ local cn=$1; shift
+
+ key "$key"
+ openssl req -new -sha256 -key "${key}.pem" 2>/dev/null \
+ -config <(printf "[req]\n%s\n%s\n[dn]\nCN=%s\n" \
+ "prompt = no" "distinguished_name = dn" "${cn}")
+}
+
+req_nocn() {
+ local key=$1; shift
+
+ key "$key"
+ openssl req -new -sha256 -subj / -key "${key}.pem" 2>/dev/null \
+ -config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \
+ "distinguished_name = dn")
+}
+
+cert() {
+ local cert=$1; shift
+ local exts=$1; shift
+
+ openssl x509 -req -sha256 -out "${cert}.pem" 2>/dev/null \
+ -extfile <(printf "%s\n" "$exts") "$@"
+}
+
+genroot() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local skid=$1; shift
+ local akid=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days 30
+}
+
+genca() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local skid=$1; shift
+ local akid=$1; shift
+ local ca=$1; shift
+ local cakey=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days 30 "$@"
+}
+
+genee() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+ local ca=$1; shift
+ local cakey=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:false" \
+ "extendedKeyUsage = serverAuth" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
+ -set_serial 2 -days 30 "$@"
+}
+
+genss() {
+ local cn=$1; shift
+ local key=$1; shift
+ local cert=$1; shift
+
+ exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
+ "subjectKeyIdentifier = hash" \
+ "authorityKeyIdentifier = keyid, issuer" \
+ "basicConstraints = CA:true" \
+ "extendedKeyUsage = serverAuth" \
+ "subjectAltName = @alts" "DNS=${cn}")
+ req "$key" "$cn" |
+ cert "$cert" "$exts" -set_serial 1 -days 30 -signkey "${key}.pem" "$@"
+}
+
+gennocn() {
+ local key=$1; shift
+ local cert=$1; shift
+
+ req_nocn "$key" |
+ cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days -1 "$@"
+}
+
+runtest() {
+ local desc=$1; shift
+ local usage=$1; shift
+ local selector=$1; shift
+ local mtype=$1; shift
+ local tlsa=$1; shift
+ local ca=$1; shift
+ local chain=$1; shift
+ local digest
+
+ case $mtype in
+ 0) digest="";;
+ 1) digest=sha256;;
+ 2) digest=sha512;;
+ *) echo "bad mtype: $mtype"; exit 1;;
+ esac
+
+ printf "%d %d %d %-24s %s: " "$usage" "$selector" "$mtype" "$tlsa" "$desc"
+
+ if [ -n "$ca" ]; then ca="$ca.pem"; fi
+ "$TEST" "$usage" "$selector" "$digest" "$tlsa.pem" "$ca" "$chain.pem" \
+ "$@" > /dev/null
+}
+
+checkpass() { runtest "$@" && { echo pass; } || { echo fail; exit 1; }; }
+checkfail() { runtest "$@" && { echo fail; exit 1; } || { echo pass; }; }
+
+#---------
+
+genss "$HOST" sskey sscert
+gennocn akey acert
+
+# Tests that might depend on akid/skid chaining
+#
+for rakid in "" \
+ "authorityKeyIdentifier = keyid,issuer" \
+ "authorityKeyIdentifier = issuer" \
+ "authorityKeyIdentifier = keyid"
+do
+for cakid in "" \
+ "authorityKeyIdentifier = keyid,issuer" \
+ "authorityKeyIdentifier = issuer" \
+ "authorityKeyIdentifier = keyid"
+do
+for rskid in "" "subjectKeyIdentifier = hash"
+do
+for caskid in "" "subjectKeyIdentifier = hash"
+do
+
+genroot "Root CA" rootkey rootcert "$rskid" "$rakid"
+genca "CA 1" cakey1 cacert1 "$caskid" "$cakid" rootcert rootkey
+genca "CA 2" cakey2 cacert2 "$caskid" "$cakid" cacert1 cakey1
+genee "$HOST" eekey eecert cacert2 cakey2
+cat eecert.pem cacert2.pem cacert1.pem rootcert.pem > chain.pem
+cat eecert.pem cacert2.pem cacert1.pem > chain1.pem
+
+for s in 0 1
+do
+ checkpass "OOB root TA" 2 "$s" 0 rootcert "" chain1 "$HOST"
+ checkpass "OOB TA" 2 "$s" 0 cacert2 "" eecert "$HOST"
+ checkpass "in-chain root TA" 2 "$s" 1 rootcert "" chain "$HOST"
+
+ for m in 0 1 2
+ do
+ checkpass "valid TA" 2 "$s" "$m" rootcert "" chain "$HOST"
+ for ca in "cacert1" "cacert2"; do
+ checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain "$HOST"
+ checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain1 "$HOST"
+ checkpass "valid TA+CA" 2 "$s" "$m" "$ca" rootcert chain1 "$HOST"
+ checkpass "sub-domain" 2 "$s" "$m" "$ca" "" chain1 whatever ".$DOMAIN"
+ checkfail "wrong name" 2 "$s" "$m" "$ca" "" chain1 "whatever"
+ done
+ done
+done
+
+done
+done
+done
+done
+
+# These tests don't depend in the akid/skid chaining:
+#
+for s in 0 1
+do
+ checkfail "missing TA" 2 "$s" 1 rootcert "" chain1 "$HOST"
+ for m in 0 1 2
+ do
+ checkpass "depth 0 TA" 2 "$s" "$m" sscert "" sscert "$HOST"
+ checkfail "non-TA" 2 "$s" "$m" eecert rootcert chain "$HOST"
+ checkfail "depth 0 TA namecheck" 2 "$s" "$m" sscert sscert sscert whatever
+
+ checkpass "valid EE" 3 "$s" "$m" eecert "" chain whatever
+ checkpass "key-only EE" 3 "$s" "$m" acert "" acert whatever
+ checkfail "wrong EE" 3 "$s" "$m" cacert2 "" chain whatever
+ done
+done
+
+rm -f *.pem
diff --git a/src/tls/tls_dh.c b/src/tls/tls_dh.c
new file mode 100644
index 0000000..70db8e9
--- /dev/null
+++ b/src/tls/tls_dh.c
@@ -0,0 +1,387 @@
+/*++
+/* NAME
+/* tls_dh
+/* SUMMARY
+/* Diffie-Hellman parameter support
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* void tls_set_dh_from_file(path, bits)
+/* const char *path;
+/* int bits;
+/*
+/* void tls_auto_eecdh_curves(ctx, configured)
+/* SSL_CTX *ctx;
+/* char *configured;
+/*
+/* void tls_set_eecdh_curve(server_ctx, grade)
+/* SSL_CTX *server_ctx;
+/* const char *grade;
+/*
+/* DH *tls_tmp_dh_cb(ssl, export, keylength)
+/* SSL *ssl; /* unused */
+/* int export;
+/* int keylength;
+/* DESCRIPTION
+/* This module maintains parameters for Diffie-Hellman key generation.
+/*
+/* tls_tmp_dh_cb() is a call-back routine for the
+/* SSL_CTX_set_tmp_dh_callback() function.
+/*
+/* tls_set_dh_from_file() overrides compiled-in DH parameters
+/* with those specified in the named files. The file format
+/* is as expected by the PEM_read_DHparams() routine. The
+/* "bits" argument must be 512 or 1024.
+/*
+/* tls_auto_eecdh_curves() enables negotiation of the most preferred curve
+/* among the curves specified by the "configured" argument.
+/*
+/* tls_set_eecdh_curve() enables ephemeral Elliptic-Curve DH
+/* key exchange algorithms by instantiating in the server SSL
+/* context a suitable curve (corresponding to the specified
+/* EECDH security grade) from the set of named curves in RFC
+/* 4492 Section 5.1.1. Errors generate warnings, but do not
+/* disable TLS, rather we continue without EECDH. A zero
+/* result indicates that the grade is invalid or the corresponding
+/* curve could not be used. The "auto" grade enables multiple
+/* curves, with the actual curve chosen as the most preferred
+/* among those supported by both the server and the client.
+/* DIAGNOSTICS
+/* In case of error, tls_set_dh_from_file() logs a warning and
+/* ignores the request.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+ * Global library
+ */
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+#include <openssl/dh.h>
+#ifndef OPENSSL_NO_ECDH
+#include <openssl/ec.h>
+#endif
+
+/* Application-specific. */
+
+ /*
+ * Compiled-in DH parameters. Used when no parameters are explicitly loaded
+ * from a site-specific file. Using an ASN.1 DER encoding avoids the need
+ * to explicitly manipulate the internal representation of DH parameter
+ * objects.
+ *
+ * 512-bit parameters are used for export ciphers, and 2048-bit parameters are
+ * used for non-export ciphers. The non-export group is now 2048-bit, as
+ * 1024 bits is increasingly considered to weak by clients. When greater
+ * security is required, use EECDH.
+ */
+
+ /*-
+ * Generated via:
+ * $ openssl dhparam -2 -outform DER 512 2>/dev/null |
+ * hexdump -ve '/1 "0x%02x, "' | fmt
+ * TODO: generate at compile-time. But that is no good for the majority of
+ * sites that install pre-compiled binaries, and breaks reproducible builds.
+ * Instead, generate at installation time and use main.cf configuration.
+ */
+static unsigned char dh512_der[] = {
+ 0x30, 0x46, 0x02, 0x41, 0x00, 0xd8, 0xbf, 0x11, 0xd6, 0x41, 0x2a, 0x7a,
+ 0x9c, 0x78, 0xb2, 0xaa, 0x41, 0x23, 0x0a, 0xdc, 0xcf, 0xb7, 0x19, 0xc5,
+ 0x16, 0x4c, 0xcb, 0x4a, 0xd0, 0xd2, 0x1f, 0x1f, 0x70, 0x24, 0x86, 0x6f,
+ 0x51, 0x52, 0xc6, 0x5b, 0x28, 0xbb, 0x82, 0xe1, 0x24, 0x91, 0x3d, 0x4d,
+ 0x95, 0x56, 0xf8, 0x0b, 0x2c, 0xe0, 0x36, 0x67, 0x88, 0x64, 0x15, 0x1f,
+ 0x45, 0xd5, 0xb8, 0x0a, 0x00, 0x03, 0x76, 0x32, 0x0b, 0x02, 0x01, 0x02,
+};
+
+ /*-
+ * Generated via:
+ * $ openssl dhparam -2 -outform DER 2048 2>/dev/null |
+ * hexdump -ve '/1 "0x%02x, "' | fmt
+ * TODO: generate at compile-time. But that is no good for the majority of
+ * sites that install pre-compiled binaries, and breaks reproducible builds.
+ * Instead, generate at installation time and use main.cf configuration.
+ */
+static unsigned char dh2048_der[] = {
+ 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbf, 0x28, 0x1b,
+ 0x68, 0x69, 0x90, 0x2f, 0x37, 0x9f, 0x5a, 0x50, 0x23, 0x73, 0x2c, 0x11,
+ 0xf2, 0xac, 0x7c, 0x3e, 0x58, 0xb9, 0x23, 0x3e, 0x02, 0x07, 0x4d, 0xba,
+ 0xd9, 0x2c, 0xc1, 0x9e, 0xf9, 0xc4, 0x2f, 0xbc, 0x8d, 0x86, 0x4b, 0x2a,
+ 0x87, 0x86, 0x93, 0x32, 0x0f, 0x72, 0x40, 0xfe, 0x7e, 0xa2, 0xc1, 0x32,
+ 0xf0, 0x65, 0x9c, 0xc3, 0x19, 0x25, 0x2d, 0xeb, 0x6a, 0x49, 0x94, 0x79,
+ 0x2d, 0xa1, 0xbe, 0x05, 0x26, 0xac, 0x8d, 0x69, 0xdc, 0x2e, 0x7e, 0xb5,
+ 0xfd, 0x3c, 0x2b, 0x7d, 0x43, 0x22, 0x53, 0xf6, 0x1e, 0x04, 0x45, 0xd7,
+ 0x53, 0x84, 0xfd, 0x6b, 0x12, 0x72, 0x47, 0x04, 0xaf, 0xa4, 0xac, 0x4b,
+ 0x55, 0xb6, 0x79, 0x42, 0x40, 0x88, 0x54, 0x48, 0xd5, 0x4d, 0x3a, 0xb2,
+ 0xbf, 0x6c, 0x26, 0x95, 0x29, 0xdd, 0x8b, 0x9e, 0xed, 0xb8, 0x60, 0x8e,
+ 0xb5, 0x35, 0xb6, 0x22, 0x44, 0x1f, 0xfb, 0x56, 0x74, 0xfe, 0xf0, 0x2c,
+ 0xe6, 0x0c, 0x22, 0xc9, 0x35, 0xb3, 0x1b, 0x96, 0xbb, 0x0a, 0x5a, 0xc3,
+ 0x09, 0xa0, 0xcc, 0xa5, 0x40, 0x90, 0x0f, 0x59, 0xa2, 0x89, 0x69, 0x2a,
+ 0x69, 0x79, 0xe4, 0xd3, 0x24, 0xc6, 0x8c, 0xda, 0xbc, 0x98, 0x3a, 0x5b,
+ 0x16, 0xae, 0x63, 0x6c, 0x0b, 0x43, 0x4f, 0xf3, 0x2e, 0xc8, 0xa9, 0x6b,
+ 0x58, 0x6a, 0xa9, 0x8e, 0x64, 0x09, 0x3d, 0x88, 0x44, 0x4f, 0x97, 0x2c,
+ 0x1d, 0x98, 0xb0, 0xa9, 0xc0, 0xb6, 0x8d, 0x19, 0x37, 0x1f, 0xb7, 0xc9,
+ 0x86, 0xa8, 0xdc, 0x37, 0x4d, 0x64, 0x27, 0xf3, 0xf5, 0x2b, 0x7b, 0x6b,
+ 0x76, 0x84, 0x3f, 0xc1, 0x23, 0x97, 0x2d, 0x71, 0xf7, 0xb6, 0xc2, 0x35,
+ 0x28, 0x10, 0x96, 0xd6, 0x69, 0x0c, 0x2e, 0x1f, 0x9f, 0xdf, 0x82, 0x81,
+ 0x57, 0x57, 0x39, 0xa5, 0xf2, 0x81, 0x29, 0x57, 0xf9, 0x2f, 0xd0, 0x03,
+ 0xab, 0x02, 0x01, 0x02,
+};
+
+ /*
+ * Cached results.
+ */
+static DH *dh_1024 = 0;
+static DH *dh_512 = 0;
+
+/* tls_set_dh_from_file - set Diffie-Hellman parameters from file */
+
+void tls_set_dh_from_file(const char *path, int bits)
+{
+ FILE *paramfile;
+ DH **dhPtr;
+
+ switch (bits) {
+ case 512:
+ dhPtr = &dh_512;
+ break;
+ case 1024:
+ dhPtr = &dh_1024;
+ break;
+ default:
+ msg_panic("Invalid DH parameters size %d, file %s", bits, path);
+ }
+
+ /*
+ * This function is the first to set the DH parameters, but free any
+ * prior value just in case the call sequence changes some day.
+ */
+ if (*dhPtr) {
+ DH_free(*dhPtr);
+ *dhPtr = 0;
+ }
+ if ((paramfile = fopen(path, "r")) != 0) {
+ if ((*dhPtr = PEM_read_DHparams(paramfile, 0, 0, 0)) == 0) {
+ msg_warn("cannot load %d-bit DH parameters from file %s"
+ " -- using compiled-in defaults", bits, path);
+ tls_print_errors();
+ }
+ (void) fclose(paramfile); /* 200411 */
+ } else {
+ msg_warn("cannot load %d-bit DH parameters from file %s: %m"
+ " -- using compiled-in defaults", bits, path);
+ }
+}
+
+/* tls_get_dh - get compiled-in DH parameters */
+
+static DH *tls_get_dh(const unsigned char *p, size_t plen)
+{
+ const unsigned char *endp = p;
+ DH *dh = 0;
+
+ if (d2i_DHparams(&dh, &endp, plen) && plen == endp - p)
+ return (dh);
+
+ msg_warn("cannot load compiled-in DH parameters");
+ if (dh)
+ DH_free(dh);
+ return (0);
+}
+
+/* tls_tmp_dh_cb - call-back for Diffie-Hellman parameters */
+
+DH *tls_tmp_dh_cb(SSL *unused_ssl, int export, int keylength)
+{
+ DH *dh_tmp;
+
+ if (export && keylength == 512) { /* 40-bit export cipher */
+ if (dh_512 == 0)
+ dh_512 = tls_get_dh(dh512_der, sizeof(dh512_der));
+ dh_tmp = dh_512;
+ } else { /* ADH, DHE-RSA or DSA */
+ if (dh_1024 == 0)
+ dh_1024 = tls_get_dh(dh2048_der, sizeof(dh2048_der));
+ dh_tmp = dh_1024;
+ }
+ return (dh_tmp);
+}
+
+void tls_auto_eecdh_curves(SSL_CTX *ctx, const char *configured)
+{
+#ifndef OPENSSL_NO_ECDH
+ SSL_CTX *tmpctx;
+ int *nids;
+ int space = 5;
+ int n = 0;
+ int unknown = 0;
+ char *save;
+ char *curves;
+ char *curve;
+
+ if ((tmpctx = SSL_CTX_new(TLS_method())) == 0) {
+ msg_warn("cannot allocate temp SSL_CTX, using default ECDHE curves");
+ tls_print_errors();
+ return;
+ }
+ nids = mymalloc(space * sizeof(int));
+ curves = save = mystrdup(configured);
+#define RETURN do { \
+ myfree(save); \
+ myfree(nids); \
+ SSL_CTX_free(tmpctx); \
+ return; \
+ } while (0)
+
+ while ((curve = mystrtok(&curves, CHARS_COMMA_SP)) != 0) {
+ int nid = EC_curve_nist2nid(curve);
+
+ if (nid == NID_undef)
+ nid = OBJ_sn2nid(curve);
+ if (nid == NID_undef)
+ nid = OBJ_ln2nid(curve);
+ if (nid == NID_undef) {
+ msg_warn("ignoring unknown ECDHE curve \"%s\"",
+ curve);
+ continue;
+ }
+
+ /*
+ * Validate the NID by trying it as the sole EC curve for a
+ * throw-away SSL context. Silently skip unsupported code points.
+ * This way, we can list X25519 and X448 as soon as the nids are
+ * assigned, and before the supporting code is implemented. They'll
+ * be silently skipped when not yet supported.
+ */
+ if (SSL_CTX_set1_curves(tmpctx, &nid, 1) <= 0) {
+ ++unknown;
+ continue;
+ }
+ if (++n > space) {
+ space *= 2;
+ nids = myrealloc(nids, space * sizeof(int));
+ }
+ nids[n - 1] = nid;
+ }
+
+ if (n == 0) {
+ if (unknown > 0)
+ msg_warn("none of the configured ECDHE curves are supported");
+ RETURN;
+ }
+ if (SSL_CTX_set1_curves(ctx, nids, n) <= 0) {
+ msg_warn("failed to configure ECDHE curves");
+ tls_print_errors();
+ RETURN;
+ }
+
+ /*
+ * This is a NOP in OpenSSL 1.1.0 and later, where curves are always
+ * auto-negotiated.
+ */
+#if OPENSSL_VERSION_NUMBER < 0x10100000UL
+ if (SSL_CTX_set_ecdh_auto(ctx, 1) <= 0) {
+ msg_warn("failed to enable automatic ECDHE curve selection");
+ tls_print_errors();
+ RETURN;
+ }
+#endif
+ RETURN;
+#endif
+}
+
+#define TLS_EECDH_INVALID 0
+#define TLS_EECDH_NONE 1
+#define TLS_EECDH_STRONG 2
+#define TLS_EECDH_ULTRA 3
+#define TLS_EECDH_AUTO 4
+
+void tls_set_eecdh_curve(SSL_CTX *server_ctx, const char *grade)
+{
+#ifndef OPENSSL_NO_ECDH
+ int g;
+ static NAME_CODE eecdh_table[] = {
+ "none", TLS_EECDH_NONE,
+ "strong", TLS_EECDH_STRONG,
+ "ultra", TLS_EECDH_ULTRA,
+ "auto", TLS_EECDH_AUTO,
+ 0, TLS_EECDH_INVALID,
+ };
+
+ switch (g = name_code(eecdh_table, NAME_CODE_FLAG_NONE, grade)) {
+ default:
+ msg_panic("Invalid eecdh grade code: %d", g);
+ case TLS_EECDH_INVALID:
+ msg_warn("Invalid TLS eecdh grade \"%s\": EECDH disabled", grade);
+ return;
+ case TLS_EECDH_STRONG:
+ tls_auto_eecdh_curves(server_ctx, var_tls_eecdh_strong);
+ return;
+ case TLS_EECDH_ULTRA:
+ tls_auto_eecdh_curves(server_ctx, var_tls_eecdh_ultra);
+ return;
+ case TLS_EECDH_NONE:
+
+ /*
+ * Pretend "none" is "auto", the former is no longer supported or
+ * wise
+ */
+ msg_warn("The \"none\" eecdh grade is no longer supported, "
+ "using \"auto\" instead");
+ case TLS_EECDH_AUTO:
+ tls_auto_eecdh_curves(server_ctx, var_tls_eecdh_auto);
+ return;
+ }
+#endif
+ return;
+}
+
+#ifdef TEST
+
+int main(int unused_argc, char **unused_argv)
+{
+ tls_tmp_dh_cb(0, 1, 512);
+ tls_tmp_dh_cb(0, 1, 1024);
+ tls_tmp_dh_cb(0, 1, 2048);
+ tls_tmp_dh_cb(0, 0, 512);
+ return (0);
+}
+
+#endif
+
+#endif
diff --git a/src/tls/tls_fprint.c b/src/tls/tls_fprint.c
new file mode 100644
index 0000000..03e4dc6
--- /dev/null
+++ b/src/tls/tls_fprint.c
@@ -0,0 +1,413 @@
+/*++
+/* NAME
+/* tls_fprint 3
+/* SUMMARY
+/* Digests fingerprints and all that.
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr)
+/* const char *mdalg;
+/* EVP_MD_CTX **mdctxPtr;
+/*
+/* char *tls_serverid_digest(props, protomask, ciphers)
+/* const TLS_CLIENT_START_PROPS *props;
+/* long protomask;
+/* const char *ciphers;
+/*
+/* char *tls_digest_encode(md_buf, md_len)
+/* const unsigned char *md_buf;
+/* const char *md_len;
+/*
+/* char *tls_data_fprint(buf, len, mdalg)
+/* const char *buf;
+/* int len;
+/* const char *mdalg;
+/*
+/* char *tls_cert_fprint(peercert, mdalg)
+/* X509 *peercert;
+/* const char *mdalg;
+/*
+/* char *tls_pkey_fprint(peercert, mdalg)
+/* X509 *peercert;
+/* const char *mdalg;
+/* DESCRIPTION
+/* tls_digest_byname() constructs, and optionally returns, an EVP_MD_CTX
+/* handle for performing digest operations with the algorithm named by the
+/* mdalg parameter. The return value is non-null on success, and holds a
+/* digest algorithm handle. If the mdctxPtr argument is non-null the
+/* created context is returned to the caller, who is then responsible for
+/* deleting it by calling EVP_MD_ctx_free() once it is no longer needed.
+/*
+/* tls_digest_encode() converts a binary message digest to a hex ASCII
+/* format with ':' separators between each pair of hex digits.
+/* The return value is dynamically allocated with mymalloc(),
+/* and the caller must eventually free it with myfree().
+/*
+/* tls_data_fprint() digests unstructured data, and encodes the digested
+/* result via tls_digest_encode(). The return value is dynamically
+/* allocated with mymalloc(), and the caller must eventually free it
+/* with myfree().
+/*
+/* tls_cert_fprint() returns a fingerprint of the the given
+/* certificate using the requested message digest, formatted
+/* with tls_digest_encode(). Panics if the
+/* (previously verified) digest algorithm is not found. The return
+/* value is dynamically allocated with mymalloc(), and the caller
+/* must eventually free it with myfree().
+/*
+/* tls_pkey_fprint() returns a public-key fingerprint; in all
+/* other respects the function behaves as tls_cert_fprint().
+/* The var_tls_bc_pkey_fprint variable enables an incorrect
+/* algorithm that was used in Postfix versions 2.9.[0-5].
+/* The return value is dynamically allocated with mymalloc(),
+/* and the caller must eventually free it with myfree().
+/*
+/* tls_serverid_digest() suffixes props->serverid computed by the SMTP
+/* client with "&" plus a digest of additional parameters
+/* needed to ensure that re-used sessions are more likely to
+/* be reused and that they will satisfy all protocol and
+/* security requirements.
+/* The return value is dynamically allocated with mymalloc(),
+/* and the caller must eventually free it with myfree().
+/*
+/* Arguments:
+/* .IP mdalg
+/* A digest algorithm name, such as "sha256".
+/* .IP peercert
+/* Server or client X.509 certificate.
+/* .IP md_buf
+/* The raw binary digest.
+/* .IP md_len
+/* The digest length in bytes.
+/* .IP mdalg
+/* Name of a message digest algorithm suitable for computing secure
+/* (1st pre-image resistant) message digests of certificates. For now,
+/* md5, sha1, or member of SHA-2 family if supported by OpenSSL.
+/* .IP mdctxPtr
+/* Pointer to an (EVP_MD_CTX *) handle, or NULL if only probing for
+/* algorithm support without immediate use in mind.
+/* .IP buf
+/* Input data for the message digest algorithm mdalg.
+/* .IP len
+/* The length of the input data.
+/* .IP props
+/* The client start properties for the session, which contains the
+/* initial serverid from the SMTP client and the DANE verification
+/* parameters.
+/* .IP protomask
+/* The mask of protocol exclusions.
+/* .IP ciphers
+/* The SSL client cipherlist.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Viktor Dukhovni
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+static const char hexcodes[] = "0123456789ABCDEF";
+
+#define checkok(stillok) (ok = ok && (stillok))
+#define digest_data(p, l) checkok(EVP_DigestUpdate(mdctx, (char *)(p), (l)))
+#define digest_object(p) digest_data((p), sizeof(*(p)))
+#define digest_string(s) digest_data((s), strlen(s)+1)
+
+#define digest_dane(dane, memb) do { \
+ if ((dane)->memb != 0) \
+ checkok(digest_tlsa_usage(mdctx, (dane)->memb, #memb)); \
+ } while (0)
+
+#define digest_tlsa_argv(tlsa, memb) do { \
+ if ((tlsa)->memb) { \
+ digest_string(#memb); \
+ for (dgst = (tlsa)->memb->argv; *dgst; ++dgst) \
+ digest_string(*dgst); \
+ } \
+ } while (0)
+
+/* digest_tlsa_usage - digest TA or EE match list sorted by alg and value */
+
+static int digest_tlsa_usage(EVP_MD_CTX * mdctx, TLS_TLSA *tlsa,
+ const char *usage)
+{
+ char **dgst;
+ int ok = 1;
+
+ for (digest_string(usage); tlsa; tlsa = tlsa->next) {
+ digest_string(tlsa->mdalg);
+ digest_tlsa_argv(tlsa, pkeys);
+ digest_tlsa_argv(tlsa, certs);
+ }
+ return (ok);
+}
+
+/* tls_digest_byname - test availability or prepare to use digest */
+
+const EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr)
+{
+ const EVP_MD *md;
+ EVP_MD_CTX *mdctx = NULL;
+ int ok = 1;
+
+ /*
+ * In OpenSSL 3.0, because of dynamically variable algorithm providers,
+ * there is a time-of-check/time-of-use issue that means that abstract
+ * algorithm handles returned by EVP_get_digestbyname() can (and not
+ * infrequently do) return ultimately unusable algorithms, to check for
+ * actual availability, one needs to use the new EVP_MD_fetch() API, or
+ * indirectly check usability by creating a concrete context. We take the
+ * latter approach here (works for 1.1.1 without #ifdef).
+ *
+ * Note that EVP_MD_CTX_{create,destroy} were renamed to, respectively,
+ * EVP_MD_CTX_{new,free} in OpenSSL 1.1.0.
+ */
+ checkok(md = EVP_get_digestbyname(mdalg));
+
+ /*
+ * Sanity check: Newer shared libraries could (hypothetical ABI break)
+ * allow larger digests, we avoid such poison algorithms.
+ */
+ checkok(EVP_MD_size(md) <= EVP_MAX_MD_SIZE);
+ checkok(mdctx = EVP_MD_CTX_new());
+ checkok(EVP_DigestInit_ex(mdctx, md, NULL));
+
+
+ if (ok && mdctxPtr != 0)
+ *mdctxPtr = mdctx;
+ else
+ EVP_MD_CTX_free(mdctx);
+ return (ok ? md : 0);
+}
+
+/* tls_serverid_digest - suffix props->serverid with parameter digest */
+
+char *tls_serverid_digest(const TLS_CLIENT_START_PROPS *props, long protomask,
+ const char *ciphers)
+{
+ EVP_MD_CTX *mdctx;
+ const char *mdalg;
+ unsigned char md_buf[EVP_MAX_MD_SIZE];
+ unsigned int md_len;
+ int ok = 1;
+ int i;
+ long sslversion;
+ VSTRING *result;
+
+ /*
+ * Try to use sha256: our serverid choice should be strong enough to
+ * resist 2nd-preimage attacks with a difficulty comparable to that of
+ * DANE TLSA digests. Failing that, we compute serverid digests with the
+ * default digest, but DANE requires sha256 and sha512, so if we must
+ * fall back to our default digest, DANE support won't be available. We
+ * panic if the fallback algorithm is not available, as it was verified
+ * available in tls_client_init() and must not simply vanish. Our
+ * provider set is not expected to change once the OpenSSL library is
+ * initialized.
+ */
+ if (tls_digest_byname(mdalg = LN_sha256, &mdctx) == 0
+ && tls_digest_byname(mdalg = props->mdalg, &mdctx) == 0)
+ msg_panic("digest algorithm \"%s\" not found", props->mdalg);
+
+ /* Salt the session lookup key with the OpenSSL runtime version. */
+ sslversion = OpenSSL_version_num();
+
+ digest_string(props->helo ? props->helo : "");
+ digest_object(&sslversion);
+ digest_object(&protomask);
+ digest_string(ciphers);
+
+ /*
+ * All we get from the session cache is a single bit telling us whether
+ * the certificate is trusted or not, but we need to know whether the
+ * trust is CA-based (in that case we must do name checks) or whether it
+ * is a direct end-point match. We mustn't confuse the two, so it is
+ * best to process only TA trust in the verify callback and check the EE
+ * trust after. This works since re-used sessions always have access to
+ * the leaf certificate, while only the original session has the leaf and
+ * the full trust chain.
+ *
+ * Only the trust anchor matchlist is hashed into the session key. The end
+ * entity certs are not used to determine whether a certificate is
+ * trusted or not, rather these are rechecked against the leaf cert
+ * outside the verification callback, each time a session is created or
+ * reused.
+ *
+ * Therefore, the security context of the session does not depend on the EE
+ * matching data, which is checked separately each time. So we exclude
+ * the EE part of the DANE structure from the serverid digest.
+ *
+ * If the security level is "dane", we send SNI information to the peer.
+ * This may cause it to respond with a non-default certificate. Since
+ * certificates for sessions with no or different SNI data may not match,
+ * we must include the SNI name in the session id.
+ */
+ if (props->dane) {
+ digest_dane(props->dane, ta);
+#if 0
+ digest_dane(props->dane, ee); /* See above */
+#endif
+ digest_string(TLS_DANE_BASED(props->tls_level) ? props->host : "");
+ }
+ checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
+ EVP_MD_CTX_destroy(mdctx);
+ if (!ok)
+ msg_fatal("error computing %s message digest", mdalg);
+
+ /* Check for OpenSSL contract violation */
+ if (md_len > EVP_MAX_MD_SIZE)
+ msg_panic("unexpectedly large %s digest size: %u", mdalg, md_len);
+
+ /*
+ * Append the digest to the serverid. We don't compare this digest to
+ * any user-specified fingerprints. Therefore, we don't need to use a
+ * colon-separated format, which saves space in the TLS session cache and
+ * makes logging of session cache lookup keys more readable.
+ *
+ * This does however duplicate a few lines of code from the digest encoder
+ * for colon-separated cert and pkey fingerprints. If that is a
+ * compelling reason to consolidate, we could use that and append the
+ * result.
+ */
+ result = vstring_alloc(strlen(props->serverid) + 1 + 2 * md_len);
+ vstring_strcpy(result, props->serverid);
+ VSTRING_ADDCH(result, '&');
+ for (i = 0; i < md_len; i++) {
+ VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0xf0) >> 4U]);
+ VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0x0f)]);
+ }
+ VSTRING_TERMINATE(result);
+ return (vstring_export(result));
+}
+
+/* tls_digest_encode - encode message digest binary blob as xx:xx:... */
+
+char *tls_digest_encode(const unsigned char *md_buf, int md_len)
+{
+ int i;
+ char *result = mymalloc(md_len * 3);
+
+ /* Check for contract violation */
+ if (md_len > EVP_MAX_MD_SIZE || md_len >= INT_MAX / 3)
+ msg_panic("unexpectedly large message digest size: %u", md_len);
+
+ /* No risk of overrunes, len is bounded by OpenSSL digest length */
+ for (i = 0; i < md_len; i++) {
+ result[i * 3] = hexcodes[(md_buf[i] & 0xf0) >> 4U];
+ result[(i * 3) + 1] = hexcodes[(md_buf[i] & 0x0f)];
+ result[(i * 3) + 2] = (i + 1 != md_len) ? ':' : '\0';
+ }
+ return (result);
+}
+
+/* tls_data_fprint - compute and encode digest of binary object */
+
+char *tls_data_fprint(const char *buf, int len, const char *mdalg)
+{
+ EVP_MD_CTX *mdctx = NULL;
+ unsigned char md_buf[EVP_MAX_MD_SIZE];
+ unsigned int md_len;
+ int ok = 1;
+
+ /* Previously available in "init" routine. */
+ if (tls_digest_byname(mdalg, &mdctx) == 0)
+ msg_panic("digest algorithm \"%s\" not found", mdalg);
+
+ digest_data(buf, len);
+ checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
+ EVP_MD_CTX_destroy(mdctx);
+ if (!ok)
+ msg_fatal("error computing %s message digest", mdalg);
+
+ return (tls_digest_encode(md_buf, md_len));
+}
+
+/* tls_cert_fprint - extract certificate fingerprint */
+
+char *tls_cert_fprint(X509 *peercert, const char *mdalg)
+{
+ int len;
+ char *buf;
+ char *buf2;
+ char *result;
+
+ len = i2d_X509(peercert, NULL);
+ buf2 = buf = mymalloc(len);
+ i2d_X509(peercert, (unsigned char **) &buf2);
+ if (buf2 - buf != len)
+ msg_panic("i2d_X509 invalid result length");
+
+ result = tls_data_fprint(buf, len, mdalg);
+ myfree(buf);
+
+ return (result);
+}
+
+/* tls_pkey_fprint - extract public key fingerprint from certificate */
+
+char *tls_pkey_fprint(X509 *peercert, const char *mdalg)
+{
+ if (var_tls_bc_pkey_fprint) {
+ const char *myname = "tls_pkey_fprint";
+ ASN1_BIT_STRING *key;
+ char *result;
+
+ key = X509_get0_pubkey_bitstr(peercert);
+ if (key == 0)
+ msg_fatal("%s: error extracting legacy public-key fingerprint: %m",
+ myname);
+
+ result = tls_data_fprint((char *) key->data, key->length, mdalg);
+ return (result);
+ } else {
+ int len;
+ char *buf;
+ char *buf2;
+ char *result;
+
+ len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), NULL);
+ buf2 = buf = mymalloc(len);
+ i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), (unsigned char **) &buf2);
+ if (buf2 - buf != len)
+ msg_panic("i2d_X509_PUBKEY invalid result length");
+
+ result = tls_data_fprint(buf, len, mdalg);
+ myfree(buf);
+ return (result);
+ }
+}
+
+#endif
diff --git a/src/tls/tls_level.c b/src/tls/tls_level.c
new file mode 100644
index 0000000..eec15fd
--- /dev/null
+++ b/src/tls/tls_level.c
@@ -0,0 +1,95 @@
+/*++
+/* NAME
+/* tls_level 3
+/* SUMMARY
+/* TLS security level conversion
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* int tls_level_lookup(name)
+/* const char *name;
+/*
+/* const char *str_tls_level(level)
+/* int level;
+/* DESCRIPTION
+/* The functions in this module convert TLS levels from symbolic
+/* name to internal form and vice versa.
+/*
+/* tls_level_lookup() converts a TLS level from symbolic name
+/* to internal form. When an unknown level is specified,
+/* tls_level_lookup() logs no warning, and returns TLS_LEV_INVALID.
+/*
+/* str_tls_level() converts a TLS level from internal form to
+/* symbolic name. The result is a null pointer for an unknown
+/* level. The "halfdane" level is not a valid user-selected TLS level,
+/* it is generated internally and is only valid output for the
+/* str_tls_level() function.
+/* SEE ALSO
+/* name_code(3) name to number mapping
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <name_code.h>
+
+/* TLS library. */
+
+#include <tls.h>
+
+/* Application-specific. */
+
+ /*
+ * Numerical order of levels is critical (see tls.h):
+ *
+ * - With "may" and higher, TLS is enabled.
+ *
+ * - With "encrypt" and higher, TLS is required.
+ *
+ * - With "fingerprint" and higher, the peer certificate must match.
+ *
+ * - With "dane" and higher, the peer certificate must also be trusted,
+ * possibly via TLSA RRs that make it its own authority.
+ *
+ * The smtp(8) client will report trust failure in preference to reporting
+ * failure to match, so we make "dane" larger than "fingerprint".
+ */
+static const NAME_CODE tls_level_table[] = {
+ "none", TLS_LEV_NONE,
+ "may", TLS_LEV_MAY,
+ "encrypt", TLS_LEV_ENCRYPT,
+ "fingerprint", TLS_LEV_FPRINT,
+ "halfdane", TLS_LEV_HALF_DANE, /* output only */
+ "dane", TLS_LEV_DANE,
+ "dane-only", TLS_LEV_DANE_ONLY,
+ "verify", TLS_LEV_VERIFY,
+ "secure", TLS_LEV_SECURE,
+ 0, TLS_LEV_INVALID,
+};
+
+int tls_level_lookup(const char *name)
+{
+ int level = name_code(tls_level_table, NAME_CODE_FLAG_NONE, name);
+
+ return ((level != TLS_LEV_HALF_DANE) ? level : TLS_LEV_INVALID);
+}
+
+const char *str_tls_level(int level)
+{
+ return (str_name_code(tls_level_table, level));
+}
diff --git a/src/tls/tls_mgr.c b/src/tls/tls_mgr.c
new file mode 100644
index 0000000..b2bdcdc
--- /dev/null
+++ b/src/tls/tls_mgr.c
@@ -0,0 +1,471 @@
+/*++
+/* NAME
+/* tls_mgr 3
+/* SUMMARY
+/* tlsmgr client interface
+/* SYNOPSIS
+/* #include <tls_mgr.h>
+/*
+/* int tls_mgr_seed(buf, len)
+/* VSTRING *buf;
+/* int len;
+/*
+/* int tls_mgr_policy(cache_type, cachable, timeout)
+/* const char *cache_type;
+/* int *cachable;
+/* int *timeout;
+/*
+/* int tls_mgr_update(cache_type, cache_id, buf, len)
+/* const char *cache_type;
+/* const char *cache_id;
+/* const char *buf;
+/* ssize_t len;
+/*
+/* int tls_mgr_lookup(cache_type, cache_id, buf)
+/* const char *cache_type;
+/* const char *cache_id;
+/* VSTRING *buf;
+/*
+/* int tls_mgr_delete(cache_type, cache_id)
+/* const char *cache_type;
+/* const char *cache_id;
+/*
+/* TLS_TICKET_KEY *tls_mgr_key(keyname, timeout)
+/* unsigned char *keyname;
+/* int timeout;
+/* DESCRIPTION
+/* These routines communicate with the tlsmgr(8) server for
+/* entropy and session cache management. Since these are
+/* non-critical services, requests are allowed to fail without
+/* disrupting Postfix.
+/*
+/* tls_mgr_seed() requests entropy from the tlsmgr(8)
+/* Pseudo Random Number Generator (PRNG) pool.
+/*
+/* tls_mgr_policy() requests the session caching policy.
+/*
+/* tls_mgr_lookup() loads the specified session from
+/* the specified session cache.
+/*
+/* tls_mgr_update() saves the specified session to
+/* the specified session cache.
+/*
+/* tls_mgr_delete() removes specified session from
+/* the specified session cache.
+/*
+/* tls_mgr_key() is used to retrieve the current TLS session ticket
+/* encryption or decryption keys.
+/*
+/* Arguments:
+/* .IP cache_type
+/* One of TLS_MGR_SCACHE_SMTPD, TLS_MGR_SCACHE_SMTP or
+/* TLS_MGR_SCACHE_LMTP.
+/* .IP cachable
+/* Pointer to int, set non-zero if the requested cache_type
+/* is enabled.
+/* .IP timeout
+/* Pointer to int, returns the cache entry timeout.
+/* .IP cache_id
+/* The session cache lookup key.
+/* .IP buf
+/* The result or input buffer.
+/* .IP len
+/* The length of the input buffer, or the amount of data requested.
+/* .IP keyname
+/* Is null when requesting the current encryption keys. Otherwise,
+/* keyname is a pointer to an array of TLS_TICKET_NAMELEN unsigned
+/* chars (not NUL terminated) that is an identifier for a key
+/* previously used to encrypt a session ticket. When encrypting
+/* a null result indicates that session tickets are not supported, when
+/* decrypting it indicates that no matching keys were found.
+/* .IP timeout
+/* The encryption key timeout. Once a key has been active for this many
+/* seconds it is retired and used only for decrypting previously issued
+/* session tickets for another timeout seconds, and is then destroyed.
+/* The timeout must not be longer than half the SSL session lifetime.
+/* DIAGNOSTICS
+/* All client functions return one of the following status codes:
+/* .IP TLS_MGR_STAT_OK
+/* The request completed, and the requested operation was
+/* successful (for example, the requested session was found,
+/* or the specified session was saved or removed).
+/* .IP TLS_MGR_STAT_ERR
+/* The request completed, but the requested operation failed
+/* (for example, the requested object was not found or the
+/* specified session was not saved or removed).
+/* .IP TLS_MGR_STAT_FAIL
+/* The request could not complete (the client could not
+/* communicate with the tlsmgr(8) server).
+/* SEE ALSO
+/* tlsmgr(8) TLS session and PRNG management
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <attr.h>
+#include <attr_clnt.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* TLS library. */
+#include <tls_mgr.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+static ATTR_CLNT *tls_mgr;
+
+/* tls_mgr_open - create client handle */
+
+static void tls_mgr_open(void)
+{
+ char *service;
+
+ /*
+ * Sanity check.
+ */
+ if (tls_mgr != 0)
+ msg_panic("tls_mgr_open: multiple initialization");
+
+ /*
+ * Use whatever IPC is preferred for internal use: UNIX-domain sockets or
+ * Solaris streams.
+ */
+ service = concatenate("local:" TLS_MGR_CLASS "/", var_tls_mgr_service,
+ (char *) 0);
+ tls_mgr = attr_clnt_create(service, var_ipc_timeout,
+ var_ipc_idle_limit, var_ipc_ttl_limit);
+ myfree(service);
+
+ attr_clnt_control(tls_mgr,
+ ATTR_CLNT_CTL_PROTO, attr_vprint, attr_vscan,
+ ATTR_CLNT_CTL_END);
+}
+
+/* tls_mgr_seed - request PRNG seed */
+
+int tls_mgr_seed(VSTRING *buf, int len)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Request seed.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request attributes */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_SEED),
+ SEND_ATTR_INT(TLS_MGR_ATTR_SIZE, len),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_SEED, buf),
+ ATTR_TYPE_END) != 2)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_policy - request caching policy */
+
+int tls_mgr_policy(const char *cache_type, int *cachable, int *timeout)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Request policy.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request attributes */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_POLICY),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply attributes */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_INT(TLS_MGR_ATTR_CACHABLE, cachable),
+ RECV_ATTR_INT(TLS_MGR_ATTR_SESSTOUT, timeout),
+ ATTR_TYPE_END) != 3)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_lookup - request cached session */
+
+int tls_mgr_lookup(const char *cache_type, const char *cache_id,
+ VSTRING *buf)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_LOOKUP),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_SESSION, buf),
+ ATTR_TYPE_END) != 2)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_update - save session to cache */
+
+int tls_mgr_update(const char *cache_type, const char *cache_id,
+ const char *buf, ssize_t len)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_UPDATE),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_SESSION, len, buf),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* tls_mgr_delete - remove cached session */
+
+int tls_mgr_delete(const char *cache_type, const char *cache_id)
+{
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_DELETE),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
+ SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1)
+ status = TLS_MGR_STAT_FAIL;
+ return (status);
+}
+
+/* request_scache_key - ask tlsmgr(8) for matching key */
+
+static TLS_TICKET_KEY *request_scache_key(unsigned char *keyname)
+{
+ TLS_TICKET_KEY tmp;
+ static VSTRING *keybuf;
+ char *name;
+ size_t len;
+ int status;
+
+ /*
+ * Create the tlsmgr client handle.
+ */
+ if (tls_mgr == 0)
+ tls_mgr_open();
+
+ if (keybuf == 0)
+ keybuf = vstring_alloc(sizeof(tmp));
+
+ /* In tlsmgr requests we encode null key names as empty strings. */
+ name = keyname ? (char *) keyname : "";
+ len = keyname ? TLS_TICKET_NAMELEN : 0;
+
+ /*
+ * Send the request and receive the reply.
+ */
+ if (attr_clnt_request(tls_mgr,
+ ATTR_FLAG_NONE, /* Request */
+ SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_TKTKEY),
+ SEND_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, len, name),
+ ATTR_TYPE_END,
+ ATTR_FLAG_MISSING, /* Reply */
+ RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status),
+ RECV_ATTR_DATA(TLS_MGR_ATTR_KEYBUF, keybuf),
+ ATTR_TYPE_END) != 2
+ || status != TLS_MGR_STAT_OK
+ || LEN(keybuf) != sizeof(tmp))
+ return (0);
+
+ memcpy((void *) &tmp, STR(keybuf), sizeof(tmp));
+ return (tls_scache_key_rotate(&tmp));
+}
+
+/* tls_mgr_key - session ticket key lookup, local cache, then tlsmgr(8) */
+
+TLS_TICKET_KEY *tls_mgr_key(unsigned char *keyname, int timeout)
+{
+ TLS_TICKET_KEY *key = 0;
+ time_t now = time((time_t *) 0);
+
+ /* A zero timeout disables session tickets. */
+ if (timeout <= 0)
+ return (0);
+
+ if ((key = tls_scache_key(keyname, now, timeout)) == 0)
+ key = request_scache_key(keyname);
+ return (key);
+}
+
+#ifdef TEST
+
+/* System library. */
+
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <argv.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <hex_code.h>
+
+/* Global library. */
+
+#include <config.h>
+
+/* Application-specific. */
+
+int main(int unused_ac, char **av)
+{
+ VSTRING *inbuf = vstring_alloc(10);
+ int status;
+ ARGV *argv = 0;
+
+ msg_vstream_init(av[0], VSTREAM_ERR);
+
+ msg_verbose = 3;
+
+ mail_conf_read();
+ msg_info("using config files in %s", var_config_dir);
+
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) {
+ argv = argv_split(STR(inbuf), CHARS_SPACE);
+ if (argv->argc == 0) {
+ argv_free(argv);
+ continue;
+ }
+#define COMMAND(argv, str, len) \
+ (strcasecmp(argv->argv[0], str) == 0 && argv->argc == len)
+
+ if (COMMAND(argv, "policy", 2)) {
+ int cachable;
+ int timeout;
+
+ status = tls_mgr_policy(argv->argv[1], &cachable, &timeout);
+ vstream_printf("status=%d cachable=%d timeout=%d\n",
+ status, cachable, timeout);
+ } else if (COMMAND(argv, "seed", 2)) {
+ VSTRING *buf = vstring_alloc(10);
+ VSTRING *hex = vstring_alloc(10);
+ int len = atoi(argv->argv[1]);
+
+ status = tls_mgr_seed(buf, len);
+ hex_encode(hex, STR(buf), LEN(buf));
+ vstream_printf("status=%d seed=%s\n", status, STR(hex));
+ vstring_free(hex);
+ vstring_free(buf);
+ } else if (COMMAND(argv, "lookup", 3)) {
+ VSTRING *buf = vstring_alloc(10);
+
+ status = tls_mgr_lookup(argv->argv[1], argv->argv[2], buf);
+ vstream_printf("status=%d session=%.*s\n",
+ status, (int) LEN(buf), STR(buf));
+ vstring_free(buf);
+ } else if (COMMAND(argv, "update", 4)) {
+ status = tls_mgr_update(argv->argv[1], argv->argv[2],
+ argv->argv[3], strlen(argv->argv[3]));
+ vstream_printf("status=%d\n", status);
+ } else if (COMMAND(argv, "delete", 3)) {
+ status = tls_mgr_delete(argv->argv[1], argv->argv[2]);
+ vstream_printf("status=%d\n", status);
+ } else {
+ vstream_printf("usage:\n"
+ "seed byte_count\n"
+ "policy smtpd|smtp|lmtp\n"
+ "lookup smtpd|smtp|lmtp cache_id\n"
+ "update smtpd|smtp|lmtp cache_id session\n"
+ "delete smtpd|smtp|lmtp cache_id\n");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(argv);
+ }
+
+ vstring_free(inbuf);
+ return (0);
+}
+
+#endif /* TEST */
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_mgr.h b/src/tls/tls_mgr.h
new file mode 100644
index 0000000..c584175
--- /dev/null
+++ b/src/tls/tls_mgr.h
@@ -0,0 +1,71 @@
+#ifndef _TLS_MGR_CLNT_H_INCLUDED_
+#define _TLS_MGR_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_mgr 3h
+/* SUMMARY
+/* tlsmgr client interface
+/* SYNOPSIS
+/* #include <tls_mgr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * TLS library
+ */
+#include <tls_scache.h> /* Session ticket keys */
+
+ /*
+ * TLS manager protocol.
+ */
+#define TLS_MGR_SERVICE "tlsmgr"
+#define TLS_MGR_CLASS "private"
+
+#define TLS_MGR_ATTR_REQ "request"
+#define TLS_MGR_REQ_SEED "seed"
+#define TLS_MGR_REQ_POLICY "policy"
+#define TLS_MGR_REQ_LOOKUP "lookup"
+#define TLS_MGR_REQ_UPDATE "update"
+#define TLS_MGR_REQ_DELETE "delete"
+#define TLS_MGR_REQ_TKTKEY "tktkey"
+#define TLS_MGR_ATTR_CACHABLE "cachable"
+#define TLS_MGR_ATTR_CACHE_TYPE "cache_type"
+#define TLS_MGR_ATTR_SEED "seed"
+#define TLS_MGR_ATTR_CACHE_ID "cache_id"
+#define TLS_MGR_ATTR_SESSION "session"
+#define TLS_MGR_ATTR_SIZE "size"
+#define TLS_MGR_ATTR_STATUS "status"
+#define TLS_MGR_ATTR_KEYNAME "keyname"
+#define TLS_MGR_ATTR_KEYBUF "keybuf"
+#define TLS_MGR_ATTR_SESSTOUT "timeout"
+
+ /*
+ * TLS manager request status codes.
+ */
+#define TLS_MGR_STAT_OK 0 /* success */
+#define TLS_MGR_STAT_ERR (-1) /* object not found */
+#define TLS_MGR_STAT_FAIL (-2) /* protocol error */
+
+ /*
+ * Functional interface.
+ */
+extern int tls_mgr_seed(VSTRING *, int);
+extern int tls_mgr_policy(const char *, int *, int *);
+extern int tls_mgr_lookup(const char *, const char *, VSTRING *);
+extern int tls_mgr_update(const char *, const char *, const char *, ssize_t);
+extern int tls_mgr_delete(const char *, const char *);
+extern TLS_TICKET_KEY *tls_mgr_key(unsigned char *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/tls/tls_misc.c b/src/tls/tls_misc.c
new file mode 100644
index 0000000..b780709
--- /dev/null
+++ b/src/tls/tls_misc.c
@@ -0,0 +1,1678 @@
+/*++
+/* NAME
+/* tls_misc 3
+/* SUMMARY
+/* miscellaneous TLS support routines
+/* SYNOPSIS
+/* .SH Public functions
+/* .nf
+/* .na
+/* #include <tls.h>
+/*
+/* void tls_log_summary(role, usage, TLScontext)
+/* TLS_ROLE role;
+/* TLS_USAGE usage;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* const char *tls_compile_version(void)
+/*
+/* const char *tls_run_version(void)
+/*
+/* const char **tls_pkey_algorithms(void)
+/*
+/* void tls_pre_jail_init(TLS_ROLE)
+/* TLS_ROLE role;
+/*
+/* .SH Internal functions
+/* .nf
+/* .na
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* char *var_tls_cnf_file;
+/* char *var_tls_cnf_name;
+/* char *var_tls_high_clist;
+/* char *var_tls_medium_clist;
+/* char *var_tls_low_clist;
+/* char *var_tls_export_clist;
+/* char *var_tls_null_clist;
+/* char *var_tls_eecdh_auto;
+/* char *var_tls_eecdh_strong;
+/* char *var_tls_eecdh_ultra;
+/* char *var_tls_dane_digests;
+/* int var_tls_daemon_rand_bytes;
+/* bool var_tls_append_def_CA;
+/* bool var_tls_preempt_clist;
+/* bool var_tls_bc_pkey_fprint;
+/* bool var_tls_multi_wildcard;
+/* char *var_tls_mgr_service;
+/* char *var_tls_tkt_cipher;
+/* char *var_openssl_path;
+/* char *var_tls_server_sni_maps;
+/* bool var_tls_fast_shutdown;
+/*
+/* TLS_APPL_STATE *tls_alloc_app_context(ssl_ctx, log_mask)
+/* SSL_CTX *ssl_ctx;
+/* int log_mask;
+/*
+/* void tls_free_app_context(app_ctx)
+/* void *app_ctx;
+/*
+/* TLS_SESS_STATE *tls_alloc_sess_context(log_mask, namaddr)
+/* int log_mask;
+/* const char *namaddr;
+/*
+/* void tls_free_context(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_check_version()
+/*
+/* long tls_bug_bits()
+/*
+/* void tls_param_init()
+/*
+/* int tls_library_init(void)
+/*
+/* int tls_protocol_mask(plist)
+/* const char *plist;
+/*
+/* int tls_cipher_grade(name)
+/* const char *name;
+/*
+/* const char *str_tls_cipher_grade(grade)
+/* int grade;
+/*
+/* const char *tls_set_ciphers(TLScontext, grade, exclusions)
+/* TLS_SESS_STATE *TLScontext;
+/* int grade;
+/* const char *exclusions;
+/*
+/* void tls_get_signature_params(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_print_errors()
+/*
+/* void tls_info_callback(ssl, where, ret)
+/* const SSL *ssl; /* unused */
+/* int where;
+/* int ret;
+/*
+/* long tls_bio_dump_cb(bio, cmd, argp, argi, argl, ret)
+/* BIO *bio;
+/* int cmd;
+/* const char *argp;
+/* int argi;
+/* long argl; /* unused */
+/* long ret;
+/*
+/* int tls_log_mask(log_param, log_level)
+/* const char *log_param;
+/* const char *log_level;
+/*
+/* void tls_update_app_logmask(app_ctx, log_mask)
+/* TLS_APPL_STATE *app_ctx;
+/* int log_mask;
+/*
+/* int tls_validate_digest(dgst)
+/* const char *dgst;
+/* DESCRIPTION
+/* This module implements public and internal routines that
+/* support the TLS client and server.
+/*
+/* tls_log_summary() logs a summary of a completed TLS connection.
+/* The "role" argument must be TLS_ROLE_CLIENT for outgoing client
+/* connections, or TLS_ROLE_SERVER for incoming server connections,
+/* and the "usage" must be TLS_USAGE_NEW or TLS_USAGE_USED.
+/*
+/* tls_compile_version() returns a text string description of
+/* the compile-time TLS library.
+/*
+/* tls_run_version() is just tls_compile_version() but with the runtime
+/* version instead of the compile-time version.
+/*
+/* tls_pkey_algorithms() returns a pointer to null-terminated
+/* array of string constants with the names of the supported
+/* public-key algorithms.
+/*
+/* tls_alloc_app_context() creates an application context that
+/* holds the SSL context for the application and related cached state.
+/*
+/* tls_free_app_context() deallocates the application context and its
+/* contents (the application context is stored outside the TLS library).
+/*
+/* tls_alloc_sess_context() creates an initialized TLS session context
+/* structure with the specified log mask and peer name[addr].
+/*
+/* tls_free_context() destroys a TLScontext structure
+/* together with OpenSSL structures that are attached to it.
+/*
+/* tls_check_version() logs a warning when the run-time OpenSSL
+/* library differs in its major, minor or micro number from
+/* the compile-time OpenSSL headers.
+/*
+/* tls_bug_bits() returns the bug compatibility mask appropriate
+/* for the run-time library. Some of the bug work-arounds are
+/* not appropriate for some library versions.
+/*
+/* tls_param_init() loads main.cf parameters used internally in
+/* TLS library. Any errors are fatal.
+/*
+/* tls_library_init() initializes the OpenSSL library, optionally
+/* loading an OpenSSL configuration file.
+/*
+/* tls_pre_jail_init() opens any tables that need to be opened before
+/* entering a chroot jail. The "role" parameter must be TLS_ROLE_CLIENT
+/* for clients and TLS_ROLE_SERVER for servers. Any errors are fatal.
+/*
+/* tls_protocol_mask() returns a bitmask of excluded protocols, given
+/* a list (plist) of protocols to include or (preceded by a '!') exclude.
+/* If "plist" contains invalid protocol names, TLS_PROTOCOL_INVALID is
+/* returned and no warning is logged.
+/*
+/* tls_cipher_grade() converts a case-insensitive cipher grade
+/* name (high, medium, low, export, null) to the corresponding
+/* TLS_CIPHER_ constant. When the input specifies an unrecognized
+/* grade, tls_cipher_grade() logs no warning, and returns
+/* TLS_CIPHER_NONE.
+/*
+/* str_tls_cipher_grade() converts a cipher grade to a name.
+/* When the input specifies an undefined grade, str_tls_cipher_grade()
+/* logs no warning, returns a null pointer.
+/*
+/* tls_set_ciphers() applies the requested cipher grade and exclusions
+/* to the provided TLS session context, returning the resulting cipher
+/* list string. The return value is the cipherlist used and is
+/* overwritten upon each call. When the input is invalid,
+/* tls_set_ciphers() logs a warning, and returns a null result.
+/*
+/* tls_get_signature_params() updates the "TLScontext" with handshake
+/* signature parameters pertaining to TLS 1.3, where the ciphersuite
+/* no longer describes the asymmetric algorithms employed in the
+/* handshake, which are negotiated separately. This function
+/* has no effect for TLS 1.2 and earlier.
+/*
+/* tls_print_errors() queries the OpenSSL error stack,
+/* logs the error messages, and clears the error stack.
+/*
+/* tls_info_callback() is a call-back routine for the
+/* SSL_CTX_set_info_callback() routine. It logs SSL events
+/* to the Postfix logfile.
+/*
+/* tls_bio_dump_cb() is a call-back routine for the
+/* BIO_set_callback() routine. It logs SSL content to the
+/* Postfix logfile.
+/*
+/* tls_log_mask() converts a TLS log_level value from string
+/* to mask. The main.cf parameter name is passed along for
+/* diagnostics.
+/*
+/* tls_update_app_logmask() changes the log mask of the
+/* application TLS context to the new setting.
+/*
+/* tls_validate_digest() returns non-zero if the named digest
+/* is usable and zero otherwise.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <vstream.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <argv.h>
+#include <name_mask.h>
+#include <name_code.h>
+#include <dict.h>
+#include <valid_hostname.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <maps.h>
+
+ /*
+ * TLS library.
+ */
+#define TLS_INTERNAL
+#include <tls.h>
+
+ /* Application-specific. */
+
+ /*
+ * Tunable parameters.
+ */
+char *var_tls_cnf_file;
+char *var_tls_cnf_name;
+char *var_tls_high_clist;
+char *var_tls_medium_clist;
+char *var_tls_low_clist;
+char *var_tls_export_clist;
+char *var_tls_null_clist;
+int var_tls_daemon_rand_bytes;
+char *var_tls_eecdh_auto;
+char *var_tls_eecdh_strong;
+char *var_tls_eecdh_ultra;
+char *var_tls_dane_digests;
+bool var_tls_append_def_CA;
+char *var_tls_bug_tweaks;
+char *var_tls_ssl_options;
+bool var_tls_bc_pkey_fprint;
+bool var_tls_multi_wildcard;
+char *var_tls_mgr_service;
+char *var_tls_tkt_cipher;
+char *var_openssl_path;
+char *var_tls_server_sni_maps;
+bool var_tls_fast_shutdown;
+
+static MAPS *tls_server_sni_maps;
+
+#ifdef VAR_TLS_PREEMPT_CLIST
+bool var_tls_preempt_clist;
+
+#endif
+
+ /*
+ * Index to attach TLScontext pointers to SSL objects, so that they can be
+ * accessed by call-back routines.
+ */
+int TLScontext_index = -1;
+
+ /*
+ * Protocol name <=> mask conversion.
+ */
+static const NAME_CODE protocol_table[] = {
+ SSL_TXT_SSLV2, TLS_PROTOCOL_SSLv2,
+ SSL_TXT_SSLV3, TLS_PROTOCOL_SSLv3,
+ SSL_TXT_TLSV1, TLS_PROTOCOL_TLSv1,
+ SSL_TXT_TLSV1_1, TLS_PROTOCOL_TLSv1_1,
+ SSL_TXT_TLSV1_2, TLS_PROTOCOL_TLSv1_2,
+ TLS_PROTOCOL_TXT_TLSV1_3, TLS_PROTOCOL_TLSv1_3,
+ 0, TLS_PROTOCOL_INVALID,
+};
+
+ /*
+ * SSL_OP_MUMBLE bug work-around name <=> mask conversion.
+ */
+#define NAMEBUG(x) #x, SSL_OP_##x
+static const LONG_NAME_MASK ssl_bug_tweaks[] = {
+
+#ifndef SSL_OP_MICROSOFT_SESS_ID_BUG
+#define SSL_OP_MICROSOFT_SESS_ID_BUG 0
+#endif
+ NAMEBUG(MICROSOFT_SESS_ID_BUG),
+
+#ifndef SSL_OP_NETSCAPE_CHALLENGE_BUG
+#define SSL_OP_NETSCAPE_CHALLENGE_BUG 0
+#endif
+ NAMEBUG(NETSCAPE_CHALLENGE_BUG),
+
+#ifndef SSL_OP_LEGACY_SERVER_CONNECT
+#define SSL_OP_LEGACY_SERVER_CONNECT 0
+#endif
+ NAMEBUG(LEGACY_SERVER_CONNECT),
+
+#ifndef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+#define SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG 0
+#endif
+ NAMEBUG(NETSCAPE_REUSE_CIPHER_CHANGE_BUG),
+ "CVE-2010-4180", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG,
+
+#ifndef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+#define SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG 0
+#endif
+ NAMEBUG(SSLREF2_REUSE_CERT_TYPE_BUG),
+
+#ifndef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+#define SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER 0
+#endif
+ NAMEBUG(MICROSOFT_BIG_SSLV3_BUFFER),
+
+#ifndef SSL_OP_MSIE_SSLV2_RSA_PADDING
+#define SSL_OP_MSIE_SSLV2_RSA_PADDING 0
+#endif
+ NAMEBUG(MSIE_SSLV2_RSA_PADDING),
+ "CVE-2005-2969", SSL_OP_MSIE_SSLV2_RSA_PADDING,
+
+#ifndef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+#define SSL_OP_SSLEAY_080_CLIENT_DH_BUG 0
+#endif
+ NAMEBUG(SSLEAY_080_CLIENT_DH_BUG),
+
+#ifndef SSL_OP_TLS_D5_BUG
+#define SSL_OP_TLS_D5_BUG 0
+#endif
+ NAMEBUG(TLS_D5_BUG),
+
+#ifndef SSL_OP_TLS_BLOCK_PADDING_BUG
+#define SSL_OP_TLS_BLOCK_PADDING_BUG 0
+#endif
+ NAMEBUG(TLS_BLOCK_PADDING_BUG),
+
+#ifndef SSL_OP_TLS_ROLLBACK_BUG
+#define SSL_OP_TLS_ROLLBACK_BUG 0
+#endif
+ NAMEBUG(TLS_ROLLBACK_BUG),
+
+#ifndef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+#define SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS 0
+#endif
+ NAMEBUG(DONT_INSERT_EMPTY_FRAGMENTS),
+
+#ifndef SSL_OP_CRYPTOPRO_TLSEXT_BUG
+#define SSL_OP_CRYPTOPRO_TLSEXT_BUG 0
+#endif
+ NAMEBUG(CRYPTOPRO_TLSEXT_BUG),
+
+#ifndef SSL_OP_TLSEXT_PADDING
+#define SSL_OP_TLSEXT_PADDING 0
+#endif
+ NAMEBUG(TLSEXT_PADDING),
+
+#if 0
+
+ /*
+ * XXX: New with OpenSSL 1.1.1, this is turned on implicitly in
+ * SSL_CTX_new() and is not included in SSL_OP_ALL. Allowing users to
+ * disable this would thus be a code change that would require clearing
+ * bug work-around bits in SSL_CTX, after setting SSL_OP_ALL. Since this
+ * is presumably required for TLS 1.3 on today's Internet, the code
+ * change will be done separately later. For now this implicit bug
+ * work-around cannot be disabled via supported Postfix mechanisms.
+ */
+#ifndef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+#define SSL_OP_ENABLE_MIDDLEBOX_COMPAT 0
+#endif
+ NAMEBUG(ENABLE_MIDDLEBOX_COMPAT),
+#endif
+
+ 0, 0,
+};
+
+ /*
+ * SSL_OP_MUMBLE option name <=> mask conversion for options that are not
+ * (or may in the future not be) in SSL_OP_ALL. These enable optional
+ * behavior, rather than bug interoperability work-arounds.
+ */
+#define NAME_SSL_OP(x) #x, SSL_OP_##x
+static const LONG_NAME_MASK ssl_op_tweaks[] = {
+
+#ifndef SSL_OP_LEGACY_SERVER_CONNECT
+#define SSL_OP_LEGACY_SERVER_CONNECT 0
+#endif
+ NAME_SSL_OP(LEGACY_SERVER_CONNECT),
+
+#ifndef SSL_OP_NO_TICKET
+#define SSL_OP_NO_TICKET 0
+#endif
+ NAME_SSL_OP(NO_TICKET),
+
+#ifndef SSL_OP_NO_COMPRESSION
+#define SSL_OP_NO_COMPRESSION 0
+#endif
+ NAME_SSL_OP(NO_COMPRESSION),
+
+#ifndef SSL_OP_NO_RENEGOTIATION
+#define SSL_OP_NO_RENEGOTIATION 0
+#endif
+ NAME_SSL_OP(NO_RENEGOTIATION),
+
+#ifndef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+#define SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION 0
+#endif
+ NAME_SSL_OP(NO_SESSION_RESUMPTION_ON_RENEGOTIATION),
+
+#ifndef SSL_OP_PRIORITIZE_CHACHA
+#define SSL_OP_PRIORITIZE_CHACHA 0
+#endif
+ NAME_SSL_OP(PRIORITIZE_CHACHA),
+
+#ifndef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+#define SSL_OP_ENABLE_MIDDLEBOX_COMPAT 0
+#endif
+ NAME_SSL_OP(ENABLE_MIDDLEBOX_COMPAT),
+
+ 0, 0,
+};
+
+ /*
+ * Once these have been a NOOP long enough, they might some day be removed
+ * from OpenSSL. The defines below will avoid bitrot issues if/when that
+ * happens.
+ */
+#ifndef SSL_OP_SINGLE_DH_USE
+#define SSL_OP_SINGLE_DH_USE 0
+#endif
+#ifndef SSL_OP_SINGLE_ECDH_USE
+#define SSL_OP_SINGLE_ECDH_USE 0
+#endif
+
+ /*
+ * Ciphersuite name <=> code conversion.
+ */
+const NAME_CODE tls_cipher_grade_table[] = {
+ "high", TLS_CIPHER_HIGH,
+ "medium", TLS_CIPHER_MEDIUM,
+ "low", TLS_CIPHER_LOW,
+ "export", TLS_CIPHER_EXPORT,
+ "null", TLS_CIPHER_NULL,
+ "invalid", TLS_CIPHER_NONE,
+ 0, TLS_CIPHER_NONE,
+};
+
+ /*
+ * Log keyword <=> mask conversion.
+ */
+#define TLS_LOG_0 TLS_LOG_NONE
+#define TLS_LOG_1 TLS_LOG_SUMMARY
+#define TLS_LOG_2 (TLS_LOG_1 | TLS_LOG_VERBOSE | TLS_LOG_CACHE | TLS_LOG_DEBUG)
+#define TLS_LOG_3 (TLS_LOG_2 | TLS_LOG_TLSPKTS)
+#define TLS_LOG_4 (TLS_LOG_3 | TLS_LOG_ALLPKTS)
+
+static const NAME_MASK tls_log_table[] = {
+ "0", TLS_LOG_0,
+ "none", TLS_LOG_NONE,
+ "1", TLS_LOG_1,
+ "routine", TLS_LOG_1,
+ "2", TLS_LOG_2,
+ "debug", TLS_LOG_2,
+ "3", TLS_LOG_3,
+ "ssl-expert", TLS_LOG_3,
+ "4", TLS_LOG_4,
+ "ssl-developer", TLS_LOG_4,
+ "5", TLS_LOG_4, /* for good measure */
+ "6", TLS_LOG_4, /* for good measure */
+ "7", TLS_LOG_4, /* for good measure */
+ "8", TLS_LOG_4, /* for good measure */
+ "9", TLS_LOG_4, /* for good measure */
+ "summary", TLS_LOG_SUMMARY,
+ "untrusted", TLS_LOG_UNTRUSTED,
+ "peercert", TLS_LOG_PEERCERT,
+ "certmatch", TLS_LOG_CERTMATCH,
+ "verbose", TLS_LOG_VERBOSE, /* Postfix TLS library verbose */
+ "cache", TLS_LOG_CACHE,
+ "ssl-debug", TLS_LOG_DEBUG, /* SSL library debug/verbose */
+ "ssl-handshake-packet-dump", TLS_LOG_TLSPKTS,
+ "ssl-session-packet-dump", TLS_LOG_TLSPKTS | TLS_LOG_ALLPKTS,
+ 0, 0,
+};
+
+ /*
+ * Parsed OpenSSL version number.
+ */
+typedef struct {
+ int major;
+ int minor;
+ int micro;
+ int patch;
+ int status;
+} TLS_VINFO;
+
+/* tls_log_mask - Convert user TLS loglevel to internal log feature mask */
+
+int tls_log_mask(const char *log_param, const char *log_level)
+{
+ int mask;
+
+ mask = name_mask_opt(log_param, tls_log_table, log_level,
+ NAME_MASK_ANY_CASE | NAME_MASK_RETURN);
+ return (mask);
+}
+
+/* tls_update_app_logmask - update log level after init */
+
+void tls_update_app_logmask(TLS_APPL_STATE *app_ctx, int log_mask)
+{
+ app_ctx->log_mask = log_mask;
+}
+
+/* tls_protocol_mask - Bitmask of protocols to exclude */
+
+int tls_protocol_mask(const char *plist)
+{
+ char *save;
+ char *tok;
+ char *cp;
+ int code;
+ int exclude = 0;
+ int include = 0;
+
+#define FREE_AND_RETURN(ptr, res) do { \
+ myfree(ptr); \
+ return (res); \
+ } while (0)
+
+ save = cp = mystrdup(plist);
+ while ((tok = mystrtok(&cp, CHARS_COMMA_SP ":")) != 0) {
+ if (*tok == '!')
+ exclude |= code =
+ name_code(protocol_table, NAME_CODE_FLAG_NONE, ++tok);
+ else
+ include |= code =
+ name_code(protocol_table, NAME_CODE_FLAG_NONE, tok);
+ if (code == TLS_PROTOCOL_INVALID)
+ FREE_AND_RETURN(save, TLS_PROTOCOL_INVALID);
+ }
+
+ /*
+ * When the include list is empty, use only the explicit exclusions.
+ * Otherwise, also exclude the complement of the include list from the
+ * built-in list of known protocols. There is no way to exclude protocols
+ * we don't know about at compile time, and this is unavoidable because
+ * the OpenSSL API works with compile-time *exclusion* bit-masks.
+ */
+ FREE_AND_RETURN(save,
+ (include ? (exclude | (TLS_KNOWN_PROTOCOLS & ~include)) : exclude));
+}
+
+/* tls_param_init - Load TLS related config parameters */
+
+void tls_param_init(void)
+{
+ /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TLS_CNF_FILE, DEF_TLS_CNF_FILE, &var_tls_cnf_file, 0, 0,
+ VAR_TLS_CNF_NAME, DEF_TLS_CNF_NAME, &var_tls_cnf_name, 0, 0,
+ VAR_TLS_HIGH_CLIST, DEF_TLS_HIGH_CLIST, &var_tls_high_clist, 1, 0,
+ VAR_TLS_MEDIUM_CLIST, DEF_TLS_MEDIUM_CLIST, &var_tls_medium_clist, 1, 0,
+ VAR_TLS_LOW_CLIST, DEF_TLS_LOW_CLIST, &var_tls_low_clist, 1, 0,
+ VAR_TLS_EXPORT_CLIST, DEF_TLS_EXPORT_CLIST, &var_tls_export_clist, 1, 0,
+ VAR_TLS_NULL_CLIST, DEF_TLS_NULL_CLIST, &var_tls_null_clist, 1, 0,
+ VAR_TLS_EECDH_AUTO, DEF_TLS_EECDH_AUTO, &var_tls_eecdh_auto, 1, 0,
+ VAR_TLS_EECDH_STRONG, DEF_TLS_EECDH_STRONG, &var_tls_eecdh_strong, 1, 0,
+ VAR_TLS_EECDH_ULTRA, DEF_TLS_EECDH_ULTRA, &var_tls_eecdh_ultra, 1, 0,
+ VAR_TLS_BUG_TWEAKS, DEF_TLS_BUG_TWEAKS, &var_tls_bug_tweaks, 0, 0,
+ VAR_TLS_SSL_OPTIONS, DEF_TLS_SSL_OPTIONS, &var_tls_ssl_options, 0, 0,
+ VAR_TLS_DANE_DIGESTS, DEF_TLS_DANE_DIGESTS, &var_tls_dane_digests, 1, 0,
+ VAR_TLS_MGR_SERVICE, DEF_TLS_MGR_SERVICE, &var_tls_mgr_service, 1, 0,
+ VAR_TLS_TKT_CIPHER, DEF_TLS_TKT_CIPHER, &var_tls_tkt_cipher, 0, 0,
+ VAR_OPENSSL_PATH, DEF_OPENSSL_PATH, &var_openssl_path, 1, 0,
+ 0,
+ };
+
+ /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_TLS_DAEMON_RAND_BYTES, DEF_TLS_DAEMON_RAND_BYTES, &var_tls_daemon_rand_bytes, 1, 0,
+ 0,
+ };
+
+ /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_TLS_APPEND_DEF_CA, DEF_TLS_APPEND_DEF_CA, &var_tls_append_def_CA,
+ VAR_TLS_BC_PKEY_FPRINT, DEF_TLS_BC_PKEY_FPRINT, &var_tls_bc_pkey_fprint,
+ VAR_TLS_PREEMPT_CLIST, DEF_TLS_PREEMPT_CLIST, &var_tls_preempt_clist,
+ VAR_TLS_MULTI_WILDCARD, DEF_TLS_MULTI_WILDCARD, &var_tls_multi_wildcard,
+ VAR_TLS_FAST_SHUTDOWN, DEF_TLS_FAST_SHUTDOWN, &var_tls_fast_shutdown,
+ 0,
+ };
+ static int init_done;
+
+ if (init_done)
+ return;
+ init_done = 1;
+
+ get_mail_conf_str_table(str_table);
+ get_mail_conf_int_table(int_table);
+ get_mail_conf_bool_table(bool_table);
+}
+
+/* tls_library_init - perform OpenSSL library initialization */
+
+int tls_library_init(void)
+{
+ OPENSSL_INIT_SETTINGS *init_settings;
+ char *conf_name = *var_tls_cnf_name ? var_tls_cnf_name : 0;
+ char *conf_file = 0;
+ unsigned long init_opts = 0;
+
+#define TLS_LIB_INIT_TODO (-1)
+#define TLS_LIB_INIT_ERR (0)
+#define TLS_LIB_INIT_OK (1)
+
+ static int init_res = TLS_LIB_INIT_TODO;
+
+ if (init_res != TLS_LIB_INIT_TODO)
+ return (init_res);
+
+ /*
+ * Backwards compatibility: skip this function unless the Postfix
+ * configuration actually has non-default tls_config_xxx settings.
+ */
+ if (strcmp(var_tls_cnf_file, DEF_TLS_CNF_FILE) == 0
+ && strcmp(var_tls_cnf_name, DEF_TLS_CNF_NAME) == 0) {
+ if (msg_verbose)
+ msg_info("tls_library_init: using backwards-compatible defaults");
+ return (init_res = TLS_LIB_INIT_OK);
+ }
+ if ((init_settings = OPENSSL_INIT_new()) == 0) {
+ msg_warn("error allocating OpenSSL init settings, "
+ "disabling TLS support");
+ return (init_res = TLS_LIB_INIT_ERR);
+ }
+#define TLS_LIB_INIT_RETURN(x) \
+ do { OPENSSL_INIT_free(init_settings); return (init_res = (x)); } while(0)
+
+#if OPENSSL_VERSION_NUMBER < 0x1010102fL
+
+ /*
+ * OpenSSL 1.1.0 through 1.1.1a, no support for custom configuration
+ * files, disabling loading of the file, or getting strict error
+ * handling. Thus, the only supported configuration file is "default".
+ */
+ if (strcmp(var_tls_cnf_file, "default") != 0) {
+ msg_warn("non-default %s = %s requires OpenSSL 1.1.1b or later, "
+ "disabling TLS support", VAR_TLS_CNF_FILE, var_tls_cnf_file);
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR);
+ }
+#else
+ {
+ unsigned long file_flags = 0;
+
+ /*-
+ * OpenSSL 1.1.1b or later:
+ * We can now use a non-default configuration file, or
+ * use none at all. We can also request strict error
+ * reporting.
+ */
+ if (strcmp(var_tls_cnf_file, "none") == 0) {
+ init_opts |= OPENSSL_INIT_NO_LOAD_CONFIG;
+ } else if (strcmp(var_tls_cnf_file, "default") == 0) {
+
+ /*
+ * The default global config file is optional. With "default"
+ * initialisation we don't insist on a match for the requested
+ * application name, allowing fallback to the default application
+ * name, even when a non-default application name is specified.
+ * Errors in loading the default configuration are ignored.
+ */
+ conf_file = 0;
+ file_flags |= CONF_MFLAGS_IGNORE_MISSING_FILE;
+ file_flags |= CONF_MFLAGS_DEFAULT_SECTION;
+ file_flags |= CONF_MFLAGS_IGNORE_RETURN_CODES | CONF_MFLAGS_SILENT;
+ } else if (*var_tls_cnf_file == '/') {
+
+ /*
+ * A custom config file must be present, error reporting is
+ * strict and the configuration section for the requested
+ * application name does not fall back to "openssl_conf" when
+ * missing.
+ */
+ conf_file = var_tls_cnf_file;
+ } else {
+ msg_warn("non-default %s = %s is not an absolute pathname, "
+ "disabling TLS support", VAR_TLS_CNF_FILE, var_tls_cnf_file);
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR);
+ }
+
+ OPENSSL_INIT_set_config_file_flags(init_settings, file_flags);
+ }
+#endif
+
+ if (conf_file)
+ OPENSSL_INIT_set_config_filename(init_settings, conf_file);
+ if (conf_name)
+ OPENSSL_INIT_set_config_appname(init_settings, conf_name);
+
+ if (OPENSSL_init_ssl(init_opts, init_settings) <= 0) {
+ if ((init_opts & OPENSSL_INIT_NO_LOAD_CONFIG) == 0)
+ msg_warn("error loading the '%s' settings from the %s OpenSSL "
+ "configuration file, disabling TLS support",
+ conf_name ? conf_name : "global",
+ conf_file ? conf_file : "default");
+ else
+ msg_warn("error initializing the OpenSSL library, "
+ "disabling TLS support");
+ tls_print_errors();
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR);
+ }
+ TLS_LIB_INIT_RETURN(TLS_LIB_INIT_OK);
+}
+
+/* tls_pre_jail_init - Load TLS related pre-jail tables */
+
+void tls_pre_jail_init(TLS_ROLE role)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TLS_SERVER_SNI_MAPS, DEF_TLS_SERVER_SNI_MAPS, &var_tls_server_sni_maps, 0, 0,
+ 0,
+ };
+ int flags;
+
+ tls_param_init();
+
+ /* Nothing for clients at this time */
+ if (role != TLS_ROLE_SERVER)
+ return;
+
+ get_mail_conf_str_table(str_table);
+ if (*var_tls_server_sni_maps == 0)
+ return;
+
+ flags = DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX | DICT_FLAG_SRC_RHS_IS_FILE;
+ tls_server_sni_maps =
+ maps_create(VAR_TLS_SERVER_SNI_MAPS, var_tls_server_sni_maps, flags);
+}
+
+/* server_sni_callback - process client's SNI extension */
+
+static int server_sni_callback(SSL *ssl, int *alert, void *arg)
+{
+ SSL_CTX *sni_ctx = (SSL_CTX *) arg;
+ TLS_SESS_STATE *TLScontext = SSL_get_ex_data(ssl, TLScontext_index);
+ const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ const char *cp = sni;
+ const char *pem;
+
+ /* SNI is silently ignored when we don't care or is NULL or empty */
+ if (!sni_ctx || !tls_server_sni_maps || !sni || !*sni)
+ return SSL_TLSEXT_ERR_NOACK;
+
+ if (!valid_hostname(sni, DONT_GRIPE)) {
+ msg_warn("TLS SNI from %s is invalid: %s",
+ TLScontext->namaddr, sni);
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ /*
+ * With TLS 1.3, when the client's proposed key share is not supported by
+ * the server, the server may issue a HelloRetryRequest (HRR), and the
+ * client will then retry with a new key share on a curve supported by
+ * the server. This results in the SNI callback running twice for the
+ * same connection.
+ *
+ * When that happens, The client MUST send the essentially the same hello
+ * message, including the SNI name, and since we've already loaded our
+ * certificate chain, we don't need to do it again! Therefore, if we've
+ * already recorded the peer SNI name, just check that it has not
+ * changed, and return success.
+ */
+ if (TLScontext->peer_sni) {
+ if (strcmp(sni, TLScontext->peer_sni) == 0)
+ return SSL_TLSEXT_ERR_OK;
+ msg_warn("TLS SNI changed from %s initially %s, %s after hello retry",
+ TLScontext->namaddr, TLScontext->peer_sni, sni);
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ do {
+ /* Don't silently skip maps opened with the wrong flags. */
+ pem = maps_file_find(tls_server_sni_maps, cp, 0);
+ } while (!pem
+ && !tls_server_sni_maps->error
+ && (cp = strchr(cp + 1, '.')) != 0);
+
+ if (!pem) {
+ if (tls_server_sni_maps->error) {
+ msg_warn("%s: %s map lookup problem",
+ tls_server_sni_maps->title, sni);
+ *alert = SSL_AD_INTERNAL_ERROR;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ msg_info("TLS SNI %s from %s not matched, using default chain",
+ sni, TLScontext->namaddr);
+
+ /*
+ * XXX: We could lie and pretend to accept the name, but since we've
+ * previously not implemented the callback (with OpenSSL then
+ * declining the extension), and nothing bad happened, declining it
+ * explicitly should be safe.
+ */
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ SSL_set_SSL_CTX(ssl, sni_ctx);
+ if (tls_load_pem_chain(ssl, pem, sni) != 0) {
+ /* errors already logged */
+ *alert = SSL_AD_INTERNAL_ERROR;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ TLScontext->peer_sni = mystrdup(sni);
+ return SSL_TLSEXT_ERR_OK;
+}
+
+/* tls_set_ciphers - Set SSL context cipher list */
+
+const char *tls_set_ciphers(TLS_SESS_STATE *TLScontext, const char *grade,
+ const char *exclusions)
+{
+ const char *myname = "tls_set_ciphers";
+ static VSTRING *buf;
+ char *save;
+ char *cp;
+ char *tok;
+
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ VSTRING_RESET(buf);
+
+ switch (tls_cipher_grade(grade)) {
+ case TLS_CIPHER_NONE:
+ msg_warn("%s: invalid cipher grade: \"%s\"",
+ TLScontext->namaddr, grade);
+ return (0);
+ case TLS_CIPHER_HIGH:
+ vstring_strcpy(buf, var_tls_high_clist);
+ break;
+ case TLS_CIPHER_MEDIUM:
+ vstring_strcpy(buf, var_tls_medium_clist);
+ break;
+ case TLS_CIPHER_LOW:
+ vstring_strcpy(buf, var_tls_low_clist);
+ break;
+ case TLS_CIPHER_EXPORT:
+ vstring_strcpy(buf, var_tls_export_clist);
+ break;
+ case TLS_CIPHER_NULL:
+ vstring_strcpy(buf, var_tls_null_clist);
+ break;
+ default:
+ /* Internal error, valid grade, but missing case label. */
+ msg_panic("%s: unexpected cipher grade: %s", myname, grade);
+ }
+
+ /*
+ * The base lists for each grade can't be empty.
+ */
+ if (VSTRING_LEN(buf) == 0)
+ msg_panic("%s: empty \"%s\" cipherlist", myname, grade);
+
+ /*
+ * Apply locally-specified exclusions.
+ */
+#define CIPHER_SEP CHARS_COMMA_SP ":"
+ if (exclusions != 0) {
+ cp = save = mystrdup(exclusions);
+ while ((tok = mystrtok(&cp, CIPHER_SEP)) != 0) {
+
+ /*
+ * Can't exclude ciphers that start with modifiers.
+ */
+ if (strchr("!+-@", *tok)) {
+ msg_warn("%s: invalid unary '!+-@' in cipher exclusion: %s",
+ TLScontext->namaddr, tok);
+ return (0);
+ }
+ vstring_sprintf_append(buf, ":!%s", tok);
+ }
+ myfree(save);
+ }
+ ERR_clear_error();
+ if (SSL_set_cipher_list(TLScontext->con, vstring_str(buf)) == 0) {
+ msg_warn("%s: error setting cipher grade: \"%s\"",
+ TLScontext->namaddr, grade);
+ tls_print_errors();
+ return (0);
+ }
+ return (vstring_str(buf));
+}
+
+/* tls_get_signature_params - TLS 1.3 signature details */
+
+void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x1010100fUL && defined(TLS1_3_VERSION)
+ const char *kex_name = 0;
+ const char *kex_curve = 0;
+ const char *locl_sig_name = 0;
+ const char *locl_sig_curve = 0;
+ const char *locl_sig_dgst = 0;
+ const char *peer_sig_name = 0;
+ const char *peer_sig_curve = 0;
+ const char *peer_sig_dgst = 0;
+ int nid;
+ SSL *ssl = TLScontext->con;
+ int srvr = SSL_is_server(ssl);
+ X509 *cert;
+ EVP_PKEY *pkey = 0;
+
+#ifndef OPENSSL_NO_EC
+ const EC_KEY *eckey;
+
+#endif
+
+#define SIG_PROP(c, s, p) (*((s) ? &c->srvr_sig_##p : &c->clnt_sig_##p))
+
+ if (SSL_version(ssl) < TLS1_3_VERSION)
+ return;
+
+ if (tls_get_peer_dh_pubkey(ssl, &pkey)) {
+ switch (nid = EVP_PKEY_id(pkey)) {
+ default:
+ kex_name = OBJ_nid2sn(EVP_PKEY_type(nid));
+ break;
+
+ case EVP_PKEY_DH:
+ kex_name = "DHE";
+ TLScontext->kex_bits = EVP_PKEY_bits(pkey);
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ kex_name = "ECDHE";
+ eckey = EVP_PKEY_get0_EC_KEY(pkey);
+ nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey));
+ kex_curve = EC_curve_nid2nist(nid);
+ if (!kex_curve)
+ kex_curve = OBJ_nid2sn(nid);
+ break;
+#endif
+ }
+ EVP_PKEY_free(pkey);
+ }
+
+ /*
+ * On the client end, the certificate may be preset, but not used, so we
+ * check via SSL_get_signature_nid(). This means that local signature
+ * data on clients requires at least 1.1.1a.
+ */
+ if (srvr || SSL_get_signature_nid(ssl, &nid))
+ cert = SSL_get_certificate(ssl);
+ else
+ cert = 0;
+
+ /* Signature algorithms for the local end of the connection */
+ if (cert) {
+ pkey = X509_get0_pubkey(cert);
+
+ /*
+ * Override the built-in name for the "ECDSA" algorithms OID, with
+ * the more familiar name. For "RSA" keys report "RSA-PSS", which
+ * must be used with TLS 1.3.
+ */
+ if ((nid = EVP_PKEY_type(EVP_PKEY_id(pkey))) != NID_undef) {
+ switch (nid) {
+ default:
+ locl_sig_name = OBJ_nid2sn(nid);
+ break;
+
+ case EVP_PKEY_RSA:
+ /* For RSA, TLS 1.3 mandates PSS signatures */
+ locl_sig_name = "RSA-PSS";
+ SIG_PROP(TLScontext, srvr, bits) = EVP_PKEY_bits(pkey);
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ locl_sig_name = "ECDSA";
+ eckey = EVP_PKEY_get0_EC_KEY(pkey);
+ nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey));
+ locl_sig_curve = EC_curve_nid2nist(nid);
+ if (!locl_sig_curve)
+ locl_sig_curve = OBJ_nid2sn(nid);
+ break;
+#endif
+ }
+ }
+
+ /*
+ * With Ed25519 and Ed448 there is no pre-signature digest, but the
+ * accessor does not fail, rather we get NID_undef.
+ */
+ if (SSL_get_signature_nid(ssl, &nid) && nid != NID_undef)
+ locl_sig_dgst = OBJ_nid2sn(nid);
+ }
+ /* Signature algorithms for the peer end of the connection */
+ if ((cert = SSL_get_peer_certificate(ssl)) != 0) {
+ pkey = X509_get0_pubkey(cert);
+
+ /*
+ * Override the built-in name for the "ECDSA" algorithms OID, with
+ * the more familiar name. For "RSA" keys report "RSA-PSS", which
+ * must be used with TLS 1.3.
+ */
+ if ((nid = EVP_PKEY_type(EVP_PKEY_id(pkey))) != NID_undef) {
+ switch (nid) {
+ default:
+ peer_sig_name = OBJ_nid2sn(nid);
+ break;
+
+ case EVP_PKEY_RSA:
+ /* For RSA, TLS 1.3 mandates PSS signatures */
+ peer_sig_name = "RSA-PSS";
+ SIG_PROP(TLScontext, !srvr, bits) = EVP_PKEY_bits(pkey);
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ peer_sig_name = "ECDSA";
+ eckey = EVP_PKEY_get0_EC_KEY(pkey);
+ nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey));
+ peer_sig_curve = EC_curve_nid2nist(nid);
+ if (!peer_sig_curve)
+ peer_sig_curve = OBJ_nid2sn(nid);
+ break;
+#endif
+ }
+ }
+
+ /*
+ * With Ed25519 and Ed448 there is no pre-signature digest, but the
+ * accessor does not fail, rather we get NID_undef.
+ */
+ if (SSL_get_peer_signature_nid(ssl, &nid) && nid != NID_undef)
+ peer_sig_dgst = OBJ_nid2sn(nid);
+
+ X509_free(cert);
+ }
+ if (kex_name) {
+ TLScontext->kex_name = mystrdup(kex_name);
+ if (kex_curve)
+ TLScontext->kex_curve = mystrdup(kex_curve);
+ }
+ if (locl_sig_name) {
+ SIG_PROP(TLScontext, srvr, name) = mystrdup(locl_sig_name);
+ if (locl_sig_curve)
+ SIG_PROP(TLScontext, srvr, curve) = mystrdup(locl_sig_curve);
+ if (locl_sig_dgst)
+ SIG_PROP(TLScontext, srvr, dgst) = mystrdup(locl_sig_dgst);
+ }
+ if (peer_sig_name) {
+ SIG_PROP(TLScontext, !srvr, name) = mystrdup(peer_sig_name);
+ if (peer_sig_curve)
+ SIG_PROP(TLScontext, !srvr, curve) = mystrdup(peer_sig_curve);
+ if (peer_sig_dgst)
+ SIG_PROP(TLScontext, !srvr, dgst) = mystrdup(peer_sig_dgst);
+ }
+#endif /* OPENSSL_VERSION_NUMBER ... */
+}
+
+/* tls_log_summary - TLS loglevel 1 one-liner, embellished with TLS 1.3 details */
+
+void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
+{
+ VSTRING *msg = vstring_alloc(100);
+ const char *direction = (role == TLS_ROLE_CLIENT) ? "to" : "from";
+ const char *sni = (role == TLS_ROLE_CLIENT) ? 0 : ctx->peer_sni;
+
+ /*
+ * When SNI was sent and accepted, the server-side log message now
+ * includes a "to <sni-name>" detail after the "from <namaddr>" detail
+ * identifying the remote client. We don't presently log (purportedly)
+ * accepted SNI on the client side.
+ */
+ vstring_sprintf(msg, "%s TLS connection %s %s %s%s%s: %s"
+ " with cipher %s (%d/%d bits)",
+ !TLS_CERT_IS_PRESENT(ctx) ? "Anonymous" :
+ TLS_CERT_IS_SECURED(ctx) ? "Verified" :
+ TLS_CERT_IS_TRUSTED(ctx) ? "Trusted" : "Untrusted",
+ usage == TLS_USAGE_NEW ? "established" : "reused",
+ direction, ctx->namaddr, sni ? " to " : "", sni ? sni : "",
+ ctx->protocol, ctx->cipher_name, ctx->cipher_usebits,
+ ctx->cipher_algbits);
+
+ if (ctx->kex_name && *ctx->kex_name) {
+ vstring_sprintf_append(msg, " key-exchange %s", ctx->kex_name);
+ if (ctx->kex_curve && *ctx->kex_curve)
+ vstring_sprintf_append(msg, " (%s)", ctx->kex_curve);
+ else if (ctx->kex_bits > 0)
+ vstring_sprintf_append(msg, " (%d bits)", ctx->kex_bits);
+ }
+ if (ctx->srvr_sig_name && *ctx->srvr_sig_name) {
+ vstring_sprintf_append(msg, " server-signature %s",
+ ctx->srvr_sig_name);
+ if (ctx->srvr_sig_curve && *ctx->srvr_sig_curve)
+ vstring_sprintf_append(msg, " (%s)", ctx->srvr_sig_curve);
+ else if (ctx->srvr_sig_bits > 0)
+ vstring_sprintf_append(msg, " (%d bits)", ctx->srvr_sig_bits);
+ if (ctx->srvr_sig_dgst && *ctx->srvr_sig_dgst)
+ vstring_sprintf_append(msg, " server-digest %s",
+ ctx->srvr_sig_dgst);
+ }
+ if (ctx->clnt_sig_name && *ctx->clnt_sig_name) {
+ vstring_sprintf_append(msg, " client-signature %s",
+ ctx->clnt_sig_name);
+ if (ctx->clnt_sig_curve && *ctx->clnt_sig_curve)
+ vstring_sprintf_append(msg, " (%s)", ctx->clnt_sig_curve);
+ else if (ctx->clnt_sig_bits > 0)
+ vstring_sprintf_append(msg, " (%d bits)", ctx->clnt_sig_bits);
+ if (ctx->clnt_sig_dgst && *ctx->clnt_sig_dgst)
+ vstring_sprintf_append(msg, " client-digest %s",
+ ctx->clnt_sig_dgst);
+ }
+ msg_info("%s", vstring_str(msg));
+ vstring_free(msg);
+}
+
+/* tls_alloc_app_context - allocate TLS application context */
+
+TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *ssl_ctx, SSL_CTX *sni_ctx,
+ int log_mask)
+{
+ TLS_APPL_STATE *app_ctx;
+
+ app_ctx = (TLS_APPL_STATE *) mymalloc(sizeof(*app_ctx));
+
+ /* See portability note below with other memset() call. */
+ memset((void *) app_ctx, 0, sizeof(*app_ctx));
+ app_ctx->ssl_ctx = ssl_ctx;
+ app_ctx->sni_ctx = sni_ctx;
+ app_ctx->log_mask = log_mask;
+
+ /* See also: cache purging code in tls_set_ciphers(). */
+ app_ctx->cache_type = 0;
+
+ if (tls_server_sni_maps) {
+ SSL_CTX_set_tlsext_servername_callback(ssl_ctx, server_sni_callback);
+ SSL_CTX_set_tlsext_servername_arg(ssl_ctx, (void *) sni_ctx);
+ }
+ return (app_ctx);
+}
+
+/* tls_free_app_context - Free TLS application context */
+
+void tls_free_app_context(TLS_APPL_STATE *app_ctx)
+{
+ if (app_ctx->ssl_ctx)
+ SSL_CTX_free(app_ctx->ssl_ctx);
+ if (app_ctx->sni_ctx)
+ SSL_CTX_free(app_ctx->sni_ctx);
+ if (app_ctx->cache_type)
+ myfree(app_ctx->cache_type);
+ myfree((void *) app_ctx);
+}
+
+/* tls_alloc_sess_context - allocate TLS session context */
+
+TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr)
+{
+ TLS_SESS_STATE *TLScontext;
+
+ /*
+ * PORTABILITY: Do not assume that null pointers are all-zero bits. Use
+ * explicit assignments to initialize pointers.
+ *
+ * See the C language FAQ item 5.17, or if you have time to burn,
+ * http://www.google.com/search?q=zero+bit+null+pointer
+ *
+ * However, it's OK to use memset() to zero integer values.
+ */
+ TLScontext = (TLS_SESS_STATE *) mymalloc(sizeof(TLS_SESS_STATE));
+ memset((void *) TLScontext, 0, sizeof(*TLScontext));
+ TLScontext->con = 0;
+ TLScontext->cache_type = 0;
+ TLScontext->serverid = 0;
+ TLScontext->peer_CN = 0;
+ TLScontext->issuer_CN = 0;
+ TLScontext->peer_sni = 0;
+ TLScontext->peer_cert_fprint = 0;
+ TLScontext->peer_pkey_fprint = 0;
+ TLScontext->protocol = 0;
+ TLScontext->cipher_name = 0;
+ TLScontext->kex_name = 0;
+ TLScontext->kex_curve = 0;
+ TLScontext->clnt_sig_name = 0;
+ TLScontext->clnt_sig_curve = 0;
+ TLScontext->clnt_sig_dgst = 0;
+ TLScontext->srvr_sig_name = 0;
+ TLScontext->srvr_sig_curve = 0;
+ TLScontext->srvr_sig_dgst = 0;
+ TLScontext->log_mask = log_mask;
+ TLScontext->namaddr = lowercase(mystrdup(namaddr));
+ TLScontext->mdalg = 0; /* Alias for props->mdalg */
+ TLScontext->dane = 0; /* Alias for props->dane */
+ TLScontext->errordepth = -1;
+ TLScontext->tadepth = -1;
+ TLScontext->errorcode = X509_V_OK;
+ TLScontext->errorcert = 0;
+ TLScontext->untrusted = 0;
+ TLScontext->trusted = 0;
+
+ return (TLScontext);
+}
+
+/* tls_free_context - deallocate TLScontext and members */
+
+void tls_free_context(TLS_SESS_STATE *TLScontext)
+{
+
+ /*
+ * Free the SSL structure and the BIOs. Warning: the internal_bio is
+ * connected to the SSL structure and is automatically freed with it. Do
+ * not free it again (core dump)!! Only free the network_bio.
+ */
+ if (TLScontext->con != 0)
+ SSL_free(TLScontext->con);
+
+ if (TLScontext->namaddr)
+ myfree(TLScontext->namaddr);
+ if (TLScontext->serverid)
+ myfree(TLScontext->serverid);
+
+ if (TLScontext->peer_CN)
+ myfree(TLScontext->peer_CN);
+ if (TLScontext->issuer_CN)
+ myfree(TLScontext->issuer_CN);
+ if (TLScontext->peer_sni)
+ myfree(TLScontext->peer_sni);
+ if (TLScontext->peer_cert_fprint)
+ myfree(TLScontext->peer_cert_fprint);
+ if (TLScontext->peer_pkey_fprint)
+ myfree(TLScontext->peer_pkey_fprint);
+ if (TLScontext->kex_name)
+ myfree((void *) TLScontext->kex_name);
+ if (TLScontext->kex_curve)
+ myfree((void *) TLScontext->kex_curve);
+ if (TLScontext->clnt_sig_name)
+ myfree((void *) TLScontext->clnt_sig_name);
+ if (TLScontext->clnt_sig_curve)
+ myfree((void *) TLScontext->clnt_sig_curve);
+ if (TLScontext->clnt_sig_dgst)
+ myfree((void *) TLScontext->clnt_sig_dgst);
+ if (TLScontext->srvr_sig_name)
+ myfree((void *) TLScontext->srvr_sig_name);
+ if (TLScontext->srvr_sig_curve)
+ myfree((void *) TLScontext->srvr_sig_curve);
+ if (TLScontext->srvr_sig_dgst)
+ myfree((void *) TLScontext->srvr_sig_dgst);
+ if (TLScontext->errorcert)
+ X509_free(TLScontext->errorcert);
+ if (TLScontext->untrusted)
+ sk_X509_pop_free(TLScontext->untrusted, X509_free);
+ if (TLScontext->trusted)
+ sk_X509_pop_free(TLScontext->trusted, X509_free);
+
+ myfree((void *) TLScontext);
+}
+
+/* tls_version_split - Split OpenSSL version number into major, minor, ... */
+
+static void tls_version_split(unsigned long version, TLS_VINFO *info)
+{
+
+ /*
+ * OPENSSL_VERSION_NUMBER(3):
+ *
+ * OPENSSL_VERSION_NUMBER is a numeric release version identifier:
+ *
+ * MMNNFFPPS: major minor fix patch status
+ *
+ * The status nibble has one of the values 0 for development, 1 to e for
+ * betas 1 to 14, and f for release. Parsed OpenSSL version number. for
+ * example
+ *
+ * 0x000906000 == 0.9.6 dev 0x000906023 == 0.9.6b beta 3 0x00090605f ==
+ * 0.9.6e release
+ *
+ * Versions prior to 0.9.3 have identifiers < 0x0930. Versions between
+ * 0.9.3 and 0.9.5 had a version identifier with this interpretation:
+ *
+ * MMNNFFRBB major minor fix final beta/patch
+ *
+ * for example
+ *
+ * 0x000904100 == 0.9.4 release 0x000905000 == 0.9.5 dev
+ *
+ * Version 0.9.5a had an interim interpretation that is like the current
+ * one, except the patch level got the highest bit set, to keep continu-
+ * ity. The number was therefore 0x0090581f.
+ */
+
+ if (version < 0x0930) {
+ info->status = 0;
+ info->patch = version & 0x0f;
+ version >>= 4;
+ info->micro = version & 0x0f;
+ version >>= 4;
+ info->minor = version & 0x0f;
+ version >>= 4;
+ info->major = version & 0x0f;
+ } else if (version < 0x00905800L) {
+ info->patch = version & 0xff;
+ version >>= 8;
+ info->status = version & 0xf;
+ version >>= 4;
+ info->micro = version & 0xff;
+ version >>= 8;
+ info->minor = version & 0xff;
+ version >>= 8;
+ info->major = version & 0xff;
+ } else {
+ info->status = version & 0xf;
+ version >>= 4;
+ info->patch = version & 0xff;
+ version >>= 8;
+ info->micro = version & 0xff;
+ version >>= 8;
+ info->minor = version & 0xff;
+ version >>= 8;
+ info->major = version & 0xff;
+ if (version < 0x00906000L)
+ info->patch &= ~0x80;
+ }
+}
+
+/* tls_check_version - Detect mismatch between headers and library. */
+
+void tls_check_version(void)
+{
+ TLS_VINFO hdr_info;
+ TLS_VINFO lib_info;
+
+ tls_version_split(OPENSSL_VERSION_NUMBER, &hdr_info);
+ tls_version_split(OpenSSL_version_num(), &lib_info);
+
+ /*
+ * Warn if run-time library is different from compile-time library,
+ * allowing later run-time "micro" versions starting with 1.1.0.
+ */
+ if (lib_info.major != hdr_info.major
+ || lib_info.minor != hdr_info.minor
+ || (lib_info.micro != hdr_info.micro
+ && (lib_info.micro < hdr_info.micro
+ || hdr_info.major == 0
+ || (hdr_info.major == 1 && hdr_info.minor == 0))))
+ msg_warn("run-time library vs. compile-time header version mismatch: "
+ "OpenSSL %d.%d.%d may not be compatible with OpenSSL %d.%d.%d",
+ lib_info.major, lib_info.minor, lib_info.micro,
+ hdr_info.major, hdr_info.minor, hdr_info.micro);
+}
+
+/* tls_compile_version - compile-time OpenSSL version */
+
+const char *tls_compile_version(void)
+{
+ return (OPENSSL_VERSION_TEXT);
+}
+
+/* tls_run_version - run-time version "major.minor.micro" */
+
+const char *tls_run_version(void)
+{
+ return (OpenSSL_version(OPENSSL_VERSION));
+}
+
+const char **tls_pkey_algorithms(void)
+{
+
+ /*
+ * Return an array, not string, so that the result can be inspected
+ * without parsing. Sort the result alphabetically, not chronologically.
+ */
+ static const char *algs[] = {
+#ifndef OPENSSL_NO_DSA
+ "dsa",
+#endif
+#ifndef OPENSSL_NO_ECDSA
+ "ecdsa",
+#endif
+#ifndef OPENSSL_NO_RSA
+ "rsa",
+#endif
+ 0,
+ };
+
+ return (algs);
+}
+
+/* tls_bug_bits - SSL bug compatibility bits for this OpenSSL version */
+
+long tls_bug_bits(void)
+{
+ long bits = SSL_OP_ALL; /* Work around all known bugs */
+
+ /*
+ * Silently ignore any strings that don't appear in the tweaks table, or
+ * hex bits that are not in SSL_OP_ALL.
+ */
+ if (*var_tls_bug_tweaks) {
+ bits &= ~long_name_mask_opt(VAR_TLS_BUG_TWEAKS, ssl_bug_tweaks,
+ var_tls_bug_tweaks, NAME_MASK_ANY_CASE |
+ NAME_MASK_NUMBER | NAME_MASK_WARN);
+#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
+ /* Not relevant to SMTP */
+ bits &= ~SSL_OP_SAFARI_ECDHE_ECDSA_BUG;
+#endif
+ }
+
+ /*
+ * Allow users to set options not in SSL_OP_ALL, and not already managed
+ * via other Postfix parameters.
+ */
+ if (*var_tls_ssl_options) {
+ long enable;
+
+ enable = long_name_mask_opt(VAR_TLS_SSL_OPTIONS, ssl_op_tweaks,
+ var_tls_ssl_options, NAME_MASK_ANY_CASE |
+ NAME_MASK_NUMBER | NAME_MASK_WARN);
+ enable &= ~(SSL_OP_ALL | TLS_SSL_OP_MANAGED_BITS);
+ bits |= enable;
+ }
+
+ /*
+ * We unconditionally avoid re-use of ephemeral keys, note that we set DH
+ * keys via a callback, so reuse was never possible, but the ECDH key is
+ * set statically, so that is potentially subject to reuse. Set both
+ * options just in case.
+ */
+ bits |= SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE;
+ return (bits);
+}
+
+/* tls_print_errors - print and clear the error stack */
+
+void tls_print_errors(void)
+{
+ unsigned long err;
+ char buffer[1024]; /* XXX */
+ const char *file;
+ const char *data;
+ int line;
+ int flags;
+
+ while ((err = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) {
+ ERR_error_string_n(err, buffer, sizeof(buffer));
+ if (flags & ERR_TXT_STRING)
+ msg_warn("TLS library problem: %s:%s:%d:%s:",
+ buffer, file, line, data);
+ else
+ msg_warn("TLS library problem: %s:%s:%d:", buffer, file, line);
+ }
+}
+
+/* tls_info_callback - callback for logging SSL events via Postfix */
+
+void tls_info_callback(const SSL *s, int where, int ret)
+{
+ char *str;
+ int w;
+
+ /* Adapted from OpenSSL apps/s_cb.c. */
+
+ w = where & ~SSL_ST_MASK;
+
+ if (w & SSL_ST_CONNECT)
+ str = "SSL_connect";
+ else if (w & SSL_ST_ACCEPT)
+ str = "SSL_accept";
+ else
+ str = "unknown";
+
+ if (where & SSL_CB_LOOP) {
+ msg_info("%s:%s", str, SSL_state_string_long((SSL *) s));
+ } else if (where & SSL_CB_ALERT) {
+ str = (where & SSL_CB_READ) ? "read" : "write";
+ if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY)
+ msg_info("SSL3 alert %s:%s:%s", str,
+ SSL_alert_type_string_long(ret),
+ SSL_alert_desc_string_long(ret));
+ } else if (where & SSL_CB_EXIT) {
+ if (ret == 0)
+ msg_info("%s:failed in %s",
+ str, SSL_state_string_long((SSL *) s));
+ else if (ret < 0) {
+#ifndef LOG_NON_ERROR_STATES
+ switch (SSL_get_error((SSL *) s, ret)) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ /* Don't log non-error states. */
+ break;
+ default:
+#endif
+ msg_info("%s:error in %s",
+ str, SSL_state_string_long((SSL *) s));
+#ifndef LOG_NON_ERROR_STATES
+ }
+#endif
+ }
+ }
+}
+
+ /*
+ * taken from OpenSSL crypto/bio/b_dump.c.
+ *
+ * Modified to save a lot of strcpy and strcat by Matti Aarnio.
+ *
+ * Rewritten by Wietse to elimate fixed-size stack buffer, array index
+ * multiplication and division, sprintf() and strcpy(), and lots of strlen()
+ * calls. We could make it a little faster by using a fixed-size stack-based
+ * buffer.
+ *
+ * 200412 - use %lx to print pointers, after casting them to unsigned long.
+ */
+
+#define TRUNCATE_SPACE_NULL
+#define DUMP_WIDTH 16
+#define VERT_SPLIT 7
+
+static void tls_dump_buffer(const unsigned char *start, int len)
+{
+ VSTRING *buf = vstring_alloc(100);
+ const unsigned char *last = start + len - 1;
+ const unsigned char *row;
+ const unsigned char *col;
+ int ch;
+
+#ifdef TRUNCATE_SPACE_NULL
+ while (last >= start && (*last == ' ' || *last == 0))
+ last--;
+#endif
+
+ for (row = start; row <= last; row += DUMP_WIDTH) {
+ VSTRING_RESET(buf);
+ vstring_sprintf(buf, "%04lx ", (unsigned long) (row - start));
+ for (col = row; col < row + DUMP_WIDTH; col++) {
+ if (col > last) {
+ vstring_strcat(buf, " ");
+ } else {
+ ch = *col;
+ vstring_sprintf_append(buf, "%02x%c",
+ ch, col - row == VERT_SPLIT ? '|' : ' ');
+ }
+ }
+ VSTRING_ADDCH(buf, ' ');
+ for (col = row; col < row + DUMP_WIDTH; col++) {
+ if (col > last)
+ break;
+ ch = *col;
+ if (!ISPRINT(ch))
+ ch = '.';
+ VSTRING_ADDCH(buf, ch);
+ if (col - row == VERT_SPLIT)
+ VSTRING_ADDCH(buf, ' ');
+ }
+ VSTRING_TERMINATE(buf);
+ msg_info("%s", vstring_str(buf));
+ }
+#ifdef TRUNCATE_SPACE_NULL
+ if ((last + 1) - start < len)
+ msg_info("%04lx - <SPACES/NULLS>",
+ (unsigned long) ((last + 1) - start));
+#endif
+ vstring_free(buf);
+}
+
+/* taken from OpenSSL apps/s_cb.c */
+
+long tls_bio_dump_cb(BIO *bio, int cmd, const char *argp, int argi,
+ long unused_argl, long ret)
+{
+ if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
+ msg_info("read from %08lX [%08lX] (%d bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, argi,
+ ret, (unsigned long) ret);
+ tls_dump_buffer((unsigned char *) argp, (int) ret);
+ } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
+ msg_info("write to %08lX [%08lX] (%d bytes => %ld (0x%lX))",
+ (unsigned long) bio, (unsigned long) argp, argi,
+ ret, (unsigned long) ret);
+ tls_dump_buffer((unsigned char *) argp, (int) ret);
+ }
+ return (ret);
+}
+
+int tls_validate_digest(const char *dgst)
+{
+ const EVP_MD *md_alg;
+
+ /*
+ * Register SHA-2 digests, if implemented and not already registered.
+ * Improves interoperability with clients and servers that prematurely
+ * deploy SHA-2 certificates. Also facilitates DANE and TA support.
+ */
+#if defined(LN_sha256) && defined(NID_sha256) && !defined(OPENSSL_NO_SHA256)
+ if (!tls_digest_byname(LN_sha224, NULL))
+ EVP_add_digest(EVP_sha224());
+ if (!tls_digest_byname(LN_sha256, NULL))
+ EVP_add_digest(EVP_sha256());
+#endif
+#if defined(LN_sha512) && defined(NID_sha512) && !defined(OPENSSL_NO_SHA512)
+ if (!tls_digest_byname(LN_sha384, NULL))
+ EVP_add_digest(EVP_sha384());
+ if (!tls_digest_byname(LN_sha512, NULL))
+ EVP_add_digest(EVP_sha512());
+#endif
+
+ /*
+ * If the administrator specifies an unsupported digest algorithm, fail
+ * now, rather than in the middle of a TLS handshake.
+ */
+ if ((md_alg = tls_digest_byname(dgst, NULL)) == 0) {
+ msg_warn("Digest algorithm \"%s\" not found", dgst);
+ return (0);
+ }
+ return (1);
+}
+
+#else
+
+ /*
+ * Broken linker workaround.
+ */
+int tls_dummy_for_broken_linkers;
+
+#endif
diff --git a/src/tls/tls_prng.h b/src/tls/tls_prng.h
new file mode 100644
index 0000000..df7fad9
--- /dev/null
+++ b/src/tls/tls_prng.h
@@ -0,0 +1,50 @@
+#ifndef _TLS_PRNG_SRC_H_INCLUDED_
+#define _TLS_PRNG_SRC_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_prng_src 3h
+/* SUMMARY
+/* OpenSSL PRNG maintenance routines
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct TLS_PRNG_SRC {
+ int fd; /* file handle */
+ char *name; /* resource name */
+ int timeout; /* time limit of applicable */
+} TLS_PRNG_SRC;
+
+extern TLS_PRNG_SRC *tls_prng_egd_open(const char *, int);
+extern ssize_t tls_prng_egd_read(TLS_PRNG_SRC *, size_t);
+extern int tls_prng_egd_close(TLS_PRNG_SRC *);
+
+extern TLS_PRNG_SRC *tls_prng_dev_open(const char *, int);
+extern ssize_t tls_prng_dev_read(TLS_PRNG_SRC *, size_t);
+extern int tls_prng_dev_close(TLS_PRNG_SRC *);
+
+extern TLS_PRNG_SRC *tls_prng_file_open(const char *, int);
+extern ssize_t tls_prng_file_read(TLS_PRNG_SRC *, size_t);
+extern int tls_prng_file_close(TLS_PRNG_SRC *);
+
+extern TLS_PRNG_SRC *tls_prng_exch_open(const char *);
+extern void tls_prng_exch_update(TLS_PRNG_SRC *);
+extern void tls_prng_exch_close(TLS_PRNG_SRC *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/tls/tls_prng_dev.c b/src/tls/tls_prng_dev.c
new file mode 100644
index 0000000..47abf47
--- /dev/null
+++ b/src/tls/tls_prng_dev.c
@@ -0,0 +1,155 @@
+/*++
+/* NAME
+/* tls_prng_dev 3
+/* SUMMARY
+/* seed OpenSSL PRNG from entropy device
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_dev_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* ssize_t tls_prng_dev_read(dev, length)
+/* TLS_PRNG_SRC *dev;
+/* size_t length;
+/*
+/* int tls_prng_dev_close(dev)
+/* TLS_PRNG_SRC *dev;
+/* DESCRIPTION
+/* tls_prng_dev_open() opens the specified entropy device
+/* and returns a handle that should be used with all subsequent
+/* access.
+/*
+/* tls_prng_dev_read() reads the requested number of bytes from
+/* the entropy device and updates the OpenSSL PRNG.
+/*
+/* tls_prng_dev_close() closes the specified entropy device
+/* and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The pathname of the entropy device.
+/* .IP length
+/* The number of bytes to read from the entropy device.
+/* Request lengths will be truncated at 255 bytes.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* tls_prng_dev_open() returns a null pointer on error.
+/*
+/* tls_prng_dev_read() returns -1 on error, the number
+/* of bytes received on success.
+/*
+/* tls_prng_dev_close() returns -1 on error, 0 on success.
+/*
+/* In all cases the errno variable indicates the type of error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* tls_prng_dev_open - open entropy device */
+
+TLS_PRNG_SRC *tls_prng_dev_open(const char *name, int timeout)
+{
+ const char *myname = "tls_prng_dev_open";
+ TLS_PRNG_SRC *dev;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY, 0)) < 0) {
+ if (msg_verbose)
+ msg_info("%s: cannot open entropy device %s: %m", myname, name);
+ return (0);
+ } else {
+ dev = (TLS_PRNG_SRC *) mymalloc(sizeof(*dev));
+ dev->fd = fd;
+ dev->name = mystrdup(name);
+ dev->timeout = timeout;
+ if (msg_verbose)
+ msg_info("%s: opened entropy device %s", myname, name);
+ return (dev);
+ }
+}
+
+/* tls_prng_dev_read - update internal PRNG from device */
+
+ssize_t tls_prng_dev_read(TLS_PRNG_SRC *dev, size_t len)
+{
+ const char *myname = "tls_prng_dev_read";
+ unsigned char buffer[UCHAR_MAX];
+ ssize_t count;
+ size_t rand_bytes;
+
+ if (len <= 0)
+ msg_panic("%s: bad read length: %ld", myname, (long) len);
+
+ if (len > sizeof(buffer))
+ rand_bytes = sizeof(buffer);
+ else
+ rand_bytes = len;
+ errno = 0;
+ count = timed_read(dev->fd, buffer, rand_bytes, dev->timeout, (void *) 0);
+ if (count > 0) {
+ if (msg_verbose)
+ msg_info("%s: read %ld bytes from entropy device %s",
+ myname, (long) count, dev->name);
+ RAND_seed(buffer, count);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: cannot read %ld bytes from entropy device %s: %m",
+ myname, (long) rand_bytes, dev->name);
+ }
+ return (count);
+}
+
+/* tls_prng_dev_close - disconnect from EGD server */
+
+int tls_prng_dev_close(TLS_PRNG_SRC *dev)
+{
+ const char *myname = "tls_prng_dev_close";
+ int err;
+
+ if (msg_verbose)
+ msg_info("%s: close entropy device %s", myname, dev->name);
+ err = close(dev->fd);
+ myfree(dev->name);
+ myfree((void *) dev);
+ return (err);
+}
+
+#endif
diff --git a/src/tls/tls_prng_egd.c b/src/tls/tls_prng_egd.c
new file mode 100644
index 0000000..e4a4cd5
--- /dev/null
+++ b/src/tls/tls_prng_egd.c
@@ -0,0 +1,166 @@
+/*++
+/* NAME
+/* tls_prng_egd 3
+/* SUMMARY
+/* seed OpenSSL PRNG from EGD server
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_egd_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* ssize_t tls_prng_egd_read(egd, length)
+/* TLS_PRNG_SRC *egd;
+/* size_t length;
+/*
+/* int tls_prng_egd_close(egd)
+/* TLS_PRNG_SRC *egd;
+/* DESCRIPTION
+/* tls_prng_egd_open() connect to the specified UNIX-domain service
+/* and returns a handle that should be used with all subsequent
+/* access.
+/*
+/* tls_prng_egd_read() reads the requested number of bytes from
+/* the EGD server and updates the OpenSSL PRNG.
+/*
+/* tls_prng_egd_close() disconnects from the specified EGD server
+/* and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The UNIX-domain pathname of the EGD service.
+/* .IP length
+/* The number of bytes to read from the EGD server.
+/* Request lengths will be truncated at 255 bytes.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* tls_prng_egd_open() returns a null pointer on error.
+/*
+/* tls_prng_egd_read() returns -1 on error, the number
+/* of bytes received on success.
+/*
+/* tls_prng_egd_close() returns -1 on error, 0 on success.
+/*
+/* In all cases the errno variable indicates the type of error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <limits.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* tls_prng_egd_open - connect to EGD server */
+
+TLS_PRNG_SRC *tls_prng_egd_open(const char *name, int timeout)
+{
+ const char *myname = "tls_prng_egd_open";
+ TLS_PRNG_SRC *egd;
+ int fd;
+
+ if (msg_verbose)
+ msg_info("%s: connect to EGD server %s", myname, name);
+
+ if ((fd = unix_connect(name, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_info("%s: cannot connect to EGD server %s: %m", myname, name);
+ return (0);
+ } else {
+ egd = (TLS_PRNG_SRC *) mymalloc(sizeof(*egd));
+ egd->fd = fd;
+ egd->name = mystrdup(name);
+ egd->timeout = timeout;
+ if (msg_verbose)
+ msg_info("%s: connected to EGD server %s", myname, name);
+ return (egd);
+ }
+}
+
+/* tls_prng_egd_read - update internal PRNG from EGD server */
+
+ssize_t tls_prng_egd_read(TLS_PRNG_SRC *egd, size_t len)
+{
+ const char *myname = "tls_prng_egd_read";
+ unsigned char buffer[UCHAR_MAX];
+ ssize_t count;
+
+ if (len <= 0)
+ msg_panic("%s: bad length %ld", myname, (long) len);
+
+ buffer[0] = 1;
+ buffer[1] = (len > UCHAR_MAX ? UCHAR_MAX : len);
+
+ if (timed_write(egd->fd, buffer, 2, egd->timeout, (void *) 0) != 2) {
+ msg_info("cannot write to EGD server %s: %m", egd->name);
+ return (-1);
+ }
+ if (timed_read(egd->fd, buffer, 1, egd->timeout, (void *) 0) != 1) {
+ msg_info("cannot read from EGD server %s: %m", egd->name);
+ return (-1);
+ }
+ count = buffer[0];
+ if (count > sizeof(buffer))
+ count = sizeof(buffer);
+ if (count == 0) {
+ msg_info("EGD server %s reports zero bytes available", egd->name);
+ return (-1);
+ }
+ if (timed_read(egd->fd, buffer, count, egd->timeout, (void *) 0) != count) {
+ msg_info("cannot read %ld bytes from EGD server %s: %m",
+ (long) count, egd->name);
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: got %ld bytes from EGD server %s", myname,
+ (long) count, egd->name);
+ RAND_seed(buffer, count);
+ return (count);
+}
+
+/* tls_prng_egd_close - disconnect from EGD server */
+
+int tls_prng_egd_close(TLS_PRNG_SRC *egd)
+{
+ const char *myname = "tls_prng_egd_close";
+ int err;
+
+ if (msg_verbose)
+ msg_info("%s: close EGD server %s", myname, egd->name);
+ err = close(egd->fd);
+ myfree(egd->name);
+ myfree((void *) egd);
+ return (err);
+}
+
+#endif
diff --git a/src/tls/tls_prng_exch.c b/src/tls/tls_prng_exch.c
new file mode 100644
index 0000000..31523a3
--- /dev/null
+++ b/src/tls/tls_prng_exch.c
@@ -0,0 +1,142 @@
+/*++
+/* NAME
+/* tls_prng_exch 3
+/* SUMMARY
+/* maintain PRNG exchange file
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_exch_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* void tls_prng_exch_update(fh, length)
+/* TLS_PRNG_SRC *fh;
+/* size_t length;
+/*
+/* void tls_prng_exch_close(fh)
+/* TLS_PRNG_SRC *fh;
+/* DESCRIPTION
+/* tls_prng_exch_open() opens the specified PRNG exchange file
+/* and returns a handle that should be used with all subsequent
+/* access.
+/*
+/* tls_prng_exch_update() reads the requested number of bytes
+/* from the PRNG exchange file, updates the OpenSSL PRNG, and
+/* writes the requested number of bytes to the exchange file.
+/* The file is locked for exclusive access.
+/*
+/* tls_prng_exch_close() closes the specified PRNG exchange
+/* file and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The name of the PRNG exchange file.
+/* .IP length
+/* The number of bytes to read from/write to the entropy file.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <myflock.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* Application specific. */
+
+#define TLS_PRNG_EXCH_SIZE 1024 /* XXX Why not configurable? */
+
+/* tls_prng_exch_open - open PRNG exchange file */
+
+TLS_PRNG_SRC *tls_prng_exch_open(const char *name)
+{
+ const char *myname = "tls_prng_exch_open";
+ TLS_PRNG_SRC *eh;
+ int fd;
+
+ if ((fd = open(name, O_RDWR | O_CREAT, 0600)) < 0)
+ msg_fatal("%s: cannot open PRNG exchange file %s: %m", myname, name);
+ eh = (TLS_PRNG_SRC *) mymalloc(sizeof(*eh));
+ eh->fd = fd;
+ eh->name = mystrdup(name);
+ eh->timeout = 0;
+ if (msg_verbose)
+ msg_info("%s: opened PRNG exchange file %s", myname, name);
+ return (eh);
+}
+
+/* tls_prng_exch_update - update PRNG exchange file */
+
+void tls_prng_exch_update(TLS_PRNG_SRC *eh)
+{
+ unsigned char buffer[TLS_PRNG_EXCH_SIZE];
+ ssize_t count;
+
+ /*
+ * Update the PRNG exchange file. Since other processes may have added
+ * entropy, we use a read-stir-write cycle.
+ */
+ if (myflock(eh->fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) != 0)
+ msg_fatal("cannot lock PRNG exchange file %s: %m", eh->name);
+ if (lseek(eh->fd, 0, SEEK_SET) < 0)
+ msg_fatal("cannot seek PRNG exchange file %s: %m", eh->name);
+ if ((count = read(eh->fd, buffer, sizeof(buffer))) < 0)
+ msg_fatal("cannot read PRNG exchange file %s: %m", eh->name);
+
+ if (count > 0)
+ RAND_seed(buffer, count);
+ RAND_bytes(buffer, sizeof(buffer));
+
+ if (lseek(eh->fd, 0, SEEK_SET) < 0)
+ msg_fatal("cannot seek PRNG exchange file %s: %m", eh->name);
+ if (write(eh->fd, buffer, sizeof(buffer)) != sizeof(buffer))
+ msg_fatal("cannot write PRNG exchange file %s: %m", eh->name);
+ if (myflock(eh->fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) != 0)
+ msg_fatal("cannot unlock PRNG exchange file %s: %m", eh->name);
+}
+
+/* tls_prng_exch_close - close PRNG exchange file */
+
+void tls_prng_exch_close(TLS_PRNG_SRC *eh)
+{
+ const char *myname = "tls_prng_exch_close";
+
+ if (close(eh->fd) < 0)
+ msg_fatal("close PRNG exchange file %s: %m", eh->name);
+ if (msg_verbose)
+ msg_info("%s: closed PRNG exchange file %s", myname, eh->name);
+ myfree(eh->name);
+ myfree((void *) eh);
+}
+
+#endif
diff --git a/src/tls/tls_prng_file.c b/src/tls/tls_prng_file.c
new file mode 100644
index 0000000..23865be
--- /dev/null
+++ b/src/tls/tls_prng_file.c
@@ -0,0 +1,155 @@
+/*++
+/* NAME
+/* tls_prng_file 3
+/* SUMMARY
+/* seed OpenSSL PRNG from entropy file
+/* SYNOPSIS
+/* #include <tls_prng_src.h>
+/*
+/* TLS_PRNG_SRC *tls_prng_file_open(name, timeout)
+/* const char *name;
+/* int timeout;
+/*
+/* ssize_t tls_prng_file_read(fh, length)
+/* TLS_PRNG_SRC *fh;
+/* size_t length;
+/*
+/* int tls_prng_file_close(fh)
+/* TLS_PRNG_SRC *fh;
+/* DESCRIPTION
+/* tls_prng_file_open() open the specified file and returns
+/* a handle that should be used with all subsequent access.
+/*
+/* tls_prng_file_read() reads the requested number of bytes from
+/* the entropy file and updates the OpenSSL PRNG. The file is not
+/* locked for shared or exclusive access.
+/*
+/* tls_prng_file_close() closes the specified entropy file
+/* and releases memory that was allocated for the handle.
+/*
+/* Arguments:
+/* .IP name
+/* The pathname of the entropy file.
+/* .IP length
+/* The number of bytes to read from the entropy file.
+/* .IP timeout
+/* Time limit on individual I/O operations.
+/* DIAGNOSTICS
+/* tls_prng_file_open() returns a null pointer on error.
+/*
+/* tls_prng_file_read() returns -1 on error, the number
+/* of bytes received on success.
+/*
+/* tls_prng_file_close() returns -1 on error, 0 on success.
+/*
+/* In all cases the errno variable indicates the type of error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+/* OpenSSL library. */
+
+#ifdef USE_TLS
+#include <openssl/rand.h> /* For the PRNG */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* TLS library. */
+
+#include <tls_prng.h>
+
+/* tls_prng_file_open - open entropy file */
+
+TLS_PRNG_SRC *tls_prng_file_open(const char *name, int timeout)
+{
+ const char *myname = "tls_prng_file_open";
+ TLS_PRNG_SRC *fh;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY, 0)) < 0) {
+ if (msg_verbose)
+ msg_info("%s: cannot open entropy file %s: %m", myname, name);
+ return (0);
+ } else {
+ fh = (TLS_PRNG_SRC *) mymalloc(sizeof(*fh));
+ fh->fd = fd;
+ fh->name = mystrdup(name);
+ fh->timeout = timeout;
+ if (msg_verbose)
+ msg_info("%s: opened entropy file %s", myname, name);
+ return (fh);
+ }
+}
+
+/* tls_prng_file_read - update internal PRNG from entropy file */
+
+ssize_t tls_prng_file_read(TLS_PRNG_SRC *fh, size_t len)
+{
+ const char *myname = "tls_prng_file_read";
+ char buffer[8192];
+ ssize_t to_read;
+ ssize_t count;
+
+ if (msg_verbose)
+ msg_info("%s: seed internal pool from file %s", myname, fh->name);
+
+ if (lseek(fh->fd, 0, SEEK_SET) < 0) {
+ if (msg_verbose)
+ msg_info("cannot seek entropy file %s: %m", fh->name);
+ return (-1);
+ }
+ errno = 0;
+ for (to_read = len; to_read > 0; to_read -= count) {
+ if ((count = timed_read(fh->fd, buffer, to_read > sizeof(buffer) ?
+ sizeof(buffer) : to_read,
+ fh->timeout, (void *) 0)) < 0) {
+ if (msg_verbose)
+ msg_info("cannot read entropy file %s: %m", fh->name);
+ return (-1);
+ }
+ if (count == 0)
+ break;
+ RAND_seed(buffer, count);
+ }
+ if (msg_verbose)
+ msg_info("read %ld bytes from entropy file %s: %m",
+ (long) (len - to_read), fh->name);
+ return (len - to_read);
+}
+
+/* tls_prng_file_close - close entropy file */
+
+int tls_prng_file_close(TLS_PRNG_SRC *fh)
+{
+ const char *myname = "tls_prng_file_close";
+ int err;
+
+ if (msg_verbose)
+ msg_info("%s: close entropy file %s", myname, fh->name);
+ err = close(fh->fd);
+ myfree(fh->name);
+ myfree((void *) fh);
+ return (err);
+}
+
+#endif
diff --git a/src/tls/tls_proxy.h b/src/tls/tls_proxy.h
new file mode 100644
index 0000000..8277caa
--- /dev/null
+++ b/src/tls/tls_proxy.h
@@ -0,0 +1,303 @@
+#ifndef _TLS_PROXY_H_INCLUDED_
+#define _TLS_PROXY_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_proxy_clnt 3h
+/* SUMMARY
+/* postscreen TLS proxy support
+/* SYNOPSIS
+/* #include <tls_proxy_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <attr.h>
+
+ /*
+ * TLS library.
+ */
+#include <tls.h>
+
+ /*
+ * External interface.
+ */
+#define TLS_PROXY_FLAG_ROLE_SERVER (1<<0) /* request server role */
+#define TLS_PROXY_FLAG_ROLE_CLIENT (1<<1) /* request client role */
+#define TLS_PROXY_FLAG_SEND_CONTEXT (1<<2) /* send TLS context */
+
+#ifdef USE_TLS
+
+ /*
+ * TLS_CLIENT_PARAMS structure. If this changes, update all
+ * TLS_CLIENT_PARAMS related functions in tls_proxy_client_*.c.
+ *
+ * In the serialization these attributes are identified by their configuration
+ * parameter names.
+ *
+ * NOTE: this does not include openssl_path.
+ *
+ * TODO: TLS_SERVER_PARAM structure, like TLS_CLIENT_PARAMS plus
+ * VAR_TLS_SERVER_SNI_MAPS.
+ */
+typedef struct TLS_CLIENT_PARAMS {
+ char *tls_cnf_file;
+ char *tls_cnf_name;
+ char *tls_high_clist;
+ char *tls_medium_clist;
+ char *tls_low_clist;
+ char *tls_export_clist;
+ char *tls_null_clist;
+ char *tls_eecdh_auto;
+ char *tls_eecdh_strong;
+ char *tls_eecdh_ultra;
+ char *tls_bug_tweaks;
+ char *tls_ssl_options;
+ char *tls_dane_digests;
+ char *tls_mgr_service;
+ char *tls_tkt_cipher;
+ int tls_daemon_rand_bytes;
+ int tls_append_def_CA;
+ int tls_bc_pkey_fprint;
+ int tls_preempt_clist;
+ int tls_multi_wildcard;
+} TLS_CLIENT_PARAMS;
+
+#define TLS_PROXY_PARAMS(params, a1, a2, a3, a4, a5, a6, a7, a8, \
+ a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \
+ (((params)->a1), ((params)->a2), ((params)->a3), \
+ ((params)->a4), ((params)->a5), ((params)->a6), ((params)->a7), \
+ ((params)->a8), ((params)->a9), ((params)->a10), ((params)->a11), \
+ ((params)->a12), ((params)->a13), ((params)->a14), ((params)->a15), \
+ ((params)->a16), ((params)->a17), ((params)->a18), ((params)->a19), \
+ ((params)->a20))
+
+ /*
+ * tls_proxy_client_param_misc.c, tls_proxy_client_param_print.c, and
+ * tls_proxy_client_param_scan.c.
+ */
+extern TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(TLS_CLIENT_PARAMS *);
+extern char *tls_proxy_client_param_to_string(VSTRING *, TLS_CLIENT_PARAMS *);
+extern char *tls_proxy_client_param_with_names_to_string(VSTRING *, TLS_CLIENT_PARAMS *);
+extern int tls_proxy_client_param_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *);
+extern void tls_proxy_client_param_free(TLS_CLIENT_PARAMS *);
+extern int tls_proxy_client_param_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *);
+
+ /*
+ * Functions that handle TLS_XXX_INIT_PROPS and TLS_XXX_START_PROPS. These
+ * data structures are defined elsewhere, because they are also used in
+ * non-proxied requests.
+ */
+#define tls_proxy_legacy_open(service, flags, peer_stream, peer_addr, \
+ peer_port, timeout, serverid) \
+ tls_proxy_open((service), (flags), (peer_stream), (peer_addr), \
+ (peer_port), (timeout), (timeout), (serverid), \
+ (void *) 0, (void *) 0, (void *) 0)
+
+extern VSTREAM *tls_proxy_open(const char *, int, VSTREAM *, const char *,
+ const char *, int, int, const char *,
+ void *, void *, void *);
+
+#define TLS_PROXY_CLIENT_INIT_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \
+ a9, a10, a11, a12, a13, a14) \
+ (((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14))
+
+#define TLS_PROXY_CLIENT_START_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \
+ a9, a10, a11, a12, a13, a14) \
+ (((props)->a1), ((props)->a2), ((props)->a3), \
+ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ ((props)->a12), ((props)->a13), ((props)->a14))
+
+extern TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *);
+extern void tls_proxy_context_free(TLS_SESS_STATE *);
+extern int tls_proxy_context_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *);
+extern int tls_proxy_context_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *);
+
+extern int tls_proxy_client_init_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *);
+extern int tls_proxy_client_init_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *);
+extern void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *);
+extern char *tls_proxy_client_init_to_string(VSTRING *, TLS_CLIENT_INIT_PROPS *);
+extern char *tls_proxy_client_init_with_names_to_string(VSTRING *, TLS_CLIENT_INIT_PROPS *);
+
+extern int tls_proxy_client_start_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *);
+extern int tls_proxy_client_start_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *);
+extern void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *);
+
+extern int tls_proxy_server_init_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *);
+extern int tls_proxy_server_init_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *);
+extern void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *);
+
+extern int tls_proxy_server_start_print(ATTR_PRINT_MASTER_FN, VSTREAM *, int, void *);
+extern int tls_proxy_server_start_scan(ATTR_SCAN_MASTER_FN, VSTREAM *, int, void *);
+
+extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *);
+
+#endif /* USE_TLS */
+
+ /*
+ * TLSPROXY attributes, unconditionally exposed.
+ */
+#define TLS_ATTR_REMOTE_ENDPT "remote_endpoint" /* name[addr]:port */
+#define TLS_ATTR_FLAGS "flags"
+#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_SERVERID "serverid"
+
+#ifdef USE_TLS
+
+ /*
+ * Misc attributes.
+ */
+#define TLS_ATTR_COUNT "count"
+
+ /*
+ * TLS_SESS_STATE attributes.
+ */
+#define TLS_ATTR_PEER_CN "peer_CN"
+#define TLS_ATTR_ISSUER_CN "issuer_CN"
+#define TLS_ATTR_PEER_CERT_FPT "peer_fingerprint"
+#define TLS_ATTR_PEER_PKEY_FPT "peer_pubkey_fingerprint"
+#define TLS_ATTR_PEER_STATUS "peer_status"
+#define TLS_ATTR_CIPHER_PROTOCOL "cipher_protocol"
+#define TLS_ATTR_CIPHER_NAME "cipher_name"
+#define TLS_ATTR_CIPHER_USEBITS "cipher_usebits"
+#define TLS_ATTR_CIPHER_ALGBITS "cipher_algbits"
+#define TLS_ATTR_KEX_NAME "key_exchange"
+#define TLS_ATTR_KEX_CURVE "key_exchange_curve"
+#define TLS_ATTR_KEX_BITS "key_exchange_bits"
+#define TLS_ATTR_CLNT_SIG_NAME "clnt_signature"
+#define TLS_ATTR_CLNT_SIG_CURVE "clnt_signature_curve"
+#define TLS_ATTR_CLNT_SIG_BITS "clnt_signature_bits"
+#define TLS_ATTR_CLNT_SIG_DGST "clnt_signature_digest"
+#define TLS_ATTR_SRVR_SIG_NAME "srvr_signature"
+#define TLS_ATTR_SRVR_SIG_CURVE "srvr_signature_curve"
+#define TLS_ATTR_SRVR_SIG_BITS "srvr_signature_bits"
+#define TLS_ATTR_SRVR_SIG_DGST "srvr_signature_digest"
+#define TLS_ATTR_NAMADDR "namaddr"
+
+ /*
+ * TLS_SERVER_INIT_PROPS attributes.
+ */
+#define TLS_ATTR_LOG_PARAM "log_param"
+#define TLS_ATTR_LOG_LEVEL "log_level"
+#define TLS_ATTR_VERIFYDEPTH "verifydepth"
+#define TLS_ATTR_CACHE_TYPE "cache_type"
+#define TLS_ATTR_SET_SESSID "set_sessid"
+#define TLS_ATTR_CHAIN_FILES "chain_files"
+#define TLS_ATTR_CERT_FILE "cert_file"
+#define TLS_ATTR_KEY_FILE "key_file"
+#define TLS_ATTR_DCERT_FILE "dcert_file"
+#define TLS_ATTR_DKEY_FILE "dkey_file"
+#define TLS_ATTR_ECCERT_FILE "eccert_file"
+#define TLS_ATTR_ECKEY_FILE "eckey_file"
+#define TLS_ATTR_CAFILE "CAfile"
+#define TLS_ATTR_CAPATH "CApath"
+#define TLS_ATTR_PROTOCOLS "protocols"
+#define TLS_ATTR_EECDH_GRADE "eecdh_grade"
+#define TLS_ATTR_DH1K_PARAM_FILE "dh1024_param_file"
+#define TLS_ATTR_DH512_PARAM_FILE "dh512_param_file"
+#define TLS_ATTR_ASK_CCERT "ask_ccert"
+#define TLS_ATTR_MDALG "mdalg"
+
+ /*
+ * TLS_SERVER_START_PROPS attributes.
+ */
+#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_REQUIRECERT "requirecert"
+#define TLS_ATTR_SERVERID "serverid"
+#define TLS_ATTR_NAMADDR "namaddr"
+#define TLS_ATTR_CIPHER_GRADE "cipher_grade"
+#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions"
+#define TLS_ATTR_MDALG "mdalg"
+
+ /*
+ * TLS_CLIENT_INIT_PROPS attributes.
+ */
+#define TLS_ATTR_CNF_FILE "config_file"
+#define TLS_ATTR_CNF_NAME "config_name"
+#define TLS_ATTR_LOG_PARAM "log_param"
+#define TLS_ATTR_LOG_LEVEL "log_level"
+#define TLS_ATTR_VERIFYDEPTH "verifydepth"
+#define TLS_ATTR_CACHE_TYPE "cache_type"
+#define TLS_ATTR_CHAIN_FILES "chain_files"
+#define TLS_ATTR_CERT_FILE "cert_file"
+#define TLS_ATTR_KEY_FILE "key_file"
+#define TLS_ATTR_DCERT_FILE "dcert_file"
+#define TLS_ATTR_DKEY_FILE "dkey_file"
+#define TLS_ATTR_ECCERT_FILE "eccert_file"
+#define TLS_ATTR_ECKEY_FILE "eckey_file"
+#define TLS_ATTR_CAFILE "CAfile"
+#define TLS_ATTR_CAPATH "CApath"
+#define TLS_ATTR_MDALG "mdalg"
+
+ /*
+ * TLS_CLIENT_START_PROPS attributes.
+ */
+#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_TLS_LEVEL "tls_level"
+#define TLS_ATTR_NEXTHOP "nexthop"
+#define TLS_ATTR_HOST "host"
+#define TLS_ATTR_NAMADDR "namaddr"
+#define TLS_ATTR_SNI "sni"
+#define TLS_ATTR_SERVERID "serverid"
+#define TLS_ATTR_HELO "helo"
+#define TLS_ATTR_PROTOCOLS "protocols"
+#define TLS_ATTR_CIPHER_GRADE "cipher_grade"
+#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions"
+#define TLS_ATTR_MATCHARGV "matchargv"
+#define TLS_ATTR_MDALG "mdalg"
+#define TLS_ATTR_DANE "dane"
+
+ /*
+ * TLS_TLSA attributes.
+ */
+#define TLS_ATTR_MDALG "mdalg"
+#define TLS_ATTR_CERTS "certs"
+#define TLS_ATTR_PKEYS "pkeys"
+
+ /*
+ * TLS_CERTS attributes.
+ */
+#define TLS_ATTR_CERT "cert"
+
+ /*
+ * TLS_PKEYS attributes.
+ */
+#define TLS_ATTR_PKEY "pkey"
+
+ /*
+ * TLS_DANE attributes.
+ */
+#define TLS_ATTR_TA "ta"
+#define TLS_ATTR_EE "ee"
+#define TLS_ATTR_CERTS "certs"
+#define TLS_ATTR_PKEYS "pkeys"
+#define TLS_ATTR_DOMAIN "domain"
+#define TLS_ATTR_FLAGS "flags"
+#define TLS_ATTR_EXP "exp"
+
+#endif
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/tls/tls_proxy_client_misc.c b/src/tls/tls_proxy_client_misc.c
new file mode 100644
index 0000000..3bf7369
--- /dev/null
+++ b/src/tls/tls_proxy_client_misc.c
@@ -0,0 +1,192 @@
+/*++
+/* NAME
+/* tls_proxy_client_misc 3
+/* SUMMARY
+/* TLS_CLIENT_XXX structure support
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(params)
+/* TLS_CLIENT_PARAMS *params;
+/*
+/* char *tls_proxy_client_param_to_string(buf, params)
+/* VSTRING *buf;
+/* TLS_CLIENT_PARAMS *params;
+/*
+/* char *tls_proxy_client_param_with_names_to_string(buf, params)
+/* VSTRING *buf;
+/* TLS_CLIENT_PARAMS *params;
+/*
+/* char *tls_proxy_client_init_to_string(buf, init_props)
+/* VSTRING *buf;
+/* TLS_CLIENT_INIT_PROPS *init_props;
+/* DESCRIPTION
+/* tls_proxy_client_param_from_config() initializes a TLS_CLIENT_PARAMS
+/* structure from configuration parameters and returns its
+/* argument. Strings are not copied. The result must therefore
+/* not be passed to tls_proxy_client_param_free().
+/*
+/* tls_proxy_client_param_to_string() produces a lookup key
+/* that is unique for the TLS_CLIENT_PARAMS member values.
+/*
+/* tls_proxy_client_param_with_names_to_string() produces a
+/* string with "name = value\n" for each TLS_CLIENT_PARAMS
+/* member. This may be useful for reporting differences between
+/* TLS_CLIENT_PARAMS instances.
+/*
+/* tls_proxy_client_init_to_string() produces a lookup key
+/* that is unique for the properties received by
+/* tls_proxy_client_init_scan().
+/*
+/* tls_proxy_client_init_with_names_to_string() produces a
+/* string with "name = value\n" for each TLS_CLIENT_INIT_PROPS
+/* member. This may be useful for reporting differences between
+/* TLS_CLIENT_INIT_PROPS instances.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_client_param_from_config - initialize TLS_CLIENT_PARAMS from configuration */
+
+TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(TLS_CLIENT_PARAMS *params)
+{
+ TLS_PROXY_PARAMS(params,
+ tls_cnf_file = var_tls_cnf_file,
+ tls_cnf_name = var_tls_cnf_name,
+ tls_high_clist = var_tls_high_clist,
+ tls_medium_clist = var_tls_medium_clist,
+ tls_low_clist = var_tls_low_clist,
+ tls_export_clist = var_tls_export_clist,
+ tls_null_clist = var_tls_null_clist,
+ tls_eecdh_auto = var_tls_eecdh_auto,
+ tls_eecdh_strong = var_tls_eecdh_strong,
+ tls_eecdh_ultra = var_tls_eecdh_ultra,
+ tls_bug_tweaks = var_tls_bug_tweaks,
+ tls_ssl_options = var_tls_ssl_options,
+ tls_dane_digests = var_tls_dane_digests,
+ tls_mgr_service = var_tls_mgr_service,
+ tls_tkt_cipher = var_tls_tkt_cipher,
+ tls_daemon_rand_bytes = var_tls_daemon_rand_bytes,
+ tls_append_def_CA = var_tls_append_def_CA,
+ tls_bc_pkey_fprint = var_tls_bc_pkey_fprint,
+ tls_preempt_clist = var_tls_preempt_clist,
+ tls_multi_wildcard = var_tls_multi_wildcard);
+ return (params);
+}
+
+/* tls_proxy_client_param_to_string - serialize TLS_CLIENT_PARAMS to string */
+
+char *tls_proxy_client_param_to_string(VSTRING *buf, TLS_CLIENT_PARAMS *params)
+{
+ vstring_sprintf(buf, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n"
+ "%s\n%s\n%d\n%d\n%d\n%d\n%d\n",
+ params->tls_high_clist, params->tls_medium_clist,
+ params->tls_low_clist, params->tls_export_clist,
+ params->tls_null_clist, params->tls_eecdh_auto,
+ params->tls_eecdh_strong, params->tls_eecdh_ultra,
+ params->tls_bug_tweaks, params->tls_ssl_options,
+ params->tls_dane_digests, params->tls_mgr_service,
+ params->tls_tkt_cipher, params->tls_daemon_rand_bytes,
+ params->tls_append_def_CA, params->tls_bc_pkey_fprint,
+ params->tls_preempt_clist, params->tls_multi_wildcard);
+ return (vstring_str(buf));
+}
+
+/* tls_proxy_client_param_with_names_to_string - serialize TLS_CLIENT_PARAMS to string */
+
+char *tls_proxy_client_param_with_names_to_string(VSTRING *buf, TLS_CLIENT_PARAMS *params)
+{
+ vstring_sprintf(buf, "%s = %s\n%s = %s\n%s = %s\n%s = %s\n%s = %s\n"
+ "%s = %s\n%s = %s\n%s = %s\n%s = %s\n%s = %s\n%s = %s\n"
+ "%s = %s\n%s = %s\n%s = %d\n"
+ "%s = %d\n%s = %d\n%s = %d\n%s = %d\n",
+ VAR_TLS_HIGH_CLIST, params->tls_high_clist,
+ VAR_TLS_MEDIUM_CLIST, params->tls_medium_clist,
+ VAR_TLS_LOW_CLIST, params->tls_low_clist,
+ VAR_TLS_EXPORT_CLIST, params->tls_export_clist,
+ VAR_TLS_NULL_CLIST, params->tls_null_clist,
+ VAR_TLS_EECDH_AUTO, params->tls_eecdh_auto,
+ VAR_TLS_EECDH_STRONG, params->tls_eecdh_strong,
+ VAR_TLS_EECDH_ULTRA, params->tls_eecdh_ultra,
+ VAR_TLS_BUG_TWEAKS, params->tls_bug_tweaks,
+ VAR_TLS_SSL_OPTIONS, params->tls_ssl_options,
+ VAR_TLS_DANE_DIGESTS, params->tls_dane_digests,
+ VAR_TLS_MGR_SERVICE, params->tls_mgr_service,
+ VAR_TLS_TKT_CIPHER, params->tls_tkt_cipher,
+ VAR_TLS_DAEMON_RAND_BYTES, params->tls_daemon_rand_bytes,
+ VAR_TLS_APPEND_DEF_CA, params->tls_append_def_CA,
+ VAR_TLS_BC_PKEY_FPRINT, params->tls_bc_pkey_fprint,
+ VAR_TLS_PREEMPT_CLIST, params->tls_preempt_clist,
+ VAR_TLS_MULTI_WILDCARD, params->tls_multi_wildcard);
+ return (vstring_str(buf));
+}
+
+/* tls_proxy_client_init_to_string - serialize to string */
+
+char *tls_proxy_client_init_to_string(VSTRING *buf,
+ TLS_CLIENT_INIT_PROPS *props)
+{
+ vstring_sprintf(buf, "%s\n%s\n%d\n%s\n%s\n%s\n%s\n%s\n%s\n"
+ "%s\n%s\n%s\n%s\n%s\n", props->log_param,
+ props->log_level, props->verifydepth,
+ props->cache_type, props->chain_files,
+ props->cert_file, props->key_file,
+ props->dcert_file, props->dkey_file,
+ props->eccert_file, props->eckey_file,
+ props->CAfile, props->CApath, props->mdalg);
+ return (vstring_str(buf));
+}
+
+/* tls_proxy_client_init_with_names_to_string - serialize to string */
+
+char *tls_proxy_client_init_with_names_to_string(VSTRING *buf,
+ TLS_CLIENT_INIT_PROPS *props)
+{
+ vstring_sprintf(buf, "%s = %s\n%s = %s\n%s = %d\n%s = %s\n%s = %s\n"
+ "%s = %s\n%s = %s\n%s = %s\n%s = %s\n%s = %s\n"
+ "%s = %s\n%s = %s\n%s = %s\n%s = %s\n",
+ TLS_ATTR_LOG_PARAM, props->log_param,
+ TLS_ATTR_LOG_LEVEL, props->log_level,
+ TLS_ATTR_VERIFYDEPTH, props->verifydepth,
+ TLS_ATTR_CACHE_TYPE, props->cache_type,
+ TLS_ATTR_CHAIN_FILES, props->chain_files,
+ TLS_ATTR_CERT_FILE, props->cert_file,
+ TLS_ATTR_KEY_FILE, props->key_file,
+ TLS_ATTR_DCERT_FILE, props->dcert_file,
+ TLS_ATTR_DKEY_FILE, props->dkey_file,
+ TLS_ATTR_ECCERT_FILE, props->eccert_file,
+ TLS_ATTR_ECKEY_FILE, props->eckey_file,
+ TLS_ATTR_CAFILE, props->CAfile,
+ TLS_ATTR_CAPATH, props->CApath,
+ TLS_ATTR_MDALG, props->mdalg);
+ return (vstring_str(buf));
+}
+
+#endif
diff --git a/src/tls/tls_proxy_client_print.c b/src/tls/tls_proxy_client_print.c
new file mode 100644
index 0000000..7f31550
--- /dev/null
+++ b/src/tls/tls_proxy_client_print.c
@@ -0,0 +1,398 @@
+/*++
+/* NAME
+/* tls_proxy_client_print 3
+/* SUMMARY
+/* write TLS_CLIENT_XXX structures to stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_client_param_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_MASTER_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* int tls_proxy_client_init_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_MASTER_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* int tls_proxy_client_start_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_MASTER_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* tls_proxy_client_param_print() writes a TLS_CLIENT_PARAMS structure to
+/* the named stream using the specified attribute print routine.
+/* tls_proxy_client_param_print() is meant to be passed as a call-back to
+/* attr_print(), thusly:
+/*
+/* SEND_ATTR_FUNC(tls_proxy_client_param_print, (void *) param), ...
+/*
+/* tls_proxy_client_init_print() writes a full TLS_CLIENT_INIT_PROPS
+/* structure to the named stream using the specified attribute
+/* print routine. tls_proxy_client_init_print() is meant to
+/* be passed as a call-back to attr_print(), thusly:
+/*
+/* SEND_ATTR_FUNC(tls_proxy_client_init_print, (void *) init_props), ...
+/*
+/* tls_proxy_client_start_print() writes a TLS_CLIENT_START_PROPS
+/* structure, without stream or file descriptor members, to
+/* the named stream using the specified attribute print routine.
+/* tls_proxy_client_start_print() is meant to be passed as a
+/* call-back to attr_print(), thusly:
+/*
+/* SEND_ATTR_FUNC(tls_proxy_client_start_print, (void *) start_props), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <argv_attr.h>
+#include <attr.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* tls_proxy_client_param_print - send TLS_CLIENT_PARAMS over stream */
+
+int tls_proxy_client_param_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_PARAMS *params = (TLS_CLIENT_PARAMS *) ptr;
+ int ret;
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_param_print");
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_CNF_FILE, params->tls_cnf_file),
+ SEND_ATTR_STR(TLS_ATTR_CNF_NAME, params->tls_cnf_name),
+ SEND_ATTR_STR(VAR_TLS_HIGH_CLIST, params->tls_high_clist),
+ SEND_ATTR_STR(VAR_TLS_MEDIUM_CLIST,
+ params->tls_medium_clist),
+ SEND_ATTR_STR(VAR_TLS_LOW_CLIST, params->tls_low_clist),
+ SEND_ATTR_STR(VAR_TLS_EXPORT_CLIST,
+ params->tls_export_clist),
+ SEND_ATTR_STR(VAR_TLS_NULL_CLIST, params->tls_null_clist),
+ SEND_ATTR_STR(VAR_TLS_EECDH_AUTO, params->tls_eecdh_auto),
+ SEND_ATTR_STR(VAR_TLS_EECDH_STRONG,
+ params->tls_eecdh_strong),
+ SEND_ATTR_STR(VAR_TLS_EECDH_ULTRA,
+ params->tls_eecdh_ultra),
+ SEND_ATTR_STR(VAR_TLS_BUG_TWEAKS, params->tls_bug_tweaks),
+ SEND_ATTR_STR(VAR_TLS_SSL_OPTIONS,
+ params->tls_ssl_options),
+ SEND_ATTR_STR(VAR_TLS_DANE_DIGESTS,
+ params->tls_dane_digests),
+ SEND_ATTR_STR(VAR_TLS_MGR_SERVICE,
+ params->tls_mgr_service),
+ SEND_ATTR_STR(VAR_TLS_TKT_CIPHER, params->tls_tkt_cipher),
+ SEND_ATTR_INT(VAR_TLS_DAEMON_RAND_BYTES,
+ params->tls_daemon_rand_bytes),
+ SEND_ATTR_INT(VAR_TLS_APPEND_DEF_CA,
+ params->tls_append_def_CA),
+ SEND_ATTR_INT(VAR_TLS_BC_PKEY_FPRINT,
+ params->tls_bc_pkey_fprint),
+ SEND_ATTR_INT(VAR_TLS_PREEMPT_CLIST,
+ params->tls_preempt_clist),
+ SEND_ATTR_INT(VAR_TLS_MULTI_WILDCARD,
+ params->tls_multi_wildcard),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_param_print ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_init_print - send TLS_CLIENT_INIT_PROPS over stream */
+
+int tls_proxy_client_init_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_INIT_PROPS *props = (TLS_CLIENT_INIT_PROPS *) ptr;
+ int ret;
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_init_print");
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_LOG_PARAM,
+ STRING_OR_EMPTY(props->log_param)),
+ SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL,
+ STRING_OR_EMPTY(props->log_level)),
+ SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth),
+ SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE,
+ STRING_OR_EMPTY(props->cache_type)),
+ SEND_ATTR_STR(TLS_ATTR_CHAIN_FILES,
+ STRING_OR_EMPTY(props->chain_files)),
+ SEND_ATTR_STR(TLS_ATTR_CERT_FILE,
+ STRING_OR_EMPTY(props->cert_file)),
+ SEND_ATTR_STR(TLS_ATTR_KEY_FILE,
+ STRING_OR_EMPTY(props->key_file)),
+ SEND_ATTR_STR(TLS_ATTR_DCERT_FILE,
+ STRING_OR_EMPTY(props->dcert_file)),
+ SEND_ATTR_STR(TLS_ATTR_DKEY_FILE,
+ STRING_OR_EMPTY(props->dkey_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE,
+ STRING_OR_EMPTY(props->eccert_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE,
+ STRING_OR_EMPTY(props->eckey_file)),
+ SEND_ATTR_STR(TLS_ATTR_CAFILE,
+ STRING_OR_EMPTY(props->CAfile)),
+ SEND_ATTR_STR(TLS_ATTR_CAPATH,
+ STRING_OR_EMPTY(props->CApath)),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_init_print ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_certs_print - send x509 certificates over stream */
+
+static int tls_proxy_client_certs_print(ATTR_PRINT_MASTER_FN print_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ TLS_CERTS *tls_certs = (TLS_CERTS *) ptr;
+ TLS_CERTS *tp;
+ int count;
+ int ret;
+
+ for (tp = tls_certs, count = 0; tp != 0; tp = tp->next, count++)
+ /* void */ ;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_certs_print count=%d", count);
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_COUNT, count),
+ ATTR_TYPE_END);
+
+ if (ret == 0 && count > 0) {
+ VSTRING *buf = vstring_alloc(100);
+ int n;
+
+ for (tp = tls_certs, n = 0; ret == 0 && n < count; tp = tp->next, n++) {
+ size_t len = i2d_X509(tp->cert, (unsigned char **) 0);
+ unsigned char *bp;
+
+ VSTRING_RESET(buf);
+ VSTRING_SPACE(buf, len);
+ bp = (unsigned char *) STR(buf);
+ i2d_X509(tp->cert, &bp);
+ if ((char *) bp - STR(buf) != len)
+ msg_panic("i2d_X509 failed to encode certificate");
+ vstring_set_payload_size(buf, len);
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_DATA(TLS_ATTR_CERT, LEN(buf), STR(buf)),
+ ATTR_TYPE_END);
+ }
+ vstring_free(buf);
+ }
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_certs_print ret=%d", count);
+ return (ret);
+}
+
+/* tls_proxy_client_pkeys_print - send public keys over stream */
+
+static int tls_proxy_client_pkeys_print(ATTR_PRINT_MASTER_FN print_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ TLS_PKEYS *tls_pkeys = (TLS_PKEYS *) ptr;
+ TLS_PKEYS *tp;
+ int count;
+ int ret;
+
+ for (tp = tls_pkeys, count = 0; tp != 0; tp = tp->next, count++)
+ /* void */ ;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_pkeys_print count=%d", count);
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_COUNT, count),
+ ATTR_TYPE_END);
+
+ if (ret == 0 && count > 0) {
+ VSTRING *buf = vstring_alloc(100);
+ int n;
+
+ for (tp = tls_pkeys, n = 0; ret == 0 && n < count; tp = tp->next, n++) {
+ size_t len = i2d_PUBKEY(tp->pkey, (unsigned char **) 0);
+ unsigned char *bp;
+
+ VSTRING_RESET(buf);
+ VSTRING_SPACE(buf, len);
+ bp = (unsigned char *) STR(buf);
+ i2d_PUBKEY(tp->pkey, &bp);
+ if ((char *) bp - STR(buf) != len)
+ msg_panic("i2d_PUBKEY failed to encode public key");
+ vstring_set_payload_size(buf, len);
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_DATA(TLS_ATTR_PKEY, LEN(buf), STR(buf)),
+ ATTR_TYPE_END);
+ }
+ vstring_free(buf);
+ }
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_pkeys_print ret=%d", count);
+ return (ret);
+}
+
+/* tls_proxy_client_tlsa_print - send TLS_TLSA over stream */
+
+static int tls_proxy_client_tlsa_print(ATTR_PRINT_MASTER_FN print_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ TLS_TLSA *tls_tlsa = (TLS_TLSA *) ptr;
+ TLS_TLSA *tp;
+ int count;
+ int ret;
+
+ for (tp = tls_tlsa, count = 0; tp != 0; tp = tp->next, count++)
+ /* void */ ;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_print count=%d", count);
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_COUNT, count),
+ ATTR_TYPE_END);
+
+ if (ret == 0 && count > 0) {
+ int n;
+
+ for (tp = tls_tlsa, n = 0; ret == 0 && n < count; tp = tp->next, n++) {
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_MDALG, tp->mdalg),
+ SEND_ATTR_FUNC(argv_attr_print,
+ (void *) tp->certs),
+ SEND_ATTR_FUNC(argv_attr_print,
+ (void *) tp->pkeys),
+ ATTR_TYPE_END);
+ }
+ }
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_print ret=%d", count);
+ return (ret);
+}
+
+/* tls_proxy_client_dane_print - send TLS_DANE over stream */
+
+static int tls_proxy_client_dane_print(ATTR_PRINT_MASTER_FN print_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ TLS_DANE *dane = (TLS_DANE *) ptr;
+ int ret;
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_DANE, dane != 0),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_print dane=%d", dane != 0);
+
+ if (ret == 0 && dane != 0) {
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_FUNC(tls_proxy_client_tlsa_print,
+ (void *) dane->ta),
+ SEND_ATTR_FUNC(tls_proxy_client_tlsa_print,
+ (void *) dane->ee),
+ SEND_ATTR_FUNC(tls_proxy_client_certs_print,
+ (void *) dane->certs),
+ SEND_ATTR_FUNC(tls_proxy_client_pkeys_print,
+ (void *) dane->pkeys),
+ SEND_ATTR_STR(TLS_ATTR_DOMAIN,
+ STRING_OR_EMPTY(dane->base_domain)),
+ SEND_ATTR_INT(TLS_ATTR_FLAGS, dane->flags),
+ SEND_ATTR_LONG(TLS_ATTR_EXP, dane->expires),
+ ATTR_TYPE_END);
+ }
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_print ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_start_print - send TLS_CLIENT_START_PROPS over stream */
+
+int tls_proxy_client_start_print(ATTR_PRINT_MASTER_FN print_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ TLS_CLIENT_START_PROPS *props = (TLS_CLIENT_START_PROPS *) ptr;
+ int ret;
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_start_print");
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout),
+ SEND_ATTR_INT(TLS_ATTR_TLS_LEVEL, props->tls_level),
+ SEND_ATTR_STR(TLS_ATTR_NEXTHOP,
+ STRING_OR_EMPTY(props->nexthop)),
+ SEND_ATTR_STR(TLS_ATTR_HOST,
+ STRING_OR_EMPTY(props->host)),
+ SEND_ATTR_STR(TLS_ATTR_NAMADDR,
+ STRING_OR_EMPTY(props->namaddr)),
+ SEND_ATTR_STR(TLS_ATTR_SNI,
+ STRING_OR_EMPTY(props->sni)),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID,
+ STRING_OR_EMPTY(props->serverid)),
+ SEND_ATTR_STR(TLS_ATTR_HELO,
+ STRING_OR_EMPTY(props->helo)),
+ SEND_ATTR_STR(TLS_ATTR_PROTOCOLS,
+ STRING_OR_EMPTY(props->protocols)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE,
+ STRING_OR_EMPTY(props->cipher_grade)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ STRING_OR_EMPTY(props->cipher_exclusions)),
+ SEND_ATTR_FUNC(argv_attr_print,
+ (void *) props->matchargv),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ SEND_ATTR_FUNC(tls_proxy_client_dane_print,
+ (void *) props->dane),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ if (msg_verbose)
+ msg_info("tls_proxy_client_start_print ret=%d", ret);
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_client_scan.c b/src/tls/tls_proxy_client_scan.c
new file mode 100644
index 0000000..831127b
--- /dev/null
+++ b/src/tls/tls_proxy_client_scan.c
@@ -0,0 +1,692 @@
+/*++
+/* NAME
+/* tls_proxy_client_scan 3
+/* SUMMARY
+/* read TLS_CLIENT_XXX structures from stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_client_param_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_MASTER_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_client_param_free(params)
+/* TLS_CLIENT_PARAMS *params;
+/*
+/* int tls_proxy_client_init_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_MASTER_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_client_init_free(init_props)
+/* TLS_CLIENT_INIT_PROPS *init_props;
+/*
+/* int tls_proxy_client_start_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_MASTER_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_client_start_free(start_props)
+/* TLS_CLIENT_START_PROPS *start_props;
+/* DESCRIPTION
+/* tls_proxy_client_param_scan() reads a TLS_CLIENT_PARAMS structure from
+/* the named stream using the specified attribute scan routine.
+/* tls_proxy_client_param_scan() is meant to be passed as a call-back
+/* function to attr_scan(), as shown below.
+/*
+/* tls_proxy_client_param_free() destroys a TLS_CLIENT_PARAMS structure
+/* that was created by tls_proxy_client_param_scan().
+/*
+/* TLS_CLIENT_PARAMS *param = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_client_param_scan, (void *) &param)
+/* ...
+/* if (param != 0)
+/* tls_proxy_client_param_free(param);
+/*
+/* tls_proxy_client_init_scan() reads a full TLS_CLIENT_INIT_PROPS
+/* structure from the named stream using the specified attribute
+/* scan routine. tls_proxy_client_init_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_client_init_free() destroys a TLS_CLIENT_INIT_PROPS
+/* structure that was created by tls_proxy_client_init_scan().
+/*
+/* TLS_CLIENT_INIT_PROPS *init_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_client_init_scan, (void *) &init_props)
+/* ...
+/* if (init_props != 0)
+/* tls_proxy_client_init_free(init_props);
+/*
+/* tls_proxy_client_start_scan() reads a TLS_CLIENT_START_PROPS
+/* structure, without the stream of file descriptor members,
+/* from the named stream using the specified attribute scan
+/* routine. tls_proxy_client_start_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_client_start_free() destroys a TLS_CLIENT_START_PROPS
+/* structure that was created by tls_proxy_client_start_scan().
+/*
+/* TLS_CLIENT_START_PROPS *start_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_client_start_scan, (void *) &start_props)
+/* ...
+/* if (start_props != 0)
+/* tls_proxy_client_start_free(start_props);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <argv_attr.h>
+#include <attr.h>
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* tls_proxy_client_param_free - destroy TLS_CLIENT_PARAMS structure */
+
+void tls_proxy_client_param_free(TLS_CLIENT_PARAMS *params)
+{
+ myfree(params->tls_cnf_file);
+ myfree(params->tls_cnf_name);
+ myfree(params->tls_high_clist);
+ myfree(params->tls_medium_clist);
+ myfree(params->tls_low_clist);
+ myfree(params->tls_export_clist);
+ myfree(params->tls_null_clist);
+ myfree(params->tls_eecdh_auto);
+ myfree(params->tls_eecdh_strong);
+ myfree(params->tls_eecdh_ultra);
+ myfree(params->tls_bug_tweaks);
+ myfree(params->tls_ssl_options);
+ myfree(params->tls_dane_digests);
+ myfree(params->tls_mgr_service);
+ myfree(params->tls_tkt_cipher);
+ myfree((void *) params);
+}
+
+/* tls_proxy_client_param_scan - receive TLS_CLIENT_PARAMS from stream */
+
+int tls_proxy_client_param_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_PARAMS *params
+ = (TLS_CLIENT_PARAMS *) mymalloc(sizeof(*params));
+ int ret;
+ VSTRING *cnf_file = vstring_alloc(25);
+ VSTRING *cnf_name = vstring_alloc(25);
+ VSTRING *tls_high_clist = vstring_alloc(25);
+ VSTRING *tls_medium_clist = vstring_alloc(25);
+ VSTRING *tls_low_clist = vstring_alloc(25);
+ VSTRING *tls_export_clist = vstring_alloc(25);
+ VSTRING *tls_null_clist = vstring_alloc(25);
+ VSTRING *tls_eecdh_auto = vstring_alloc(25);
+ VSTRING *tls_eecdh_strong = vstring_alloc(25);
+ VSTRING *tls_eecdh_ultra = vstring_alloc(25);
+ VSTRING *tls_bug_tweaks = vstring_alloc(25);
+ VSTRING *tls_ssl_options = vstring_alloc(25);
+ VSTRING *tls_dane_digests = vstring_alloc(25);
+ VSTRING *tls_mgr_service = vstring_alloc(25);
+ VSTRING *tls_tkt_cipher = vstring_alloc(25);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_param_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(params, 0, sizeof(*params));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_CNF_FILE, cnf_file),
+ RECV_ATTR_STR(TLS_ATTR_CNF_NAME, cnf_name),
+ RECV_ATTR_STR(VAR_TLS_HIGH_CLIST, tls_high_clist),
+ RECV_ATTR_STR(VAR_TLS_MEDIUM_CLIST, tls_medium_clist),
+ RECV_ATTR_STR(VAR_TLS_LOW_CLIST, tls_low_clist),
+ RECV_ATTR_STR(VAR_TLS_EXPORT_CLIST, tls_export_clist),
+ RECV_ATTR_STR(VAR_TLS_NULL_CLIST, tls_null_clist),
+ RECV_ATTR_STR(VAR_TLS_EECDH_AUTO, tls_eecdh_auto),
+ RECV_ATTR_STR(VAR_TLS_EECDH_STRONG, tls_eecdh_strong),
+ RECV_ATTR_STR(VAR_TLS_EECDH_ULTRA, tls_eecdh_ultra),
+ RECV_ATTR_STR(VAR_TLS_BUG_TWEAKS, tls_bug_tweaks),
+ RECV_ATTR_STR(VAR_TLS_SSL_OPTIONS, tls_ssl_options),
+ RECV_ATTR_STR(VAR_TLS_DANE_DIGESTS, tls_dane_digests),
+ RECV_ATTR_STR(VAR_TLS_MGR_SERVICE, tls_mgr_service),
+ RECV_ATTR_STR(VAR_TLS_TKT_CIPHER, tls_tkt_cipher),
+ RECV_ATTR_INT(VAR_TLS_DAEMON_RAND_BYTES,
+ &params->tls_daemon_rand_bytes),
+ RECV_ATTR_INT(VAR_TLS_APPEND_DEF_CA,
+ &params->tls_append_def_CA),
+ RECV_ATTR_INT(VAR_TLS_BC_PKEY_FPRINT,
+ &params->tls_bc_pkey_fprint),
+ RECV_ATTR_INT(VAR_TLS_PREEMPT_CLIST,
+ &params->tls_preempt_clist),
+ RECV_ATTR_INT(VAR_TLS_MULTI_WILDCARD,
+ &params->tls_multi_wildcard),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ params->tls_cnf_file = vstring_export(cnf_file);
+ params->tls_cnf_name = vstring_export(cnf_name);
+ params->tls_high_clist = vstring_export(tls_high_clist);
+ params->tls_medium_clist = vstring_export(tls_medium_clist);
+ params->tls_low_clist = vstring_export(tls_low_clist);
+ params->tls_export_clist = vstring_export(tls_export_clist);
+ params->tls_null_clist = vstring_export(tls_null_clist);
+ params->tls_eecdh_auto = vstring_export(tls_eecdh_auto);
+ params->tls_eecdh_strong = vstring_export(tls_eecdh_strong);
+ params->tls_eecdh_ultra = vstring_export(tls_eecdh_ultra);
+ params->tls_bug_tweaks = vstring_export(tls_bug_tweaks);
+ params->tls_ssl_options = vstring_export(tls_ssl_options);
+ params->tls_dane_digests = vstring_export(tls_dane_digests);
+ params->tls_mgr_service = vstring_export(tls_mgr_service);
+ params->tls_tkt_cipher = vstring_export(tls_tkt_cipher);
+
+ ret = (ret == 20 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_client_param_free(params);
+ params = 0;
+ }
+ *(TLS_CLIENT_PARAMS **) ptr = params;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_param_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_init_free - destroy TLS_CLIENT_INIT_PROPS structure */
+
+void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *props)
+{
+ myfree((void *) props->log_param);
+ myfree((void *) props->log_level);
+ myfree((void *) props->cache_type);
+ myfree((void *) props->chain_files);
+ myfree((void *) props->cert_file);
+ myfree((void *) props->key_file);
+ myfree((void *) props->dcert_file);
+ myfree((void *) props->dkey_file);
+ myfree((void *) props->eccert_file);
+ myfree((void *) props->eckey_file);
+ myfree((void *) props->CAfile);
+ myfree((void *) props->CApath);
+ myfree((void *) props->mdalg);
+ myfree((void *) props);
+}
+
+/* tls_proxy_client_init_scan - receive TLS_CLIENT_INIT_PROPS from stream */
+
+int tls_proxy_client_init_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_INIT_PROPS *props
+ = (TLS_CLIENT_INIT_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *log_param = vstring_alloc(25);
+ VSTRING *log_level = vstring_alloc(25);
+ VSTRING *cache_type = vstring_alloc(25);
+ VSTRING *chain_files = vstring_alloc(25);
+ VSTRING *cert_file = vstring_alloc(25);
+ VSTRING *key_file = vstring_alloc(25);
+ VSTRING *dcert_file = vstring_alloc(25);
+ VSTRING *dkey_file = vstring_alloc(25);
+ VSTRING *eccert_file = vstring_alloc(25);
+ VSTRING *eckey_file = vstring_alloc(25);
+ VSTRING *CAfile = vstring_alloc(25);
+ VSTRING *CApath = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_init_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param),
+ RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level),
+ RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth),
+ RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_STR(TLS_ATTR_CHAIN_FILES, chain_files),
+ RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file),
+ RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file),
+ RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file),
+ RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file),
+ RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file),
+ RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file),
+ RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile),
+ RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ props->log_param = vstring_export(log_param);
+ props->log_level = vstring_export(log_level);
+ props->cache_type = vstring_export(cache_type);
+ props->chain_files = vstring_export(chain_files);
+ props->cert_file = vstring_export(cert_file);
+ props->key_file = vstring_export(key_file);
+ props->dcert_file = vstring_export(dcert_file);
+ props->dkey_file = vstring_export(dkey_file);
+ props->eccert_file = vstring_export(eccert_file);
+ props->eckey_file = vstring_export(eckey_file);
+ props->CAfile = vstring_export(CAfile);
+ props->CApath = vstring_export(CApath);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 14 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_client_init_free(props);
+ props = 0;
+ }
+ *(TLS_CLIENT_INIT_PROPS **) ptr = props;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_init_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_certs_free - destroy TLS_PKEYS from stream */
+
+static void tls_proxy_client_certs_free(TLS_CERTS *tp)
+{
+ if (tp->next)
+ tls_proxy_client_certs_free(tp->next);
+ if (tp->cert)
+ X509_free(tp->cert);
+ myfree((void *) tp);
+}
+
+/* tls_proxy_client_pkeys_free - destroy TLS_PKEYS from stream */
+
+static void tls_proxy_client_pkeys_free(TLS_PKEYS *tp)
+{
+ if (tp->next)
+ tls_proxy_client_pkeys_free(tp->next);
+ if (tp->pkey)
+ EVP_PKEY_free(tp->pkey);
+ myfree((void *) tp);
+}
+
+/* tls_proxy_client_tlsa_free - destroy TLS_TLSA from stream */
+
+static void tls_proxy_client_tlsa_free(TLS_TLSA *tp)
+{
+ if (tp->next)
+ tls_proxy_client_tlsa_free(tp->next);
+ myfree(tp->mdalg);
+ if (tp->certs)
+ argv_free(tp->certs);
+ if (tp->pkeys)
+ argv_free(tp->pkeys);
+ myfree((void *) tp);
+}
+
+/* tls_proxy_client_dane_free - destroy TLS_DANE from stream */
+
+static void tls_proxy_client_dane_free(TLS_DANE *dane)
+{
+ if (dane->ta)
+ tls_proxy_client_tlsa_free(dane->ta);
+ if (dane->ee)
+ tls_proxy_client_tlsa_free(dane->ee);
+ if (dane->certs)
+ tls_proxy_client_certs_free(dane->certs);
+ if (dane->pkeys)
+ tls_proxy_client_pkeys_free(dane->pkeys);
+ myfree(dane->base_domain);
+ if (dane->refs-- == 1)
+ myfree((void *) dane);
+}
+
+/* tls_proxy_client_start_free - destroy TLS_CLIENT_START_PROPS structure */
+
+void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *props)
+{
+ myfree((void *) props->nexthop);
+ myfree((void *) props->host);
+ myfree((void *) props->namaddr);
+ myfree((void *) props->sni);
+ myfree((void *) props->serverid);
+ myfree((void *) props->helo);
+ myfree((void *) props->protocols);
+ myfree((void *) props->cipher_grade);
+ myfree((void *) props->cipher_exclusions);
+ if (props->matchargv)
+ argv_free((ARGV *) props->matchargv);
+ myfree((void *) props->mdalg);
+ if (props->dane)
+ tls_proxy_client_dane_free((TLS_DANE *) props->dane);
+ myfree((void *) props);
+}
+
+/* tls_proxy_client_certs_scan - receive TLS_CERTS from stream */
+
+static int tls_proxy_client_certs_scan(ATTR_SCAN_MASTER_FN scan_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ int ret;
+ int count;
+ VSTRING *buf = 0;
+ TLS_CERTS **tpp;
+ TLS_CERTS *head = 0;
+ TLS_CERTS *tp;
+ int n;
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_COUNT, &count),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("tls_proxy_client_certs_scan count=%d", count);
+
+ for (tpp = &head, n = 0; ret == 1 && n < count; n++, tpp = &tp->next) {
+ *tpp = tp = (TLS_CERTS *) mymalloc(sizeof(*tp));
+ const unsigned char *bp;
+
+ if (buf == 0)
+ buf = vstring_alloc(100);
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer
+ * types.
+ */
+ memset(tp, 0, sizeof(*tp));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_DATA(TLS_ATTR_CERT, buf),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ if (ret == 1) {
+ bp = (const unsigned char *) STR(buf);
+ if (d2i_X509(&tp->cert, &bp, LEN(buf)) == 0
+ || LEN(buf) != ((char *) bp) - STR(buf)) {
+ msg_warn("malformed certificate in TLS_CERTS");
+ ret = -1;
+ }
+ } else {
+ tp->cert = 0;
+ }
+ tp->next = 0;
+ }
+ if (buf)
+ vstring_free(buf);
+ if (ret != 1) {
+ if (head)
+ tls_proxy_client_certs_free(head);
+ head = 0;
+ }
+ *(TLS_CERTS **) ptr = head;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_certs_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_pkeys_scan - receive TLS_PKEYS from stream */
+
+static int tls_proxy_client_pkeys_scan(ATTR_SCAN_MASTER_FN scan_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ int ret;
+ int count;
+ VSTRING *buf = vstring_alloc(100);
+ TLS_PKEYS **tpp;
+ TLS_PKEYS *head = 0;
+ TLS_PKEYS *tp;
+ int n;
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_COUNT, &count),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("tls_proxy_client_pkeys_scan count=%d", count);
+
+ for (tpp = &head, n = 0; ret == 1 && n < count; n++, tpp = &tp->next) {
+ *tpp = tp = (TLS_PKEYS *) mymalloc(sizeof(*tp));
+ const unsigned char *bp;
+
+ if (buf == 0)
+ buf = vstring_alloc(100);
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer
+ * types.
+ */
+ memset(tp, 0, sizeof(*tp));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_DATA(TLS_ATTR_PKEY, buf),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ if (ret == 1) {
+ bp = (const unsigned char *) STR(buf);
+ if (d2i_PUBKEY(&tp->pkey, &bp, LEN(buf)) == 0
+ || LEN(buf) != (char *) bp - STR(buf)) {
+ msg_warn("malformed public key in TLS_PKEYS");
+ ret = -1;
+ }
+ } else {
+ tp->pkey = 0;
+ }
+ tp->next = 0;
+ }
+ if (buf)
+ vstring_free(buf);
+ if (ret != 1) {
+ if (head)
+ tls_proxy_client_pkeys_free(head);
+ head = 0;
+ }
+ *(TLS_PKEYS **) ptr = head;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_pkeys_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_tlsa_scan - receive TLS_TLSA from stream */
+
+static int tls_proxy_client_tlsa_scan(ATTR_SCAN_MASTER_FN scan_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ int ret;
+ int count;
+ TLS_TLSA **tpp;
+ TLS_TLSA *head = 0;
+ TLS_TLSA *tp;
+ int n;
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_COUNT, &count),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_scan count=%d", count);
+
+ for (tpp = &head, n = 0; ret == 1 && n < count; n++, tpp = &tp->next) {
+ *tpp = tp = (TLS_TLSA *) mymalloc(sizeof(*tp));
+ VSTRING *mdalg = vstring_alloc(25);
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer
+ * types.
+ */
+ memset(tp, 0, sizeof(*tp));
+ /* Always construct a well-formed structure. */
+ tp->certs = 0; /* scan_fn may return early */
+ tp->pkeys = 0;
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ RECV_ATTR_FUNC(argv_attr_scan, &tp->certs),
+ RECV_ATTR_FUNC(argv_attr_scan, &tp->pkeys),
+ ATTR_TYPE_END);
+ tp->mdalg = vstring_export(mdalg);
+ tp->next = 0;
+ ret = (ret == 3 ? 1 : -1);
+ }
+ if (ret != 1) {
+ if (head)
+ tls_proxy_client_tlsa_free(head);
+ head = 0;
+ }
+ *(TLS_TLSA **) ptr = head;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_tlsa_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_dane_scan - receive TLS_DANE from stream */
+
+static int tls_proxy_client_dane_scan(ATTR_SCAN_MASTER_FN scan_fn,
+ VSTREAM *fp, int flags, void *ptr)
+{
+ TLS_DANE *dane = 0;
+ int ret;
+ int have_dane = 0;
+
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_DANE, &have_dane),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_scan have_dane=%d", have_dane);
+
+ if (ret == 1 && have_dane) {
+ VSTRING *base_domain = vstring_alloc(25);
+ long expires;
+
+ dane = (TLS_DANE *) mymalloc(sizeof(*dane));
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer
+ * types.
+ */
+ memset(dane, 0, sizeof(*dane));
+ /* Always construct a well-formed structure. */
+ dane->ta = 0; /* scan_fn may return early */
+ dane->ee = 0;
+ dane->certs = 0;
+ dane->pkeys = 0;
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_FUNC(tls_proxy_client_tlsa_scan,
+ &dane->ta),
+ RECV_ATTR_FUNC(tls_proxy_client_tlsa_scan,
+ &dane->ee),
+ RECV_ATTR_FUNC(tls_proxy_client_certs_scan,
+ &dane->certs),
+ RECV_ATTR_FUNC(tls_proxy_client_pkeys_scan,
+ &dane->pkeys),
+ RECV_ATTR_STR(TLS_ATTR_DOMAIN, base_domain),
+ RECV_ATTR_INT(TLS_ATTR_FLAGS, &dane->flags),
+ RECV_ATTR_LONG(TLS_ATTR_EXP, &expires),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ dane->base_domain = vstring_export(base_domain);
+ dane->expires = expires;
+ dane->refs = 1;
+ ret = (ret == 7 ? 1 : -1);
+ /* XXX if scan_fn() completed normally, sanity check dane->flags. */
+ if (ret != 1) {
+ tls_proxy_client_dane_free(dane);
+ dane = 0;
+ }
+ }
+ *(TLS_DANE **) ptr = dane;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_dane_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_client_start_scan - receive TLS_CLIENT_START_PROPS from stream */
+
+int tls_proxy_client_start_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_CLIENT_START_PROPS *props
+ = (TLS_CLIENT_START_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *nexthop = vstring_alloc(25);
+ VSTRING *host = vstring_alloc(25);
+ VSTRING *namaddr = vstring_alloc(25);
+ VSTRING *sni = vstring_alloc(25);
+ VSTRING *serverid = vstring_alloc(25);
+ VSTRING *helo = vstring_alloc(25);
+ VSTRING *protocols = vstring_alloc(25);
+ VSTRING *cipher_grade = vstring_alloc(25);
+ VSTRING *cipher_exclusions = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_client_start_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ props->ctx = 0;
+ props->stream = 0;
+ props->fd = -1;
+ props->dane = 0; /* scan_fn may return early */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout),
+ RECV_ATTR_INT(TLS_ATTR_TLS_LEVEL, &props->tls_level),
+ RECV_ATTR_STR(TLS_ATTR_NEXTHOP, nexthop),
+ RECV_ATTR_STR(TLS_ATTR_HOST, host),
+ RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr),
+ RECV_ATTR_STR(TLS_ATTR_SNI, sni),
+ RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid),
+ RECV_ATTR_STR(TLS_ATTR_HELO, helo),
+ RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ cipher_exclusions),
+ RECV_ATTR_FUNC(argv_attr_scan, &props->matchargv),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ RECV_ATTR_FUNC(tls_proxy_client_dane_scan,
+ &props->dane),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ props->nexthop = vstring_export(nexthop);
+ props->host = vstring_export(host);
+ props->namaddr = vstring_export(namaddr);
+ props->sni = vstring_export(sni);
+ props->serverid = vstring_export(serverid);
+ props->helo = vstring_export(helo);
+ props->protocols = vstring_export(protocols);
+ props->cipher_grade = vstring_export(cipher_grade);
+ props->cipher_exclusions = vstring_export(cipher_exclusions);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 14 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_client_start_free(props);
+ props = 0;
+ }
+ *(TLS_CLIENT_START_PROPS **) ptr = props;
+ if (msg_verbose)
+ msg_info("tls_proxy_client_start_scan ret=%d", ret);
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_clnt.c b/src/tls/tls_proxy_clnt.c
new file mode 100644
index 0000000..beeafa7
--- /dev/null
+++ b/src/tls/tls_proxy_clnt.c
@@ -0,0 +1,292 @@
+/*++
+/* NAME
+/* tlsproxy_clnt 3
+/* SUMMARY
+/* tlsproxy(8) client support
+/* SYNOPSIS
+/* #include <tlsproxy_clnt.h>
+/*
+/* VSTREAM *tls_proxy_open(service, flags, peer_stream, peer_addr,
+/* peer_port, handshake_timeout, session_timeout,
+/* serverid, tls_params, init_props, start_props)
+/* const char *service;
+/* int flags;
+/* VSTREAM *peer_stream;
+/* const char *peer_addr;
+/* const char *peer_port;
+/* int handshake_timeout;
+/* int session_timeout;
+/* const char *serverid;
+/* void *tls_params;
+/* void *init_props;
+/* void *start_props;
+/*
+/* TLS_SESS_STATE *tls_proxy_context_receive(proxy_stream)
+/* VSTREAM *proxy_stream;
+/* AUXILIARY FUNCTIONS
+/* VSTREAM *tls_proxy_legacy_open(service, flags, peer_stream,
+/* peer_addr, peer_port,
+/* timeout, serverid)
+/* const char *service;
+/* int flags;
+/* VSTREAM *peer_stream;
+/* const char *peer_addr;
+/* const char *peer_port;
+/* int timeout;
+/* const char *serverid;
+/* DESCRIPTION
+/* tls_proxy_open() prepares for inserting the tlsproxy(8)
+/* daemon between the current process and a remote peer (the
+/* actual insert operation is described in the next paragraph).
+/* The result value is a null pointer on failure. The peer_stream
+/* is not closed. The resulting proxy stream is single-buffered.
+/*
+/* After this, it is a good idea to use the CA_VSTREAM_CTL_SWAP_FD
+/* request to swap the file descriptors between the plaintext
+/* peer_stream and the proxy stream from tls_proxy_open().
+/* This avoids the loss of application-configurable VSTREAM
+/* attributes on the plaintext peer_stream (such as longjmp
+/* buffer, timeout, etc.). Once the file descriptors are
+/* swapped, the proxy stream should be closed.
+/*
+/* tls_proxy_context_receive() receives the TLS context object
+/* for the named proxy stream. This function must be called
+/* only if the TLS_PROXY_SEND_CONTEXT flag was specified in
+/* the tls_proxy_open() call. Note that this TLS context object
+/* is not compatible with tls_session_free(). It must be given
+/* to tls_proxy_context_free() instead.
+/*
+/* After this, the proxy_stream is ready for plain-text I/O.
+/*
+/* tls_proxy_legacy_open() is a backwards-compatibility feature
+/* that provides a historical interface.
+/*
+/* Arguments:
+/* .IP service
+/* The (base) name of the tlsproxy service.
+/* .IP flags
+/* Bit-wise OR of:
+/* .RS
+/* .IP TLS_PROXY_FLAG_ROLE_SERVER
+/* Request the TLS server proxy role.
+/* .IP TLS_PROXY_FLAG_ROLE_CLIENT
+/* Request the TLS client proxy role.
+/* .IP TLS_PROXY_FLAG_SEND_CONTEXT
+/* Send the TLS context object.
+/* .RE
+/* .IP peer_stream
+/* Stream that connects the current process to a remote peer.
+/* .IP peer_addr
+/* Printable IP address of the remote peer_stream endpoint.
+/* .IP peer_port
+/* Printable TCP port of the remote peer_stream endpoint.
+/* .IP handshake_timeout
+/* Time limit that the tlsproxy(8) daemon should use during
+/* the TLS handshake.
+/* .IP session_timeout
+/* Time limit that the tlsproxy(8) daemon should use after the
+/* TLS handshake.
+/* .IP serverid
+/* Unique service identifier.
+/* .IP tls_params
+/* Pointer to TLS_CLIENT_PARAMS or TLS_SERVER_PARAMS.
+/* .IP init_props
+/* Pointer to TLS_CLIENT_INIT_PROPS or TLS_SERVER_INIT_PROPS.
+/* .IP start_props
+/* Pointer to TLS_CLIENT_START_PROPS or TLS_SERVER_START_PROPS.
+/* .IP proxy_stream
+/* Stream from tls_proxy_open().
+/* .IP tls_context
+/* TLS session object from tls_proxy_context_receive().
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <stringops.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <mail_params.h>
+
+/* TLS library-specific. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+#define TLSPROXY_INIT_TIMEOUT 10
+
+/* SLMs. */
+
+#define STR vstring_str
+
+/* tls_proxy_open - open negotiations with TLS proxy */
+
+VSTREAM *tls_proxy_open(const char *service, int flags,
+ VSTREAM *peer_stream,
+ const char *peer_addr,
+ const char *peer_port,
+ int handshake_timeout,
+ int session_timeout,
+ const char *serverid,
+ void *tls_params,
+ void *init_props,
+ void *start_props)
+{
+ const char myname[] = "tls_proxy_open";
+ VSTREAM *tlsproxy_stream;
+ int status;
+ int fd;
+ static VSTRING *tlsproxy_service = 0;
+ static VSTRING *remote_endpt = 0;
+
+ /*
+ * Initialize.
+ */
+ if (tlsproxy_service == 0) {
+ tlsproxy_service = vstring_alloc(20);
+ remote_endpt = vstring_alloc(20);
+ }
+
+ /*
+ * Connect to the tlsproxy(8) daemon.
+ */
+ vstring_sprintf(tlsproxy_service, "%s/%s", MAIL_CLASS_PRIVATE, service);
+ if ((fd = LOCAL_CONNECT(STR(tlsproxy_service), BLOCKING,
+ TLSPROXY_INIT_TIMEOUT)) < 0) {
+ msg_warn("connect to %s service: %m", STR(tlsproxy_service));
+ return (0);
+ }
+
+ /*
+ * Initial handshake. Send common data attributes now, and send the
+ * remote peer file descriptor in a later transaction.
+ */
+ tlsproxy_stream = vstream_fdopen(fd, O_RDWR);
+ vstring_sprintf(remote_endpt, "[%s]:%s", peer_addr, peer_port);
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, STR(remote_endpt)),
+ SEND_ATTR_INT(TLS_ATTR_FLAGS, flags),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, handshake_timeout),
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, session_timeout),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID, serverid),
+ ATTR_TYPE_END);
+ /* Do not flush the stream yet. */
+ if (vstream_ferror(tlsproxy_stream) != 0) {
+ msg_warn("error sending request to %s service: %m",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+ switch (flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) {
+ case TLS_PROXY_FLAG_ROLE_CLIENT:
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_client_param_print, tls_params),
+ SEND_ATTR_FUNC(tls_proxy_client_init_print, init_props),
+ SEND_ATTR_FUNC(tls_proxy_client_start_print, start_props),
+ ATTR_TYPE_END);
+ break;
+ case TLS_PROXY_FLAG_ROLE_SERVER:
+#if 0
+ attr_print(tlsproxy_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(tls_proxy_server_param_print, tls_params),
+ SEND_ATTR_FUNC(tls_proxy_server_init_print, init_props),
+ SEND_ATTR_FUNC(tls_proxy_server_start_print, start_props),
+ ATTR_TYPE_END);
+#endif
+ break;
+ default:
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+ }
+ if (vstream_fflush(tlsproxy_stream) != 0) {
+ msg_warn("error sending request to %s service: %m",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+
+ /*
+ * Receive the "TLS is available" indication.
+ *
+ * This may seem out of order, but we must have a read transaction between
+ * sending the request attributes and sending the plaintext file
+ * descriptor. We can't assume UNIX-domain socket semantics here.
+ */
+ if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ /* TODO: informative message. */
+ ATTR_TYPE_END) != 1 || status == 0) {
+
+ /*
+ * The TLS proxy reports that the TLS engine is not available (due to
+ * configuration error, or other causes).
+ */
+ msg_warn("%s service role \"%s\" is not available",
+ STR(tlsproxy_service),
+ (flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "server" :
+ (flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "client" :
+ "bogus role");
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+
+ /*
+ * Send the remote peer file descriptor.
+ */
+ if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream),
+ vstream_fileno(peer_stream)) < 0) {
+
+ /*
+ * Some error: drop the TLS proxy stream.
+ */
+ msg_warn("sending file handle to %s service: %m",
+ STR(tlsproxy_service));
+ vstream_fclose(tlsproxy_stream);
+ return (0);
+ }
+ return (tlsproxy_stream);
+}
+
+
+/* tls_proxy_context_receive - receive TLS session object from tlsproxy(8) */
+
+TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *proxy_stream)
+{
+ TLS_SESS_STATE *tls_context = 0;
+
+ if (attr_scan(proxy_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context),
+ ATTR_TYPE_END) != 1) {
+ if (tls_context)
+ tls_proxy_context_free(tls_context);
+ return (0);
+ } else {
+ return (tls_context);
+ }
+}
+
+#endif
diff --git a/src/tls/tls_proxy_context_print.c b/src/tls/tls_proxy_context_print.c
new file mode 100644
index 0000000..a9cbc5c
--- /dev/null
+++ b/src/tls/tls_proxy_context_print.c
@@ -0,0 +1,112 @@
+/*++
+/* NAME
+/* tls_proxy_context_print
+/* SUMMARY
+/* write TLS_ATTR_STATE structure to stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_context_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_MASTER_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* tls_proxy_context_print() writes the public members of a
+/* TLS_ATTR_STATE structure to the named stream using the
+/* specified attribute print routine. tls_proxy_context_print()
+/* is meant to be passed as a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(tls_proxy_context_print, (void *) tls_context), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_context_print - send TLS session state over stream */
+
+int tls_proxy_context_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SESS_STATE *tp = (TLS_SESS_STATE *) ptr;
+ int ret;
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_PEER_CN,
+ STRING_OR_EMPTY(tp->peer_CN)),
+ SEND_ATTR_STR(TLS_ATTR_ISSUER_CN,
+ STRING_OR_EMPTY(tp->issuer_CN)),
+ SEND_ATTR_STR(TLS_ATTR_PEER_CERT_FPT,
+ STRING_OR_EMPTY(tp->peer_cert_fprint)),
+ SEND_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT,
+ STRING_OR_EMPTY(tp->peer_pkey_fprint)),
+ SEND_ATTR_INT(TLS_ATTR_PEER_STATUS,
+ tp->peer_status),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL,
+ STRING_OR_EMPTY(tp->protocol)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_NAME,
+ STRING_OR_EMPTY(tp->cipher_name)),
+ SEND_ATTR_INT(TLS_ATTR_CIPHER_USEBITS,
+ tp->cipher_usebits),
+ SEND_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS,
+ tp->cipher_algbits),
+ SEND_ATTR_STR(TLS_ATTR_KEX_NAME,
+ STRING_OR_EMPTY(tp->kex_name)),
+ SEND_ATTR_STR(TLS_ATTR_KEX_CURVE,
+ STRING_OR_EMPTY(tp->kex_curve)),
+ SEND_ATTR_INT(TLS_ATTR_KEX_BITS,
+ tp->kex_bits),
+ SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME,
+ STRING_OR_EMPTY(tp->clnt_sig_name)),
+ SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE,
+ STRING_OR_EMPTY(tp->clnt_sig_curve)),
+ SEND_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS,
+ tp->clnt_sig_bits),
+ SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_DGST,
+ STRING_OR_EMPTY(tp->clnt_sig_dgst)),
+ SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_NAME,
+ STRING_OR_EMPTY(tp->srvr_sig_name)),
+ SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_CURVE,
+ STRING_OR_EMPTY(tp->srvr_sig_curve)),
+ SEND_ATTR_INT(TLS_ATTR_SRVR_SIG_BITS,
+ tp->srvr_sig_bits),
+ SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_DGST,
+ STRING_OR_EMPTY(tp->srvr_sig_dgst)),
+ SEND_ATTR_STR(TLS_ATTR_NAMADDR,
+ STRING_OR_EMPTY(tp->namaddr)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_context_scan.c b/src/tls/tls_proxy_context_scan.c
new file mode 100644
index 0000000..96afdda
--- /dev/null
+++ b/src/tls/tls_proxy_context_scan.c
@@ -0,0 +1,188 @@
+/*++
+/* NAME
+/* tls_proxy_context_scan
+/* SUMMARY
+/* read TLS session state from stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_context_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_MASTER_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_context_free(tls_context)
+/* TLS_SESS_STATE *tls_context;
+/* DESCRIPTION
+/* tls_proxy_context_scan() reads the public members of a
+/* TLS_ATTR_STATE structure from the named stream using the
+/* specified attribute scan routine. tls_proxy_context_scan()
+/* is meant to be passed as a call-back to attr_scan() as shown
+/* below.
+/*
+/* tls_proxy_context_free() destroys a TLS context object that
+/* was received with tls_proxy_context_scan().
+/*
+/* TLS_ATTR_STATE *tls_context = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context), ...
+/* ...
+/* if (tls_context)
+/* tls_proxy_context_free(tls_context);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+#include <msg.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_context_scan - receive TLS session state from stream */
+
+int tls_proxy_context_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SESS_STATE *tls_context
+ = (TLS_SESS_STATE *) mymalloc(sizeof(*tls_context));;
+ int ret;
+ VSTRING *peer_CN = vstring_alloc(25);
+ VSTRING *issuer_CN = vstring_alloc(25);
+ VSTRING *peer_cert_fprint = vstring_alloc(60); /* 60 for SHA-1 */
+ VSTRING *peer_pkey_fprint = vstring_alloc(60); /* 60 for SHA-1 */
+ VSTRING *protocol = vstring_alloc(25);
+ VSTRING *cipher_name = vstring_alloc(25);
+ VSTRING *kex_name = vstring_alloc(25);
+ VSTRING *kex_curve = vstring_alloc(25);
+ VSTRING *clnt_sig_name = vstring_alloc(25);
+ VSTRING *clnt_sig_curve = vstring_alloc(25);
+ VSTRING *clnt_sig_dgst = vstring_alloc(25);
+ VSTRING *srvr_sig_name = vstring_alloc(25);
+ VSTRING *srvr_sig_curve = vstring_alloc(25);
+ VSTRING *srvr_sig_dgst = vstring_alloc(25);
+ VSTRING *namaddr = vstring_alloc(100);
+
+ if (msg_verbose)
+ msg_info("begin tls_proxy_context_scan");
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(tls_context, 0, sizeof(*tls_context));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_PEER_CN, peer_CN),
+ RECV_ATTR_STR(TLS_ATTR_ISSUER_CN, issuer_CN),
+ RECV_ATTR_STR(TLS_ATTR_PEER_CERT_FPT, peer_cert_fprint),
+ RECV_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT, peer_pkey_fprint),
+ RECV_ATTR_INT(TLS_ATTR_PEER_STATUS,
+ &tls_context->peer_status),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL, protocol),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_NAME, cipher_name),
+ RECV_ATTR_INT(TLS_ATTR_CIPHER_USEBITS,
+ &tls_context->cipher_usebits),
+ RECV_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS,
+ &tls_context->cipher_algbits),
+ RECV_ATTR_STR(TLS_ATTR_KEX_NAME, kex_name),
+ RECV_ATTR_STR(TLS_ATTR_KEX_CURVE, kex_curve),
+ RECV_ATTR_INT(TLS_ATTR_KEX_BITS, &tls_context->kex_bits),
+ RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME, clnt_sig_name),
+ RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE, clnt_sig_curve),
+ RECV_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS, &tls_context->clnt_sig_bits),
+ RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_DGST, clnt_sig_dgst),
+ RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_NAME, srvr_sig_name),
+ RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_CURVE, srvr_sig_curve),
+ RECV_ATTR_INT(TLS_ATTR_SRVR_SIG_BITS, &tls_context->srvr_sig_bits),
+ RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_DGST, srvr_sig_dgst),
+ RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ tls_context->peer_CN = vstring_export(peer_CN);
+ tls_context->issuer_CN = vstring_export(issuer_CN);
+ tls_context->peer_cert_fprint = vstring_export(peer_cert_fprint);
+ tls_context->peer_pkey_fprint = vstring_export(peer_pkey_fprint);
+ tls_context->protocol = vstring_export(protocol);
+ tls_context->cipher_name = vstring_export(cipher_name);
+ tls_context->kex_name = vstring_export(kex_name);
+ tls_context->kex_curve = vstring_export(kex_curve);
+ tls_context->clnt_sig_name = vstring_export(clnt_sig_name);
+ tls_context->clnt_sig_curve = vstring_export(clnt_sig_curve);
+ tls_context->clnt_sig_dgst = vstring_export(clnt_sig_dgst);
+ tls_context->srvr_sig_name = vstring_export(srvr_sig_name);
+ tls_context->srvr_sig_curve = vstring_export(srvr_sig_curve);
+ tls_context->srvr_sig_dgst = vstring_export(srvr_sig_dgst);
+ tls_context->namaddr = vstring_export(namaddr);
+ ret = (ret == 21 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_context_free(tls_context);
+ tls_context = 0;
+ }
+ *(TLS_SESS_STATE **) ptr = tls_context;
+ if (msg_verbose)
+ msg_info("tls_proxy_context_scan ret=%d", ret);
+ return (ret);
+}
+
+/* tls_proxy_context_free - destroy object from tls_proxy_context_receive() */
+
+void tls_proxy_context_free(TLS_SESS_STATE *tls_context)
+{
+ if (tls_context->peer_CN)
+ myfree(tls_context->peer_CN);
+ if (tls_context->issuer_CN)
+ myfree(tls_context->issuer_CN);
+ if (tls_context->peer_cert_fprint)
+ myfree(tls_context->peer_cert_fprint);
+ if (tls_context->peer_pkey_fprint)
+ myfree(tls_context->peer_pkey_fprint);
+ if (tls_context->protocol)
+ myfree((void *) tls_context->protocol);
+ if (tls_context->cipher_name)
+ myfree((void *) tls_context->cipher_name);
+ if (tls_context->kex_name)
+ myfree((void *) tls_context->kex_name);
+ if (tls_context->kex_curve)
+ myfree((void *) tls_context->kex_curve);
+ if (tls_context->clnt_sig_name)
+ myfree((void *) tls_context->clnt_sig_name);
+ if (tls_context->clnt_sig_curve)
+ myfree((void *) tls_context->clnt_sig_curve);
+ if (tls_context->clnt_sig_dgst)
+ myfree((void *) tls_context->clnt_sig_dgst);
+ if (tls_context->srvr_sig_name)
+ myfree((void *) tls_context->srvr_sig_name);
+ if (tls_context->srvr_sig_curve)
+ myfree((void *) tls_context->srvr_sig_curve);
+ if (tls_context->srvr_sig_dgst)
+ myfree((void *) tls_context->srvr_sig_dgst);
+ if (tls_context->namaddr)
+ myfree((void *) tls_context->namaddr);
+ myfree((void *) tls_context);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_server_print.c b/src/tls/tls_proxy_server_print.c
new file mode 100644
index 0000000..a0a567f
--- /dev/null
+++ b/src/tls/tls_proxy_server_print.c
@@ -0,0 +1,143 @@
+/*++
+/* NAME
+/* tls_proxy_server_print 3
+/* SUMMARY
+/* write TLS_SERVER_XXX structures to stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_server_init_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_MASTER_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* int tls_proxy_server_start_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_MASTER_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* tls_proxy_server_init_print() writes a TLS_SERVER_INIT_PROPS
+/* structure to the named stream using the specified attribute print
+/* routine. tls_proxy_server_init_print() is meant to be passed as
+/* a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(tls_proxy_server_init_print, (void *) init_props), ...
+/*
+/* tls_proxy_server_start_print() writes a TLS_SERVER_START_PROPS
+/* structure to the named stream using the specified attribute print
+/* routine. tls_proxy_server_start_print() is meant to be passed as
+/* a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(tls_proxy_server_start_print, (void *) start_props), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_server_init_print - send TLS_SERVER_INIT_PROPS over stream */
+
+int tls_proxy_server_init_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SERVER_INIT_PROPS *props = (TLS_SERVER_INIT_PROPS *) ptr;
+ int ret;
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(TLS_ATTR_LOG_PARAM,
+ STRING_OR_EMPTY(props->log_param)),
+ SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL,
+ STRING_OR_EMPTY(props->log_level)),
+ SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth),
+ SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE,
+ STRING_OR_EMPTY(props->cache_type)),
+ SEND_ATTR_INT(TLS_ATTR_SET_SESSID, props->set_sessid),
+ SEND_ATTR_STR(TLS_ATTR_CHAIN_FILES,
+ STRING_OR_EMPTY(props->chain_files)),
+ SEND_ATTR_STR(TLS_ATTR_CERT_FILE,
+ STRING_OR_EMPTY(props->cert_file)),
+ SEND_ATTR_STR(TLS_ATTR_KEY_FILE,
+ STRING_OR_EMPTY(props->key_file)),
+ SEND_ATTR_STR(TLS_ATTR_DCERT_FILE,
+ STRING_OR_EMPTY(props->dcert_file)),
+ SEND_ATTR_STR(TLS_ATTR_DKEY_FILE,
+ STRING_OR_EMPTY(props->dkey_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE,
+ STRING_OR_EMPTY(props->eccert_file)),
+ SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE,
+ STRING_OR_EMPTY(props->eckey_file)),
+ SEND_ATTR_STR(TLS_ATTR_CAFILE,
+ STRING_OR_EMPTY(props->CAfile)),
+ SEND_ATTR_STR(TLS_ATTR_CAPATH,
+ STRING_OR_EMPTY(props->CApath)),
+ SEND_ATTR_STR(TLS_ATTR_PROTOCOLS,
+ STRING_OR_EMPTY(props->protocols)),
+ SEND_ATTR_STR(TLS_ATTR_EECDH_GRADE,
+ STRING_OR_EMPTY(props->eecdh_grade)),
+ SEND_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE,
+ STRING_OR_EMPTY(props->dh1024_param_file)),
+ SEND_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE,
+ STRING_OR_EMPTY(props->dh512_param_file)),
+ SEND_ATTR_INT(TLS_ATTR_ASK_CCERT, props->ask_ccert),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ return (ret);
+}
+
+/* tls_proxy_server_start_print - send TLS_SERVER_START_PROPS over stream */
+
+int tls_proxy_server_start_print(ATTR_PRINT_MASTER_FN print_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SERVER_START_PROPS *props = (TLS_SERVER_START_PROPS *) ptr;
+ int ret;
+
+#define STRING_OR_EMPTY(s) ((s) ? (s) : "")
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout),
+ SEND_ATTR_INT(TLS_ATTR_REQUIRECERT, props->requirecert),
+ SEND_ATTR_STR(TLS_ATTR_SERVERID,
+ STRING_OR_EMPTY(props->serverid)),
+ SEND_ATTR_STR(TLS_ATTR_NAMADDR,
+ STRING_OR_EMPTY(props->namaddr)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE,
+ STRING_OR_EMPTY(props->cipher_grade)),
+ SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ STRING_OR_EMPTY(props->cipher_exclusions)),
+ SEND_ATTR_STR(TLS_ATTR_MDALG,
+ STRING_OR_EMPTY(props->mdalg)),
+ ATTR_TYPE_END);
+ /* Do not flush the stream. */
+ return (ret);
+}
+
+#endif
diff --git a/src/tls/tls_proxy_server_scan.c b/src/tls/tls_proxy_server_scan.c
new file mode 100644
index 0000000..f7b3fb6
--- /dev/null
+++ b/src/tls/tls_proxy_server_scan.c
@@ -0,0 +1,245 @@
+/*++
+/* NAME
+/* tls_proxy_server_scan 3
+/* SUMMARY
+/* read TLS_SERVER_XXX structures from stream
+/* SYNOPSIS
+/* #include <tls_proxy.h>
+/*
+/* int tls_proxy_server_init_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_MASTER_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* tls_proxy_server_init_free(init_props)
+/* TLS_SERVER_INIT_PROPS *init_props;
+/*
+/* int tls_proxy_server_start_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_MASTER_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* void tls_proxy_server_start_free(start_props)
+/* TLS_SERVER_START_PROPS *start_props;
+/* DESCRIPTION
+/* tls_proxy_server_init_scan() reads a TLS_SERVER_INIT_PROPS
+/* structure from the named stream using the specified attribute
+/* scan routine. tls_proxy_server_init_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_server_init_free() destroys a TLS_SERVER_INIT_PROPS
+/* structure that was created by tls_proxy_server_init_scan().
+/*
+/* TLS_SERVER_INIT_PROPS *init_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_server_init_scan, (void *) &init_props)
+/* ...
+/* if (init_props)
+/* tls_proxy_client_init_free(init_props);
+/*
+/* tls_proxy_server_start_scan() reads a TLS_SERVER_START_PROPS
+/* structure from the named stream using the specified attribute
+/* scan routine. tls_proxy_server_start_scan() is meant to be passed
+/* as a call-back function to attr_scan(), as shown below.
+/*
+/* tls_proxy_server_start_free() destroys a TLS_SERVER_START_PROPS
+/* structure that was created by tls_proxy_server_start_scan().
+/*
+/* TLS_SERVER_START_PROPS *start_props = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(tls_proxy_server_start_scan, (void *) &start_props)
+/* ...
+/* if (start_props)
+/* tls_proxy_server_start_free(start_props);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library */
+
+#include <attr.h>
+
+/* TLS library. */
+
+#include <tls.h>
+#include <tls_proxy.h>
+
+/* tls_proxy_server_init_scan - receive TLS_SERVER_INIT_PROPS from stream */
+
+int tls_proxy_server_init_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SERVER_INIT_PROPS *props
+ = (TLS_SERVER_INIT_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *log_param = vstring_alloc(25);
+ VSTRING *log_level = vstring_alloc(25);
+ VSTRING *cache_type = vstring_alloc(25);
+ VSTRING *chain_files = vstring_alloc(25);
+ VSTRING *cert_file = vstring_alloc(25);
+ VSTRING *key_file = vstring_alloc(25);
+ VSTRING *dcert_file = vstring_alloc(25);
+ VSTRING *dkey_file = vstring_alloc(25);
+ VSTRING *eccert_file = vstring_alloc(25);
+ VSTRING *eckey_file = vstring_alloc(25);
+ VSTRING *CAfile = vstring_alloc(25);
+ VSTRING *CApath = vstring_alloc(25);
+ VSTRING *protocols = vstring_alloc(25);
+ VSTRING *eecdh_grade = vstring_alloc(25);
+ VSTRING *dh1024_param_file = vstring_alloc(25);
+ VSTRING *dh512_param_file = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param),
+ RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level),
+ RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth),
+ RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type),
+ RECV_ATTR_INT(TLS_ATTR_SET_SESSID, &props->set_sessid),
+ RECV_ATTR_STR(TLS_ATTR_CHAIN_FILES, chain_files),
+ RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file),
+ RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file),
+ RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file),
+ RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file),
+ RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file),
+ RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file),
+ RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile),
+ RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath),
+ RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols),
+ RECV_ATTR_STR(TLS_ATTR_EECDH_GRADE, eecdh_grade),
+ RECV_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE, dh1024_param_file),
+ RECV_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE, dh512_param_file),
+ RECV_ATTR_INT(TLS_ATTR_ASK_CCERT, &props->ask_ccert),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ ATTR_TYPE_END);
+ /* Always construct a well-formed structure. */
+ props->log_param = vstring_export(log_param);
+ props->log_level = vstring_export(log_level);
+ props->cache_type = vstring_export(cache_type);
+ props->chain_files = vstring_export(chain_files);
+ props->cert_file = vstring_export(cert_file);
+ props->key_file = vstring_export(key_file);
+ props->dcert_file = vstring_export(dcert_file);
+ props->dkey_file = vstring_export(dkey_file);
+ props->eccert_file = vstring_export(eccert_file);
+ props->eckey_file = vstring_export(eckey_file);
+ props->CAfile = vstring_export(CAfile);
+ props->CApath = vstring_export(CApath);
+ props->protocols = vstring_export(protocols);
+ props->eecdh_grade = vstring_export(eecdh_grade);
+ props->dh1024_param_file = vstring_export(dh1024_param_file);
+ props->dh512_param_file = vstring_export(dh512_param_file);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 20 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_server_init_free(props);
+ props = 0;
+ }
+ *(TLS_SERVER_INIT_PROPS **) ptr = props;
+ return (ret);
+}
+
+/* tls_proxy_server_init_free - destroy TLS_SERVER_INIT_PROPS structure */
+
+void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *props)
+{
+ myfree((void *) props->log_param);
+ myfree((void *) props->log_level);
+ myfree((void *) props->cache_type);
+ myfree((void *) props->chain_files);
+ myfree((void *) props->cert_file);
+ myfree((void *) props->key_file);
+ myfree((void *) props->dcert_file);
+ myfree((void *) props->dkey_file);
+ myfree((void *) props->eccert_file);
+ myfree((void *) props->eckey_file);
+ myfree((void *) props->CAfile);
+ myfree((void *) props->CApath);
+ myfree((void *) props->protocols);
+ myfree((void *) props->eecdh_grade);
+ myfree((void *) props->dh1024_param_file);
+ myfree((void *) props->dh512_param_file);
+ myfree((void *) props->mdalg);
+ myfree((void *) props);
+}
+
+/* tls_proxy_server_start_scan - receive TLS_SERVER_START_PROPS from stream */
+
+int tls_proxy_server_start_scan(ATTR_SCAN_MASTER_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ TLS_SERVER_START_PROPS *props
+ = (TLS_SERVER_START_PROPS *) mymalloc(sizeof(*props));
+ int ret;
+ VSTRING *serverid = vstring_alloc(25);
+ VSTRING *namaddr = vstring_alloc(25);
+ VSTRING *cipher_grade = vstring_alloc(25);
+ VSTRING *cipher_exclusions = vstring_alloc(25);
+ VSTRING *mdalg = vstring_alloc(25);
+
+ /*
+ * Note: memset() is not a portable way to initialize non-integer types.
+ */
+ memset(props, 0, sizeof(*props));
+ props->ctx = 0;
+ props->stream = 0;
+ /* XXX Caller sets fd. */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout),
+ RECV_ATTR_INT(TLS_ATTR_REQUIRECERT, &props->requirecert),
+ RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid),
+ RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade),
+ RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS,
+ cipher_exclusions),
+ RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg),
+ ATTR_TYPE_END);
+ props->serverid = vstring_export(serverid);
+ props->namaddr = vstring_export(namaddr);
+ props->cipher_grade = vstring_export(cipher_grade);
+ props->cipher_exclusions = vstring_export(cipher_exclusions);
+ props->mdalg = vstring_export(mdalg);
+ ret = (ret == 7 ? 1 : -1);
+ if (ret != 1) {
+ tls_proxy_server_start_free(props);
+ props = 0;
+ }
+ *(TLS_SERVER_START_PROPS **) ptr = props;
+ return (ret);
+}
+
+/* tls_proxy_server_start_free - destroy TLS_SERVER_START_PROPS structure */
+
+void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *props)
+{
+ /* XXX Caller closes fd. */
+ myfree((void *) props->serverid);
+ myfree((void *) props->namaddr);
+ myfree((void *) props->cipher_grade);
+ myfree((void *) props->cipher_exclusions);
+ myfree((void *) props->mdalg);
+ myfree((void *) props);
+}
+
+#endif
diff --git a/src/tls/tls_rsa.c b/src/tls/tls_rsa.c
new file mode 100644
index 0000000..67f2a2e
--- /dev/null
+++ b/src/tls/tls_rsa.c
@@ -0,0 +1,127 @@
+/*++
+/* NAME
+/* tls_rsa
+/* SUMMARY
+/* RSA support
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* RSA *tls_tmp_rsa_cb(ssl, export, keylength)
+/* SSL *ssl; /* unused */
+/* int export;
+/* int keylength;
+/* DESCRIPTION
+/* tls_tmp_rsa_cb() is a call-back routine for the
+/* SSL_CTX_set_tmp_rsa_callback() function.
+/*
+/* This implementation will generate only 512-bit ephemeral
+/* RSA keys for export ciphersuites. It will log a warning in
+/* all other usage contexts.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Viktor Dukhovni.
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <msg.h>
+
+#ifdef USE_TLS
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+#include <openssl/rsa.h>
+
+ /*
+ * 2015-12-05: Ephemeral RSA removed from OpenSSL 1.1.0-dev
+ */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+/* tls_tmp_rsa_cb - call-back to generate ephemeral RSA key */
+
+RSA *tls_tmp_rsa_cb(SSL *unused_ssl, int export, int keylength)
+{
+ static RSA *rsa_tmp;
+
+ /*
+ * We generate ephemeral RSA keys only for export ciphersuites. In all
+ * other contexts use of ephemeral RSA keys violates the SSL/TLS
+ * protocol, and only takes place when applications ask for trouble and
+ * set the SSL_OP_EPHEMERAL_RSA option. Postfix should never do that.
+ */
+ if (!export || keylength != 512) {
+ msg_warn("%sexport %d-bit ephemeral RSA key requested",
+ export ? "" : "non-", keylength);
+ return 0;
+ }
+ if (rsa_tmp == 0) {
+ BIGNUM *e = BN_new();
+
+ if (e != 0 && BN_set_word(e, RSA_F4) && (rsa_tmp = RSA_new()) != 0)
+ if (!RSA_generate_key_ex(rsa_tmp, keylength, e, 0)) {
+ RSA_free(rsa_tmp);
+ rsa_tmp = 0;
+ }
+ if (e)
+ BN_free(e);
+ }
+ return (rsa_tmp);
+}
+
+#endif /* OPENSSL_VERSION_NUMBER */
+
+#ifdef TEST
+
+#include <msg_vstream.h>
+
+int main(int unused_argc, char *const argv[])
+{
+ int ok = 0;
+
+ /*
+ * 2015-12-05: Ephemeral RSA removed from OpenSSL 1.1.0-dev
+ */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ RSA *rsa;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /* Export at 512-bits should work */
+ rsa = tls_tmp_rsa_cb(0, 1, 512);
+ ok = rsa != 0 && RSA_size(rsa) == 512 / 8;
+ ok = ok && PEM_write_RSAPrivateKey(stdout, rsa, 0, 0, 0, 0, 0);
+ tls_print_errors();
+
+ /* Non-export or unexpected bit length should fail */
+ ok = ok && tls_tmp_rsa_cb(0, 0, 512) == 0;
+ ok = ok && tls_tmp_rsa_cb(0, 1, 1024) == 0;
+#endif
+
+ return ok ? 0 : 1;
+}
+
+#endif
+
+#endif
diff --git a/src/tls/tls_scache.c b/src/tls/tls_scache.c
new file mode 100644
index 0000000..dcfd3d0
--- /dev/null
+++ b/src/tls/tls_scache.c
@@ -0,0 +1,595 @@
+/*++
+/* NAME
+/* tls_scache 3
+/* SUMMARY
+/* TLS session cache manager
+/* SYNOPSIS
+/* #include <tls_scache.h>
+/*
+/* TLS_SCACHE *tls_scache_open(dbname, cache_label, verbose, timeout)
+/* const char *dbname
+/* const char *cache_label;
+/* int verbose;
+/* int timeout;
+/*
+/* void tls_scache_close(cache)
+/* TLS_SCACHE *cache;
+/*
+/* int tls_scache_lookup(cache, cache_id, out_session)
+/* TLS_SCACHE *cache;
+/* const char *cache_id;
+/* VSTRING *out_session;
+/*
+/* int tls_scache_update(cache, cache_id, session, session_len)
+/* TLS_SCACHE *cache;
+/* const char *cache_id;
+/* const char *session;
+/* ssize_t session_len;
+/*
+/* int tls_scache_sequence(cache, first_next, out_cache_id,
+/* VSTRING *out_session)
+/* TLS_SCACHE *cache;
+/* int first_next;
+/* char **out_cache_id;
+/* VSTRING *out_session;
+/*
+/* int tls_scache_delete(cache, cache_id)
+/* TLS_SCACHE *cache;
+/* const char *cache_id;
+/*
+/* TLS_TICKET_KEY *tls_scache_key(keyname, now, timeout)
+/* unsigned char *keyname;
+/* time_t now;
+/* int timeout;
+/*
+/* TLS_TICKET_KEY *tls_scache_key_rotate(newkey)
+/* TLS_TICKET_KEY *newkey;
+/* DESCRIPTION
+/* This module maintains Postfix TLS session cache files.
+/* each session is stored under a lookup key (hostname or
+/* session ID).
+/*
+/* tls_scache_open() opens the specified TLS session cache
+/* and returns a handle that must be used for subsequent
+/* access.
+/*
+/* tls_scache_close() closes the specified TLS session cache
+/* and releases memory that was allocated by tls_scache_open().
+/*
+/* tls_scache_lookup() looks up the specified session in the
+/* specified cache, and applies session timeout restrictions.
+/* Entries that are too old are silently deleted.
+/*
+/* tls_scache_update() updates the specified TLS session cache
+/* with the specified session information.
+/*
+/* tls_scache_sequence() iterates over the specified TLS session
+/* cache and either returns the first or next entry that has not
+/* timed out, or returns no data. Entries that are too old are
+/* silently deleted. Specify TLS_SCACHE_SEQUENCE_NOTHING as the
+/* third and last argument to disable saving of cache entry
+/* content or cache entry ID information. This is useful when
+/* purging expired entries. A result value of zero means that
+/* the end of the cache was reached.
+/*
+/* tls_scache_delete() removes the specified cache entry from
+/* the specified TLS session cache.
+/*
+/* tls_scache_key() locates a TLS session ticket key in a 2-element
+/* in-memory cache. A null result is returned if no unexpired matching
+/* key is found.
+/*
+/* tls_scache_key_rotate() saves a TLS session tickets key in the
+/* in-memory cache.
+/*
+/* Arguments:
+/* .IP dbname
+/* The base name of the session cache file.
+/* .IP cache_label
+/* A string that is used in logging and error messages.
+/* .IP verbose
+/* Do verbose logging of cache operations? (zero == no)
+/* .IP timeout
+/* The time after which a session cache entry is considered too old.
+/* .IP first_next
+/* One of DICT_SEQ_FUN_FIRST (first cache element) or DICT_SEQ_FUN_NEXT
+/* (next cache element).
+/* .IP cache_id
+/* Session cache lookup key.
+/* .IP session
+/* Storage for session information.
+/* .IP session_len
+/* The size of the session information in bytes.
+/* .IP out_cache_id
+/* .IP out_session
+/* Storage for saving the cache_id or session information of the
+/* current cache entry.
+/*
+/* Specify TLS_SCACHE_DONT_NEED_CACHE_ID to avoid saving
+/* the session cache ID of the cache entry.
+/*
+/* Specify TLS_SCACHE_DONT_NEED_SESSION to avoid
+/* saving the session information in the cache entry.
+/* .IP keyname
+/* Is null when requesting the current encryption keys. Otherwise,
+/* keyname is a pointer to an array of TLS_TICKET_NAMELEN unsigned
+/* chars (not NUL terminated) that is an identifier for a key
+/* previously used to encrypt a session ticket.
+/* .IP now
+/* Current epoch time passed by caller.
+/* .IP timeout
+/* TLS session ticket encryption lifetime.
+/* .IP newkey
+/* TLS session ticket key obtained from tlsmgr(8) to be added to
+ * internal cache.
+/* DIAGNOSTICS
+/* These routines terminate with a fatal run-time error
+/* for unrecoverable database errors. This allows the
+/* program to restart and reset the database to an
+/* empty initial state.
+/*
+/* tls_scache_open() never returns on failure. All other
+/* functions return non-zero on success, zero when the
+/* operation could not be completed.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+#include <string.h>
+#include <stddef.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <dict.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <hex_code.h>
+#include <myflock.h>
+#include <vstring.h>
+#include <timecmp.h>
+
+/* Global library. */
+
+/* TLS library. */
+
+#include <tls_scache.h>
+
+/* Application-specific. */
+
+ /*
+ * Session cache entry format.
+ */
+typedef struct {
+ time_t timestamp; /* time when saved */
+ char session[1]; /* actually a bunch of bytes */
+} TLS_SCACHE_ENTRY;
+
+static TLS_TICKET_KEY *keys[2];
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* tls_scache_encode - encode TLS session cache entry */
+
+static VSTRING *tls_scache_encode(TLS_SCACHE *cp, const char *cache_id,
+ const char *session,
+ ssize_t session_len)
+{
+ TLS_SCACHE_ENTRY *entry;
+ VSTRING *hex_data;
+ ssize_t binary_data_len;
+
+ /*
+ * Assemble the TLS session cache entry.
+ *
+ * We could eliminate some copying by using incremental encoding, but
+ * sessions are so small that it really does not matter.
+ */
+ binary_data_len = session_len + offsetof(TLS_SCACHE_ENTRY, session);
+ entry = (TLS_SCACHE_ENTRY *) mymalloc(binary_data_len);
+ entry->timestamp = time((time_t *) 0);
+ memcpy(entry->session, session, session_len);
+
+ /*
+ * Encode the TLS session cache entry.
+ */
+ hex_data = vstring_alloc(2 * binary_data_len + 1);
+ hex_encode(hex_data, (char *) entry, binary_data_len);
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("write %s TLS cache entry %s: time=%ld [data %ld bytes]",
+ cp->cache_label, cache_id, (long) entry->timestamp,
+ (long) session_len);
+
+ /*
+ * Clean up.
+ */
+ myfree((void *) entry);
+
+ return (hex_data);
+}
+
+/* tls_scache_decode - decode TLS session cache entry */
+
+static int tls_scache_decode(TLS_SCACHE *cp, const char *cache_id,
+ const char *hex_data, ssize_t hex_data_len,
+ VSTRING *out_session)
+{
+ TLS_SCACHE_ENTRY *entry;
+ VSTRING *bin_data;
+
+ /*
+ * Sanity check.
+ */
+ if (hex_data_len < 2 * (offsetof(TLS_SCACHE_ENTRY, session))) {
+ msg_warn("%s TLS cache: truncated entry for %s: %.100s",
+ cp->cache_label, cache_id, hex_data);
+ return (0);
+ }
+
+ /*
+ * Disassemble the TLS session cache entry.
+ *
+ * No early returns or we have a memory leak.
+ */
+#define FREE_AND_RETURN(ptr, x) { vstring_free(ptr); return (x); }
+
+ bin_data = vstring_alloc(hex_data_len / 2 + 1);
+ if (hex_decode(bin_data, hex_data, hex_data_len) == 0) {
+ msg_warn("%s TLS cache: malformed entry for %s: %.100s",
+ cp->cache_label, cache_id, hex_data);
+ FREE_AND_RETURN(bin_data, 0);
+ }
+ entry = (TLS_SCACHE_ENTRY *) STR(bin_data);
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("read %s TLS cache entry %s: time=%ld [data %ld bytes]",
+ cp->cache_label, cache_id, (long) entry->timestamp,
+ (long) (LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session)));
+
+ /*
+ * Other mandatory restrictions.
+ */
+ if (entry->timestamp + cp->timeout < time((time_t *) 0))
+ FREE_AND_RETURN(bin_data, 0);
+
+ /*
+ * Optional output.
+ */
+ if (out_session != 0)
+ vstring_memcpy(out_session, entry->session,
+ LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session));
+
+ /*
+ * Clean up.
+ */
+ FREE_AND_RETURN(bin_data, 1);
+}
+
+/* tls_scache_lookup - load session from cache */
+
+int tls_scache_lookup(TLS_SCACHE *cp, const char *cache_id,
+ VSTRING *session)
+{
+ const char *hex_data;
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("lookup %s session id=%s", cp->cache_label, cache_id);
+
+ /*
+ * Initialize. Don't leak data.
+ */
+ if (session)
+ VSTRING_RESET(session);
+
+ /*
+ * Search the cache database.
+ */
+ if ((hex_data = dict_get(cp->db, cache_id)) == 0)
+ return (0);
+
+ /*
+ * Decode entry and delete if expired or malformed.
+ */
+ if (tls_scache_decode(cp, cache_id, hex_data, strlen(hex_data),
+ session) == 0) {
+ tls_scache_delete(cp, cache_id);
+ return (0);
+ } else {
+ return (1);
+ }
+}
+
+/* tls_scache_update - save session to cache */
+
+int tls_scache_update(TLS_SCACHE *cp, const char *cache_id,
+ const char *buf, ssize_t len)
+{
+ VSTRING *hex_data;
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("put %s session id=%s [data %ld bytes]",
+ cp->cache_label, cache_id, (long) len);
+
+ /*
+ * Encode the cache entry.
+ */
+ hex_data = tls_scache_encode(cp, cache_id, buf, len);
+
+ /*
+ * Store the cache entry.
+ *
+ * XXX Berkeley DB supports huge database keys and values. SDBM seems to
+ * have a finite limit, and DBM simply can't be used at all.
+ */
+ dict_put(cp->db, cache_id, STR(hex_data));
+
+ /*
+ * Clean up.
+ */
+ vstring_free(hex_data);
+
+ return (1);
+}
+
+/* tls_scache_sequence - get first/next TLS session cache entry */
+
+int tls_scache_sequence(TLS_SCACHE *cp, int first_next,
+ char **out_cache_id,
+ VSTRING *out_session)
+{
+ const char *member;
+ const char *value;
+ char *saved_cursor;
+ int found_entry;
+ int keep_entry;
+ char *saved_member;
+
+ /*
+ * XXX Deleting entries while enumerating a map can he tricky. Some map
+ * types have a concept of cursor and support a "delete the current
+ * element" operation. Some map types without cursors don't behave well
+ * when the current first/next entry is deleted (example: with Berkeley
+ * DB < 2, the "next" operation produces garbage). To avoid trouble, we
+ * delete an expired entry after advancing the current first/next
+ * position beyond it, and ignore client requests to delete the current
+ * entry.
+ */
+
+ /*
+ * Find the first or next database entry. Activate the passivated entry
+ * and check the time stamp. Schedule the entry for deletion if it is too
+ * old.
+ *
+ * Save the member (cache id) so that it will not be clobbered by the
+ * tls_scache_lookup() call below.
+ */
+ found_entry = (dict_seq(cp->db, first_next, &member, &value) == 0);
+ if (found_entry) {
+ keep_entry = tls_scache_decode(cp, member, value, strlen(value),
+ out_session);
+ if (keep_entry && out_cache_id)
+ *out_cache_id = mystrdup(member);
+ saved_member = mystrdup(member);
+ }
+
+ /*
+ * Delete behind. This is a no-op if an expired cache entry was updated
+ * in the mean time. Use the saved lookup criteria so that the "delete
+ * behind" operation works as promised.
+ *
+ * The delete-behind strategy assumes that all updates are made by a single
+ * process. Otherwise, delete-behind may remove an entry that was updated
+ * after it was scheduled for deletion.
+ */
+ if (cp->flags & TLS_SCACHE_FLAG_DEL_SAVED_CURSOR) {
+ cp->flags &= ~TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
+ saved_cursor = cp->saved_cursor;
+ cp->saved_cursor = 0;
+ tls_scache_lookup(cp, saved_cursor, (VSTRING *) 0);
+ myfree(saved_cursor);
+ }
+
+ /*
+ * Otherwise, clean up if this is not the first iteration.
+ */
+ else {
+ if (cp->saved_cursor)
+ myfree(cp->saved_cursor);
+ cp->saved_cursor = 0;
+ }
+
+ /*
+ * Protect the current first/next entry against explicit or implied
+ * client delete requests, and schedule a bad or expired entry for
+ * deletion. Save the lookup criteria so that the "delete behind"
+ * operation will work as promised.
+ */
+ if (found_entry) {
+ cp->saved_cursor = saved_member;
+ if (keep_entry == 0)
+ cp->flags |= TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
+ }
+ return (found_entry);
+}
+
+/* tls_scache_delete - delete session from cache */
+
+int tls_scache_delete(TLS_SCACHE *cp, const char *cache_id)
+{
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("delete %s session id=%s", cp->cache_label, cache_id);
+
+ /*
+ * Do it, unless we would delete the current first/next entry. Some map
+ * types don't have cursors, and some of those don't behave when the
+ * "current" entry is deleted.
+ */
+ return ((cp->saved_cursor != 0 && strcmp(cp->saved_cursor, cache_id) == 0)
+ || dict_del(cp->db, cache_id) == 0);
+}
+
+/* tls_scache_open - open TLS session cache file */
+
+TLS_SCACHE *tls_scache_open(const char *dbname, const char *cache_label,
+ int verbose, int timeout)
+{
+ TLS_SCACHE *cp;
+ DICT *dict;
+
+ /*
+ * Logging.
+ */
+ if (verbose)
+ msg_info("open %s TLS cache %s", cache_label, dbname);
+
+ /*
+ * Open the dictionary with O_TRUNC, so that we never have to worry about
+ * opening a damaged file after some process terminated abnormally.
+ */
+#ifdef SINGLE_UPDATER
+#define DICT_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_OPEN_LOCK \
+ | DICT_FLAG_UTF8_REQUEST)
+#else
+#define DICT_FLAGS \
+ (DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE \
+ | DICT_FLAG_UTF8_REQUEST)
+#endif
+
+ dict = dict_open(dbname, O_RDWR | O_CREAT | O_TRUNC, DICT_FLAGS);
+
+ /*
+ * Sanity checks.
+ */
+ if (dict->update == 0)
+ msg_fatal("dictionary %s does not support update operations", dbname);
+ if (dict->delete == 0)
+ msg_fatal("dictionary %s does not support delete operations", dbname);
+ if (dict->sequence == 0)
+ msg_fatal("dictionary %s does not support sequence operations", dbname);
+
+ /*
+ * Create the TLS_SCACHE object.
+ */
+ cp = (TLS_SCACHE *) mymalloc(sizeof(*cp));
+ cp->flags = 0;
+ cp->db = dict;
+ cp->cache_label = mystrdup(cache_label);
+ cp->verbose = verbose;
+ cp->timeout = timeout;
+ cp->saved_cursor = 0;
+
+ return (cp);
+}
+
+/* tls_scache_close - close TLS session cache file */
+
+void tls_scache_close(TLS_SCACHE *cp)
+{
+
+ /*
+ * Logging.
+ */
+ if (cp->verbose)
+ msg_info("close %s TLS cache %s", cp->cache_label, cp->db->name);
+
+ /*
+ * Destroy the TLS_SCACHE object.
+ */
+ dict_close(cp->db);
+ myfree(cp->cache_label);
+ if (cp->saved_cursor)
+ myfree(cp->saved_cursor);
+ myfree((void *) cp);
+}
+
+/* tls_scache_key - find session ticket key for given key name */
+
+TLS_TICKET_KEY *tls_scache_key(unsigned char *keyname, time_t now, int timeout)
+{
+ int i;
+
+ /*
+ * The keys array contains 2 elements, the current signing key and the
+ * previous key.
+ *
+ * When name == 0 we are issuing a ticket, otherwise decrypting an existing
+ * ticket with the given key name. For new tickets we always use the
+ * current key if unexpired. For existing tickets, we use either the
+ * current or previous key with a validation expiration that is timeout
+ * longer than the signing expiration.
+ */
+ if (keyname) {
+ for (i = 0; i < 2 && keys[i]; ++i) {
+ if (memcmp(keyname, keys[i]->name, TLS_TICKET_NAMELEN) == 0) {
+ if (timecmp(keys[i]->tout + timeout, now) > 0)
+ return (keys[i]);
+ break;
+ }
+ }
+ } else if (keys[0]) {
+ if (timecmp(keys[0]->tout, now) > 0)
+ return (keys[0]);
+ }
+ return (0);
+}
+
+/* tls_scache_key_rotate - rotate session ticket keys */
+
+TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *newkey)
+{
+
+ /*
+ * Allocate or re-use storage of retired key, then overwrite it, since
+ * caller's key data is ephemeral.
+ */
+ if (keys[1] == 0)
+ keys[1] = (TLS_TICKET_KEY *) mymalloc(sizeof(*newkey));
+ *keys[1] = *newkey;
+ newkey = keys[1];
+
+ /*
+ * Rotate if required, ensuring that the keys are sorted by expiration
+ * time with keys[0] expiring last.
+ */
+ if (keys[0] == 0 || keys[0]->tout < keys[1]->tout) {
+ keys[1] = keys[0];
+ keys[0] = newkey;
+ }
+ return (newkey);
+}
+
+#endif
diff --git a/src/tls/tls_scache.h b/src/tls/tls_scache.h
new file mode 100644
index 0000000..06c727a
--- /dev/null
+++ b/src/tls/tls_scache.h
@@ -0,0 +1,73 @@
+#ifndef _TLS_SCACHE_H_INCLUDED_
+#define _TLS_SCACHE_H_INCLUDED_
+
+/*++
+/* NAME
+/* tls_scache 3h
+/* SUMMARY
+/* TLS session cache manager
+/* SYNOPSIS
+/* #include <tls_scache.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ int flags; /* see below */
+ DICT *db; /* database handle */
+ char *cache_label; /* "smtpd", "smtp" or "lmtp" */
+ int verbose; /* enable verbose logging */
+ int timeout; /* smtp(d)_tls_session_cache_timeout */
+ char *saved_cursor; /* cursor cache ID */
+} TLS_SCACHE;
+
+#define TLS_TICKET_NAMELEN 16 /* RFC 5077 ticket key name length */
+#define TLS_TICKET_IVLEN 16 /* RFC 5077 ticket IV length */
+#define TLS_TICKET_KEYLEN 32 /* AES-256-CBC key size */
+#define TLS_TICKET_MACLEN 32 /* RFC 5077 HMAC key size */
+#define TLS_SESSION_LIFEMIN 120 /* May you live to 120! */
+
+typedef struct TLS_TICKET_KEY {
+ unsigned char name[TLS_TICKET_NAMELEN];
+ unsigned char bits[TLS_TICKET_KEYLEN];
+ unsigned char hmac[TLS_TICKET_MACLEN];
+ time_t tout;
+} TLS_TICKET_KEY;
+
+#define TLS_SCACHE_FLAG_DEL_SAVED_CURSOR (1<<0)
+
+extern TLS_SCACHE *tls_scache_open(const char *, const char *, int, int);
+extern void tls_scache_close(TLS_SCACHE *);
+extern int tls_scache_lookup(TLS_SCACHE *, const char *, VSTRING *);
+extern int tls_scache_update(TLS_SCACHE *, const char *, const char *, ssize_t);
+extern int tls_scache_delete(TLS_SCACHE *, const char *);
+extern int tls_scache_sequence(TLS_SCACHE *, int, char **, VSTRING *);
+extern TLS_TICKET_KEY *tls_scache_key(unsigned char *, time_t, int);
+extern TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *);
+
+#define TLS_SCACHE_DONT_NEED_CACHE_ID ((char **) 0)
+#define TLS_SCACHE_DONT_NEED_SESSION ((VSTRING *) 0)
+
+#define TLS_SCACHE_SEQUENCE_NOTHING \
+ TLS_SCACHE_DONT_NEED_CACHE_ID, TLS_SCACHE_DONT_NEED_SESSION
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/tls/tls_seed.c b/src/tls/tls_seed.c
new file mode 100644
index 0000000..edb7cd9
--- /dev/null
+++ b/src/tls/tls_seed.c
@@ -0,0 +1,88 @@
+/*++
+/* NAME
+/* tls_seed 3
+/* SUMMARY
+/* TLS PRNG seeding routines
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_ext_seed(nbytes)
+/* int nbytes;
+/*
+/* void tls_int_seed()
+/* DESCRIPTION
+/* tls_ext_seed() requests the specified number of bytes
+/* from the tlsmgr(8) PRNG pool and updates the local PRNG.
+/* The result is zero in case of success, -1 otherwise.
+/*
+/* tls_int_seed() mixes the process ID and time of day into
+/* the PRNG pool. This adds a few bits of entropy with each
+/* call, provided that the calls aren't made frequently.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this
+/* software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/time.h> /* gettimeofday() */
+#include <unistd.h> /* getpid() */
+
+#ifdef USE_TLS
+
+/* OpenSSL library. */
+
+#include <openssl/rand.h> /* RAND_seed() */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* TLS library. */
+
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+/* tls_int_seed - add entropy to the pool by adding the time and PID */
+
+void tls_int_seed(void)
+{
+ static struct {
+ pid_t pid;
+ struct timeval tv;
+ } randseed;
+
+ if (randseed.pid == 0)
+ randseed.pid = getpid();
+ GETTIMEOFDAY(&randseed.tv);
+ RAND_seed(&randseed, sizeof(randseed));
+}
+
+/* tls_ext_seed - request entropy from tlsmgr(8) server */
+
+int tls_ext_seed(int nbytes)
+{
+ VSTRING *buf;
+ int status;
+
+ buf = vstring_alloc(nbytes);
+ status = tls_mgr_seed(buf, nbytes);
+ RAND_seed(vstring_str(buf), VSTRING_LEN(buf));
+ vstring_free(buf);
+ return (status == TLS_MGR_STAT_OK ? 0 : -1);
+}
+
+#endif
diff --git a/src/tls/tls_server.c b/src/tls/tls_server.c
new file mode 100644
index 0000000..8b94c3c
--- /dev/null
+++ b/src/tls/tls_server.c
@@ -0,0 +1,1032 @@
+/*++
+/* NAME
+/* tls_server 3
+/* SUMMARY
+/* server-side TLS engine
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* TLS_APPL_STATE *tls_server_init(props)
+/* const TLS_SERVER_INIT_PROPS *props;
+/*
+/* TLS_SESS_STATE *tls_server_start(props)
+/* const TLS_SERVER_START_PROPS *props;
+/*
+/* TLS_SESS_STATE *tls_server_post_accept(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* void tls_server_stop(app_ctx, stream, failure, TLScontext)
+/* TLS_APPL_STATE *app_ctx;
+/* VSTREAM *stream;
+/* int failure;
+/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* This module is the interface between Postfix TLS servers,
+/* the OpenSSL library, and the TLS entropy and cache manager.
+/*
+/* See "EVENT_DRIVEN APPLICATIONS" below for using this code
+/* in event-driven programs.
+/*
+/* tls_server_init() is called once when the SMTP server
+/* initializes.
+/* Certificate details are also decided during this phase,
+/* so that peer-specific behavior is not possible.
+/*
+/* tls_server_start() activates the TLS feature for the VSTREAM
+/* passed as argument. We assume that network buffers are flushed
+/* and the TLS handshake can begin immediately.
+/*
+/* tls_server_stop() sends the "close notify" alert via
+/* SSL_shutdown() to the peer and resets all connection specific
+/* TLS data. As RFC2487 does not specify a separate shutdown, it
+/* is assumed that the underlying TCP connection is shut down
+/* immediately afterwards. Any further writes to the channel will
+/* be discarded, and any further reads will report end-of-file.
+/* If the failure flag is set, no SSL_shutdown() handshake is performed.
+/*
+/* Once the TLS connection is initiated, information about the TLS
+/* state is available via the TLScontext structure:
+/* .IP TLScontext->protocol
+/* the protocol name (SSLv2, SSLv3, TLSv1),
+/* .IP TLScontext->cipher_name
+/* the cipher name (e.g. RC4/MD5),
+/* .IP TLScontext->cipher_usebits
+/* the number of bits actually used (e.g. 40),
+/* .IP TLScontext->cipher_algbits
+/* the number of bits the algorithm is based on (e.g. 128).
+/* .PP
+/* The last two values may differ from each other when export-strength
+/* encryption is used.
+/*
+/* If the peer offered a certificate, part of the certificate data are
+/* available as:
+/* .IP TLScontext->peer_status
+/* A bitmask field that records the status of the peer certificate
+/* verification. One or more of TLS_CERT_FLAG_PRESENT and
+/* TLS_CERT_FLAG_TRUSTED.
+/* .IP TLScontext->peer_CN
+/* Extracted CommonName of the peer, or zero-length string
+/* when information could not be extracted.
+/* .IP TLScontext->issuer_CN
+/* Extracted CommonName of the issuer, or zero-length string
+/* when information could not be extracted.
+/* .IP TLScontext->peer_cert_fprint
+/* Fingerprint of the certificate, or zero-length string when no peer
+/* certificate is available.
+/* .PP
+/* If no peer certificate is presented the peer_status is set to 0.
+/* EVENT_DRIVEN APPLICATIONS
+/* .ad
+/* .fi
+/* Event-driven programs manage multiple I/O channels. Such
+/* programs cannot use the synchronous VSTREAM-over-TLS
+/* implementation that the current TLS library provides,
+/* including tls_server_stop() and the underlying tls_stream(3)
+/* and tls_bio_ops(3) routines.
+/*
+/* With the current TLS library implementation, this means
+/* that the application is responsible for calling and retrying
+/* SSL_accept(), SSL_read(), SSL_write() and SSL_shutdown().
+/*
+/* To maintain control over TLS I/O, an event-driven server
+/* invokes tls_server_start() with a null VSTREAM argument and
+/* with an fd argument that specifies the I/O file descriptor.
+/* Then, tls_server_start() performs all the necessary
+/* preparations before the TLS handshake and returns a partially
+/* populated TLS context. The event-driven application is then
+/* responsible for invoking SSL_accept(), and if successful,
+/* for invoking tls_server_post_accept() to finish the work
+/* that was started by tls_server_start(). In case of unrecoverable
+/* failure, tls_server_post_accept() destroys the TLS context
+/* and returns a null pointer value.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <dict.h>
+#include <stringops.h>
+#include <msg.h>
+#include <hex_code.h>
+#include <iostuff.h> /* non-blocking */
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#include <tls_mgr.h>
+#define TLS_INTERNAL
+#include <tls.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* Application-specific. */
+
+ /*
+ * The session_id_context indentifies the service that created a session.
+ * This information is used to distinguish between multiple TLS-based
+ * servers running on the same server. We use the name of the mail system.
+ */
+static const char server_session_id_context[] = "Postfix/TLS";
+
+#ifndef OPENSSL_NO_TLSEXT
+ /*
+ * We retain the cipher handle for lifetime of the process, it is not freed.
+ */
+static const EVP_CIPHER *tkt_cipher;
+#endif
+
+#define GET_SID(s, v, lptr) ((v) = SSL_SESSION_get_id((s), (lptr)))
+
+ /* OpenSSL 1.1.0 bitrot */
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+typedef const unsigned char *session_id_t;
+
+#else
+typedef unsigned char *session_id_t;
+
+#endif
+
+/* get_server_session_cb - callback to retrieve session from server cache */
+
+static SSL_SESSION *get_server_session_cb(SSL *ssl, session_id_t session_id,
+ int session_id_length,
+ int *unused_copy)
+{
+ const char *myname = "get_server_session_cb";
+ TLS_SESS_STATE *TLScontext;
+ VSTRING *cache_id;
+ VSTRING *session_data = vstring_alloc(2048);
+ SSL_SESSION *session = 0;
+
+ if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
+ msg_panic("%s: null TLScontext in session lookup callback", myname);
+
+#define GEN_CACHE_ID(buf, id, len, service) \
+ do { \
+ buf = vstring_alloc(2 * (len + strlen(service))); \
+ hex_encode(buf, (char *) (id), (len)); \
+ vstring_sprintf_append(buf, "&s=%s", (service)); \
+ vstring_sprintf_append(buf, "&l=%ld", (long) OpenSSL_version_num()); \
+ } while (0)
+
+
+ GEN_CACHE_ID(cache_id, session_id, session_id_length, TLScontext->serverid);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: looking up session %s in %s cache", TLScontext->namaddr,
+ STR(cache_id), TLScontext->cache_type);
+
+ /*
+ * Load the session from cache and decode it.
+ */
+ if (tls_mgr_lookup(TLScontext->cache_type, STR(cache_id),
+ session_data) == TLS_MGR_STAT_OK) {
+ session = tls_session_activate(STR(session_data), LEN(session_data));
+ if (session && (TLScontext->log_mask & TLS_LOG_CACHE))
+ msg_info("%s: reloaded session %s from %s cache",
+ TLScontext->namaddr, STR(cache_id),
+ TLScontext->cache_type);
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(cache_id);
+ vstring_free(session_data);
+
+ return (session);
+}
+
+/* uncache_session - remove session from internal & external cache */
+
+static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
+{
+ VSTRING *cache_id;
+ SSL_SESSION *session = SSL_get_session(TLScontext->con);
+ const unsigned char *sid;
+ unsigned int sid_length;
+
+ SSL_CTX_remove_session(ctx, session);
+
+ if (TLScontext->cache_type == 0)
+ return;
+
+ GET_SID(session, sid, &sid_length);
+ GEN_CACHE_ID(cache_id, sid, sid_length, TLScontext->serverid);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: remove session %s from %s cache", TLScontext->namaddr,
+ STR(cache_id), TLScontext->cache_type);
+
+ tls_mgr_delete(TLScontext->cache_type, STR(cache_id));
+ vstring_free(cache_id);
+}
+
+/* new_server_session_cb - callback to save session to server cache */
+
+static int new_server_session_cb(SSL *ssl, SSL_SESSION *session)
+{
+ const char *myname = "new_server_session_cb";
+ VSTRING *cache_id;
+ TLS_SESS_STATE *TLScontext;
+ VSTRING *session_data;
+ const unsigned char *sid;
+ unsigned int sid_length;
+
+ if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
+ msg_panic("%s: null TLScontext in new session callback", myname);
+
+ GET_SID(session, sid, &sid_length);
+ GEN_CACHE_ID(cache_id, sid, sid_length, TLScontext->serverid);
+
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: save session %s to %s cache", TLScontext->namaddr,
+ STR(cache_id), TLScontext->cache_type);
+
+ /*
+ * Passivate and save the session state.
+ */
+ session_data = tls_session_passivate(session);
+ if (session_data)
+ tls_mgr_update(TLScontext->cache_type, STR(cache_id),
+ STR(session_data), LEN(session_data));
+
+ /*
+ * Clean up.
+ */
+ if (session_data)
+ vstring_free(session_data);
+ vstring_free(cache_id);
+ SSL_SESSION_free(session); /* 200502 */
+
+ return (1);
+}
+
+#define NOENGINE ((ENGINE *) 0)
+#define TLS_TKT_NOKEYS -1 /* No keys for encryption */
+#define TLS_TKT_STALE 0 /* No matching keys for decryption */
+#define TLS_TKT_ACCEPT 1 /* Ticket decryptable and re-usable */
+#define TLS_TKT_REISSUE 2 /* Ticket decryptable, not re-usable */
+
+/* ticket_cb - configure tls session ticket encrypt/decrypt context */
+
+#if !defined(OPENSSL_NO_TLSEXT)
+
+static int ticket_cb(SSL *con, unsigned char name[], unsigned char iv[],
+ EVP_CIPHER_CTX * ctx, HMAC_CTX * hctx, int create)
+{
+ static const EVP_MD *sha256;
+ TLS_TICKET_KEY *key;
+ TLS_SESS_STATE *TLScontext = SSL_get_ex_data(con, TLScontext_index);
+ int timeout = ((int) SSL_CTX_get_timeout(SSL_get_SSL_CTX(con))) / 2;
+
+ if ((!sha256 && (sha256 = EVP_sha256()) == 0)
+ || (key = tls_mgr_key(create ? 0 : name, timeout)) == 0
+ || (create && RAND_bytes(iv, TLS_TICKET_IVLEN) <= 0))
+ return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
+
+ HMAC_Init_ex(hctx, key->hmac, TLS_TICKET_MACLEN, sha256, NOENGINE);
+
+ if (create) {
+ EVP_EncryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ memcpy((void *) name, (void *) key->name, TLS_TICKET_NAMELEN);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Issuing session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ } else {
+ EVP_DecryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
+ if (TLScontext->log_mask & TLS_LOG_CACHE)
+ msg_info("%s: Decrypting session ticket, key expiration: %ld",
+ TLScontext->namaddr, (long) key->tout);
+ }
+ TLScontext->ticketed = 1;
+ return (TLS_TKT_ACCEPT);
+}
+
+#endif
+
+/* tls_server_init - initialize the server-side TLS engine */
+
+TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
+{
+ SSL_CTX *server_ctx;
+ SSL_CTX *sni_ctx;
+ X509_STORE *cert_store;
+ long off = 0;
+ int verify_flags = SSL_VERIFY_NONE;
+ int cachable;
+ int scache_timeout;
+ int ticketable = 0;
+ int protomask;
+ TLS_APPL_STATE *app_ctx;
+ int log_mask;
+
+ /*
+ * Convert user loglevel to internal logmask.
+ */
+ log_mask = tls_log_mask(props->log_param, props->log_level);
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("initializing the server-side TLS engine");
+
+ /*
+ * Load (mostly cipher related) TLS-library internal main.cf parameters.
+ */
+ tls_param_init();
+
+ /*
+ * Detect mismatch between compile-time headers and run-time library.
+ */
+ tls_check_version();
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+ /*
+ * Initialize the OpenSSL library by the book! To start with, we must
+ * initialize the algorithms. We want cleartext error messages instead of
+ * just error codes, so we load the error_strings.
+ */
+ SSL_load_error_strings();
+ OpenSSL_add_ssl_algorithms();
+#endif
+
+ /*
+ * Initialize the OpenSSL library, possibly loading its configuration
+ * file.
+ */
+ if (tls_library_init() == 0)
+ return (0);
+
+ /*
+ * First validate the protocols. If these are invalid, we can't continue.
+ */
+ protomask = tls_protocol_mask(props->protocols);
+ if (protomask == TLS_PROTOCOL_INVALID) {
+ /* tls_protocol_mask() logs no warning. */
+ msg_warn("Invalid TLS protocol list \"%s\": disabling TLS support",
+ props->protocols);
+ return (0);
+ }
+
+ /*
+ * Create an application data index for SSL objects, so that we can
+ * attach TLScontext information; this information is needed inside
+ * tls_verify_certificate_callback().
+ */
+ if (TLScontext_index < 0) {
+ if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) {
+ msg_warn("Cannot allocate SSL application data index: "
+ "disabling TLS support");
+ return (0);
+ }
+ }
+
+ /*
+ * If the administrator specifies an unsupported digest algorithm, fail
+ * now, rather than in the middle of a TLS handshake.
+ */
+ if (!tls_validate_digest(props->mdalg)) {
+ msg_warn("disabling TLS support");
+ return (0);
+ }
+
+ /*
+ * Initialize the PRNG (Pseudo Random Number Generator) with some seed
+ * from external and internal sources. Don't enable TLS without some real
+ * entropy.
+ */
+ if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) {
+ msg_warn("no entropy for TLS key generation: disabling TLS support");
+ return (0);
+ }
+ tls_int_seed();
+
+ /*
+ * The SSL/TLS specifications require the client to send a message in the
+ * oldest specification it understands with the highest level it
+ * understands in the message. Netscape communicator can still
+ * communicate with SSLv2 servers, so it sends out a SSLv2 client hello.
+ * To deal with it, our server must be SSLv2 aware (even if we don't like
+ * SSLv2), so we need to have the SSLv23 server here. If we want to limit
+ * the protocol level, we can add an option to not use SSLv2/v3/TLSv1
+ * later.
+ */
+ ERR_clear_error();
+ server_ctx = SSL_CTX_new(TLS_server_method());
+ if (server_ctx == 0) {
+ msg_warn("cannot allocate server SSL_CTX: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+ sni_ctx = SSL_CTX_new(TLS_server_method());
+ if (sni_ctx == 0) {
+ SSL_CTX_free(server_ctx);
+ msg_warn("cannot allocate server SNI SSL_CTX: disabling TLS support");
+ tls_print_errors();
+ return (0);
+ }
+#ifdef SSL_SECOP_PEER
+ /* Backwards compatible security as a base for opportunistic TLS. */
+ SSL_CTX_set_security_level(server_ctx, 0);
+ SSL_CTX_set_security_level(sni_ctx, 0);
+#endif
+
+ /*
+ * See the verify callback in tls_verify.c
+ */
+ SSL_CTX_set_verify_depth(server_ctx, props->verifydepth + 1);
+ SSL_CTX_set_verify_depth(sni_ctx, props->verifydepth + 1);
+
+ /*
+ * The session cache is implemented by the tlsmgr(8) server.
+ *
+ * XXX 200502 Surprise: when OpenSSL purges an entry from the in-memory
+ * cache, it also attempts to purge the entry from the on-disk cache.
+ * This is undesirable, especially when we set the in-memory cache size
+ * to 1. For this reason we don't allow OpenSSL to purge on-disk cache
+ * entries, and leave it up to the tlsmgr process instead. Found by
+ * Victor Duchovni.
+ */
+ if (tls_mgr_policy(props->cache_type, &cachable,
+ &scache_timeout) != TLS_MGR_STAT_OK)
+ scache_timeout = 0;
+ if (scache_timeout <= 0)
+ cachable = 0;
+
+ /*
+ * Presently we use TLS only with SMTP where truncation attacks are not
+ * possible as a result of application framing. If we ever use TLS in
+ * some other application protocol where truncation could be relevant,
+ * we'd need to disable truncation detection conditionally, or explicitly
+ * clear the option in that code path.
+ */
+ off |= SSL_OP_IGNORE_UNEXPECTED_EOF;
+
+ /*
+ * Protocol work-arounds, OpenSSL version dependent.
+ */
+ off |= tls_bug_bits();
+
+ /*
+ * Add SSL_OP_NO_TICKET when the timeout is zero or library support is
+ * incomplete.
+ */
+#ifndef OPENSSL_NO_TLSEXT
+ ticketable = (*var_tls_tkt_cipher && scache_timeout > 0
+ && !(off & SSL_OP_NO_TICKET));
+ if (ticketable) {
+#if OPENSSL_VERSION_PREREQ(3,0)
+ tkt_cipher = EVP_CIPHER_fetch(NULL, var_tls_tkt_cipher, NULL);
+#else
+ tkt_cipher = EVP_get_cipherbyname(var_tls_tkt_cipher);
+#endif
+ if (tkt_cipher == 0
+ || EVP_CIPHER_mode(tkt_cipher) != EVP_CIPH_CBC_MODE
+ || EVP_CIPHER_iv_length(tkt_cipher) != TLS_TICKET_IVLEN
+ || EVP_CIPHER_key_length(tkt_cipher) < TLS_TICKET_IVLEN
+ || EVP_CIPHER_key_length(tkt_cipher) > TLS_TICKET_KEYLEN) {
+ msg_warn("%s: invalid value: %s; session tickets disabled",
+ VAR_TLS_TKT_CIPHER, var_tls_tkt_cipher);
+ ticketable = 0;
+ }
+ }
+ if (ticketable) {
+ SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_cb);
+
+ /*
+ * OpenSSL 1.1.1 introduces support for TLS 1.3, which can issue more
+ * than one ticket per handshake. While this may be appropriate for
+ * communication between browsers and webservers, it is not terribly
+ * useful for MTAs, many of which other than Postfix don't do TLS
+ * session caching at all, and Postfix has no mechanism for storing
+ * multiple session tickets, if more than one sent, the second
+ * clobbers the first. OpenSSL 1.1.1 servers default to issuing two
+ * tickets for non-resumption handshakes, we reduce this to one. Our
+ * ticket decryption callback already (since 2.11) asks OpenSSL to
+ * avoid issuing new tickets when the presented ticket is re-usable.
+ */
+ SSL_CTX_set_num_tickets(server_ctx, 1);
+ }
+#endif
+ if (!ticketable)
+ off |= SSL_OP_NO_TICKET;
+
+ SSL_CTX_set_options(server_ctx, off);
+
+ /* Enable all supported protocols */
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fUL
+ SSL_CTX_set_min_proto_version(server_ctx, 0);
+ SSL_CTX_set_min_proto_version(sni_ctx, 0);
+#endif
+
+ /*
+ * Global protocol selection.
+ */
+ if (protomask != 0)
+ SSL_CTX_set_options(server_ctx, TLS_SSL_OP_PROTOMASK(protomask));
+
+ /*
+ * Some sites may want to give the client less rope. On the other hand,
+ * this could trigger inter-operability issues, the client should not
+ * offer ciphers it implements poorly, but this hasn't stopped some
+ * vendors from getting it wrong.
+ */
+ if (var_tls_preempt_clist)
+ SSL_CTX_set_options(server_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+ /* Done with server_ctx options, clone to sni_ctx */
+ SSL_CTX_clear_options(sni_ctx, ~0);
+ SSL_CTX_set_options(sni_ctx, SSL_CTX_get_options(server_ctx));
+
+ /*
+ * Set the call-back routine to debug handshake progress.
+ */
+ if (log_mask & TLS_LOG_DEBUG) {
+ SSL_CTX_set_info_callback(server_ctx, tls_info_callback);
+ SSL_CTX_set_info_callback(sni_ctx, tls_info_callback);
+ }
+
+ /*
+ * Load the CA public key certificates for both the server cert and for
+ * the verification of client certificates. As provided by OpenSSL we
+ * support two types of CA certificate handling: One possibility is to
+ * add all CA certificates to one large CAfile, the other possibility is
+ * a directory pointed to by CApath, containing separate files for each
+ * CA with softlinks named after the hash values of the certificate. The
+ * first alternative has the advantage that the file is opened and read
+ * at startup time, so that you don't have the hassle to maintain another
+ * copy of the CApath directory for chroot-jail.
+ */
+ if (tls_set_ca_certificate_info(server_ctx,
+ props->CAfile, props->CApath) < 0) {
+ /* tls_set_ca_certificate_info() already logs a warning. */
+ SSL_CTX_free(server_ctx); /* 200411 */
+ SSL_CTX_free(sni_ctx);
+ return (0);
+ }
+
+ /*
+ * Upref and share the cert store. Sadly we can't yet use
+ * SSL_CTX_set1_cert_store(3) which was added in OpenSSL 1.1.0.
+ */
+ cert_store = SSL_CTX_get_cert_store(server_ctx);
+ X509_STORE_up_ref(cert_store);
+ SSL_CTX_set_cert_store(sni_ctx, cert_store);
+
+ /*
+ * Load the server public key certificate and private key from file and
+ * check whether the cert matches the key. We can use RSA certificates
+ * ("cert") DSA certificates ("dcert") or ECDSA certificates ("eccert").
+ * All three can be made available at the same time. The CA certificates
+ * for all three are handled in the same setup already finished. Which
+ * one is used depends on the cipher negotiated (that is: the first
+ * cipher listed by the client which does match the server). A client
+ * with RSA only (e.g. Netscape) will use the RSA certificate only. A
+ * client with openssl-library will use RSA first if not especially
+ * changed in the cipher setup.
+ */
+ if (tls_set_my_certificate_key_info(server_ctx,
+ props->chain_files,
+ props->cert_file,
+ props->key_file,
+ props->dcert_file,
+ props->dkey_file,
+ props->eccert_file,
+ props->eckey_file) < 0) {
+ /* tls_set_my_certificate_key_info() already logs a warning. */
+ SSL_CTX_free(server_ctx); /* 200411 */
+ SSL_CTX_free(sni_ctx);
+ return (0);
+ }
+
+ /*
+ * 2015-12-05: Ephemeral RSA removed from OpenSSL 1.1.0-dev
+ */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+ /*
+ * According to OpenSSL documentation, a temporary RSA key is needed when
+ * export ciphers are in use, because the certified key cannot be
+ * directly used.
+ */
+ SSL_CTX_set_tmp_rsa_callback(server_ctx, tls_tmp_rsa_cb);
+ SSL_CTX_set_tmp_rsa_callback(sni_ctx, tls_tmp_rsa_cb);
+#endif
+
+ /*
+ * Diffie-Hellman key generation parameters can either be loaded from
+ * files (preferred) or taken from compiled in values. First, set the
+ * callback that will select the values when requested, then load the
+ * (possibly) available DH parameters from files. We are generous with
+ * the error handling, since we do have default values compiled in, so we
+ * will not abort but just log the error message.
+ */
+ SSL_CTX_set_tmp_dh_callback(server_ctx, tls_tmp_dh_cb);
+ SSL_CTX_set_tmp_dh_callback(sni_ctx, tls_tmp_dh_cb);
+ if (*props->dh1024_param_file != 0)
+ tls_set_dh_from_file(props->dh1024_param_file, 1024);
+ if (*props->dh512_param_file != 0)
+ tls_set_dh_from_file(props->dh512_param_file, 512);
+
+ /*
+ * Enable EECDH if available, errors are not fatal, we just keep going
+ * with any remaining key-exchange algorithms.
+ */
+ tls_set_eecdh_curve(server_ctx, props->eecdh_grade);
+ tls_set_eecdh_curve(sni_ctx, props->eecdh_grade);
+
+ /*
+ * If we want to check client certificates, we have to indicate it in
+ * advance. By now we only allow to decide on a global basis. If we want
+ * to allow certificate based relaying, we must ask the client to provide
+ * one with SSL_VERIFY_PEER. The client now can decide, whether it
+ * provides one or not. We can enforce a failure of the negotiation with
+ * SSL_VERIFY_FAIL_IF_NO_PEER_CERT, if we do not allow a connection
+ * without one. In the "server hello" following the initialization by the
+ * "client hello" the server must provide a list of CAs it is willing to
+ * accept. Some clever clients will then select one from the list of
+ * available certificates matching these CAs. Netscape Communicator will
+ * present the list of certificates for selecting the one to be sent, or
+ * it will issue a warning, if there is no certificate matching the
+ * available CAs.
+ *
+ * With regard to the purpose of the certificate for relaying, we might like
+ * a later negotiation, maybe relaying would already be allowed for other
+ * reasons, but this would involve severe changes in the internal postfix
+ * logic, so we have to live with it the way it is.
+ */
+ if (props->ask_ccert)
+ verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
+ SSL_CTX_set_verify(server_ctx, verify_flags,
+ tls_verify_certificate_callback);
+ SSL_CTX_set_verify(sni_ctx, verify_flags,
+ tls_verify_certificate_callback);
+ if (props->ask_ccert && *props->CAfile) {
+ STACK_OF(X509_NAME) *calist = SSL_load_client_CA_file(props->CAfile);
+
+ if (calist == 0) {
+ /* Not generally critical */
+ msg_warn("error loading client CA names from: %s",
+ props->CAfile);
+ tls_print_errors();
+ }
+ SSL_CTX_set_client_CA_list(server_ctx, calist);
+
+ if (calist != 0 && sk_X509_NAME_num(calist) > 0) {
+ calist = SSL_dup_CA_list(calist);
+
+ if (calist == 0) {
+ msg_warn("error duplicating client CA names for SNI");
+ tls_print_errors();
+ } else {
+ SSL_CTX_set_client_CA_list(sni_ctx, calist);
+ }
+ }
+ }
+
+ /*
+ * Initialize our own TLS server handle, before diving into the details
+ * of TLS session cache management.
+ */
+ app_ctx = tls_alloc_app_context(server_ctx, sni_ctx, log_mask);
+
+ if (cachable || ticketable || props->set_sessid) {
+
+ /*
+ * Initialize the session cache.
+ *
+ * With a large number of concurrent smtpd(8) processes, it is not a
+ * good idea to cache multiple large session objects in each process.
+ * We set the internal cache size to 1, and don't register a
+ * "remove_cb" so as to avoid deleting good sessions from the
+ * external cache prematurely (when the internal cache is full,
+ * OpenSSL removes sessions from the external cache also)!
+ *
+ * This makes SSL_CTX_remove_session() not useful for flushing broken
+ * sessions from the external cache, so we must delete them directly
+ * (not via a callback).
+ *
+ * Set a session id context to identify to what type of server process
+ * created a session. In our case, the context is simply the name of
+ * the mail system: "Postfix/TLS".
+ */
+ SSL_CTX_sess_set_cache_size(server_ctx, 1);
+ SSL_CTX_set_session_id_context(server_ctx,
+ (void *) &server_session_id_context,
+ sizeof(server_session_id_context));
+ SSL_CTX_set_session_cache_mode(server_ctx,
+ SSL_SESS_CACHE_SERVER |
+ SSL_SESS_CACHE_NO_INTERNAL |
+ SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ if (cachable) {
+ app_ctx->cache_type = mystrdup(props->cache_type);
+
+ SSL_CTX_sess_set_get_cb(server_ctx, get_server_session_cb);
+ SSL_CTX_sess_set_new_cb(server_ctx, new_server_session_cb);
+ }
+
+ /*
+ * OpenSSL ignores timed-out sessions. We need to set the internal
+ * cache timeout at least as high as the external cache timeout. This
+ * applies even if no internal cache is used. We set the session
+ * lifetime to twice the cache lifetime, which is also the issuing
+ * and retired key validation lifetime of session tickets keys. This
+ * way a session always lasts longer than the server's ability to
+ * decrypt its session ticket. Otherwise, a bug in OpenSSL may fail
+ * to re-issue tickets when sessions decrypt, but are expired.
+ */
+ SSL_CTX_set_timeout(server_ctx, 2 * scache_timeout);
+ } else {
+
+ /*
+ * If we have no external cache, disable all caching. No use wasting
+ * server memory resources with sessions they are unlikely to be able
+ * to reuse.
+ */
+ SSL_CTX_set_session_cache_mode(server_ctx, SSL_SESS_CACHE_OFF);
+ }
+
+ return (app_ctx);
+}
+
+ /*
+ * This is the actual startup routine for a new connection. We expect that
+ * the SMTP buffers are flushed and the "220 Ready to start TLS" was sent to
+ * the client, so that we can immediately start the TLS handshake process.
+ */
+TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props)
+{
+ int sts;
+ TLS_SESS_STATE *TLScontext;
+ const char *cipher_list;
+ TLS_APPL_STATE *app_ctx = props->ctx;
+ int log_mask = app_ctx->log_mask;
+
+ /*
+ * Implicitly enable logging of trust chain errors when verified certs
+ * are required.
+ */
+ if (props->requirecert)
+ log_mask |= TLS_LOG_UNTRUSTED;
+
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("setting up TLS connection from %s", props->namaddr);
+
+ /*
+ * Allocate a new TLScontext for the new connection and get an SSL
+ * structure. Add the location of TLScontext to the SSL to later retrieve
+ * the information inside the tls_verify_certificate_callback().
+ */
+ TLScontext = tls_alloc_sess_context(log_mask, props->namaddr);
+ TLScontext->cache_type = app_ctx->cache_type;
+
+ ERR_clear_error();
+ if ((TLScontext->con = (SSL *) SSL_new(app_ctx->ssl_ctx)) == 0) {
+ msg_warn("Could not allocate 'TLScontext->con' with SSL_new()");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ cipher_list = tls_set_ciphers(TLScontext, props->cipher_grade,
+ props->cipher_exclusions);
+ if (cipher_list == 0) {
+ /* already warned */
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ if (log_mask & TLS_LOG_VERBOSE)
+ msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list);
+
+ TLScontext->serverid = mystrdup(props->serverid);
+ TLScontext->am_server = 1;
+ TLScontext->stream = props->stream;
+ TLScontext->mdalg = props->mdalg;
+
+ if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
+ msg_warn("Could not set application data for 'TLScontext->con'");
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
+#ifdef SSL_SECOP_PEER
+ /* When authenticating the peer, use 80-bit plus OpenSSL security level */
+ if (props->requirecert)
+ SSL_set_security_level(TLScontext->con, 1);
+#endif
+
+ /*
+ * Before really starting anything, try to seed the PRNG a little bit
+ * more.
+ */
+ tls_int_seed();
+ (void) tls_ext_seed(var_tls_daemon_rand_bytes);
+
+ /*
+ * Connect the SSL connection with the network socket.
+ */
+ if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd :
+ vstream_fileno(props->stream)) != 1) {
+ msg_info("SSL_set_fd error to %s", props->namaddr);
+ tls_print_errors();
+ uncache_session(app_ctx->ssl_ctx, TLScontext);
+ tls_free_context(TLScontext);
+ return (0);
+ }
+
+ /*
+ * If the debug level selected is high enough, all of the data is dumped:
+ * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will
+ * dump everything.
+ *
+ * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
+ * Well there is a BIO below the SSL routines that is automatically
+ * created for us, so we can use it for debugging purposes.
+ */
+ if (log_mask & TLS_LOG_TLSPKTS)
+ BIO_set_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb);
+
+ /*
+ * If we don't trigger the handshake in the library, leave control over
+ * SSL_accept/read/write/etc with the application.
+ */
+ if (props->stream == 0)
+ return (TLScontext);
+
+ /*
+ * Turn on non-blocking I/O so that we can enforce timeouts on network
+ * I/O.
+ */
+ non_blocking(vstream_fileno(props->stream), NON_BLOCKING);
+
+ /*
+ * Start TLS negotiations. This process is a black box that invokes our
+ * call-backs for session caching and certificate verification.
+ *
+ * Error handling: If the SSL handshake fails, we print out an error message
+ * and remove all TLS state concerning this session.
+ */
+ sts = tls_bio_accept(vstream_fileno(props->stream), props->timeout,
+ TLScontext);
+ if (sts <= 0) {
+ if (ERR_peek_error() != 0) {
+ msg_info("SSL_accept error from %s: %d", props->namaddr, sts);
+ tls_print_errors();
+ } else if (errno != 0) {
+ msg_info("SSL_accept error from %s: %m", props->namaddr);
+ } else {
+ msg_info("SSL_accept error from %s: lost connection",
+ props->namaddr);
+ }
+ tls_free_context(TLScontext);
+ return (0);
+ }
+ return (tls_server_post_accept(TLScontext));
+}
+
+/* tls_server_post_accept - post-handshake processing */
+
+TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
+{
+ const SSL_CIPHER *cipher;
+ X509 *peer;
+ char buf[CCERT_BUFSIZ];
+
+ /* Turn off packet dump if only dumping the handshake */
+ if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0)
+ BIO_set_callback(SSL_get_rbio(TLScontext->con), 0);
+
+ /*
+ * The caller may want to know if this session was reused or if a new
+ * session was negotiated.
+ */
+ TLScontext->session_reused = SSL_session_reused(TLScontext->con);
+ if ((TLScontext->log_mask & TLS_LOG_CACHE) && TLScontext->session_reused)
+ msg_info("%s: Reusing old session%s", TLScontext->namaddr,
+ TLScontext->ticketed ? " (RFC 5077 session ticket)" : "");
+
+ /*
+ * Let's see whether a peer certificate is available and what is the
+ * actual information. We want to save it for later use.
+ */
+ peer = SSL_get_peer_certificate(TLScontext->con);
+ if (peer != NULL) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK)
+ TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
+
+ if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
+ X509_NAME_oneline(X509_get_subject_name(peer),
+ buf, sizeof(buf));
+ msg_info("subject=%s", printable(buf, '?'));
+ X509_NAME_oneline(X509_get_issuer_name(peer),
+ buf, sizeof(buf));
+ msg_info("issuer=%s", printable(buf, '?'));
+ }
+ TLScontext->peer_CN = tls_peer_CN(peer, TLScontext);
+ TLScontext->issuer_CN = tls_issuer_CN(peer, TLScontext);
+ TLScontext->peer_cert_fprint = tls_cert_fprint(peer, TLScontext->mdalg);
+ TLScontext->peer_pkey_fprint = tls_pkey_fprint(peer, TLScontext->mdalg);
+
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) {
+ msg_info("%s: subject_CN=%s, issuer=%s, fingerprint=%s"
+ ", pkey_fingerprint=%s",
+ TLScontext->namaddr,
+ TLScontext->peer_CN, TLScontext->issuer_CN,
+ TLScontext->peer_cert_fprint,
+ TLScontext->peer_pkey_fprint);
+ }
+ X509_free(peer);
+
+ /*
+ * Give them a clue. Problems with trust chain verification are
+ * logged when the session is first negotiated, before the session is
+ * stored into the cache. We don't want mystery failures, so log the
+ * fact the real problem is to be found in the past.
+ */
+ if (!TLS_CERT_IS_TRUSTED(TLScontext)
+ && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+ if (TLScontext->session_reused == 0)
+ tls_log_verify_error(TLScontext);
+ else
+ msg_info("%s: re-using session with untrusted certificate, "
+ "look for details earlier in the log",
+ TLScontext->namaddr);
+ }
+ } else {
+ TLScontext->peer_CN = mystrdup("");
+ TLScontext->issuer_CN = mystrdup("");
+ TLScontext->peer_cert_fprint = mystrdup("");
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ }
+
+ /*
+ * Finally, collect information about protocol and cipher for logging
+ */
+ TLScontext->protocol = SSL_get_version(TLScontext->con);
+ cipher = SSL_get_current_cipher(TLScontext->con);
+ TLScontext->cipher_name = SSL_CIPHER_get_name(cipher);
+ TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher,
+ &(TLScontext->cipher_algbits));
+
+ /*
+ * If the library triggered the SSL handshake, switch to the
+ * tls_timed_read/write() functions and make the TLScontext available to
+ * those functions. Otherwise, leave control over SSL_read/write/etc.
+ * with the application.
+ */
+ if (TLScontext->stream != 0)
+ tls_stream_start(TLScontext->stream, TLScontext);
+
+ /*
+ * With the handshake done, extract TLS 1.3 signature metadata.
+ */
+ tls_get_signature_params(TLScontext);
+
+ /*
+ * All the key facts in a single log entry.
+ */
+ if (TLScontext->log_mask & TLS_LOG_SUMMARY)
+ tls_log_summary(TLS_ROLE_SERVER, TLS_USAGE_NEW, TLScontext);
+
+ tls_int_seed();
+
+ return (TLScontext);
+}
+
+#endif /* USE_TLS */
diff --git a/src/tls/tls_session.c b/src/tls/tls_session.c
new file mode 100644
index 0000000..a4b7a8f
--- /dev/null
+++ b/src/tls/tls_session.c
@@ -0,0 +1,185 @@
+/*++
+/* NAME
+/* tls_session
+/* SUMMARY
+/* TLS client and server session routines
+/* SYNOPSIS
+/* #include <tls.h>
+/*
+/* void tls_session_stop(ctx, stream, timeout, failure, TLScontext)
+/* TLS_APPL_STATE *ctx;
+/* VSTREAM *stream;
+/* int timeout;
+/* int failure;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* VSTRING *tls_session_passivate(session)
+/* SSL_SESSION *session;
+/*
+/* SSL_SESSION *tls_session_activate(session_data, session_data_len)
+/* char *session_data;
+/* int session_data_len;
+/* DESCRIPTION
+/* tls_session_stop() implements the tls_server_shutdown()
+/* and the tls_client_shutdown() routines.
+/*
+/* tls_session_passivate() converts an SSL_SESSION object to
+/* VSTRING. The result is a null pointer in case of problems,
+/* otherwise it should be disposed of with vstring_free().
+/*
+/* tls_session_activate() reanimates a passivated SSL_SESSION object.
+/* The result is a null pointer in case of problems,
+/* otherwise it should be disposed of with SSL_SESSION_free().
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <vstream.h>
+#include <msg.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+
+/* tls_session_stop - shut down the TLS connection and reset state */
+
+void tls_session_stop(TLS_APPL_STATE *unused_ctx, VSTREAM *stream, int timeout,
+ int failure, TLS_SESS_STATE *TLScontext)
+{
+ const char *myname = "tls_session_stop";
+ int retval;
+
+ /*
+ * Sanity check.
+ */
+ if (TLScontext == 0)
+ msg_panic("%s: stream has no active TLS context", myname);
+
+ /*
+ * According to RFC 2246 (TLS 1.0), there is no requirement to wait for
+ * the peer's close-notify. If the application protocol provides
+ * sufficient session termination signaling, then there's no need to
+ * duplicate that at the TLS close-notify layer.
+ *
+ * https://tools.ietf.org/html/rfc2246#section-7.2.1
+ * https://tools.ietf.org/html/rfc4346#section-7.2.1
+ * https://tools.ietf.org/html/rfc5246#section-7.2.1
+ *
+ * Specify 'tls_fast_shutdown = no' to enable the historical behavior
+ * described below.
+ *
+ * Perform SSL_shutdown() twice, as the first attempt will send out the
+ * shutdown alert but it will not wait for the peer's shutdown alert.
+ * Therefore, when we are the first party to send the alert, we must call
+ * SSL_shutdown() again. On failure we don't want to resume the session,
+ * so we will not perform SSL_shutdown() and the session will be removed
+ * as being bad.
+ */
+ if (!failure && !SSL_in_init(TLScontext->con)) {
+ retval = tls_bio_shutdown(vstream_fileno(stream), timeout, TLScontext);
+ if (!var_tls_fast_shutdown && retval == 0)
+ tls_bio_shutdown(vstream_fileno(stream), timeout, TLScontext);
+ }
+ tls_free_context(TLScontext);
+ tls_stream_stop(stream);
+}
+
+/* tls_session_passivate - passivate SSL_SESSION object */
+
+VSTRING *tls_session_passivate(SSL_SESSION *session)
+{
+ const char *myname = "tls_session_passivate";
+ int estimate;
+ int actual_size;
+ VSTRING *session_data;
+ unsigned char *ptr;
+
+ /*
+ * First, find out how much memory is needed for the passivated
+ * SSL_SESSION object.
+ */
+ estimate = i2d_SSL_SESSION(session, (unsigned char **) 0);
+ if (estimate <= 0) {
+ msg_warn("%s: i2d_SSL_SESSION failed: unable to cache session", myname);
+ return (0);
+ }
+
+ /*
+ * Passivate the SSL_SESSION object. The use of a VSTRING is slightly
+ * wasteful but is convenient to combine data and length.
+ */
+ session_data = vstring_alloc(estimate);
+ ptr = (unsigned char *) STR(session_data);
+ actual_size = i2d_SSL_SESSION(session, &ptr);
+ if (actual_size != estimate) {
+ msg_warn("%s: i2d_SSL_SESSION failed: unable to cache session", myname);
+ vstring_free(session_data);
+ return (0);
+ }
+ vstring_set_payload_size(session_data, actual_size);
+
+ return (session_data);
+}
+
+/* tls_session_activate - activate passivated session */
+
+SSL_SESSION *tls_session_activate(const char *session_data, int session_data_len)
+{
+ SSL_SESSION *session;
+ const unsigned char *ptr;
+
+ /*
+ * Activate the SSL_SESSION object.
+ */
+ ptr = (const unsigned char *) session_data;
+ session = d2i_SSL_SESSION((SSL_SESSION **) 0, &ptr, session_data_len);
+ if (!session)
+ tls_print_errors();
+
+ return (session);
+}
+
+#endif
diff --git a/src/tls/tls_stream.c b/src/tls/tls_stream.c
new file mode 100644
index 0000000..4cc53a7
--- /dev/null
+++ b/src/tls/tls_stream.c
@@ -0,0 +1,160 @@
+/*++
+/* NAME
+/* tls_stream
+/* SUMMARY
+/* VSTREAM over TLS
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* void tls_stream_start(stream, context)
+/* VSTREAM *stream;
+/* TLS_SESS_STATE *context;
+/*
+/* void tls_stream_stop(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements the VSTREAM over TLS support user interface.
+/* The hard work is done elsewhere.
+/*
+/* tls_stream_start() enables TLS on the named stream. All read
+/* and write operations are directed through the TLS library,
+/* using the state information specified with the context argument.
+/*
+/* tls_stream_stop() replaces the VSTREAM read/write routines
+/* by dummies that have no side effects, and deletes the
+/* VSTREAM's reference to the TLS context.
+/* DIAGNOSTICS
+/* The tls_stream(3) read/write routines return the non-zero
+/* number of plaintext bytes read/written if successful; -1
+/* after TLS protocol failure, system-call failure, or for any
+/* reason described under "in addition" below; and zero when
+/* the remote party closed the connection or sent a TLS shutdown
+/* request.
+/*
+/* Upon return from the tls_stream(3) read/write routines the
+/* global errno value is non-zero when the requested operation
+/* did not complete due to system call failure.
+/*
+/* In addition, the result value is set to -1, and the global
+/* errno value is set to ETIMEDOUT, when a network read/write
+/* request did not complete within the time limit.
+/* SEE ALSO
+/* dummy_read(3), placebo read routine
+/* dummy_write(3), placebo write routine
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Based on code that was originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+/* Utility library. */
+
+#include <iostuff.h>
+#include <vstream.h>
+#include <msg.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+ /*
+ * Interface mis-match compensation. The OpenSSL read/write routines return
+ * unspecified negative values when an operation fails, while the vstream(3)
+ * plaintext timed_read/write() functions follow the convention of UNIX
+ * system calls, and return -1 upon error. The macro below makes OpenSSL
+ * read/write results consistent with the UNIX system-call convention.
+ */
+#define NORMALIZED_VSTREAM_RETURN(retval) ((retval) < 0 ? -1 : (retval))
+
+/* tls_timed_read - read content from stream, then TLS decapsulate */
+
+static ssize_t tls_timed_read(int fd, void *buf, size_t len, int timeout,
+ void *context)
+{
+ const char *myname = "tls_timed_read";
+ ssize_t ret;
+ TLS_SESS_STATE *TLScontext;
+
+ TLScontext = (TLS_SESS_STATE *) context;
+ if (!TLScontext)
+ msg_panic("%s: no context", myname);
+
+ ret = tls_bio_read(fd, buf, len, timeout, TLScontext);
+ if (ret > 0 && (TLScontext->log_mask & TLS_LOG_ALLPKTS))
+ msg_info("Read %ld chars: %.*s",
+ (long) ret, (int) (ret > 40 ? 40 : ret), (char *) buf);
+ return (NORMALIZED_VSTREAM_RETURN(ret));
+}
+
+/* tls_timed_write - TLS encapsulate content, then write to stream */
+
+static ssize_t tls_timed_write(int fd, void *buf, size_t len, int timeout,
+ void *context)
+{
+ const char *myname = "tls_timed_write";
+ ssize_t ret;
+ TLS_SESS_STATE *TLScontext;
+
+ TLScontext = (TLS_SESS_STATE *) context;
+ if (!TLScontext)
+ msg_panic("%s: no context", myname);
+
+ if (TLScontext->log_mask & TLS_LOG_ALLPKTS)
+ msg_info("Write %ld chars: %.*s",
+ (long) len, (int) (len > 40 ? 40 : len), (char *) buf);
+ ret = tls_bio_write(fd, buf, len, timeout, TLScontext);
+ return (NORMALIZED_VSTREAM_RETURN(ret));
+}
+
+/* tls_stream_start - start VSTREAM over TLS */
+
+void tls_stream_start(VSTREAM *stream, TLS_SESS_STATE *context)
+{
+ vstream_control(stream,
+ CA_VSTREAM_CTL_READ_FN(tls_timed_read),
+ CA_VSTREAM_CTL_WRITE_FN(tls_timed_write),
+ CA_VSTREAM_CTL_CONTEXT(context),
+ CA_VSTREAM_CTL_END);
+}
+
+/* tls_stream_stop - stop VSTREAM over TLS */
+
+void tls_stream_stop(VSTREAM *stream)
+{
+
+ /*
+ * Prevent data leakage after TLS is turned off. The Postfix/TLS patch
+ * provided null function pointers; we use dummy routines that make less
+ * noise when used.
+ */
+ vstream_control(stream,
+ CA_VSTREAM_CTL_READ_FN(dummy_read),
+ CA_VSTREAM_CTL_WRITE_FN(dummy_write),
+ CA_VSTREAM_CTL_CONTEXT((void *) 0),
+ CA_VSTREAM_CTL_END);
+}
+
+#endif
diff --git a/src/tls/tls_verify.c b/src/tls/tls_verify.c
new file mode 100644
index 0000000..85d5f64
--- /dev/null
+++ b/src/tls/tls_verify.c
@@ -0,0 +1,507 @@
+/*++
+/* NAME
+/* tls_verify 3
+/* SUMMARY
+/* peer name and peer certificate verification
+/* SYNOPSIS
+/* #define TLS_INTERNAL
+/* #include <tls.h>
+/*
+/* int tls_verify_certificate_callback(ok, ctx)
+/* int ok;
+/* X509_STORE_CTX *ctx;
+/*
+/* int tls_log_verify_error(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* char *tls_peer_CN(peercert, TLScontext)
+/* X509 *peercert;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* char *tls_issuer_CN(peercert, TLScontext)
+/* X509 *peercert;
+/* TLS_SESS_STATE *TLScontext;
+/*
+/* const char *tls_dns_name(gn, TLScontext)
+/* const GENERAL_NAME *gn;
+/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* tls_verify_certificate_callback() is called several times (directly
+/* or indirectly) from crypto/x509/x509_vfy.c. It collects errors
+/* and trust information at each element of the trust chain.
+/* The last call at depth 0 sets the verification status based
+/* on the cumulative winner (lowest depth) of errors vs. trust.
+/* We always return 1 (continue the handshake) and handle trust
+/* and peer-name verification problems at the application level.
+/*
+/* tls_log_verify_error() (called only when we care about the
+/* peer certificate, that is not when opportunistic) logs the
+/* reason why the certificate failed to be verified.
+/*
+/* tls_peer_CN() returns the text CommonName for the peer
+/* certificate subject, or an empty string if no CommonName was
+/* found. The result is allocated with mymalloc() and must be
+/* freed by the caller; it contains UTF-8 without non-printable
+/* ASCII characters.
+/*
+/* tls_issuer_CN() returns the text CommonName for the peer
+/* certificate issuer, or an empty string if no CommonName was
+/* found. The result is allocated with mymalloc() and must be
+/* freed by the caller; it contains UTF-8 without non-printable
+/* ASCII characters.
+/*
+/* tls_dns_name() returns the string value of a GENERAL_NAME
+/* from a DNS subjectAltName extension. If non-printable characters
+/* are found, a null string is returned instead. Further sanity
+/* checks may be added if the need arises.
+/*
+/* Arguments:
+/* .IP ok
+/* Result of prior verification: non-zero means success. In
+/* order to reduce the noise level, some tests or error reports
+/* are disabled when verification failed because of some
+/* earlier problem.
+/* .IP ctx
+/* SSL application context. This links to the Postfix TLScontext
+/* with enforcement and logging options.
+/* .IP gn
+/* An OpenSSL GENERAL_NAME structure holding a DNS subjectAltName
+/* to be decoded and checked for validity.
+/* .IP peercert
+/* Server or client X.509 certificate.
+/* .IP TLScontext
+/* Server or client context for warning messages.
+/* DIAGNOSTICS
+/* tls_peer_CN(), tls_issuer_CN() and tls_dns_name() log a warning
+/* when 1) the requested information is not available in the specified
+/* certificate, 2) the result exceeds a fixed limit, 3) the result
+/* contains NUL characters or the result contains non-printable or
+/* non-ASCII characters.
+/* LICENSE
+/* .ad
+/* .fi
+/* This software is free. You can do with it whatever you want.
+/* The original author kindly requests that you acknowledge
+/* the use of his software.
+/* AUTHOR(S)
+/* Originally written by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*
+/* Updated by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+#ifdef USE_TLS
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* TLS library. */
+
+#define TLS_INTERNAL
+#include <tls.h>
+
+/* update_error_state - safely stash away error state */
+
+static void update_error_state(TLS_SESS_STATE *TLScontext, int depth,
+ X509 *errorcert, int errorcode)
+{
+ /* No news is good news */
+ if (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth)
+ return;
+
+ /*
+ * The certificate pointer is stable during the verification callback,
+ * but may be freed after the callback returns. Since we delay error
+ * reporting till later, we bump the refcount so we can rely on it still
+ * being there until later.
+ */
+ if (TLScontext->errorcert != 0)
+ X509_free(TLScontext->errorcert);
+ if (errorcert != 0)
+ X509_up_ref(errorcert);
+ TLScontext->errorcert = errorcert;
+ TLScontext->errorcode = errorcode;
+ TLScontext->errordepth = depth;
+}
+
+/* tls_verify_certificate_callback - verify peer certificate info */
+
+int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
+{
+ char buf[CCERT_BUFSIZ];
+ X509 *cert;
+ int err;
+ int depth;
+ int max_depth;
+ SSL *con;
+ TLS_SESS_STATE *TLScontext;
+
+ /* May be NULL as of OpenSSL 1.0, thanks for the API change! */
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ err = X509_STORE_CTX_get_error(ctx);
+ con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ TLScontext = SSL_get_ex_data(con, TLScontext_index);
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+
+ /* Don't log the internal root CA unless there's an unexpected error. */
+ if (ok && TLScontext->tadepth > 0 && depth > TLScontext->tadepth)
+ return (1);
+
+ /*
+ * Certificate chain depth limit violations are mis-reported by the
+ * OpenSSL library, from SSL_CTX_set_verify(3):
+ *
+ * The certificate verification depth set with SSL[_CTX]_verify_depth()
+ * stops the verification at a certain depth. The error message produced
+ * will be that of an incomplete certificate chain and not
+ * X509_V_ERR_CERT_CHAIN_TOO_LONG as may be expected.
+ *
+ * We set a limit that is one higher than the user requested limit. If this
+ * higher limit is reached, we raise an error even a trusted root CA is
+ * present at this depth. This disambiguates trust chain truncation from
+ * an incomplete trust chain.
+ */
+ max_depth = SSL_get_verify_depth(con) - 1;
+
+ /*
+ * We never terminate the SSL handshake in the verification callback,
+ * rather we allow the TLS handshake to continue, but mark the session as
+ * unverified. The application is responsible for closing any sessions
+ * with unverified credentials.
+ */
+ if (max_depth >= 0 && depth > max_depth) {
+ X509_STORE_CTX_set_error(ctx, err = X509_V_ERR_CERT_CHAIN_TOO_LONG);
+ ok = 0;
+ }
+ if (ok == 0)
+ update_error_state(TLScontext, depth, cert, err);
+
+ if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
+ if (cert)
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
+ else
+ strcpy(buf, "<unknown>");
+ msg_info("%s: depth=%d verify=%d subject=%s",
+ TLScontext->namaddr, depth, ok, printable(buf, '?'));
+ }
+ return (1);
+}
+
+/* tls_log_verify_error - Report final verification error status */
+
+void tls_log_verify_error(TLS_SESS_STATE *TLScontext)
+{
+ char buf[CCERT_BUFSIZ];
+ int err = TLScontext->errorcode;
+ X509 *cert = TLScontext->errorcert;
+ int depth = TLScontext->errordepth;
+
+#define PURPOSE ((depth>0) ? "CA": TLScontext->am_server ? "client": "server")
+
+ if (err == X509_V_OK)
+ return;
+
+ /*
+ * Specific causes for verification failure.
+ */
+ switch (err) {
+ case X509_V_ERR_CERT_UNTRUSTED:
+
+ /*
+ * We expect the error cert to be the leaf, but it is likely
+ * sufficient to omit it from the log, even less user confusion.
+ */
+ msg_info("certificate verification failed for %s: "
+ "not trusted by local or TLSA policy", TLScontext->namaddr);
+ break;
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ msg_info("certificate verification failed for %s: "
+ "self-signed certificate", TLScontext->namaddr);
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+
+ /*
+ * There is no difference between issuing cert not provided and
+ * provided, but not found in CAfile/CApath. Either way, we don't
+ * trust it.
+ */
+ if (cert)
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
+ else
+ strcpy(buf, "<unknown>");
+ msg_info("certificate verification failed for %s: untrusted issuer %s",
+ TLScontext->namaddr, printable(buf, '?'));
+ break;
+ case X509_V_ERR_CERT_NOT_YET_VALID:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+ msg_info("%s certificate verification failed for %s: certificate not"
+ " yet valid", PURPOSE, TLScontext->namaddr);
+ break;
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+ msg_info("%s certificate verification failed for %s: certificate has"
+ " expired", PURPOSE, TLScontext->namaddr);
+ break;
+ case X509_V_ERR_INVALID_PURPOSE:
+ msg_info("certificate verification failed for %s: not designated for "
+ "use as a %s certificate", TLScontext->namaddr, PURPOSE);
+ break;
+ case X509_V_ERR_CERT_CHAIN_TOO_LONG:
+ msg_info("certificate verification failed for %s: "
+ "certificate chain longer than limit(%d)",
+ TLScontext->namaddr, depth - 1);
+ break;
+ default:
+ msg_info("%s certificate verification failed for %s: num=%d:%s",
+ PURPOSE, TLScontext->namaddr, err,
+ X509_verify_cert_error_string(err));
+ break;
+ }
+}
+
+#ifndef DONT_GRIPE
+#define DONT_GRIPE 0
+#define DO_GRIPE 1
+#endif
+
+/* tls_text_name - extract certificate property value by name */
+
+static char *tls_text_name(X509_NAME *name, int nid, const char *label,
+ const TLS_SESS_STATE *TLScontext, int gripe)
+{
+ const char *myname = "tls_text_name";
+ int pos;
+ X509_NAME_ENTRY *entry;
+ ASN1_STRING *entry_str;
+ int asn1_type;
+ int utf8_length;
+ unsigned char *utf8_value;
+ int ch;
+ unsigned char *cp;
+
+ if (name == 0 || (pos = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) {
+ if (gripe != DONT_GRIPE) {
+ msg_warn("%s: %s: peer certificate has no %s",
+ myname, TLScontext->namaddr, label);
+ tls_print_errors();
+ }
+ return (0);
+ }
+#if 0
+
+ /*
+ * If the match is required unambiguous, insist that that no other values
+ * be present.
+ */
+ if (X509_NAME_get_index_by_NID(name, nid, pos) >= 0) {
+ msg_warn("%s: %s: multiple %ss in peer certificate",
+ myname, TLScontext->namaddr, label);
+ return (0);
+ }
+#endif
+
+ if ((entry = X509_NAME_get_entry(name, pos)) == 0) {
+ /* This should not happen */
+ msg_warn("%s: %s: error reading peer certificate %s entry",
+ myname, TLScontext->namaddr, label);
+ tls_print_errors();
+ return (0);
+ }
+ if ((entry_str = X509_NAME_ENTRY_get_data(entry)) == 0) {
+ /* This should not happen */
+ msg_warn("%s: %s: error reading peer certificate %s data",
+ myname, TLScontext->namaddr, label);
+ tls_print_errors();
+ return (0);
+ }
+
+ /*
+ * XXX Convert everything into UTF-8. This is a super-set of ASCII, so we
+ * don't have to bother with separate code paths for ASCII-like content.
+ * If the payload is ASCII then we won't waste lots of CPU cycles
+ * converting it into UTF-8. It's up to OpenSSL to do something
+ * reasonable when converting ASCII formats that contain non-ASCII
+ * content.
+ *
+ * XXX Don't bother optimizing the string length error check. It is not
+ * worth the complexity.
+ */
+ asn1_type = ASN1_STRING_type(entry_str);
+ if ((utf8_length = ASN1_STRING_to_UTF8(&utf8_value, entry_str)) < 0) {
+ msg_warn("%s: %s: error decoding peer %s of ASN.1 type=%d",
+ myname, TLScontext->namaddr, label, asn1_type);
+ tls_print_errors();
+ return (0);
+ }
+
+ /*
+ * No returns without cleaning up. A good optimizer will replace multiple
+ * blocks of identical code by jumps to just one such block.
+ */
+#define TLS_TEXT_NAME_RETURN(x) do { \
+ char *__tls_text_name_temp = (x); \
+ OPENSSL_free(utf8_value); \
+ return (__tls_text_name_temp); \
+ } while (0)
+
+ /*
+ * Remove trailing null characters. They would give false alarms with the
+ * length check and with the embedded null check.
+ */
+#define TRIM0(s, l) do { while ((l) > 0 && (s)[(l)-1] == 0) --(l); } while (0)
+
+ TRIM0(utf8_value, utf8_length);
+
+ /*
+ * Enforce the length limit, because the caller will copy the result into
+ * a fixed-length buffer.
+ */
+ if (utf8_length >= CCERT_BUFSIZ) {
+ msg_warn("%s: %s: peer %s too long: %d",
+ myname, TLScontext->namaddr, label, utf8_length);
+ TLS_TEXT_NAME_RETURN(0);
+ }
+
+ /*
+ * Reject embedded nulls in ASCII or UTF-8 names. OpenSSL is responsible
+ * for producing properly-formatted UTF-8.
+ */
+ if (utf8_length != strlen((char *) utf8_value)) {
+ msg_warn("%s: %s: NULL character in peer %s",
+ myname, TLScontext->namaddr, label);
+ TLS_TEXT_NAME_RETURN(0);
+ }
+
+ /*
+ * Reject non-printable ASCII characters in UTF-8 content.
+ *
+ * Note: the code below does not find control characters in illegal UTF-8
+ * sequences. It's OpenSSL's job to produce valid UTF-8, and reportedly,
+ * it does validation.
+ */
+ for (cp = utf8_value; (ch = *cp) != 0; cp++) {
+ if (ISASCII(ch) && !ISPRINT(ch)) {
+ msg_warn("%s: %s: non-printable content in peer %s",
+ myname, TLScontext->namaddr, label);
+ TLS_TEXT_NAME_RETURN(0);
+ }
+ }
+ TLS_TEXT_NAME_RETURN(mystrdup((char *) utf8_value));
+}
+
+/* tls_dns_name - Extract valid DNS name from subjectAltName value */
+
+const char *tls_dns_name(const GENERAL_NAME * gn,
+ const TLS_SESS_STATE *TLScontext)
+{
+ const char *myname = "tls_dns_name";
+ char *cp;
+ const char *dnsname;
+ int len;
+
+ /*
+ * Peername checks are security sensitive, carefully scrutinize the
+ * input!
+ */
+ if (gn->type != GEN_DNS)
+ msg_panic("%s: Non DNS input argument", myname);
+
+ /*
+ * We expect the OpenSSL library to construct GEN_DNS extension objects as
+ * ASN1_IA5STRING values. Check we got the right union member.
+ */
+ if (ASN1_STRING_type(gn->d.ia5) != V_ASN1_IA5STRING) {
+ msg_warn("%s: %s: invalid ASN1 value type in subjectAltName",
+ myname, TLScontext->namaddr);
+ return (0);
+ }
+
+ /*
+ * Safe to treat as an ASCII string possibly holding a DNS name
+ */
+ dnsname = (const char *) ASN1_STRING_get0_data(gn->d.ia5);
+ len = ASN1_STRING_length(gn->d.ia5);
+ TRIM0(dnsname, len);
+
+ /*
+ * Per Dr. Steven Henson of the OpenSSL development team, ASN1_IA5STRING
+ * values can have internal ASCII NUL values in this context because
+ * their length is taken from the decoded ASN1 buffer, a trailing NUL is
+ * always appended to make sure that the string is terminated, but the
+ * ASN.1 length may differ from strlen().
+ */
+ if (len != strlen(dnsname)) {
+ msg_warn("%s: %s: internal NUL in subjectAltName",
+ myname, TLScontext->namaddr);
+ return 0;
+ }
+
+ /*
+ * XXX: Should we be more strict and call valid_hostname()? So long as
+ * the name is safe to handle, if it is not a valid hostname, it will not
+ * compare equal to the expected peername, so being more strict than
+ * "printable" is likely excessive...
+ */
+ if (*dnsname && !allprint(dnsname)) {
+ cp = mystrdup(dnsname);
+ msg_warn("%s: %s: non-printable characters in subjectAltName: %.100s",
+ myname, TLScontext->namaddr, printable(cp, '?'));
+ myfree(cp);
+ return 0;
+ }
+ return (dnsname);
+}
+
+/* tls_peer_CN - extract peer common name from certificate */
+
+char *tls_peer_CN(X509 *peercert, const TLS_SESS_STATE *TLScontext)
+{
+ char *cn;
+
+ cn = tls_text_name(X509_get_subject_name(peercert), NID_commonName,
+ "subject CN", TLScontext, DONT_GRIPE);
+ return (cn ? cn : mystrdup(""));
+}
+
+/* tls_issuer_CN - extract issuer common name from certificate */
+
+char *tls_issuer_CN(X509 *peer, const TLS_SESS_STATE *TLScontext)
+{
+ X509_NAME *name;
+ char *cn;
+
+ name = X509_get_issuer_name(peer);
+
+ /*
+ * If no issuer CN field, use Organization instead. CA certs without a CN
+ * are common, so we only complain if the organization is also missing.
+ */
+ if ((cn = tls_text_name(name, NID_commonName,
+ "issuer CN", TLScontext, DONT_GRIPE)) == 0)
+ cn = tls_text_name(name, NID_organizationName,
+ "issuer Organization", TLScontext, DONT_GRIPE);
+ return (cn ? cn : mystrdup(""));
+}
+
+#endif
diff --git a/src/tls/warn-mixed-multi-key.pem b/src/tls/warn-mixed-multi-key.pem
new file mode 100644
index 0000000..ab21d3a
--- /dev/null
+++ b/src/tls/warn-mixed-multi-key.pem
@@ -0,0 +1,51 @@
+subject=/CN=mx1.example.com
+issuer=/CN=EC issuer CA
+-----BEGIN CERTIFICATE-----
+MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1
+ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV
+BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO
+wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al
+VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa
+XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK
+BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7
+CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg==
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH
+S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo
+XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3
+-----END PRIVATE KEY-----
+
+subject=/CN=EC issuer CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD
+DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M
+ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs
+G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD
+VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK
+BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U
+WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7
+-----END CERTIFICATE-----
+
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC
+rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy
+b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV
+-----END PRIVATE KEY-----
+
+subject=/CN=EC root CA
+issuer=/CN=EC root CA
+-----BEGIN CERTIFICATE-----
+MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290
+IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD
+DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo
+e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z
+UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud
+IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI
+KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X
+AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o=
+-----END CERTIFICATE-----
diff --git a/src/tls/warn-mixed-multi-key.pem.ref b/src/tls/warn-mixed-multi-key.pem.ref
new file mode 100644
index 0000000..c9ce8db
--- /dev/null
+++ b/src/tls/warn-mixed-multi-key.pem.ref
@@ -0,0 +1,13 @@
+unknown: warning: ignoring 2nd key at index 4 in warn-mixed-multi-key.pem after 1st at 2
+depth = 0
+issuer = /CN=EC issuer CA
+subject = /CN=mx1.example.com
+
+depth = 1
+issuer = /CN=EC root CA
+subject = /CN=EC issuer CA
+
+depth = 2
+issuer = /CN=EC root CA
+subject = /CN=EC root CA
+