diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:06:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:06:34 +0000 |
commit | 5e61585d76ae77fd5e9e96ebabb57afa4d74880d (patch) | |
tree | 2b467823aaeebc7ef8bc9e3cabe8074eaef1666d /src/tls | |
parent | Initial commit. (diff) | |
download | postfix-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 '')
75 files changed, 19690 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 *) ¶m) +/* ... +/* 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, + ¶ms->tls_daemon_rand_bytes), + RECV_ATTR_INT(VAR_TLS_APPEND_DEF_CA, + ¶ms->tls_append_def_CA), + RECV_ATTR_INT(VAR_TLS_BC_PKEY_FPRINT, + ¶ms->tls_bc_pkey_fprint), + RECV_ATTR_INT(VAR_TLS_PREEMPT_CLIST, + ¶ms->tls_preempt_clist), + RECV_ATTR_INT(VAR_TLS_MULTI_WILDCARD, + ¶ms->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 + diff --git a/src/tlsmgr/.indent.pro b/src/tlsmgr/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/tlsmgr/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/tlsmgr/Makefile.in b/src/tlsmgr/Makefile.in new file mode 100644 index 0000000..8e7aab6 --- /dev/null +++ b/src/tlsmgr/Makefile.in @@ -0,0 +1,100 @@ +SHELL = /bin/sh +SRCS = tlsmgr.c +OBJS = tlsmgr.o +HDRS = +TESTSRC = smtpd_token_test.c +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = tlsmgr +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +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 *core $(PROG) $(TESTPROG) junk *.db *.out *.tmp + rm -rf printfck + +tidy: clean + +tests: + +root_tests: + +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' +tlsmgr.o: ../../include/argv.h +tlsmgr.o: ../../include/attr.h +tlsmgr.o: ../../include/check_arg.h +tlsmgr.o: ../../include/data_redirect.h +tlsmgr.o: ../../include/dict.h +tlsmgr.o: ../../include/dns.h +tlsmgr.o: ../../include/events.h +tlsmgr.o: ../../include/htable.h +tlsmgr.o: ../../include/iostuff.h +tlsmgr.o: ../../include/mail_conf.h +tlsmgr.o: ../../include/mail_params.h +tlsmgr.o: ../../include/mail_proto.h +tlsmgr.o: ../../include/mail_server.h +tlsmgr.o: ../../include/mail_version.h +tlsmgr.o: ../../include/master_proto.h +tlsmgr.o: ../../include/msg.h +tlsmgr.o: ../../include/myaddrinfo.h +tlsmgr.o: ../../include/myflock.h +tlsmgr.o: ../../include/mymalloc.h +tlsmgr.o: ../../include/name_code.h +tlsmgr.o: ../../include/name_mask.h +tlsmgr.o: ../../include/nvtable.h +tlsmgr.o: ../../include/set_eugid.h +tlsmgr.o: ../../include/sock_addr.h +tlsmgr.o: ../../include/stringops.h +tlsmgr.o: ../../include/sys_defs.h +tlsmgr.o: ../../include/tls.h +tlsmgr.o: ../../include/tls_mgr.h +tlsmgr.o: ../../include/tls_prng.h +tlsmgr.o: ../../include/tls_scache.h +tlsmgr.o: ../../include/vbuf.h +tlsmgr.o: ../../include/vstream.h +tlsmgr.o: ../../include/vstring.h +tlsmgr.o: ../../include/vstring_vstream.h +tlsmgr.o: ../../include/warn_stat.h +tlsmgr.o: tlsmgr.c diff --git a/src/tlsmgr/tlsmgr.c b/src/tlsmgr/tlsmgr.c new file mode 100644 index 0000000..db48ffb --- /dev/null +++ b/src/tlsmgr/tlsmgr.c @@ -0,0 +1,1098 @@ +/*++ +/* NAME +/* tlsmgr 8 +/* SUMMARY +/* Postfix TLS session cache and PRNG manager +/* SYNOPSIS +/* \fBtlsmgr\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBtlsmgr\fR(8) manages the Postfix TLS session caches. +/* It stores and retrieves cache entries on request by +/* \fBsmtpd\fR(8) and \fBsmtp\fR(8) processes, and periodically +/* removes entries that have expired. +/* +/* The \fBtlsmgr\fR(8) also manages the PRNG (pseudo random number +/* generator) pool. It answers queries by the \fBsmtpd\fR(8) +/* and \fBsmtp\fR(8) +/* processes to seed their internal PRNG pools. +/* +/* The \fBtlsmgr\fR(8)'s PRNG pool is initially seeded from +/* an external source (EGD, /dev/urandom, or regular file). +/* It is updated at configurable pseudo-random intervals with +/* data from the external source. It is updated periodically +/* with data from TLS session cache entries and with the time +/* of day, and is updated with the time of day whenever a +/* process requests \fBtlsmgr\fR(8) service. +/* +/* The \fBtlsmgr\fR(8) saves the PRNG state to an exchange file +/* periodically and when the process terminates, and reads +/* the exchange file when initializing its PRNG. +/* SECURITY +/* .ad +/* .fi +/* The \fBtlsmgr\fR(8) is not security-sensitive. The code that maintains +/* the external and internal PRNG pools does not "trust" the +/* data that it manipulates, and the code that maintains the +/* TLS session cache does not touch the contents of the cached +/* entries, except for seeding its internal PRNG pool. +/* +/* The \fBtlsmgr\fR(8) can be run chrooted and with reduced privileges. +/* At process startup it connects to the entropy source and +/* exchange file, and creates or truncates the optional TLS +/* session cache files. +/* +/* With Postfix version 2.5 and later, the \fBtlsmgr\fR(8) no +/* longer uses root privileges when opening cache files. These +/* files should now be stored under the Postfix-owned +/* \fBdata_directory\fR. As a migration aid, an attempt to +/* open a cache file under a non-Postfix directory is redirected +/* to the Postfix-owned \fBdata_directory\fR, and a warning +/* is logged. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* There is no automatic means to limit the number of entries in the +/* TLS session caches and/or the size of the TLS cache files. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are not picked up automatically, +/* because \fBtlsmgr\fR(8) is a persistent processes. Use the +/* command "\fBpostfix reload\fR" after a configuration change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* TLS SESSION CACHE +/* .ad +/* .fi +/* .IP "\fBlmtp_tls_loglevel (0)\fR" +/* The LMTP-specific version of the smtp_tls_loglevel +/* configuration parameter. +/* .IP "\fBlmtp_tls_session_cache_database (empty)\fR" +/* The LMTP-specific version of the smtp_tls_session_cache_database +/* configuration parameter. +/* .IP "\fBlmtp_tls_session_cache_timeout (3600s)\fR" +/* The LMTP-specific version of the smtp_tls_session_cache_timeout +/* configuration parameter. +/* .IP "\fBsmtp_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP client logging of TLS activity. +/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR" +/* Name of the file containing the optional Postfix SMTP client +/* TLS session cache. +/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR" +/* The expiration time of Postfix SMTP client TLS session cache +/* information. +/* .IP "\fBsmtpd_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP server logging of TLS activity. +/* .IP "\fBsmtpd_tls_session_cache_database (empty)\fR" +/* Name of the file containing the optional Postfix SMTP server +/* TLS session cache. +/* .IP "\fBsmtpd_tls_session_cache_timeout (3600s)\fR" +/* The expiration time of Postfix SMTP server TLS session cache +/* information. +/* PSEUDO RANDOM NUMBER GENERATOR +/* .ad +/* .fi +/* .IP "\fBtls_random_source (see 'postconf -d' output)\fR" +/* The external entropy source for the in-memory \fBtlsmgr\fR(8) pseudo +/* random number generator (PRNG) pool. +/* .IP "\fBtls_random_bytes (32)\fR" +/* The number of bytes that \fBtlsmgr\fR(8) reads from $tls_random_source +/* when (re)seeding the in-memory pseudo random number generator (PRNG) +/* pool. +/* .IP "\fBtls_random_exchange_name (see 'postconf -d' output)\fR" +/* Name of the pseudo random number generator (PRNG) state file +/* that is maintained by \fBtlsmgr\fR(8). +/* .IP "\fBtls_random_prng_update_period (3600s)\fR" +/* The time between attempts by \fBtlsmgr\fR(8) to save the state of +/* the pseudo random number generator (PRNG) to the file specified +/* with $tls_random_exchange_name. +/* .IP "\fBtls_random_reseed_period (3600s)\fR" +/* The maximal time between attempts by \fBtlsmgr\fR(8) to re-seed the +/* in-memory pseudo random number generator (PRNG) pool from external +/* sources. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdata_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix-writable data files (for example: +/* caches, pseudo-random numbers). +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* smtp(8), Postfix SMTP client +/* smtpd(8), Postfix SMTP server +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* TLS_README, Postfix TLS configuration and operation +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* This service was introduced with Postfix version 2.2. +/* AUTHOR(S) +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Adapted 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> +#include <sys/stat.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <sys/time.h> /* gettimeofday, not POSIX */ +#include <limits.h> + +#ifndef UCHAR_MAX +#define UCHAR_MAX 0xff +#endif + +/* OpenSSL library. */ + +#ifdef USE_TLS +#include <openssl/rand.h> /* For the PRNG */ +#endif + +/* Utility library. */ + +#include <msg.h> +#include <events.h> +#include <stringops.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <vstream.h> +#include <vstring.h> +#include <vstring_vstream.h> +#include <attr.h> +#include <set_eugid.h> +#include <htable.h> +#include <warn_stat.h> + +/* Global library. */ + +#include <mail_conf.h> +#include <mail_params.h> +#include <mail_version.h> +#include <mail_proto.h> +#include <data_redirect.h> + +/* Master process interface. */ + +#include <master_proto.h> +#include <mail_server.h> + +/* TLS library. */ + +#ifdef USE_TLS +#include <tls_mgr.h> +#define TLS_INTERNAL +#include <tls.h> /* TLS_MGR_SCACHE_<type> */ +#include <tls_prng.h> +#include <tls_scache.h> + +/* Application-specific. */ + + /* + * Tunables. + */ +char *var_tls_rand_source; +int var_tls_rand_bytes; +int var_tls_reseed_period; +int var_tls_prng_exch_period; +char *var_smtpd_tls_loglevel; +char *var_smtpd_tls_scache_db; +int var_smtpd_tls_scache_timeout; +char *var_smtp_tls_loglevel; +char *var_smtp_tls_scache_db; +int var_smtp_tls_scache_timeout; +char *var_lmtp_tls_loglevel; +char *var_lmtp_tls_scache_db; +int var_lmtp_tls_scache_timeout; +char *var_tls_rand_exch_name; + + /* + * Bound the time that we are willing to wait for an I/O operation. This + * produces better error messages than waiting until the watchdog timer + * kills the process. + */ +#define TLS_MGR_TIMEOUT 10 + + /* + * State for updating the PRNG exchange file. + */ +static TLS_PRNG_SRC *rand_exch; + + /* + * State for seeding the internal PRNG from external source. + */ +static TLS_PRNG_SRC *rand_source_dev; +static TLS_PRNG_SRC *rand_source_egd; +static TLS_PRNG_SRC *rand_source_file; + + /* + * The external entropy source type is encoded in the source name. The + * obvious alternative is to have separate configuration parameters per + * source type, so that one process can query multiple external sources. + */ +#define DEV_PREF "dev:" +#define DEV_PREF_LEN (sizeof((DEV_PREF)) - 1) +#define DEV_PATH(dev) ((dev) + EGD_PREF_LEN) + +#define EGD_PREF "egd:" +#define EGD_PREF_LEN (sizeof((EGD_PREF)) - 1) +#define EGD_PATH(egd) ((egd) + EGD_PREF_LEN) + + /* + * State for TLS session caches. + */ +typedef struct { + char *cache_label; /* cache short-hand name */ + TLS_SCACHE *cache_info; /* cache handle */ + int cache_active; /* cache status */ + char **cache_db; /* main.cf parameter value */ + const char *log_param; /* main.cf parameter name */ + char **log_level; /* main.cf parameter value */ + int *cache_timeout; /* main.cf parameter value */ +} TLSMGR_SCACHE; + +static TLSMGR_SCACHE cache_table[] = { + TLS_MGR_SCACHE_SMTPD, 0, 0, &var_smtpd_tls_scache_db, + VAR_SMTPD_TLS_LOGLEVEL, + &var_smtpd_tls_loglevel, &var_smtpd_tls_scache_timeout, + TLS_MGR_SCACHE_SMTP, 0, 0, &var_smtp_tls_scache_db, + VAR_SMTP_TLS_LOGLEVEL, + &var_smtp_tls_loglevel, &var_smtp_tls_scache_timeout, + TLS_MGR_SCACHE_LMTP, 0, 0, &var_lmtp_tls_scache_db, + VAR_LMTP_TLS_LOGLEVEL, + &var_lmtp_tls_loglevel, &var_lmtp_tls_scache_timeout, + 0, +}; + +#define smtpd_cache (cache_table[0]) + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) +#define STREQ(x, y) (strcmp((x), (y)) == 0) + +/* tlsmgr_prng_exch_event - update PRNG exchange file */ + +static void tlsmgr_prng_exch_event(int unused_event, void *dummy) +{ + const char *myname = "tlsmgr_prng_exch_event"; + unsigned char randbyte; + int next_period; + struct stat st; + + if (msg_verbose) + msg_info("%s: update PRNG exchange file", myname); + + /* + * Sanity check. If the PRNG exchange file was removed, there is no point + * updating it further. Restart the process and update the new file. + */ + if (fstat(rand_exch->fd, &st) < 0) + msg_fatal("cannot fstat() the PRNG exchange file: %m"); + if (st.st_nlink == 0) { + msg_warn("PRNG exchange file was removed -- exiting to reopen"); + sleep(1); + exit(0); + } + tls_prng_exch_update(rand_exch); + + /* + * Make prediction difficult for outsiders and calculate the time for the + * next execution randomly. + */ + RAND_bytes(&randbyte, 1); + next_period = (var_tls_prng_exch_period * randbyte) / UCHAR_MAX; + event_request_timer(tlsmgr_prng_exch_event, dummy, next_period); +} + +/* tlsmgr_reseed_event - re-seed the internal PRNG pool */ + +static void tlsmgr_reseed_event(int unused_event, void *dummy) +{ + int next_period; + unsigned char randbyte; + int must_exit = 0; + + /* + * Reseed the internal PRNG from external source. Errors are recoverable. + * We simply restart and reconnect without making a fuss. This is OK + * because we do require that exchange file updates succeed. The exchange + * file is the only entropy source that really matters in the long term. + * + * If the administrator specifies an external randomness source that we + * could not open upon start-up, restart to see if we can open it now + * (and log a nagging warning if we can't). + */ + if (*var_tls_rand_source) { + + /* + * Source is a random device. + */ + if (rand_source_dev) { + if (tls_prng_dev_read(rand_source_dev, var_tls_rand_bytes) <= 0) { + msg_info("cannot read from entropy device %s: %m -- " + "exiting to reopen", DEV_PATH(var_tls_rand_source)); + must_exit = 1; + } + } + + /* + * Source is an EGD compatible socket. + */ + else if (rand_source_egd) { + if (tls_prng_egd_read(rand_source_egd, var_tls_rand_bytes) <= 0) { + msg_info("lost connection to EGD server %s -- " + "exiting to reconnect", EGD_PATH(var_tls_rand_source)); + must_exit = 1; + } + } + + /* + * Source is a regular file. Read the content once and close the + * file. + */ + else if (rand_source_file) { + if (tls_prng_file_read(rand_source_file, var_tls_rand_bytes) <= 0) + msg_warn("cannot read from entropy file %s: %m", + var_tls_rand_source); + tls_prng_file_close(rand_source_file); + rand_source_file = 0; + var_tls_rand_source[0] = 0; + } + + /* + * Could not open the external source upon start-up. See if we can + * open it this time. Save PRNG state before we exit. + */ + else { + msg_info("exiting to reopen external entropy source %s", + var_tls_rand_source); + must_exit = 1; + } + } + + /* + * Save PRNG state in case we must exit. + */ + if (must_exit) { + if (rand_exch) + tls_prng_exch_update(rand_exch); + sleep(1); + exit(0); + } + + /* + * Make prediction difficult for outsiders and calculate the time for the + * next execution randomly. + */ + RAND_bytes(&randbyte, 1); + next_period = (var_tls_reseed_period * randbyte) / UCHAR_MAX; + event_request_timer(tlsmgr_reseed_event, dummy, next_period); +} + +/* tlsmgr_cache_run_event - start TLS session cache scan */ + +static void tlsmgr_cache_run_event(int unused_event, void *ctx) +{ + const char *myname = "tlsmgr_cache_run_event"; + TLSMGR_SCACHE *cache = (TLSMGR_SCACHE *) ctx; + + /* + * This routine runs when it is time for another TLS session cache scan. + * Make sure this routine gets called again in the future. + * + * Don't start a new scan when the timer goes off while cache cleanup is + * still in progress. + */ + if (cache->cache_info->verbose) + msg_info("%s: start TLS %s session cache cleanup", + myname, cache->cache_label); + + if (cache->cache_active == 0) + cache->cache_active = + tls_scache_sequence(cache->cache_info, DICT_SEQ_FUN_FIRST, + TLS_SCACHE_SEQUENCE_NOTHING); + + event_request_timer(tlsmgr_cache_run_event, (void *) cache, + cache->cache_info->timeout); +} + +/* tlsmgr_key - return matching or current RFC 5077 session ticket keys */ + +static int tlsmgr_key(VSTRING *buffer, int timeout) +{ + TLS_TICKET_KEY *key; + TLS_TICKET_KEY tmp; + unsigned char *name; + time_t now = time((time_t *) 0); + + /* In tlsmgr requests we encode null key names as empty strings. */ + name = LEN(buffer) ? (unsigned char *) STR(buffer) : 0; + + /* + * Each key's encrypt and subsequent decrypt-only timeout is half of the + * total session timeout. + */ + timeout /= 2; + + /* Attempt to locate existing key */ + if ((key = tls_scache_key(name, now, timeout)) == 0) { + if (name == 0) { + /* Create new encryption key */ + if (RAND_bytes(tmp.name, TLS_TICKET_NAMELEN) <= 0 + || RAND_bytes(tmp.bits, TLS_TICKET_KEYLEN) <= 0 + || RAND_bytes(tmp.hmac, TLS_TICKET_MACLEN) <= 0) + return (TLS_MGR_STAT_ERR); + tmp.tout = now + timeout - 1; + key = tls_scache_key_rotate(&tmp); + } else { + /* No matching decryption key found */ + return (TLS_MGR_STAT_ERR); + } + } + /* Return value overrites name buffer */ + vstring_memcpy(buffer, (char *) key, sizeof(*key)); + return (TLS_MGR_STAT_OK); +} + +/* tlsmgr_loop - TLS manager main loop */ + +static int tlsmgr_loop(char *unused_name, char **unused_argv) +{ + struct timeval tv; + int active = 0; + TLSMGR_SCACHE *ent; + + /* + * Update the PRNG pool with the time of day. We do it here after every + * event (including internal timer events and external client request + * events), instead of doing it in individual event call-back routines. + */ + GETTIMEOFDAY(&tv); + RAND_seed(&tv, sizeof(struct timeval)); + + /* + * This routine runs as part of the event handling loop, after the event + * manager has delivered a timer or I/O event, or after it has waited for + * a specified amount of time. The result value of tlsmgr_loop() + * specifies how long the event manager should wait for the next event. + * + * We use this loop to interleave TLS session cache cleanup with other + * activity. Interleaved processing is needed when we use a client-server + * protocol for entropy and session state exchange with smtp(8) and + * smtpd(8) processes. + */ +#define DONT_WAIT 0 +#define WAIT_FOR_EVENT (-1) + + for (ent = cache_table; ent->cache_label; ++ent) { + if (ent->cache_info && ent->cache_active) + active |= ent->cache_active = + tls_scache_sequence(ent->cache_info, DICT_SEQ_FUN_NEXT, + TLS_SCACHE_SEQUENCE_NOTHING); + } + + return (active ? DONT_WAIT : WAIT_FOR_EVENT); +} + +/* tlsmgr_request_receive - receive request */ + +static int tlsmgr_request_receive(VSTREAM *client_stream, VSTRING *request) +{ + int count; + + /* + * Kluge: choose the protocol depending on the request size. + */ + if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) { + msg_warn("timeout while waiting for data from %s", + VSTREAM_PATH(client_stream)); + return (-1); + } + if ((count = peekfd(vstream_fileno(client_stream))) < 0) { + msg_warn("cannot examine read buffer of %s: %m", + VSTREAM_PATH(client_stream)); + return (-1); + } + + /* + * Short request: master trigger. Use the string+null protocol. + */ + if (count <= 2) { + if (vstring_get_null(request, client_stream) == VSTREAM_EOF) { + msg_warn("end-of-input while reading request from %s: %m", + VSTREAM_PATH(client_stream)); + return (-1); + } + } + + /* + * Long request: real tlsmgr client. Use the attribute list protocol. + */ + else { + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_REQ, request), + ATTR_TYPE_END) != 1) { + return (-1); + } + } + return (0); +} + +/* tlsmgr_service - respond to external request */ + +static void tlsmgr_service(VSTREAM *client_stream, char *unused_service, + char **argv) +{ + static VSTRING *request = 0; + static VSTRING *cache_type = 0; + static VSTRING *cache_id = 0; + static VSTRING *buffer = 0; + int len; + static char wakeup[] = { /* master wakeup request */ + TRIGGER_REQ_WAKEUP, + 0, + }; + TLSMGR_SCACHE *ent; + int status = TLS_MGR_STAT_FAIL; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Initialize. We're select threaded, so we can use static buffers. + */ + if (request == 0) { + request = vstring_alloc(10); + cache_type = vstring_alloc(10); + cache_id = vstring_alloc(10); + buffer = vstring_alloc(10); + } + + /* + * This routine runs whenever a client connects to the socket dedicated + * to the tlsmgr service (including wake up events sent by the master). + * All connection-management stuff is handled by the common code in + * multi_server.c. + */ + if (tlsmgr_request_receive(client_stream, request) == 0) { + + /* + * Load session from cache. + */ + if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id), + ATTR_TYPE_END) == 2) { + for (ent = cache_table; ent->cache_label; ++ent) + if (strcmp(ent->cache_label, STR(cache_type)) == 0) + break; + if (ent->cache_label == 0) { + msg_warn("bogus cache type \"%s\" in \"%s\" request", + STR(cache_type), TLS_MGR_REQ_LOOKUP); + VSTRING_RESET(buffer); + } else if (ent->cache_info == 0) { + + /* + * Cache type valid, but not enabled + */ + VSTRING_RESET(buffer); + } else { + status = tls_scache_lookup(ent->cache_info, + STR(cache_id), buffer) ? + TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_DATA(TLS_MGR_ATTR_SESSION, + LEN(buffer), STR(buffer)), + ATTR_TYPE_END); + } + + /* + * Save session to cache. + */ + else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id), + RECV_ATTR_DATA(TLS_MGR_ATTR_SESSION, buffer), + ATTR_TYPE_END) == 3) { + for (ent = cache_table; ent->cache_label; ++ent) + if (strcmp(ent->cache_label, STR(cache_type)) == 0) + break; + if (ent->cache_label == 0) { + msg_warn("bogus cache type \"%s\" in \"%s\" request", + STR(cache_type), TLS_MGR_REQ_UPDATE); + } else if (ent->cache_info != 0) { + status = + tls_scache_update(ent->cache_info, STR(cache_id), + STR(buffer), LEN(buffer)) ? + TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + ATTR_TYPE_END); + } + + /* + * Delete session from cache. + */ + else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id), + ATTR_TYPE_END) == 2) { + for (ent = cache_table; ent->cache_label; ++ent) + if (strcmp(ent->cache_label, STR(cache_type)) == 0) + break; + if (ent->cache_label == 0) { + msg_warn("bogus cache type \"%s\" in \"%s\" request", + STR(cache_type), TLS_MGR_REQ_DELETE); + } else if (ent->cache_info != 0) { + status = tls_scache_delete(ent->cache_info, + STR(cache_id)) ? + TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + ATTR_TYPE_END); + } + + /* + * RFC 5077 TLS session ticket keys + */ + else if (STREQ(STR(request), TLS_MGR_REQ_TKTKEY)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, buffer), + ATTR_TYPE_END) == 1) { + if (LEN(buffer) != 0 && LEN(buffer) != TLS_TICKET_NAMELEN) { + msg_warn("invalid session ticket key name length: %ld", + (long) LEN(buffer)); + VSTRING_RESET(buffer); + } else if (*smtpd_cache.cache_timeout <= 0) { + status = TLS_MGR_STAT_ERR; + VSTRING_RESET(buffer); + } else { + status = tlsmgr_key(buffer, *smtpd_cache.cache_timeout); + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_DATA(TLS_MGR_ATTR_KEYBUF, + LEN(buffer), STR(buffer)), + ATTR_TYPE_END); + } + + /* + * Entropy request. + */ + else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(TLS_MGR_ATTR_SIZE, &len), + ATTR_TYPE_END) == 1) { + VSTRING_RESET(buffer); + if (len <= 0 || len > 255) { + msg_warn("bogus seed length \"%d\" in \"%s\" request", + len, TLS_MGR_REQ_SEED); + } else { + VSTRING_SPACE(buffer, len); + RAND_bytes((unsigned char *) STR(buffer), len); + vstring_set_payload_size(buffer, len); + status = TLS_MGR_STAT_OK; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_DATA(TLS_MGR_ATTR_SEED, + LEN(buffer), STR(buffer)), + ATTR_TYPE_END); + } + + /* + * Caching policy request. + */ + else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) { + int cachable = 0; + int timeout = 0; + + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + ATTR_TYPE_END) == 1) { + for (ent = cache_table; ent->cache_label; ++ent) + if (strcmp(ent->cache_label, STR(cache_type)) == 0) + break; + if (ent->cache_label == 0) { + msg_warn("bogus cache type \"%s\" in \"%s\" request", + STR(cache_type), TLS_MGR_REQ_POLICY); + } else { + cachable = (ent->cache_info != 0) ? 1 : 0; + timeout = *ent->cache_timeout; + status = TLS_MGR_STAT_OK; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_INT(TLS_MGR_ATTR_CACHABLE, cachable), + SEND_ATTR_INT(TLS_MGR_ATTR_SESSTOUT, timeout), + ATTR_TYPE_END); + } + + /* + * Master trigger. Normally, these triggers arrive only after some + * other process requested the tlsmgr's service. The purpose is to + * restart the tlsmgr after it aborted due to a fatal run-time error, + * so that it can continue its housekeeping even while nothing is + * using TLS. + * + * XXX Which begs the question, if TLS isn't used often, do we need a + * tlsmgr background process? It could terminate when the session + * caches are empty. + */ + else if (STREQ(STR(request), wakeup)) { + if (msg_verbose) + msg_info("received master trigger"); + multi_server_disconnect(client_stream); + return; /* NOT: vstream_fflush */ + } + } + + /* + * Protocol error. + */ + else { + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, TLS_MGR_STAT_FAIL), + ATTR_TYPE_END); + } + vstream_fflush(client_stream); +} + +/* tlsmgr_pre_init - pre-jail initialization */ + +static void tlsmgr_pre_init(char *unused_name, char **unused_argv) +{ + char *path; + struct timeval tv; + TLSMGR_SCACHE *ent; + VSTRING *redirect; + HTABLE *dup_filter; + const char *dup_label; + + /* + * If nothing else works then at least this will get us a few bits of + * entropy. + * + * XXX This is our first call into the OpenSSL library. We should find out + * if this can be moved to the post-jail initialization phase, without + * breaking compatibility with existing installations. + */ + GETTIMEOFDAY(&tv); + tv.tv_sec ^= getpid(); + RAND_seed(&tv, sizeof(struct timeval)); + + /* + * Open the external entropy source. We will not be able to open it again + * after we are sent to chroot jail, so we keep it open. Errors are not + * fatal. The exchange file (see below) is the only entropy source that + * really matters in the long run. + * + * Security note: we open the entropy source while privileged, but we don't + * access the source until after we release privileges. This way, none of + * the OpenSSL code gets to execute while we are privileged. + */ + if (*var_tls_rand_source) { + + /* + * Source is a random device. + */ + if (!strncmp(var_tls_rand_source, DEV_PREF, DEV_PREF_LEN)) { + path = DEV_PATH(var_tls_rand_source); + rand_source_dev = tls_prng_dev_open(path, TLS_MGR_TIMEOUT); + if (rand_source_dev == 0) + msg_warn("cannot open entropy device %s: %m", path); + } + + /* + * Source is an EGD compatible socket. + */ + else if (!strncmp(var_tls_rand_source, EGD_PREF, EGD_PREF_LEN)) { + path = EGD_PATH(var_tls_rand_source); + rand_source_egd = tls_prng_egd_open(path, TLS_MGR_TIMEOUT); + if (rand_source_egd == 0) + msg_warn("cannot connect to EGD server %s: %m", path); + } + + /* + * Source is regular file. We read this only once. + */ + else { + rand_source_file = + tls_prng_file_open(var_tls_rand_source, TLS_MGR_TIMEOUT); + } + } else { + msg_warn("no entropy source specified with parameter %s", + VAR_TLS_RAND_SOURCE); + msg_warn("encryption keys etc. may be predictable"); + } + + /* + * Security: don't create root-owned files that contain untrusted data. + * And don't create Postfix-owned files in root-owned directories, + * either. We want a correct relationship between (file/directory) + * ownership and (file/directory) content. + */ + SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid); + redirect = vstring_alloc(100); + + /* + * Open the PRNG exchange file before going to jail, but don't use root + * privileges. Start the exchange file read/update pseudo thread after + * dropping privileges. + */ + if (*var_tls_rand_exch_name) { + rand_exch = + tls_prng_exch_open(data_redirect_file(redirect, + var_tls_rand_exch_name)); + if (rand_exch == 0) + msg_fatal("cannot open PRNG exchange file %s: %m", + var_tls_rand_exch_name); + } + + /* + * Open the session cache files and discard old information before going + * to jail, but don't use root privilege. Start the cache maintenance + * pseudo threads after dropping privileges. + */ + dup_filter = htable_create(sizeof(cache_table) / sizeof(cache_table[0])); + for (ent = cache_table; ent->cache_label; ++ent) { + /* Sanitize session timeout */ + if (*ent->cache_timeout > 0) { + if (*ent->cache_timeout < TLS_SESSION_LIFEMIN) + *ent->cache_timeout = TLS_SESSION_LIFEMIN; + } else { + *ent->cache_timeout = 0; + } + /* External cache database disabled if timeout is non-positive */ + if (*ent->cache_timeout > 0 && **ent->cache_db) { + if ((dup_label = htable_find(dup_filter, *ent->cache_db)) != 0) + msg_fatal("do not use the same TLS cache file %s for %s and %s", + *ent->cache_db, dup_label, ent->cache_label); + htable_enter(dup_filter, *ent->cache_db, ent->cache_label); + ent->cache_info = + tls_scache_open(data_redirect_map(redirect, *ent->cache_db), + ent->cache_label, + tls_log_mask(ent->log_param, + *ent->log_level) & TLS_LOG_CACHE, + *ent->cache_timeout); + } + } + htable_free(dup_filter, (void (*) (void *)) 0); + + /* + * Clean up and restore privilege. + */ + vstring_free(redirect); + RESTORE_SAVED_EUGID(); +} + +/* tlsmgr_post_init - post-jail initialization */ + +static void tlsmgr_post_init(char *unused_name, char **unused_argv) +{ + TLSMGR_SCACHE *ent; + +#define NULL_EVENT (0) +#define NULL_CONTEXT ((char *) 0) + + /* + * This routine runs after the skeleton code has entered the chroot jail, + * but before any client requests are serviced. Prevent automatic process + * suicide after a limited number of client requests or after a limited + * amount of idle time. + */ + var_use_limit = 0; + var_idle_limit = 0; + + /* + * Start the internal PRNG re-seeding pseudo thread first. + */ + if (*var_tls_rand_source) { + if (var_tls_reseed_period > INT_MAX / UCHAR_MAX) + var_tls_reseed_period = INT_MAX / UCHAR_MAX; + tlsmgr_reseed_event(NULL_EVENT, NULL_CONTEXT); + } + + /* + * Start the exchange file read/update pseudo thread. + */ + if (*var_tls_rand_exch_name) { + if (var_tls_prng_exch_period > INT_MAX / UCHAR_MAX) + var_tls_prng_exch_period = INT_MAX / UCHAR_MAX; + tlsmgr_prng_exch_event(NULL_EVENT, NULL_CONTEXT); + } + + /* + * Start the cache maintenance pseudo threads last. Strictly speaking + * there is nothing to clean up after we truncate the database to zero + * length, but early cleanup makes verbose logging more informative (we + * get positive confirmation that the cleanup threads are running). + */ + for (ent = cache_table; ent->cache_label; ++ent) + if (ent->cache_info) + tlsmgr_cache_run_event(NULL_EVENT, (void *) ent); +} + +/* tlsmgr_before_exit - save PRNG state before exit */ + +static void tlsmgr_before_exit(char *unused_service_name, char **unused_argv) +{ + + /* + * Save state before we exit after "postfix reload". + */ + if (rand_exch) + tls_prng_exch_update(rand_exch); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_TLS_RAND_SOURCE, DEF_TLS_RAND_SOURCE, &var_tls_rand_source, 0, 0, + VAR_TLS_RAND_EXCH_NAME, DEF_TLS_RAND_EXCH_NAME, &var_tls_rand_exch_name, 0, 0, + VAR_SMTPD_TLS_SCACHE_DB, DEF_SMTPD_TLS_SCACHE_DB, &var_smtpd_tls_scache_db, 0, 0, + VAR_SMTP_TLS_SCACHE_DB, DEF_SMTP_TLS_SCACHE_DB, &var_smtp_tls_scache_db, 0, 0, + VAR_LMTP_TLS_SCACHE_DB, DEF_LMTP_TLS_SCACHE_DB, &var_lmtp_tls_scache_db, 0, 0, + VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0, + VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_lmtp_tls_loglevel, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_TLS_RESEED_PERIOD, DEF_TLS_RESEED_PERIOD, &var_tls_reseed_period, 1, 0, + VAR_TLS_PRNG_UPD_PERIOD, DEF_TLS_PRNG_UPD_PERIOD, &var_tls_prng_exch_period, 1, 0, + VAR_SMTPD_TLS_SCACHTIME, DEF_SMTPD_TLS_SCACHTIME, &var_smtpd_tls_scache_timeout, 0, MAX_SMTPD_TLS_SCACHETIME, + VAR_SMTP_TLS_SCACHTIME, DEF_SMTP_TLS_SCACHTIME, &var_smtp_tls_scache_timeout, 0, MAX_SMTP_TLS_SCACHETIME, + VAR_LMTP_TLS_SCACHTIME, DEF_LMTP_TLS_SCACHTIME, &var_lmtp_tls_scache_timeout, 0, MAX_LMTP_TLS_SCACHETIME, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_TLS_RAND_BYTES, DEF_TLS_RAND_BYTES, &var_tls_rand_bytes, 1, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Use the multi service skeleton, and require that no-one else is + * monitoring our service port while this process runs. + */ + multi_server_main(argc, argv, tlsmgr_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_PRE_INIT(tlsmgr_pre_init), + CA_MAIL_SERVER_POST_INIT(tlsmgr_post_init), + CA_MAIL_SERVER_EXIT(tlsmgr_before_exit), + CA_MAIL_SERVER_LOOP(tlsmgr_loop), + CA_MAIL_SERVER_SOLITARY, + 0); +} + +#else + +/* tlsmgr_service - respond to external trigger(s), non-TLS version */ + +static void tlsmgr_service(VSTREAM *unused_stream, char *unused_service, + char **unused_argv) +{ + msg_info("TLS support is not compiled in -- exiting"); +} + +/* main - the main program, non-TLS version */ + +int main(int argc, char **argv) +{ + + /* + * 200411 We can't simply use msg_fatal() here, because the logging + * hasn't been initialized. The text would disappear because stderr is + * redirected to /dev/null. + * + * We invoke multi_server_main() to complete program initialization + * (including logging) and then invoke the tlsmgr_service() routine to + * log the message that says why this program will not run. + */ + multi_server_main(argc, argv, tlsmgr_service, + 0); +} + +#endif diff --git a/src/tlsproxy/.indent.pro b/src/tlsproxy/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/tlsproxy/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/tlsproxy/Makefile.in b/src/tlsproxy/Makefile.in new file mode 100644 index 0000000..c72aa81 --- /dev/null +++ b/src/tlsproxy/Makefile.in @@ -0,0 +1,117 @@ +SHELL = /bin/sh +SRCS = tlsproxy.c tlsproxy_state.c +OBJS = tlsproxy.o tlsproxy_state.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = tlsproxy +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir 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 *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +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' +tlsproxy.o: ../../include/argv.h +tlsproxy.o: ../../include/attr.h +tlsproxy.o: ../../include/been_here.h +tlsproxy.o: ../../include/check_arg.h +tlsproxy.o: ../../include/dns.h +tlsproxy.o: ../../include/events.h +tlsproxy.o: ../../include/htable.h +tlsproxy.o: ../../include/iostuff.h +tlsproxy.o: ../../include/mail_conf.h +tlsproxy.o: ../../include/mail_params.h +tlsproxy.o: ../../include/mail_proto.h +tlsproxy.o: ../../include/mail_server.h +tlsproxy.o: ../../include/mail_version.h +tlsproxy.o: ../../include/msg.h +tlsproxy.o: ../../include/myaddrinfo.h +tlsproxy.o: ../../include/mymalloc.h +tlsproxy.o: ../../include/name_code.h +tlsproxy.o: ../../include/name_mask.h +tlsproxy.o: ../../include/nbbio.h +tlsproxy.o: ../../include/nvtable.h +tlsproxy.o: ../../include/sock_addr.h +tlsproxy.o: ../../include/split_at.h +tlsproxy.o: ../../include/sys_defs.h +tlsproxy.o: ../../include/tls.h +tlsproxy.o: ../../include/tls_proxy.h +tlsproxy.o: ../../include/vbuf.h +tlsproxy.o: ../../include/vstream.h +tlsproxy.o: ../../include/vstring.h +tlsproxy.o: tlsproxy.c +tlsproxy.o: tlsproxy.h +tlsproxy_state.o: ../../include/argv.h +tlsproxy_state.o: ../../include/attr.h +tlsproxy_state.o: ../../include/check_arg.h +tlsproxy_state.o: ../../include/dns.h +tlsproxy_state.o: ../../include/events.h +tlsproxy_state.o: ../../include/htable.h +tlsproxy_state.o: ../../include/mail_conf.h +tlsproxy_state.o: ../../include/mail_server.h +tlsproxy_state.o: ../../include/msg.h +tlsproxy_state.o: ../../include/myaddrinfo.h +tlsproxy_state.o: ../../include/mymalloc.h +tlsproxy_state.o: ../../include/name_code.h +tlsproxy_state.o: ../../include/name_mask.h +tlsproxy_state.o: ../../include/nbbio.h +tlsproxy_state.o: ../../include/nvtable.h +tlsproxy_state.o: ../../include/sock_addr.h +tlsproxy_state.o: ../../include/sys_defs.h +tlsproxy_state.o: ../../include/tls.h +tlsproxy_state.o: ../../include/tls_proxy.h +tlsproxy_state.o: ../../include/vbuf.h +tlsproxy_state.o: ../../include/vstream.h +tlsproxy_state.o: ../../include/vstring.h +tlsproxy_state.o: tlsproxy.h +tlsproxy_state.o: tlsproxy_state.c diff --git a/src/tlsproxy/tlsproxy.c b/src/tlsproxy/tlsproxy.c new file mode 100644 index 0000000..40bee88 --- /dev/null +++ b/src/tlsproxy/tlsproxy.c @@ -0,0 +1,1981 @@ +/*++ +/* NAME +/* tlsproxy 8 +/* SUMMARY +/* Postfix TLS proxy +/* SYNOPSIS +/* \fBtlsproxy\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBtlsproxy\fR(8) server implements a two-way TLS proxy. It +/* is used by the \fBpostscreen\fR(8) server to talk SMTP-over-TLS +/* with remote SMTP clients that are not whitelisted (including +/* clients whose whitelist status has expired), and by the +/* \fBsmtp\fR(8) client to support TLS connection reuse, but it +/* should also work for non-SMTP protocols. +/* +/* Although one \fBtlsproxy\fR(8) process can serve multiple +/* sessions at the same time, it is a good idea to allow the +/* number of processes to increase with load, so that the +/* service remains responsive. +/* PROTOCOL EXAMPLE +/* .ad +/* .fi +/* The example below concerns \fBpostscreen\fR(8). However, +/* the \fBtlsproxy\fR(8) server is agnostic of the application +/* protocol, and the example is easily adapted to other +/* applications. +/* +/* After receiving a valid remote SMTP client STARTTLS command, +/* the \fBpostscreen\fR(8) server sends the remote SMTP client +/* endpoint string, the requested role (server), and the +/* requested timeout to \fBtlsproxy\fR(8). \fBpostscreen\fR(8) +/* then receives a "TLS available" indication from \fBtlsproxy\fR(8). +/* If the TLS service is available, \fBpostscreen\fR(8) sends +/* the remote SMTP client file descriptor to \fBtlsproxy\fR(8), +/* and sends the plaintext 220 greeting to the remote SMTP +/* client. This triggers TLS negotiations between the remote +/* SMTP client and \fBtlsproxy\fR(8). Upon completion of the +/* TLS-level handshake, \fBtlsproxy\fR(8) translates between +/* plaintext from/to \fBpostscreen\fR(8) and ciphertext to/from +/* the remote SMTP client. +/* SECURITY +/* .ad +/* .fi +/* The \fBtlsproxy\fR(8) server is moderately security-sensitive. +/* It talks to untrusted clients on the network. The process +/* can be run chrooted at fixed low privilege. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are not picked up automatically, +/* as \fBtlsproxy\fR(8) processes may run for a long time +/* depending on mail server load. Use the command "\fBpostfix +/* reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* STARTTLS GLOBAL CONTROLS +/* .ad +/* .fi +/* The following settings are global and therefore cannot be +/* overruled by information specified in a \fBtlsproxy\fR(8) +/* client request. +/* .IP "\fBtls_append_default_CA (no)\fR" +/* Append the system-supplied default Certification Authority +/* certificates to the ones specified with *_tls_CApath or *_tls_CAfile. +/* .IP "\fBtls_daemon_random_bytes (32)\fR" +/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8) +/* process requests from the \fBtlsmgr\fR(8) server in order to seed its +/* internal pseudo random number generator (PRNG). +/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "high" grade ciphers. +/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "medium" or higher grade ciphers. +/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "low" or higher grade ciphers. +/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "export" or higher grade ciphers. +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" +/* The OpenSSL cipherlist for "NULL" grade ciphers that provide +/* authentication without encryption. +/* .IP "\fBtls_eecdh_strong_curve (prime256v1)\fR" +/* The elliptic curve used by the Postfix SMTP server for sensibly +/* strong +/* ephemeral ECDH key exchange. +/* .IP "\fBtls_eecdh_ultra_curve (secp384r1)\fR" +/* The elliptic curve used by the Postfix SMTP server for maximally +/* strong +/* ephemeral ECDH key exchange. +/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" +/* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .IP "\fBtls_preempt_cipherlist (no)\fR" +/* With SSLv3 and later, use the Postfix SMTP server's cipher +/* preference order instead of the remote client's cipher preference +/* order. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBtls_legacy_public_key_fingerprints (no)\fR" +/* A temporary migration aid for sites that use certificate +/* \fIpublic-key\fR fingerprints with Postfix 2.9.0..2.9.5, which use +/* an incorrect algorithm. +/* .PP +/* Available in Postfix version 2.11-3.1: +/* .IP "\fBtls_dane_digest_agility (on)\fR" +/* Configure RFC7671 DANE TLSA digest algorithm agility. +/* .IP "\fBtls_dane_trust_anchor_digest_enable (yes)\fR" +/* Enable support for RFC 6698 (DANE TLSA) DNS records that contain +/* digests of trust-anchors with certificate usage "2". +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" +/* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBtls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc)\fR" +/* Algorithm used to encrypt RFC5077 TLS session tickets. +/* .IP "\fBopenssl_path (openssl)\fR" +/* The location of the OpenSSL command line program \fBopenssl\fR(1). +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBtls_eecdh_auto_curves (see 'postconf -d' output)\fR" +/* The prioritized list of elliptic curves supported by the Postfix +/* SMTP client and server. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBtls_server_sni_maps (empty)\fR" +/* Optional lookup tables that map names received from remote SMTP +/* clients via the TLS Server Name Indication (SNI) extension to the +/* appropriate keys and certificate chains. +/* .PP +/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later: +/* .IP "\fBtls_fast_shutdown_enable (yes)\fR" +/* A workaround for implementations that hang Postfix while shutting +/* down a TLS session, until Postfix times out. +/* .PP +/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later: +/* .IP "\fBtls_config_file (default)\fR" +/* Optional configuration file with baseline OpenSSL settings. +/* .IP "\fBtls_config_name (empty)\fR" +/* The application name passed by Postfix to OpenSSL library +/* initialization functions. +/* STARTTLS SERVER CONTROLS +/* .ad +/* .fi +/* These settings are clones of Postfix SMTP server settings. +/* They allow \fBtlsproxy\fR(8) to load the same certificate +/* and private key information as the Postfix SMTP server, +/* before dropping privileges, so that the key files can be +/* kept read-only for root. These settings can currently not +/* be overruled by information in a \fBtlsproxy\fR(8) client +/* request, but that limitation may be removed in a future +/* version. +/* .IP "\fBtlsproxy_tls_CAfile ($smtpd_tls_CAfile)\fR" +/* A file containing (PEM format) CA certificates of root CAs +/* trusted to sign either remote SMTP client certificates or intermediate +/* CA certificates. +/* .IP "\fBtlsproxy_tls_CApath ($smtpd_tls_CApath)\fR" +/* A directory containing (PEM format) CA certificates of root CAs +/* trusted to sign either remote SMTP client certificates or intermediate +/* CA certificates. +/* .IP "\fBtlsproxy_tls_always_issue_session_ids ($smtpd_tls_always_issue_session_ids)\fR" +/* Force the Postfix \fBtlsproxy\fR(8) server to issue a TLS session id, +/* even when TLS session caching is turned off. +/* .IP "\fBtlsproxy_tls_ask_ccert ($smtpd_tls_ask_ccert)\fR" +/* Ask a remote SMTP client for a client certificate. +/* .IP "\fBtlsproxy_tls_ccert_verifydepth ($smtpd_tls_ccert_verifydepth)\fR" +/* The verification depth for remote SMTP client certificates. +/* .IP "\fBtlsproxy_tls_cert_file ($smtpd_tls_cert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server RSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_tls_ciphers ($smtpd_tls_ciphers)\fR" +/* The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server +/* will use with opportunistic TLS encryption. +/* .IP "\fBtlsproxy_tls_dcert_file ($smtpd_tls_dcert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server DSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_tls_dh1024_param_file ($smtpd_tls_dh1024_param_file)\fR" +/* File with DH parameters that the Postfix \fBtlsproxy\fR(8) server +/* should use with non-export EDH ciphers. +/* .IP "\fBtlsproxy_tls_dh512_param_file ($smtpd_tls_dh512_param_file)\fR" +/* File with DH parameters that the Postfix \fBtlsproxy\fR(8) server +/* should use with export-grade EDH ciphers. +/* .IP "\fBtlsproxy_tls_dkey_file ($smtpd_tls_dkey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server DSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_tls_eccert_file ($smtpd_tls_eccert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server ECDSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_tls_eckey_file ($smtpd_tls_eckey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server ECDSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_tls_eecdh_grade ($smtpd_tls_eecdh_grade)\fR" +/* The Postfix \fBtlsproxy\fR(8) server security grade for ephemeral +/* elliptic-curve Diffie-Hellman (EECDH) key exchange. +/* .IP "\fBtlsproxy_tls_exclude_ciphers ($smtpd_tls_exclude_ciphers)\fR" +/* List of ciphers or cipher types to exclude from the \fBtlsproxy\fR(8) +/* server cipher list at all TLS security levels. +/* .IP "\fBtlsproxy_tls_fingerprint_digest ($smtpd_tls_fingerprint_digest)\fR" +/* The message digest algorithm to construct remote SMTP +/* client-certificate +/* fingerprints. +/* .IP "\fBtlsproxy_tls_key_file ($smtpd_tls_key_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server RSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_tls_loglevel ($smtpd_tls_loglevel)\fR" +/* Enable additional Postfix \fBtlsproxy\fR(8) server logging of TLS +/* activity. +/* .IP "\fBtlsproxy_tls_mandatory_ciphers ($smtpd_tls_mandatory_ciphers)\fR" +/* The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server +/* will use with mandatory TLS encryption. +/* .IP "\fBtlsproxy_tls_mandatory_exclude_ciphers ($smtpd_tls_mandatory_exclude_ciphers)\fR" +/* Additional list of ciphers or cipher types to exclude from the +/* \fBtlsproxy\fR(8) server cipher list at mandatory TLS security levels. +/* .IP "\fBtlsproxy_tls_mandatory_protocols ($smtpd_tls_mandatory_protocols)\fR" +/* The SSL/TLS protocols accepted by the Postfix \fBtlsproxy\fR(8) server +/* with mandatory TLS encryption. +/* .IP "\fBtlsproxy_tls_protocols ($smtpd_tls_protocols)\fR" +/* List of TLS protocols that the Postfix \fBtlsproxy\fR(8) server will +/* exclude or include with opportunistic TLS encryption. +/* .IP "\fBtlsproxy_tls_req_ccert ($smtpd_tls_req_ccert)\fR" +/* With mandatory TLS encryption, require a trusted remote SMTP +/* client certificate in order to allow TLS connections to proceed. +/* .IP "\fBtlsproxy_tls_security_level ($smtpd_tls_security_level)\fR" +/* The SMTP TLS security level for the Postfix \fBtlsproxy\fR(8) server; +/* when a non-empty value is specified, this overrides the obsolete +/* parameters smtpd_use_tls and smtpd_enforce_tls. +/* .IP "\fBtlsproxy_tls_chain_files ($smtpd_tls_chain_files)\fR" +/* Files with the Postfix \fBtlsproxy\fR(8) server keys and certificate +/* chains in PEM format. +/* STARTTLS CLIENT CONTROLS +/* .ad +/* .fi +/* These settings are clones of Postfix SMTP client settings. +/* They allow \fBtlsproxy\fR(8) to load the same certificate +/* and private key information as the Postfix SMTP client, +/* before dropping privileges, so that the key files can be +/* kept read-only for root. Some settings may be overruled by +/* information in a \fBtlsproxy\fR(8) client request. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBtlsproxy_client_CAfile ($smtp_tls_CAfile)\fR" +/* A file containing CA certificates of root CAs trusted to sign +/* either remote TLS server certificates or intermediate CA certificates. +/* .IP "\fBtlsproxy_client_CApath ($smtp_tls_CApath)\fR" +/* Directory with PEM format Certification Authority certificates +/* that the Postfix \fBtlsproxy\fR(8) client uses to verify a remote TLS +/* server certificate. +/* .IP "\fBtlsproxy_client_chain_files ($smtp_tls_chain_files)\fR" +/* Files with the Postfix \fBtlsproxy\fR(8) client keys and certificate +/* chains in PEM format. +/* .IP "\fBtlsproxy_client_cert_file ($smtp_tls_cert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client RSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_client_key_file ($smtp_tls_key_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client RSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_client_dcert_file ($smtp_tls_dcert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client DSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_client_dkey_file ($smtp_tls_dkey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client DSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_client_eccert_file ($smtp_tls_eccert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client ECDSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_client_eckey_file ($smtp_tls_eckey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client ECDSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_client_fingerprint_digest ($smtp_tls_fingerprint_digest)\fR" +/* The message digest algorithm used to construct remote TLS server +/* certificate fingerprints. +/* .IP "\fBtlsproxy_client_loglevel ($smtp_tls_loglevel)\fR" +/* Enable additional Postfix \fBtlsproxy\fR(8) client logging of TLS +/* activity. +/* .IP "\fBtlsproxy_client_loglevel_parameter (smtp_tls_loglevel)\fR" +/* The name of the parameter that provides the tlsproxy_client_loglevel +/* value. +/* .IP "\fBtlsproxy_client_scert_verifydepth ($smtp_tls_scert_verifydepth)\fR" +/* The verification depth for remote TLS server certificates. +/* .IP "\fBtlsproxy_client_security_level ($smtp_tls_security_level)\fR" +/* The default TLS security level for the Postfix \fBtlsproxy\fR(8) +/* client. +/* .IP "\fBtlsproxy_client_policy_maps ($smtp_tls_policy_maps)\fR" +/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS +/* security policy by next-hop destination. +/* .IP "\fBtlsproxy_client_use_tls ($smtp_use_tls)\fR" +/* Opportunistic mode: use TLS when a remote server announces TLS +/* support. +/* .IP "\fBtlsproxy_client_enforce_tls ($smtp_enforce_tls)\fR" +/* Enforcement mode: require that SMTP servers use TLS encryption. +/* .IP "\fBtlsproxy_client_per_site ($smtp_tls_per_site)\fR" +/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS +/* usage policy by next-hop destination and by remote TLS server +/* hostname. +/* OBSOLETE STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* These parameters are supported for compatibility with +/* \fBsmtpd\fR(8) legacy parameters. +/* .IP "\fBtlsproxy_use_tls ($smtpd_use_tls)\fR" +/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients, +/* but do not require that clients use TLS encryption. +/* .IP "\fBtlsproxy_enforce_tls ($smtpd_enforce_tls)\fR" +/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, and +/* require that clients use TLS encryption. +/* RESOURCE CONTROLS +/* .ad +/* .fi +/* .IP "\fBtlsproxy_watchdog_timeout (10s)\fR" +/* How much time a \fBtlsproxy\fR(8) process may take to process local +/* or remote I/O before it is terminated by a built-in watchdog timer. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* postscreen(8), Postfix zombie blocker +/* smtpd(8), Postfix SMTP server +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* This service was introduced with Postfix version 2.8. +/* 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 +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <errno.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + + /* + * Utility library. + */ +#include <msg.h> +#include <vstream.h> +#include <iostuff.h> +#include <nbbio.h> +#include <mymalloc.h> +#include <split_at.h> + + /* + * Global library. + */ +#include <been_here.h> +#include <mail_proto.h> +#include <mail_params.h> +#include <mail_conf.h> +#include <mail_version.h> + + /* + * Master library. + */ +#include <mail_server.h> + + /* + * TLS library. + */ +#ifdef USE_TLS +#define TLS_INTERNAL /* XXX */ +#include <tls.h> +#include <tls_proxy.h> + + /* + * Application-specific. + */ +#include <tlsproxy.h> + + /* + * Tunable parameters. We define our clones of the smtpd(8) parameters to + * avoid any confusion about which parameters are used by this program. + */ +int var_smtpd_tls_ccert_vd; +char *var_smtpd_tls_loglevel; +bool var_smtpd_use_tls; +bool var_smtpd_enforce_tls; +bool var_smtpd_tls_ask_ccert; +bool var_smtpd_tls_req_ccert; +bool var_smtpd_tls_set_sessid; +char *var_smtpd_relay_ccerts; +char *var_smtpd_tls_chain_files; +char *var_smtpd_tls_cert_file; +char *var_smtpd_tls_key_file; +char *var_smtpd_tls_dcert_file; +char *var_smtpd_tls_dkey_file; +char *var_smtpd_tls_eccert_file; +char *var_smtpd_tls_eckey_file; +char *var_smtpd_tls_CAfile; +char *var_smtpd_tls_CApath; +char *var_smtpd_tls_ciph; +char *var_smtpd_tls_mand_ciph; +char *var_smtpd_tls_excl_ciph; +char *var_smtpd_tls_mand_excl; +char *var_smtpd_tls_proto; +char *var_smtpd_tls_mand_proto; +char *var_smtpd_tls_dh512_param_file; +char *var_smtpd_tls_dh1024_param_file; +char *var_smtpd_tls_eecdh; +char *var_smtpd_tls_fpt_dgst; +char *var_smtpd_tls_level; + +int var_tlsp_tls_ccert_vd; +char *var_tlsp_tls_loglevel; +bool var_tlsp_use_tls; +bool var_tlsp_enforce_tls; +bool var_tlsp_tls_ask_ccert; +bool var_tlsp_tls_req_ccert; +bool var_tlsp_tls_set_sessid; +char *var_tlsp_tls_chain_files; +char *var_tlsp_tls_cert_file; +char *var_tlsp_tls_key_file; +char *var_tlsp_tls_dcert_file; +char *var_tlsp_tls_dkey_file; +char *var_tlsp_tls_eccert_file; +char *var_tlsp_tls_eckey_file; +char *var_tlsp_tls_CAfile; +char *var_tlsp_tls_CApath; +char *var_tlsp_tls_ciph; +char *var_tlsp_tls_mand_ciph; +char *var_tlsp_tls_excl_ciph; +char *var_tlsp_tls_mand_excl; +char *var_tlsp_tls_proto; +char *var_tlsp_tls_mand_proto; +char *var_tlsp_tls_dh512_param_file; +char *var_tlsp_tls_dh1024_param_file; +char *var_tlsp_tls_eecdh; +char *var_tlsp_tls_fpt_dgst; +char *var_tlsp_tls_level; + +int var_tlsp_watchdog; + + /* + * Defaults for tlsp_clnt_*. + */ +char *var_smtp_tls_loglevel; +int var_smtp_tls_scert_vd; +char *var_smtp_tls_chain_files; +char *var_smtp_tls_cert_file; +char *var_smtp_tls_key_file; +char *var_smtp_tls_dcert_file; +char *var_smtp_tls_dkey_file; +char *var_smtp_tls_eccert_file; +char *var_smtp_tls_eckey_file; +char *var_smtp_tls_CAfile; +char *var_smtp_tls_CApath; +char *var_smtp_tls_fpt_dgst; +char *var_smtp_tls_level; +bool var_smtp_use_tls; +bool var_smtp_enforce_tls; +char *var_smtp_tls_per_site; +char *var_smtp_tls_policy; + +char *var_tlsp_clnt_loglevel; +char *var_tlsp_clnt_logparam; +int var_tlsp_clnt_scert_vd; +char *var_tlsp_clnt_chain_files; +char *var_tlsp_clnt_cert_file; +char *var_tlsp_clnt_key_file; +char *var_tlsp_clnt_dcert_file; +char *var_tlsp_clnt_dkey_file; +char *var_tlsp_clnt_eccert_file; +char *var_tlsp_clnt_eckey_file; +char *var_tlsp_clnt_CAfile; +char *var_tlsp_clnt_CApath; +char *var_tlsp_clnt_fpt_dgst; +char *var_tlsp_clnt_level; +bool var_tlsp_clnt_use_tls; +bool var_tlsp_clnt_enforce_tls; +char *var_tlsp_clnt_per_site; +char *var_tlsp_clnt_policy; + + /* + * TLS per-process status. + */ +static TLS_APPL_STATE *tlsp_server_ctx; +static bool tlsp_pre_jail_done; +static int ask_client_cert; +static char *tlsp_pre_jail_client_param_key; /* pre-jail global params */ +static char *tlsp_pre_jail_client_init_key; /* pre-jail init props */ + + /* + * TLS per-client status. + */ +static HTABLE *tlsp_client_app_cache; /* per-client init props */ +static BH_TABLE *tlsp_params_mismatch_filter; /* per-client nag filter */ + + /* + * Error handling: if a function detects an error, then that function is + * responsible for destroying TLSP_STATE. Exceptions to this principle are + * indicated in the code. + */ + + /* + * Internal status API. + */ +#define TLSP_STAT_OK 0 +#define TLSP_STAT_ERR (-1) + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + + /* + * The code that implements the TLS engine looks simpler than expected. That + * is the result of a great deal of effort, mainly in design and analysis. + * + * The initial use case was to provide TLS support for postscreen(8). + * + * By design, postscreen(8) is an event-driven server that must scale up to a + * large number of clients. This means that postscreen(8) must avoid doing + * CPU-intensive operations such as those in OpenSSL. + * + * tlsproxy(8) runs the OpenSSL code on behalf of postscreen(8), translating + * plaintext SMTP messages from postscreen(8) into SMTP-over-TLS messages to + * the remote SMTP client, and vice versa. As long as postscreen(8) does not + * receive email messages, the cost of doing TLS operations will be modest. + * + * Like postscreen(8), one tlsproxy(8) process services multiple remote SMTP + * clients. Unlike postscreen(8), there can be more than one tlsproxy(8) + * process, although their number is meant to be much smaller than the + * number of remote SMTP clients that talk TLS. + * + * As with postscreen(8), all I/O must be event-driven: encrypted traffic + * between tlsproxy(8) and remote SMTP clients, and plaintext traffic + * between tlsproxy(8) and postscreen(8). Event-driven plaintext I/O is + * straightforward enough that it could be abstracted away with the nbbio(3) + * module. + * + * The event-driven TLS I/O implementation is founded on on-line OpenSSL + * documentation, supplemented by statements from OpenSSL developers on + * public mailing lists. After some field experience with this code, we may + * be able to factor it out as a library module, like nbbio(3), that can + * become part of the TLS library. + * + * Later in the life cycle, tlsproxy(8) has also become an enabler for TLS + * connection reuse across different SMTP client processes. + */ + +static void tlsp_ciphertext_event(int, void *); + +#define TLSP_INIT_TIMEOUT 100 + +static void tlsp_plaintext_event(int event, void *context); + +/* tlsp_drain - delayed exit after "postfix reload" */ + +static void tlsp_drain(char *unused_service, char **unused_argv) +{ + int count; + + /* + * After "postfix reload", complete work-in-progress in the background, + * instead of dropping already-accepted connections on the floor. + * + * All error retry counts shall be limited. Instead of blocking here, we + * could retry failed fork() operations in the event call-back routines, + * but we don't need perfection. The host system is severely overloaded + * and service levels are already way down. + */ + for (count = 0; /* see below */ ; count++) { + if (count >= 5) { + msg_fatal("fork: %m"); + } else if (event_server_drain() != 0) { + msg_warn("fork: %m"); + sleep(1); + continue; + } else { + return; + } + } +} + +/* tlsp_eval_tls_error - translate TLS "error" result into action */ + +static int tlsp_eval_tls_error(TLSP_STATE *state, int err) +{ + int ciphertext_fd = state->ciphertext_fd; + + /* + * The ciphertext file descriptor is in non-blocking mode, meaning that + * each SSL_accept/connect/read/write/shutdown request may return an + * "error" indication that it needs to read or write more ciphertext. The + * purpose of this routine is to translate those "error" indications into + * the appropriate read/write/timeout event requests. + */ + switch (err) { + + /* + * No error means a successful SSL_accept/connect/shutdown request or + * sequence of SSL_read/write requests. Disable read/write events on + * the ciphertext stream. Keep the ciphertext stream timer alive as a + * safety mechanism for the case that the plaintext pseudothreads get + * stuck. + */ + case SSL_ERROR_NONE: + if (state->ssl_last_err != SSL_ERROR_NONE) { + event_disable_readwrite(ciphertext_fd); + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + state->ssl_last_err = SSL_ERROR_NONE; + } + return (TLSP_STAT_OK); + + /* + * The TLS engine wants to write to the network. Turn on + * write/timeout events on the ciphertext stream. + */ + case SSL_ERROR_WANT_WRITE: + if (state->ssl_last_err == SSL_ERROR_WANT_READ) + event_disable_readwrite(ciphertext_fd); + if (state->ssl_last_err != SSL_ERROR_WANT_WRITE) { + event_enable_write(ciphertext_fd, tlsp_ciphertext_event, + (void *) state); + state->ssl_last_err = SSL_ERROR_WANT_WRITE; + } + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + return (TLSP_STAT_OK); + + /* + * The TLS engine wants to read from the network. Turn on + * read/timeout events on the ciphertext stream. + */ + case SSL_ERROR_WANT_READ: + if (state->ssl_last_err == SSL_ERROR_WANT_WRITE) + event_disable_readwrite(ciphertext_fd); + if (state->ssl_last_err != SSL_ERROR_WANT_READ) { + event_enable_read(ciphertext_fd, tlsp_ciphertext_event, + (void *) state); + state->ssl_last_err = SSL_ERROR_WANT_READ; + } + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + return (TLSP_STAT_OK); + + /* + * Some error. Self-destruct. This automagically cleans up all + * pending read/write and timeout event requests, making state a + * dangling pointer. + */ + case SSL_ERROR_SSL: + tls_print_errors(); + /* FALLTHROUGH */ + default: + + /* + * Allow buffered-up plaintext output to trickle out. Permanently + * disable read/write activity on the ciphertext stream, so that this + * function will no longer be called. Keep the ciphertext stream + * timer alive as a safety mechanism for the case that the plaintext + * pseudothreads get stuck. Return into tlsp_strategy(), which will + * enable plaintext write events. + */ +#define TLSP_CAN_TRICKLE_OUT_PLAINTEXT(buf) \ + ((buf) && !NBBIO_ERROR_FLAGS(buf) && NBBIO_WRITE_PEND(buf)) + + if (TLSP_CAN_TRICKLE_OUT_PLAINTEXT(state->plaintext_buf)) { + event_disable_readwrite(ciphertext_fd); + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + state->flags |= TLSP_FLAG_NO_MORE_CIPHERTEXT_IO; + return (TLSP_STAT_OK); + } + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } +} + +/* tlsp_post_handshake - post-handshake processing */ + +static int tlsp_post_handshake(TLSP_STATE *state) +{ + + /* + * Do not assume that tls_server_post_accept() and + * tls_client_post_connect() will always succeed. + */ + if (state->is_server_role) + state->tls_context = tls_server_post_accept(state->tls_context); + else + state->tls_context = tls_client_post_connect(state->tls_context, + state->client_start_props); + if (state->tls_context == 0) { + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * Report TLS handshake results to the tlsproxy client. + * + * Security: this sends internal data over the same local plaintext stream + * that will also be used for sending decrypted remote content from an + * arbitrary remote peer. For this reason we enable decrypted I/O only + * after reporting the TLS handshake results. The Postfix attribute + * protocol is robust enough that an attacker cannot append content. + */ + if ((state->req_flags & TLS_PROXY_FLAG_SEND_CONTEXT) != 0 + && (attr_print(state->plaintext_stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_context_print, + (void *) state->tls_context), + ATTR_TYPE_END) != 0 + || vstream_fflush(state->plaintext_stream) != 0)) { + msg_warn("cannot send TLS context: %m"); + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * Initialize plaintext-related session state. Once we have this behind + * us, the TLSP_STATE destructor will automagically clean up requests for + * plaintext read/write/timeout events, which makes error recovery + * easier. + */ + state->plaintext_buf = + nbbio_create(vstream_fileno(state->plaintext_stream), + VSTREAM_BUFSIZE, state->server_id, + tlsp_plaintext_event, + (void *) state); + return (TLSP_STAT_OK); +} + +/* tlsp_strategy - decide what to read or write next. */ + +static void tlsp_strategy(TLSP_STATE *state) +{ + TLS_SESS_STATE *tls_context = state->tls_context; + NBBIO *plaintext_buf; + int ssl_stat; + int ssl_read_err; + int ssl_write_err; + int handshake_err; + + /* + * This function is called after every ciphertext or plaintext event, to + * schedule new ciphertext or plaintext I/O. + */ + + /* + * Try to make an SSL I/O request. If this fails with SSL_ERROR_WANT_READ + * or SSL_ERROR_WANT_WRITE, enable ciphertext read or write events, and + * retry the SSL I/O request in a later tlsp_strategy() call. + */ + if ((state->flags & TLSP_FLAG_NO_MORE_CIPHERTEXT_IO) == 0) { + + /* + * Do not enable plain-text I/O before completing the TLS handshake. + * Otherwise the remote peer can prepend plaintext to the optional + * TLS_SESS_STATE object. + */ + if (state->flags & TLSP_FLAG_DO_HANDSHAKE) { + state->timeout = state->handshake_timeout; + ERR_clear_error(); + if (state->is_server_role) + ssl_stat = SSL_accept(tls_context->con); + else + ssl_stat = SSL_connect(tls_context->con); + if (ssl_stat != 1) { + handshake_err = SSL_get_error(tls_context->con, ssl_stat); + tlsp_eval_tls_error(state, handshake_err); + /* At this point, state could be a dangling pointer. */ + return; + } + state->flags &= ~TLSP_FLAG_DO_HANDSHAKE; + state->timeout = state->session_timeout; + if (tlsp_post_handshake(state) != TLSP_STAT_OK) { + /* At this point, state is a dangling pointer. */ + return; + } + } + + /* + * Shutdown and self-destruct after NBBIO error. This automagically + * cleans up all pending read/write and timeout event requests. + * Before shutting down TLS, we stop all plain-text I/O events but + * keep the NBBIO error flags. + */ + plaintext_buf = state->plaintext_buf; + if (NBBIO_ERROR_FLAGS(plaintext_buf)) { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) + nbbio_disable_readwrite(state->plaintext_buf); + ERR_clear_error(); + if (!SSL_in_init(tls_context->con) + && (ssl_stat = SSL_shutdown(tls_context->con)) < 0) { + handshake_err = SSL_get_error(tls_context->con, ssl_stat); + tlsp_eval_tls_error(state, handshake_err); + /* At this point, state could be a dangling pointer. */ + return; + } + tlsp_state_free(state); + return; + } + + /* + * Try to move data from the plaintext input buffer to the TLS + * engine. + * + * XXX We're supposed to repeat the exact same SSL_write() call + * arguments after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE + * result. Rumor has it that this is because each SSL_write() call + * reads from the buffer incrementally, and returns > 0 only after + * the final byte is processed. Rumor also has it that setting + * SSL_MODE_ENABLE_PARTIAL_WRITE and + * SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER voids this requirement, and + * that repeating the request with an increased request size is OK. + * Unfortunately all this is not or poorly documented, and one has to + * rely on statements from OpenSSL developers in public mailing + * archives. + */ + ssl_write_err = SSL_ERROR_NONE; + while (NBBIO_READ_PEND(plaintext_buf) > 0) { + ERR_clear_error(); + ssl_stat = SSL_write(tls_context->con, NBBIO_READ_BUF(plaintext_buf), + NBBIO_READ_PEND(plaintext_buf)); + ssl_write_err = SSL_get_error(tls_context->con, ssl_stat); + if (ssl_write_err != SSL_ERROR_NONE) + break; + /* Allow the plaintext pseudothread to read more data. */ + NBBIO_READ_PEND(plaintext_buf) -= ssl_stat; + if (NBBIO_READ_PEND(plaintext_buf) > 0) + memmove(NBBIO_READ_BUF(plaintext_buf), + NBBIO_READ_BUF(plaintext_buf) + ssl_stat, + NBBIO_READ_PEND(plaintext_buf)); + } + + /* + * Try to move data from the TLS engine to the plaintext output + * buffer. Note: data may arrive as a side effect of calling + * SSL_write(), therefore we call SSL_read() after calling + * SSL_write(). + * + * XXX We're supposed to repeat the exact same SSL_read() call arguments + * after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE result. This + * supposedly means that our plaintext writer must not memmove() the + * plaintext output buffer until after the SSL_read() call succeeds. + * For now I'll ignore this, because 1) SSL_read() is documented to + * return the bytes available, instead of returning > 0 only after + * the entire buffer is processed like SSL_write() does; and 2) there + * is no "read" equivalent of the SSL_R_BAD_WRITE_RETRY, + * SSL_MODE_ENABLE_PARTIAL_WRITE or + * SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER features. + */ + ssl_read_err = SSL_ERROR_NONE; + while (NBBIO_WRITE_PEND(state->plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { + ERR_clear_error(); + ssl_stat = SSL_read(tls_context->con, + NBBIO_WRITE_BUF(plaintext_buf) + + NBBIO_WRITE_PEND(state->plaintext_buf), + NBBIO_BUFSIZE(plaintext_buf) + - NBBIO_WRITE_PEND(state->plaintext_buf)); + ssl_read_err = SSL_get_error(tls_context->con, ssl_stat); + if (ssl_read_err != SSL_ERROR_NONE) + break; + NBBIO_WRITE_PEND(plaintext_buf) += ssl_stat; + } + + /* + * Try to enable/disable ciphertext read/write events. If SSL_write() + * was satisfied, see if SSL_read() wants to do some work. In case of + * an unrecoverable error, this automagically destroys the session + * state after cleaning up all pending read/write and timeout event + * requests. + */ + if (tlsp_eval_tls_error(state, ssl_write_err != SSL_ERROR_NONE ? + ssl_write_err : ssl_read_err) < 0) + /* At this point, state is a dangling pointer. */ + return; + } + + /* + * Destroy state when the ciphertext I/O was permanently disabled and we + * can no longer trickle out plaintext. + */ + else { + plaintext_buf = state->plaintext_buf; + if (!TLSP_CAN_TRICKLE_OUT_PLAINTEXT(plaintext_buf)) { + tlsp_state_free(state); + return; + } + } + + /* + * Try to enable/disable plaintext read/write events. Basically, if we + * have nothing to write to the plaintext stream, see if there is + * something to read. If the write buffer is empty and the read buffer is + * full, suspend plaintext I/O until conditions change (but keep the + * timer active, as a safety mechanism in case ciphertext I/O gets + * stuck). + * + * XXX In theory, if the ciphertext peer keeps writing fast enough then we + * would never read from the plaintext stream and cause the latter to + * block. In practice, postscreen(8) limits the number of client + * commands, and thus postscreen(8)'s output will fit in a kernel buffer. + * A remote SMTP server is not supposed to flood the local SMTP client + * with massive replies; it it does, then the local SMTP client should + * deal with it. + */ + if (NBBIO_WRITE_PEND(plaintext_buf) > 0) { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ) + nbbio_disable_readwrite(plaintext_buf); + nbbio_enable_write(plaintext_buf, state->timeout); + } else if (NBBIO_READ_PEND(plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_WRITE) + nbbio_disable_readwrite(plaintext_buf); + nbbio_enable_read(plaintext_buf, state->timeout); + } else { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) + nbbio_slumber(plaintext_buf, state->timeout); + } +} + +/* tlsp_plaintext_event - plaintext was read/written */ + +static void tlsp_plaintext_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + /* + * Safety alert: the plaintext pseudothreads have "slumbered" for too + * long (see code above). This means that the ciphertext pseudothreads + * are stuck. + */ + if ((NBBIO_ERROR_FLAGS(state->plaintext_buf) & NBBIO_FLAG_TIMEOUT) != 0 + && NBBIO_ACTIVE_FLAGS(state->plaintext_buf) == 0) + msg_warn("deadlock on ciphertext stream for %s", state->remote_endpt); + + /* + * This is easy, because the NBBIO layer has already done the event + * decoding and plaintext I/O for us. All we need to do is decide if we + * want to read or write more plaintext. + */ + tlsp_strategy(state); + /* At this point, state could be a dangling pointer. */ +} + +/* tlsp_ciphertext_event - ciphertext is ready to read/write */ + +static void tlsp_ciphertext_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + /* + * Without a TLS quivalent of the NBBIO layer, we must decode the events + * ourselves and do the ciphertext I/O. Then, we can decide if we want to + * read or write more ciphertext. + */ + if (event == EVENT_READ || event == EVENT_WRITE) { + tlsp_strategy(state); + /* At this point, state could be a dangling pointer. */ + } else { + if (event == EVENT_TIME && state->ssl_last_err == SSL_ERROR_NONE) + msg_warn("deadlock on plaintext stream for %s", + state->remote_endpt); + else + msg_warn("ciphertext read/write %s for %s", + event == EVENT_TIME ? "timeout" : "error", + state->remote_endpt); + tlsp_state_free(state); + } +} + +/* tlsp_client_start_pre_handshake - turn on TLS or force disconnect */ + +static int tlsp_client_start_pre_handshake(TLSP_STATE *state) +{ + state->client_start_props->ctx = state->appl_state; + state->client_start_props->fd = state->ciphertext_fd; + /* These predicates and warning belong inside tls_client_start(). */ + if (!tls_dane_avail() /* mandatory side effects!! */ + + /* + * Why not test for TLS_DANE_BASED()? Because the tlsproxy(8) client has + * already converted its DANE TLSA records into trust anchors, and + * therefore TLS_DANE_HASTA() will be true instead. That exercises the + * code path that updates the shared SSL_CTX with custom X.509 + * verification callbacks for trust anchors. + */ + &&TLS_DANE_HASTA(state->client_start_props->dane)) + msg_warn("%s: DANE or local trust anchor based chain" + " verification requested, but not available", + state->client_start_props->namaddr); + else + state->tls_context = tls_client_start(state->client_start_props); + if (state->tls_context != 0) + return (TLSP_STAT_OK); + + tlsp_state_free(state); + return (TLSP_STAT_ERR); +} + +/* tlsp_server_start_pre_handshake - turn on TLS or force disconnect */ + +static int tlsp_server_start_pre_handshake(TLSP_STATE *state) +{ + TLS_SERVER_START_PROPS props; + static char *cipher_grade; + static VSTRING *cipher_exclusions; + + /* + * The code in this routine is pasted literally from smtpd(8). I am not + * going to sanitize this because doing so surely will break things in + * unexpected ways. + */ + + /* + * Perform the before-handshake portion of per-session initialization. + * Pass a null VSTREAM to indicate that this program will do the + * ciphertext I/O, not libtls. + * + * The cipher grade and exclusions don't change between sessions. Compute + * just once and cache. + */ +#define ADD_EXCLUDE(vstr, str) \ + do { \ + if (*(str)) \ + vstring_sprintf_append((vstr), "%s%s", \ + VSTRING_LEN(vstr) ? " " : "", (str)); \ + } while (0) + + if (cipher_grade == 0) { + cipher_grade = + var_tlsp_enforce_tls ? var_tlsp_tls_mand_ciph : var_tlsp_tls_ciph; + cipher_exclusions = vstring_alloc(10); + ADD_EXCLUDE(cipher_exclusions, var_tlsp_tls_excl_ciph); + if (var_tlsp_enforce_tls) + ADD_EXCLUDE(cipher_exclusions, var_tlsp_tls_mand_excl); + if (ask_client_cert) + ADD_EXCLUDE(cipher_exclusions, "aNULL"); + } + state->tls_context = + TLS_SERVER_START(&props, + ctx = tlsp_server_ctx, + stream = (VSTREAM *) 0,/* unused */ + fd = state->ciphertext_fd, + timeout = 0, /* unused */ + requirecert = (var_tlsp_tls_req_ccert + && var_tlsp_enforce_tls), + serverid = state->server_id, + namaddr = state->remote_endpt, + cipher_grade = cipher_grade, + cipher_exclusions = STR(cipher_exclusions), + mdalg = var_tlsp_tls_fpt_dgst); + + if (state->tls_context == 0) { + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * XXX Do we care about TLS session rate limits? Good postscreen(8) + * clients will occasionally require the tlsproxy to renew their + * whitelist status, but bad clients hammering the server can suck up + * lots of CPU cycles. Per-client concurrency limits in postscreen(8) + * will divert only naive security "researchers". + */ + return (TLSP_STAT_OK); +} + + /* + * From here on down is low-level code that sets up the plumbing before + * passing control to the TLS engine above. + */ + +/* tlsp_request_read_event - pre-handshake event boiler plate */ + +static void tlsp_request_read_event(int fd, EVENT_NOTIFY_FN handler, + int timeout, void *context) +{ + event_enable_read(fd, handler, context); + event_request_timer(handler, context, timeout); +} + +/* tlsp_accept_event - pre-handshake event boiler plate */ + +static void tlsp_accept_event(int event, EVENT_NOTIFY_FN handler, + void *context) +{ + if (event != EVENT_TIME) + event_cancel_timer(handler, context); + else + errno = ETIMEDOUT; + /* tlsp_state_free() disables pre-handshake plaintext I/O events. */ +} + +/* tlsp_get_fd_event - receive final connection hand-off information */ + +static void tlsp_get_fd_event(int event, void *context) +{ + const char *myname = "tlsp_get_fd_event"; + TLSP_STATE *state = (TLSP_STATE *) context; + int plaintext_fd = vstream_fileno(state->plaintext_stream); + int status; + + /* + * At this point we still manually manage plaintext read/write/timeout + * events. Disable I/O events on the plaintext stream until the TLS + * handshake is completed. Every code path must either destroy state, or + * request the next event, otherwise we have a file and memory leak. + */ + tlsp_accept_event(event, tlsp_get_fd_event, (void *) state); + event_disable_readwrite(plaintext_fd); + + if (event != EVENT_READ + || (state->ciphertext_fd = LOCAL_RECV_FD(plaintext_fd)) < 0) { + msg_warn("%s: receive remote SMTP peer file descriptor: %m", myname); + tlsp_state_free(state); + return; + } + + /* + * This is a bit early, to ensure that timer events for this file handle + * are guaranteed to be turned off by the TLSP_STATE destructor. + */ + state->ciphertext_timer = tlsp_ciphertext_event; + non_blocking(state->ciphertext_fd, NON_BLOCKING); + + /* + * Perform the TLS layer before-handshake initialization. We perform the + * remainder after the actual TLS handshake completes. + */ + if (state->is_server_role) + status = tlsp_server_start_pre_handshake(state); + else + status = tlsp_client_start_pre_handshake(state); + if (status != TLSP_STAT_OK) + /* At this point, state is a dangling pointer. */ + return; + + /* + * Trigger the initial proxy server I/Os. + */ + tlsp_strategy(state); + /* At this point, state could be a dangling pointer. */ +} + +/* tlsp_config_diff - report server-client config differences */ + +static void tlsp_log_config_diff(const char *server_cfg, const char *client_cfg) +{ + VSTRING *diff_summary = vstring_alloc(100); + char *saved_server = mystrdup(server_cfg); + char *saved_client = mystrdup(client_cfg); + char *server_field; + char *client_field; + char *server_next; + char *client_next; + + /* + * Not using argv_split(), because it would treat multiple consecutive + * newline characters as one. + */ + for (server_field = saved_server, client_field = saved_client; + server_field && client_field; + server_field = server_next, client_field = client_next) { + server_next = split_at(server_field, '\n'); + client_next = split_at(client_field, '\n'); + if (strcmp(server_field, client_field) != 0) { + if (LEN(diff_summary) > 0) + vstring_sprintf_append(diff_summary, "; "); + vstring_sprintf_append(diff_summary, + "(server) '%s' != (client) '%s'", + server_field, client_field); + } + } + msg_warn("%s", STR(diff_summary)); + + vstring_free(diff_summary); + myfree(saved_client); + myfree(saved_server); +} + +/* tlsp_client_init - initialize a TLS client engine */ + +static TLS_APPL_STATE *tlsp_client_init(TLS_CLIENT_PARAMS *tls_params, + TLS_CLIENT_INIT_PROPS *init_props, + int dane_based) +{ + TLS_APPL_STATE *appl_state; + VSTRING *param_buf; + char *param_key; + VSTRING *init_buf; + char *init_key; + VSTRING *init_buf_for_hashing; + char *init_key_for_hashing; + int log_hints = 0; + + /* + * Use one TLS_APPL_STATE object for all requests that specify the same + * TLS_CLIENT_INIT_PROPS. Each TLS_APPL_STATE owns an SSL_CTX, which is + * expensive to create. Bug: TLS_CLIENT_PARAMS are not used when creating + * a TLS_APPL_STATE instance. + * + * First, compute the TLS_APPL_STATE cache lookup key. Save a copy of the + * pre-jail request TLS_CLIENT_PARAMS and TLSPROXY_CLIENT_INIT_PROPS + * settings, so that we can detect post-jail requests that do not match. + * + * Workaround: salt the hash-table key with DANE on/off info. This avoids + * cross-talk between DANE and non-DANE sessions. Postfix DANE support + * modifies SSL_CTX to override certificate verification because there is + * no other way to do this before OpenSSL 1.1.0. + */ + param_buf = vstring_alloc(100); + param_key = tls_proxy_client_param_with_names_to_string( + param_buf, tls_params); + init_buf = vstring_alloc(100); + init_key = tls_proxy_client_init_with_names_to_string( + init_buf, init_props); + init_buf_for_hashing = vstring_alloc(100); + init_key_for_hashing = STR(vstring_sprintf(init_buf_for_hashing, "%s%d\n", + init_key, dane_based)); + if (tlsp_pre_jail_done == 0) { + if (tlsp_pre_jail_client_param_key == 0 + || tlsp_pre_jail_client_init_key == 0) { + tlsp_pre_jail_client_param_key = mystrdup(param_key); + tlsp_pre_jail_client_init_key = mystrdup(init_key); + } else if (strcmp(tlsp_pre_jail_client_param_key, param_key) != 0 + || strcmp(tlsp_pre_jail_client_init_key, init_key) != 0) { + msg_panic("tlsp_client_init: too many pre-jail calls"); + } + } + + /* + * Log a warning if a post-jail request uses unexpected TLS_CLIENT_PARAMS + * settings. Bug: TLS_CLIENT_PARAMS settings are not used when creating a + * TLS_APPL_STATE instance; this makes a mismatch of TLS_CLIENT_PARAMS + * settings problematic. + */ + if (tlsp_pre_jail_done + && !been_here_fixed(tlsp_params_mismatch_filter, param_key) + && strcmp(tlsp_pre_jail_client_param_key, param_key) != 0) { + msg_warn("request from tlsproxy client with unexpected settings"); + tlsp_log_config_diff(tlsp_pre_jail_client_param_key, param_key); + log_hints = 1; + } + + /* + * Look up the cached TLS_APPL_STATE for this tls_client_init request. + */ + if ((appl_state = (TLS_APPL_STATE *) + htable_find(tlsp_client_app_cache, init_key_for_hashing)) == 0) { + + /* + * Before creating a TLS_APPL_STATE instance, log a warning if a + * post-jail request differs from the saved pre-jail request AND the + * post-jail request specifies file/directory pathname arguments. + * Unexpected requests containing pathnames are problematic after + * chroot (pathname resolution) and after dropping privileges (key + * files must be root read-only). Unexpected requests are not a + * problem as long as they contain no pathnames (for example a + * tls_loglevel change). + * + * We could eliminate some of this complication by adding code that + * opens a cert/key lookup table at pre-jail time, and by reading + * cert/key info on-the-fly from that table. But then all requests + * would still have to specify the same table. + */ +#define NOT_EMPTY(x) ((x) && *(x)) + + if (tlsp_pre_jail_done + && strcmp(tlsp_pre_jail_client_init_key, init_key) != 0 + && (NOT_EMPTY(init_props->chain_files) + || NOT_EMPTY(init_props->cert_file) + || NOT_EMPTY(init_props->key_file) + || NOT_EMPTY(init_props->dcert_file) + || NOT_EMPTY(init_props->dkey_file) + || NOT_EMPTY(init_props->eccert_file) + || NOT_EMPTY(init_props->eckey_file) + || NOT_EMPTY(init_props->CAfile) + || NOT_EMPTY(init_props->CApath))) { + msg_warn("request from tlsproxy client with unexpected settings"); + tlsp_log_config_diff(tlsp_pre_jail_client_init_key, init_key); + log_hints = 1; + } + } + if (log_hints) + msg_warn("to avoid this warning, 1) identify the tlsproxy " + "client that is making this request, 2) configure " + "a custom tlsproxy service with settings that " + "match that tlsproxy client, and 3) configure " + "that tlsproxy client with a tlsproxy_service_name " + "setting that resolves to that custom tlsproxy " + "service"); + + /* + * TLS_APPL_STATE creation may fail when a post-jail request specifies + * unexpected cert/key information, but that is OK because we already + * logged a warning with configuration suggestions. + */ + if (appl_state == 0 + && (appl_state = tls_client_init(init_props)) != 0) { + (void) htable_enter(tlsp_client_app_cache, init_key_for_hashing, + (void *) appl_state); + + /* + * To maintain sanity, allow partial SSL_write() operations, and + * allow SSL_write() buffer pointers to change after a WANT_READ or + * WANT_WRITE result. This is based on OpenSSL developers talking on + * a mailing list, but is not supported by documentation. If this + * code stops working then no-one can be held responsible. + */ + SSL_CTX_set_mode(appl_state->ssl_ctx, + SSL_MODE_ENABLE_PARTIAL_WRITE + | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + } + vstring_free(init_buf_for_hashing); + vstring_free(init_buf); + vstring_free(param_buf); + return (appl_state); +} + +/* tlsp_close_event - pre-handshake plaintext-client close event */ + +static void tlsp_close_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + tlsp_accept_event(event, tlsp_close_event, (void *) state); + tlsp_state_free(state); +} + +/* tlsp_get_request_event - receive initial hand-off info */ + +static void tlsp_get_request_event(int event, void *context) +{ + const char *myname = "tlsp_get_request_event"; + TLSP_STATE *state = (TLSP_STATE *) context; + VSTREAM *plaintext_stream = state->plaintext_stream; + int plaintext_fd = vstream_fileno(plaintext_stream); + static VSTRING *remote_endpt; + static VSTRING *server_id; + int req_flags; + int handshake_timeout; + int session_timeout; + int ready = 0; + + /* + * At this point we still manually manage plaintext read/write/timeout + * events. Every code path must either destroy state or request the next + * event, otherwise this pseudo-thread is idle until the client goes + * away. + */ + tlsp_accept_event(event, tlsp_get_request_event, (void *) state); + + /* + * One-time initialization. + */ + if (remote_endpt == 0) { + remote_endpt = vstring_alloc(10); + server_id = vstring_alloc(10); + } + + /* + * Receive the initial request attributes. Receive the remainder after we + * figure out what role we are expected to play. + */ + if (event != EVENT_READ + || attr_scan(plaintext_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, remote_endpt), + RECV_ATTR_INT(TLS_ATTR_FLAGS, &req_flags), + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &handshake_timeout), + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &session_timeout), + RECV_ATTR_STR(TLS_ATTR_SERVERID, server_id), + ATTR_TYPE_END) != 5) { + msg_warn("%s: receive request attributes: %m", myname); + tlsp_state_free(state); + return; + } + + /* + * XXX We use the same fixed timeout throughout the entire session for + * both plaintext and ciphertext communication. This timeout is just a + * safety feature; the real timeout will be enforced by our plaintext + * peer (except during TLS the handshake, when we intentionally disable + * plaintext I/O). + */ + state->remote_endpt = mystrdup(STR(remote_endpt)); + state->server_id = mystrdup(STR(server_id)); + msg_info("CONNECT %s %s", + (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" : + (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" : + "(bogus_direction)", state->remote_endpt); + state->req_flags = req_flags; + /* state->is_server_role is set below. */ + state->handshake_timeout = handshake_timeout; + state->session_timeout = session_timeout + 10; /* XXX */ + + /* + * Receive the TLS preferences now, to reduce the number of protocol + * roundtrips. + */ + switch (req_flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) { + case TLS_PROXY_FLAG_ROLE_CLIENT: + state->is_server_role = 0; + if (attr_scan(plaintext_stream, ATTR_FLAG_STRICT, + RECV_ATTR_FUNC(tls_proxy_client_param_scan, + (void *) &state->tls_params), + RECV_ATTR_FUNC(tls_proxy_client_init_scan, + (void *) &state->client_init_props), + RECV_ATTR_FUNC(tls_proxy_client_start_scan, + (void *) &state->client_start_props), + ATTR_TYPE_END) != 3) { + msg_warn("%s: receive client TLS settings: %m", myname); + tlsp_state_free(state); + return; + } + state->appl_state = tlsp_client_init(state->tls_params, + state->client_init_props, + + /* + * Why not test for TLS_DANE_BASED()? Because the tlsproxy(8) client + * has already converted its DANE TLSA records into trust anchors, + * and therefore TLS_DANE_HASTA() will be true instead. That + * exercises the code path that updates the shared SSL_CTX with + * custom X.509 verification callbacks for trust anchors. + */ + TLS_DANE_HASTA(state->client_start_props->dane) != 0); + ready = state->appl_state != 0; + break; + case TLS_PROXY_FLAG_ROLE_SERVER: + state->is_server_role = 1; + ready = (tlsp_server_ctx != 0); + break; + default: + state->is_server_role = 0; + msg_warn("%s: bad request flags: 0x%x", myname, req_flags); + ready = 0; + } + + /* + * For portability we must send some data, after receiving the request + * attributes and before receiving the remote file descriptor. + * + * If the requested TLS engine is unavailable, hang up after making sure + * that the plaintext peer has received our "sorry" indication. + */ + if (attr_print(plaintext_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, ready), + ATTR_TYPE_END) != 0 + || vstream_fflush(plaintext_stream) != 0 + || ready == 0) { + tlsp_request_read_event(plaintext_fd, tlsp_close_event, + TLSP_INIT_TIMEOUT, (void *) state); + return; + } else { + tlsp_request_read_event(plaintext_fd, tlsp_get_fd_event, + TLSP_INIT_TIMEOUT, (void *) state); + return; + } +} + +/* tlsp_service - handle new client connection */ + +static void tlsp_service(VSTREAM *plaintext_stream, + char *service, + char **argv) +{ + TLSP_STATE *state; + int plaintext_fd = vstream_fileno(plaintext_stream); + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This program handles multiple connections, so it must not block. We + * use event-driven code for all operations that introduce latency. + * Except that attribute lists are sent/received synchronously, once the + * socket is found to be ready for transmission. + */ + non_blocking(plaintext_fd, NON_BLOCKING); + vstream_control(plaintext_stream, + CA_VSTREAM_CTL_PATH("plaintext"), + CA_VSTREAM_CTL_TIMEOUT(5), + CA_VSTREAM_CTL_END); + + /* + * Receive postscreen's remote SMTP client address/port and socket. + */ + state = tlsp_state_create(service, plaintext_stream); + tlsp_request_read_event(plaintext_fd, tlsp_get_request_event, + TLSP_INIT_TIMEOUT, (void *) state); +} + +/* pre_jail_init_server - pre-jail initialization */ + +static void pre_jail_init_server(void) +{ + TLS_SERVER_INIT_PROPS props; + const char *cert_file; + int have_server_cert; + int no_server_cert_ok; + int require_server_cert; + + /* + * The code in this routine is pasted literally from smtpd(8). I am not + * going to sanitize this because doing so surely will break things in + * unexpected ways. + */ + if (*var_tlsp_tls_level) { + switch (tls_level_lookup(var_tlsp_tls_level)) { + default: + msg_fatal("Invalid TLS level \"%s\"", var_tlsp_tls_level); + /* NOTREACHED */ + break; + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_FPRINT: + msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"", + VAR_TLSP_TLS_LEVEL, var_tlsp_tls_level); + /* FALLTHROUGH */ + case TLS_LEV_ENCRYPT: + var_tlsp_enforce_tls = var_tlsp_use_tls = 1; + break; + case TLS_LEV_MAY: + var_tlsp_enforce_tls = 0; + var_tlsp_use_tls = 1; + break; + case TLS_LEV_NONE: + var_tlsp_enforce_tls = var_tlsp_use_tls = 0; + break; + } + } + var_tlsp_use_tls = var_tlsp_use_tls || var_tlsp_enforce_tls; + if (!var_tlsp_use_tls) { + msg_warn("TLS server role is disabled with %s or %s", + VAR_TLSP_TLS_LEVEL, VAR_TLSP_USE_TLS); + return; + } + + /* + * Load TLS keys before dropping privileges. + * + * Can't use anonymous ciphers if we want client certificates. Must use + * anonymous ciphers if we have no certificates. + */ + ask_client_cert = require_server_cert = + (var_tlsp_tls_ask_ccert + || (var_tlsp_enforce_tls && var_tlsp_tls_req_ccert)); + if (strcasecmp(var_tlsp_tls_cert_file, "none") == 0) { + no_server_cert_ok = 1; + cert_file = ""; + } else { + no_server_cert_ok = 0; + cert_file = var_tlsp_tls_cert_file; + } + have_server_cert = + (*cert_file || *var_tlsp_tls_dcert_file || *var_tlsp_tls_eccert_file); + + if (*var_tlsp_tls_chain_files != 0) { + if (!have_server_cert) + have_server_cert = 1; + else + msg_warn("Both %s and one or more of the legacy " + " %s, %s or %s are non-empty; the legacy " + " parameters will be ignored", + VAR_TLSP_TLS_CHAIN_FILES, + VAR_TLSP_TLS_CERT_FILE, + VAR_TLSP_TLS_ECCERT_FILE, + VAR_TLSP_TLS_DCERT_FILE); + } + /* Some TLS configuration errors are not show stoppers. */ + if (!have_server_cert && require_server_cert) + msg_warn("Need a server cert to request client certs"); + if (!var_tlsp_enforce_tls && var_tlsp_tls_req_ccert) + msg_warn("Can't require client certs unless TLS is required"); + /* After a show-stopper error, log a warning. */ + if (have_server_cert || (no_server_cert_ok && !require_server_cert)) { + + tls_pre_jail_init(TLS_ROLE_SERVER); + + /* + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + tlsp_server_ctx = + TLS_SERVER_INIT(&props, + log_param = VAR_TLSP_TLS_LOGLEVEL, + log_level = var_tlsp_tls_loglevel, + verifydepth = var_tlsp_tls_ccert_vd, + cache_type = TLS_MGR_SCACHE_SMTPD, + set_sessid = var_tlsp_tls_set_sessid, + chain_files = var_tlsp_tls_chain_files, + cert_file = cert_file, + key_file = var_tlsp_tls_key_file, + dcert_file = var_tlsp_tls_dcert_file, + dkey_file = var_tlsp_tls_dkey_file, + eccert_file = var_tlsp_tls_eccert_file, + eckey_file = var_tlsp_tls_eckey_file, + CAfile = var_tlsp_tls_CAfile, + CApath = var_tlsp_tls_CApath, + dh1024_param_file + = var_tlsp_tls_dh1024_param_file, + dh512_param_file + = var_tlsp_tls_dh512_param_file, + eecdh_grade = var_tlsp_tls_eecdh, + protocols = var_tlsp_enforce_tls ? + var_tlsp_tls_mand_proto : + var_tlsp_tls_proto, + ask_ccert = ask_client_cert, + mdalg = var_tlsp_tls_fpt_dgst); + } else { + msg_warn("No server certs available. TLS can't be enabled"); + } + + /* + * To maintain sanity, allow partial SSL_write() operations, and allow + * SSL_write() buffer pointers to change after a WANT_READ or WANT_WRITE + * result. This is based on OpenSSL developers talking on a mailing list, + * but is not supported by documentation. If this code stops working then + * no-one can be held responsible. + */ + if (tlsp_server_ctx) + SSL_CTX_set_mode(tlsp_server_ctx->ssl_ctx, + SSL_MODE_ENABLE_PARTIAL_WRITE + | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); +} + +/* pre_jail_init_client - pre-jail initialization */ + +static void pre_jail_init_client(void) +{ + int clnt_use_tls; + + /* + * The cache with TLS_APPL_STATE instances for different TLS_CLIENT_INIT + * configurations. + */ + tlsp_client_app_cache = htable_create(10); + + /* + * Most sites don't use TLS client certs/keys. In that case, enabling + * tlsproxy-based connection caching is trivial. + * + * But some sites do use TLS client certs/keys, and that is challenging when + * tlsproxy runs in a post-jail environment: chroot breaks pathname + * resolution, and an unprivileged process should not be able to open + * files with secrets. The workaround: assume that most of those sites + * will use a fixed TLS client identity. In that case, tlsproxy can load + * the corresponding certs/keys at pre-jail time, so that secrets can + * remain read-only for root. As long as the tlsproxy pre-jail TLS client + * configuration with cert or key pathnames is the same as the one used + * in the Postfix SMTP client, sites can selectively or globally enable + * tlsproxy-based connection caching without additional TLS + * configuration. + * + * Loading one TLS client configuration at pre-jail time is not sufficient + * for the minority of sites that want to use TLS connection caching with + * multiple TLS client identities. To alert the operator, tlsproxy will + * log a warning when a TLS_CLIENT_INIT message specifies a different + * configuration than the tlsproxy pre-jail client configuration, and + * that different configuration specifies file/directory pathname + * arguments. The workaround is to have one tlsproxy process per TLS + * client identity. + * + * The general solution for single-identity or multi-identity clients is to + * stop loading certs and keys from individual files. Instead, have a + * cert/key map, indexed by client identity, read-only by root. After + * opening the map as root at pre-jail time, tlsproxy can read certs/keys + * on-the-fly as an unprivileged process at post-jail time. This is the + * approach that was already proposed for server-side SNI support, and it + * could be reused here. It would also end the proliferation of RSA + * cert/key parameters, DSA cert/key parameters, EC cert/key parameters, + * and so on. + * + * Horror: In order to create the same pre-jail TLS client context as the + * one used in the Postfix SMTP client, we have to duplicate intricate + * SMTP client code, including a handful configuration parameters that + * tlsproxy does not need. We must duplicate the logic, so that we only + * load certs and keys when the SMTP client would load them. + */ + if (*var_tlsp_clnt_level != 0) + switch (tls_level_lookup(var_tlsp_clnt_level)) { + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_DANE_ONLY: + case TLS_LEV_FPRINT: + case TLS_LEV_ENCRYPT: + var_tlsp_clnt_use_tls = var_tlsp_clnt_enforce_tls = 1; + break; + case TLS_LEV_DANE: + case TLS_LEV_MAY: + var_tlsp_clnt_use_tls = 1; + var_tlsp_clnt_enforce_tls = 0; + break; + case TLS_LEV_NONE: + var_tlsp_clnt_use_tls = var_tlsp_clnt_enforce_tls = 0; + break; + default: + /* tls_level_lookup() logs no warning. */ + /* session_tls_init() assumes that var_tlsp_clnt_level is sane. */ + msg_fatal("Invalid TLS level \"%s\"", var_tlsp_clnt_level); + } + clnt_use_tls = (var_tlsp_clnt_use_tls || var_tlsp_clnt_enforce_tls); + + /* + * Initialize the TLS data before entering the chroot jail. + */ + if (clnt_use_tls || var_tlsp_clnt_per_site[0] || var_tlsp_clnt_policy[0]) { + TLS_CLIENT_PARAMS tls_params; + TLS_CLIENT_INIT_PROPS init_props; + int dane_based_mode; + + tls_pre_jail_init(TLS_ROLE_CLIENT); + + /* + * We get stronger type safety and a cleaner interface by combining + * the various parameters into a single tls_client_props structure. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + (void) tls_proxy_client_param_from_config(&tls_params); + (void) TLS_CLIENT_INIT_ARGS(&init_props, + log_param = var_tlsp_clnt_logparam, + log_level = var_tlsp_clnt_loglevel, + verifydepth = var_tlsp_clnt_scert_vd, + cache_type = TLS_MGR_SCACHE_SMTP, + chain_files = var_tlsp_clnt_chain_files, + cert_file = var_tlsp_clnt_cert_file, + key_file = var_tlsp_clnt_key_file, + dcert_file = var_tlsp_clnt_dcert_file, + dkey_file = var_tlsp_clnt_dkey_file, + eccert_file = var_tlsp_clnt_eccert_file, + eckey_file = var_tlsp_clnt_eckey_file, + CAfile = var_tlsp_clnt_CAfile, + CApath = var_tlsp_clnt_CApath, + mdalg = var_tlsp_clnt_fpt_dgst); + for (dane_based_mode = 0; dane_based_mode < 2; dane_based_mode++) { + if (tlsp_client_init(&tls_params, &init_props, + dane_based_mode) == 0) + msg_warn("TLS client initialization failed"); + } + } +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Initialize roles separately. + */ + pre_jail_init_server(); + pre_jail_init_client(); + + /* + * tlsp_client_init() needs to know if it is called pre-jail or + * post-jail. + */ + tlsp_pre_jail_done = 1; + + /* + * Bug: TLS_CLIENT_PARAMS attributes are not used when creating a + * TLS_APPL_STATE instance; we can only warn about attribute mismatches. + */ + tlsp_params_mismatch_filter = been_here_init(BH_BOUND_NONE, BH_FLAG_NONE); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + + /* + * Each table below initializes the named variables to their implicit + * default value, or to the explicit value in main.cf or master.cf. Here, + * "compat" means that a table initializes a variable "smtpd_blah" or + * "smtp_blah" that provides the implicit default value for variable + * "tlsproxy_blah" which is initialized by a different table. To make + * this work, the variables in a "compat" table must be initialized + * before the variables in the corresponding non-compat table. + */ + static const CONFIG_INT_TABLE compat_int_table[] = { + VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, + VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, + 0, + }; + static const CONFIG_NINT_TABLE nint_table[] = { + VAR_TLSP_TLS_CCERT_VD, DEF_TLSP_TLS_CCERT_VD, &var_tlsp_tls_ccert_vd, 0, 0, + VAR_TLSP_CLNT_SCERT_VD, DEF_TLSP_CLNT_SCERT_VD, &var_tlsp_clnt_scert_vd, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_TLSP_WATCHDOG, DEF_TLSP_WATCHDOG, &var_tlsp_watchdog, 10, 0, + 0, + }; + static const CONFIG_BOOL_TABLE compat_bool_table[] = { + VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls, + VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls, + VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert, + VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert, + VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid, + VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls, + VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_TLSP_USE_TLS, DEF_TLSP_USE_TLS, &var_tlsp_use_tls, + VAR_TLSP_ENFORCE_TLS, DEF_TLSP_ENFORCE_TLS, &var_tlsp_enforce_tls, + VAR_TLSP_TLS_ACERT, DEF_TLSP_TLS_ACERT, &var_tlsp_tls_ask_ccert, + VAR_TLSP_TLS_RCERT, DEF_TLSP_TLS_RCERT, &var_tlsp_tls_req_ccert, + VAR_TLSP_TLS_SET_SESSID, DEF_TLSP_TLS_SET_SESSID, &var_tlsp_tls_set_sessid, + VAR_TLSP_CLNT_USE_TLS, DEF_TLSP_CLNT_USE_TLS, &var_tlsp_clnt_use_tls, + VAR_TLSP_CLNT_ENFORCE_TLS, DEF_TLSP_CLNT_ENFORCE_TLS, &var_tlsp_clnt_enforce_tls, + 0, + }; + static const CONFIG_STR_TABLE compat_str_table[] = { + VAR_SMTPD_TLS_CHAIN_FILES, DEF_SMTPD_TLS_CHAIN_FILES, &var_smtpd_tls_chain_files, 0, 0, + VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0, + VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0, + VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0, + VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0, + VAR_SMTPD_TLS_ECCERT_FILE, DEF_SMTPD_TLS_ECCERT_FILE, &var_smtpd_tls_eccert_file, 0, 0, + VAR_SMTPD_TLS_ECKEY_FILE, DEF_SMTPD_TLS_ECKEY_FILE, &var_smtpd_tls_eckey_file, 0, 0, + VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0, + VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0, + VAR_SMTPD_TLS_CIPH, DEF_SMTPD_TLS_CIPH, &var_smtpd_tls_ciph, 1, 0, + VAR_SMTPD_TLS_MAND_CIPH, DEF_SMTPD_TLS_MAND_CIPH, &var_smtpd_tls_mand_ciph, 1, 0, + VAR_SMTPD_TLS_EXCL_CIPH, DEF_SMTPD_TLS_EXCL_CIPH, &var_smtpd_tls_excl_ciph, 0, 0, + VAR_SMTPD_TLS_MAND_EXCL, DEF_SMTPD_TLS_MAND_EXCL, &var_smtpd_tls_mand_excl, 0, 0, + VAR_SMTPD_TLS_PROTO, DEF_SMTPD_TLS_PROTO, &var_smtpd_tls_proto, 0, 0, + VAR_SMTPD_TLS_MAND_PROTO, DEF_SMTPD_TLS_MAND_PROTO, &var_smtpd_tls_mand_proto, 0, 0, + VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0, + VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0, + VAR_SMTPD_TLS_EECDH, DEF_SMTPD_TLS_EECDH, &var_smtpd_tls_eecdh, 1, 0, + VAR_SMTPD_TLS_FPT_DGST, DEF_SMTPD_TLS_FPT_DGST, &var_smtpd_tls_fpt_dgst, 1, 0, + VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0, + VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0, + VAR_SMTP_TLS_CHAIN_FILES, DEF_SMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0, + VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, + VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0, + VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, + VAR_SMTP_TLS_LEVEL, DEF_SMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0, + VAR_SMTP_TLS_POLICY, DEF_SMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_TLSP_TLS_CHAIN_FILES, DEF_TLSP_TLS_CHAIN_FILES, &var_tlsp_tls_chain_files, 0, 0, + VAR_TLSP_TLS_CERT_FILE, DEF_TLSP_TLS_CERT_FILE, &var_tlsp_tls_cert_file, 0, 0, + VAR_TLSP_TLS_KEY_FILE, DEF_TLSP_TLS_KEY_FILE, &var_tlsp_tls_key_file, 0, 0, + VAR_TLSP_TLS_DCERT_FILE, DEF_TLSP_TLS_DCERT_FILE, &var_tlsp_tls_dcert_file, 0, 0, + VAR_TLSP_TLS_DKEY_FILE, DEF_TLSP_TLS_DKEY_FILE, &var_tlsp_tls_dkey_file, 0, 0, + VAR_TLSP_TLS_ECCERT_FILE, DEF_TLSP_TLS_ECCERT_FILE, &var_tlsp_tls_eccert_file, 0, 0, + VAR_TLSP_TLS_ECKEY_FILE, DEF_TLSP_TLS_ECKEY_FILE, &var_tlsp_tls_eckey_file, 0, 0, + VAR_TLSP_TLS_CA_FILE, DEF_TLSP_TLS_CA_FILE, &var_tlsp_tls_CAfile, 0, 0, + VAR_TLSP_TLS_CA_PATH, DEF_TLSP_TLS_CA_PATH, &var_tlsp_tls_CApath, 0, 0, + VAR_TLSP_TLS_CIPH, DEF_TLSP_TLS_CIPH, &var_tlsp_tls_ciph, 1, 0, + VAR_TLSP_TLS_MAND_CIPH, DEF_TLSP_TLS_MAND_CIPH, &var_tlsp_tls_mand_ciph, 1, 0, + VAR_TLSP_TLS_EXCL_CIPH, DEF_TLSP_TLS_EXCL_CIPH, &var_tlsp_tls_excl_ciph, 0, 0, + VAR_TLSP_TLS_MAND_EXCL, DEF_TLSP_TLS_MAND_EXCL, &var_tlsp_tls_mand_excl, 0, 0, + VAR_TLSP_TLS_PROTO, DEF_TLSP_TLS_PROTO, &var_tlsp_tls_proto, 0, 0, + VAR_TLSP_TLS_MAND_PROTO, DEF_TLSP_TLS_MAND_PROTO, &var_tlsp_tls_mand_proto, 0, 0, + VAR_TLSP_TLS_512_FILE, DEF_TLSP_TLS_512_FILE, &var_tlsp_tls_dh512_param_file, 0, 0, + VAR_TLSP_TLS_1024_FILE, DEF_TLSP_TLS_1024_FILE, &var_tlsp_tls_dh1024_param_file, 0, 0, + VAR_TLSP_TLS_EECDH, DEF_TLSP_TLS_EECDH, &var_tlsp_tls_eecdh, 1, 0, + VAR_TLSP_TLS_FPT_DGST, DEF_TLSP_TLS_FPT_DGST, &var_tlsp_tls_fpt_dgst, 1, 0, + VAR_TLSP_TLS_LOGLEVEL, DEF_TLSP_TLS_LOGLEVEL, &var_tlsp_tls_loglevel, 0, 0, + VAR_TLSP_TLS_LEVEL, DEF_TLSP_TLS_LEVEL, &var_tlsp_tls_level, 0, 0, + VAR_TLSP_CLNT_LOGLEVEL, DEF_TLSP_CLNT_LOGLEVEL, &var_tlsp_clnt_loglevel, 0, 0, + VAR_TLSP_CLNT_LOGPARAM, DEF_TLSP_CLNT_LOGPARAM, &var_tlsp_clnt_logparam, 0, 0, + VAR_TLSP_CLNT_CHAIN_FILES, DEF_TLSP_CLNT_CHAIN_FILES, &var_tlsp_clnt_chain_files, 0, 0, + VAR_TLSP_CLNT_CERT_FILE, DEF_TLSP_CLNT_CERT_FILE, &var_tlsp_clnt_cert_file, 0, 0, + VAR_TLSP_CLNT_KEY_FILE, DEF_TLSP_CLNT_KEY_FILE, &var_tlsp_clnt_key_file, 0, 0, + VAR_TLSP_CLNT_DCERT_FILE, DEF_TLSP_CLNT_DCERT_FILE, &var_tlsp_clnt_dcert_file, 0, 0, + VAR_TLSP_CLNT_DKEY_FILE, DEF_TLSP_CLNT_DKEY_FILE, &var_tlsp_clnt_dkey_file, 0, 0, + VAR_TLSP_CLNT_ECCERT_FILE, DEF_TLSP_CLNT_ECCERT_FILE, &var_tlsp_clnt_eccert_file, 0, 0, + VAR_TLSP_CLNT_ECKEY_FILE, DEF_TLSP_CLNT_ECKEY_FILE, &var_tlsp_clnt_eckey_file, 0, 0, + VAR_TLSP_CLNT_CAFILE, DEF_TLSP_CLNT_CAFILE, &var_tlsp_clnt_CAfile, 0, 0, + VAR_TLSP_CLNT_CAPATH, DEF_TLSP_CLNT_CAPATH, &var_tlsp_clnt_CApath, 0, 0, + VAR_TLSP_CLNT_FPT_DGST, DEF_TLSP_CLNT_FPT_DGST, &var_tlsp_clnt_fpt_dgst, 1, 0, + VAR_TLSP_CLNT_LEVEL, DEF_TLSP_CLNT_LEVEL, &var_tlsp_clnt_level, 0, 0, + VAR_TLSP_CLNT_PER_SITE, DEF_TLSP_CLNT_PER_SITE, &var_tlsp_clnt_per_site, 0, 0, + VAR_TLSP_CLNT_POLICY, DEF_TLSP_CLNT_POLICY, &var_tlsp_clnt_policy, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Pass control to the event-driven service skeleton. + */ + event_server_main(argc, argv, tlsp_service, + CA_MAIL_SERVER_INT_TABLE(compat_int_table), + CA_MAIL_SERVER_NINT_TABLE(nint_table), + CA_MAIL_SERVER_STR_TABLE(compat_str_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_BOOL_TABLE(compat_bool_table), + CA_MAIL_SERVER_NBOOL_TABLE(nbool_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_SLOW_EXIT(tlsp_drain), + CA_MAIL_SERVER_RETIRE_ME, + CA_MAIL_SERVER_WATCHDOG(&var_tlsp_watchdog), + CA_MAIL_SERVER_UNLIMITED, + 0); +} + +#else + +/* tlsp_service - respond to external trigger(s), non-TLS version */ + +static void tlsp_service(VSTREAM *stream, char *unused_service, + char **unused_argv) +{ + msg_info("TLS support is not compiled in -- exiting"); + event_server_disconnect(stream); +} + +/* main - the main program */ + +int main(int argc, char **argv) +{ + + /* + * We can't simply use msg_fatal() here, because the logging hasn't been + * initialized. The text would disappear because stderr is redirected to + * /dev/null. + * + * We invoke event_server_main() to complete program initialization + * (including logging) and then invoke the tlsp_service() routine to log + * the message that says why this program will not run. + */ + event_server_main(argc, argv, tlsp_service, + 0); +} + +#endif diff --git a/src/tlsproxy/tlsproxy.h b/src/tlsproxy/tlsproxy.h new file mode 100644 index 0000000..eacbb1f --- /dev/null +++ b/src/tlsproxy/tlsproxy.h @@ -0,0 +1,69 @@ +/*++ +/* NAME +/* tlsproxy 3h +/* SUMMARY +/* tlsproxy internal interfaces +/* SYNOPSIS +/* #include <tlsproxy.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstream.h> +#include <nbbio.h> + + /* + * TLS library. + */ +#include <tls.h> + + /* + * Internal interface. + */ +typedef struct { + int flags; /* see below */ + int req_flags; /* request flags, see tls_proxy.h */ + int is_server_role; /* avoid clumsy handler code */ + char *service; /* argv[0] */ + VSTREAM *plaintext_stream; /* local peer: postscreen(8), etc. */ + NBBIO *plaintext_buf; /* plaintext buffer */ + int ciphertext_fd; /* remote peer */ + EVENT_NOTIFY_FN ciphertext_timer; /* kludge */ + int timeout; /* read/write time limit */ + int handshake_timeout; /* in-handshake time limit */ + int session_timeout; /* post-handshake time limit */ + char *remote_endpt; /* printable remote endpoint */ + char *server_id; /* cache management */ + TLS_APPL_STATE *appl_state; /* libtls state */ + TLS_SESS_STATE *tls_context; /* libtls state */ + int ssl_last_err; /* TLS I/O state */ + TLS_CLIENT_PARAMS *tls_params; /* globals not part of init_props */ + TLS_SERVER_INIT_PROPS *server_init_props; + TLS_SERVER_START_PROPS *server_start_props; + TLS_CLIENT_INIT_PROPS *client_init_props; + TLS_CLIENT_START_PROPS *client_start_props; +} TLSP_STATE; + +#define TLSP_FLAG_DO_HANDSHAKE (1<<0) +#define TLSP_FLAG_NO_MORE_CIPHERTEXT_IO (1<<1) /* overrides DO_HANDSHAKE */ + +extern TLSP_STATE *tlsp_state_create(const char *, VSTREAM *); +extern void tlsp_state_free(TLSP_STATE *); + +/* 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 +/*--*/ diff --git a/src/tlsproxy/tlsproxy_state.c b/src/tlsproxy/tlsproxy_state.c new file mode 100644 index 0000000..df6cbda --- /dev/null +++ b/src/tlsproxy/tlsproxy_state.c @@ -0,0 +1,169 @@ +/*++ +/* NAME +/* tlsproxy_state 3 +/* SUMMARY +/* Postfix SMTP server +/* SYNOPSIS +/* #include <tlsproxy.h> +/* +/* TLSP_STATE *tlsp_state_create(service, plaintext_stream) +/* const char *service; +/* VSTREAM *plaintext_stream; +/* +/* void tlsp_state_free(state) +/* TLSP_STATE *state; +/* DESCRIPTION +/* This module provides TLSP_STATE constructor and destructor +/* routines. +/* +/* tlsp_state_create() initializes session context. +/* +/* tlsp_state_free() destroys session context. If the handshake +/* was in progress, it logs a 'handshake failed' message. +/* +/* Arguments: +/* .IP service +/* The service name for the TLS library. This argument is copied. +/* The destructor will automatically destroy the string. +/* .IP plaintext_stream +/* The VSTREAM between postscreen(8) and tlsproxy(8). +/* The destructor will automatically close the stream. +/* .PP +/* Other structure members are set by the application. The +/* text below describes how the TLSP_STATE destructor +/* disposes of them. +/* .IP plaintext_buf +/* NBBIO for plaintext I/O. +/* The destructor will automatically turn off read/write/timeout +/* events and destroy the NBBIO. +/* .IP ciphertext_fd +/* The file handle for the remote SMTP client socket. +/* The destructor will automatically turn off read/write events +/* and close the file handle. +/* .IP ciphertext_timer +/* The destructor will automatically turn off this time event. +/* .IP timeout +/* Time limit for plaintext and ciphertext I/O. +/* .IP remote_endpt +/* Printable remote endpoint name. +/* The destructor will automatically destroy the string. +/* .IP server_id +/* TLS session cache identifier. +/* The destructor will automatically destroy the string. +/* 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 +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library. + */ +#include <msg.h> +#include <mymalloc.h> +#include <nbbio.h> + + /* + * Master library. + */ +#include <mail_server.h> + + /* + * TLS library. + */ +#ifdef USE_TLS +#define TLS_INTERNAL /* XXX */ +#include <tls.h> +#include <tls_proxy.h> + + /* + * Application-specific. + */ +#include <tlsproxy.h> + +/* tlsp_state_create - create TLS proxy state object */ + +TLSP_STATE *tlsp_state_create(const char *service, + VSTREAM *plaintext_stream) +{ + TLSP_STATE *state = (TLSP_STATE *) mymalloc(sizeof(*state)); + + state->flags = TLSP_FLAG_DO_HANDSHAKE; + state->service = mystrdup(service); + state->plaintext_stream = plaintext_stream; + state->plaintext_buf = 0; + state->ciphertext_fd = -1; + state->ciphertext_timer = 0; + state->timeout = -1; + state->remote_endpt = 0; + state->server_id = 0; + state->tls_context = 0; + state->tls_params = 0; + state->server_init_props = 0; + state->server_start_props = 0; + state->client_init_props = 0; + state->client_start_props = 0; + + return (state); +} + +/* tlsp_state_free - destroy state objects, connection and events */ + +void tlsp_state_free(TLSP_STATE *state) +{ + /* Don't log failure after plaintext EOF. */ + if (state->remote_endpt && state->server_id + && (state->flags & TLSP_FLAG_DO_HANDSHAKE)) + msg_info("TLS handshake failed for service=%s peer=%s", + state->server_id, state->remote_endpt); + myfree(state->service); + if (state->plaintext_buf) /* turns off plaintext events */ + nbbio_free(state->plaintext_buf); + else + event_disable_readwrite(vstream_fileno(state->plaintext_stream)); + event_server_disconnect(state->plaintext_stream); + if (state->ciphertext_fd >= 0) { + event_disable_readwrite(state->ciphertext_fd); + (void) close(state->ciphertext_fd); + } + if (state->ciphertext_timer) + event_cancel_timer(state->ciphertext_timer, (void *) state); + if (state->remote_endpt) { + msg_info("DISCONNECT %s", state->remote_endpt); + myfree(state->remote_endpt); + } + if (state->server_id) + myfree(state->server_id); + if (state->tls_context) + tls_free_context(state->tls_context); + if (state->tls_params) + tls_proxy_client_param_free(state->tls_params); + if (state->server_init_props) + tls_proxy_server_init_free(state->server_init_props); + if (state->server_start_props) + tls_proxy_server_start_free(state->server_start_props); + if (state->client_init_props) + tls_proxy_client_init_free(state->client_init_props); + if (state->client_start_props) + tls_proxy_client_start_free(state->client_start_props); + myfree((void *) state); +} + +#endif |