From: eric Date: Fri, 5 Mar 2021 12:37:32 +0000 (+0000) Subject: Start porting smtpd to libtls. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=eed85469e33c6f127ced1d9a6558429f47207e45;p=openbsd Start porting smtpd to libtls. Note that it changes the way SNI works: The certificate to use is now selected by looking at the names found in the certificates themselves, rather than the names of the pki entries in the configuration file. The set of certificates for a tls listener must be defined explicitly by using the pki listener option multiple times. ok tb@ --- diff --git a/usr.sbin/smtpd/ca.c b/usr.sbin/smtpd/ca.c index 4f17286819e..06499d5ebae 100644 --- a/usr.sbin/smtpd/ca.c +++ b/usr.sbin/smtpd/ca.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ca.c,v 1.37 2020/12/31 08:27:15 martijn Exp $ */ +/* $OpenBSD: ca.c,v 1.38 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2014 Reyk Floeter @@ -69,6 +69,7 @@ static int ecdsae_do_verify(const unsigned char *, int, const ECDSA_SIG *, EC_KEY *); +static struct dict pkeys; static uint64_t reqid = 0; static void @@ -132,26 +133,29 @@ ca_init(void) struct pki *pki; const char *k; void *iter_dict; + char *hash; log_debug("debug: init private ssl-tree"); + dict_init(&pkeys); iter_dict = NULL; while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { if (pki->pki_key == NULL) continue; - if ((in = BIO_new_mem_buf(pki->pki_key, - pki->pki_key_len)) == NULL) - fatalx("ca_launch: key"); - - if ((pkey = PEM_read_bio_PrivateKey(in, - NULL, NULL, NULL)) == NULL) - fatalx("ca_launch: PEM"); + in = BIO_new_mem_buf(pki->pki_key, pki->pki_key_len); + if (in == NULL) + fatalx("ca_init: key"); + pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL); + if (pkey == NULL) + fatalx("ca_init: PEM"); BIO_free(in); - pki->pki_pkey = pkey; - - freezero(pki->pki_key, pki->pki_key_len); - pki->pki_key = NULL; + hash = ssl_pubkey_hash(pki->pki_cert, pki->pki_cert_len); + if (dict_check(&pkeys, hash)) + EVP_PKEY_free(pkey); + else + dict_xset(&pkeys, hash, pkey); + free(hash); } } @@ -223,15 +227,15 @@ end: void ca_imsg(struct mproc *p, struct imsg *imsg) { + EVP_PKEY *pkey; RSA *rsa = NULL; EC_KEY *ecdsa = NULL; const void *from = NULL; unsigned char *to = NULL; struct msg m; - const char *pkiname; + const char *hash; size_t flen, tlen, padding; int buf_len; - struct pki *pki; int ret = 0; uint64_t id; int v; @@ -267,16 +271,15 @@ ca_imsg(struct mproc *p, struct imsg *imsg) case IMSG_CA_RSA_PRIVDEC: m_msg(&m, imsg); m_get_id(&m, &id); - m_get_string(&m, &pkiname); + m_get_string(&m, &hash); m_get_data(&m, &from, &flen); m_get_size(&m, &tlen); m_get_size(&m, &padding); m_end(&m); - pki = dict_get(env->sc_pki_dict, pkiname); - if (pki == NULL || pki->pki_pkey == NULL || - (rsa = EVP_PKEY_get1_RSA(pki->pki_pkey)) == NULL) - fatalx("ca_imsg: invalid pki"); + pkey = dict_get(&pkeys, hash); + if (pkey == NULL || (rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) + fatalx("ca_imsg: invalid pkey hash"); if ((to = calloc(1, tlen)) == NULL) fatalx("ca_imsg: calloc"); @@ -306,14 +309,14 @@ ca_imsg(struct mproc *p, struct imsg *imsg) case IMSG_CA_ECDSA_SIGN: m_msg(&m, imsg); m_get_id(&m, &id); - m_get_string(&m, &pkiname); + m_get_string(&m, &hash); m_get_data(&m, &from, &flen); m_end(&m); - pki = dict_get(env->sc_pki_dict, pkiname); - if (pki == NULL || pki->pki_pkey == NULL || - (ecdsa = EVP_PKEY_get1_EC_KEY(pki->pki_pkey)) == NULL) - fatalx("ca_imsg: invalid pki"); + pkey = dict_get(&pkeys, hash); + if (pkey == NULL || + (ecdsa = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) + fatalx("ca_imsg: invalid pkey hash"); buf_len = ECDSA_size(ecdsa); if ((to = calloc(1, buf_len)) == NULL) @@ -350,12 +353,12 @@ rsae_send_imsg(int flen, const unsigned char *from, unsigned char *to, struct imsg imsg; int n, done = 0; const void *toptr; - char *pkiname; + char *hash; size_t tlen; struct msg m; uint64_t id; - if ((pkiname = RSA_get_ex_data(rsa, 0)) == NULL) + if ((hash = RSA_get_ex_data(rsa, 0)) == NULL) return (0); /* @@ -365,7 +368,7 @@ rsae_send_imsg(int flen, const unsigned char *from, unsigned char *to, m_create(p_ca, cmd, 0, 0, -1); reqid++; m_add_id(p_ca, reqid); - m_add_string(p_ca, pkiname); + m_add_string(p_ca, hash); m_add_data(p_ca, (const void *)from, (size_t)flen); m_add_size(p_ca, (size_t)RSA_size(rsa)); m_add_size(p_ca, (size_t)padding); @@ -536,13 +539,13 @@ ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, struct imsg imsg; int n, done = 0; const void *toptr; - char *pkiname; + char *hash; size_t tlen; struct msg m; uint64_t id; ECDSA_SIG *sig = NULL; - if ((pkiname = ECDSA_get_ex_data(eckey, 0)) == NULL) + if ((hash = ECDSA_get_ex_data(eckey, 0)) == NULL) return (0); /* @@ -552,7 +555,7 @@ ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, m_create(p_ca, IMSG_CA_ECDSA_SIGN, 0, 0, -1); reqid++; m_add_id(p_ca, reqid); - m_add_string(p_ca, pkiname); + m_add_string(p_ca, hash); m_add_data(p_ca, (const void *)dgst, (size_t)dgst_len); m_flush(p_ca); diff --git a/usr.sbin/smtpd/config.c b/usr.sbin/smtpd/config.c index 094d4181404..73a76aca8fe 100644 --- a/usr.sbin/smtpd/config.c +++ b/usr.sbin/smtpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.53 2021/01/19 09:16:20 claudio Exp $ */ +/* $OpenBSD: config.c,v 1.54 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard @@ -252,6 +252,7 @@ purge_config(uint8_t what) if (what & PURGE_LISTENERS) { while ((l = TAILQ_FIRST(env->sc_listeners)) != NULL) { TAILQ_REMOVE(env->sc_listeners, l, entry); + free(l->pki); free(l); } free(env->sc_listeners); diff --git a/usr.sbin/smtpd/dispatcher.c b/usr.sbin/smtpd/dispatcher.c index a8f39ccbe93..3c67ea43918 100644 --- a/usr.sbin/smtpd/dispatcher.c +++ b/usr.sbin/smtpd/dispatcher.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dispatcher.c,v 1.1 2020/12/31 08:27:15 martijn Exp $ */ +/* $OpenBSD: dispatcher.c,v 1.2 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2014 Gilles Chehade @@ -154,6 +154,8 @@ dispatcher(void) { struct passwd *pw; + ca_engine_init(); + mda_postfork(); mta_postfork(); smtp_postfork(); @@ -196,8 +198,6 @@ dispatcher(void) config_peer(PROC_CONTROL); config_peer(PROC_CA); - ca_engine_init(); - if (pledge("stdio inet unix recvfd sendfd", NULL) == -1) err(1, "pledge"); diff --git a/usr.sbin/smtpd/iobuf.c b/usr.sbin/smtpd/iobuf.c index c899dc50332..c5b01d9da60 100644 --- a/usr.sbin/smtpd/iobuf.c +++ b/usr.sbin/smtpd/iobuf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: iobuf.c,v 1.14 2021/01/23 16:11:11 rob Exp $ */ +/* $OpenBSD: iobuf.c,v 1.15 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2012 Eric Faurot * @@ -21,15 +21,14 @@ #include #include +#include #include #include #include -#include - #ifdef IO_TLS -#include -#include +#include #endif +#include #include "iobuf.h" @@ -388,7 +387,7 @@ iobuf_flush(struct iobuf *io, int fd) #ifdef IO_TLS int -iobuf_flush_tls(struct iobuf *io, void *tls) +iobuf_flush_tls(struct iobuf *io, struct tls *tls) { ssize_t s; @@ -400,55 +399,42 @@ iobuf_flush_tls(struct iobuf *io, void *tls) } ssize_t -iobuf_write_tls(struct iobuf *io, void *tls) +iobuf_write_tls(struct iobuf *io, struct tls *tls) { struct ioqbuf *q; ssize_t n; q = io->outq; - n = SSL_write(tls, q->buf + q->rpos, q->wpos - q->rpos); - if (n <= 0) { - switch (SSL_get_error(tls, n)) { - case SSL_ERROR_WANT_READ: - return (IOBUF_WANT_READ); - case SSL_ERROR_WANT_WRITE: - return (IOBUF_WANT_WRITE); - case SSL_ERROR_ZERO_RETURN: /* connection closed */ - return (IOBUF_CLOSED); - case SSL_ERROR_SYSCALL: - if (ERR_peek_last_error()) - return (IOBUF_TLSERROR); - return (IOBUF_ERROR); - default: - return (IOBUF_TLSERROR); - } - } + + n = tls_write(tls, q->buf + q->rpos, q->wpos - q->rpos); + if (n == TLS_WANT_POLLIN) + return (IOBUF_WANT_READ); + else if (n == TLS_WANT_POLLOUT) + return (IOBUF_WANT_WRITE); + else if (n == 0) + return (IOBUF_CLOSED); + else if (n == -1) + return (IOBUF_ERROR); + iobuf_drain(io, n); return (n); } ssize_t -iobuf_read_tls(struct iobuf *io, void *tls) +iobuf_read_tls(struct iobuf *io, struct tls *tls) { ssize_t n; - n = SSL_read(tls, io->buf + io->wpos, iobuf_left(io)); - if (n < 0) { - switch (SSL_get_error(tls, n)) { - case SSL_ERROR_WANT_READ: - return (IOBUF_WANT_READ); - case SSL_ERROR_WANT_WRITE: - return (IOBUF_WANT_WRITE); - case SSL_ERROR_SYSCALL: - if (ERR_peek_last_error()) - return (IOBUF_TLSERROR); - return (IOBUF_ERROR); - default: - return (IOBUF_TLSERROR); - } - } else if (n == 0) + n = tls_read(tls, io->buf + io->wpos, iobuf_left(io)); + if (n == TLS_WANT_POLLIN) + return (IOBUF_WANT_READ); + else if (n == TLS_WANT_POLLOUT) + return (IOBUF_WANT_WRITE); + else if (n == 0) return (IOBUF_CLOSED); + else if (n == -1) + return (IOBUF_ERROR); io->wpos += n; diff --git a/usr.sbin/smtpd/iobuf.h b/usr.sbin/smtpd/iobuf.h index c454d0a1407..94f5c56ecc2 100644 --- a/usr.sbin/smtpd/iobuf.h +++ b/usr.sbin/smtpd/iobuf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: iobuf.h,v 1.5 2019/06/12 17:42:53 eric Exp $ */ +/* $OpenBSD: iobuf.h,v 1.6 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2012 Eric Faurot * @@ -35,11 +35,12 @@ struct iobuf { struct ioqbuf *outqlast; }; +struct tls; + #define IOBUF_WANT_READ -1 #define IOBUF_WANT_WRITE -2 #define IOBUF_CLOSED -3 #define IOBUF_ERROR -4 -#define IOBUF_TLSERROR -5 int iobuf_init(struct iobuf *, size_t, size_t); void iobuf_clear(struct iobuf *); @@ -53,7 +54,7 @@ size_t iobuf_left(struct iobuf *); char *iobuf_data(struct iobuf *); char *iobuf_getline(struct iobuf *, size_t *); ssize_t iobuf_read(struct iobuf *, int); -ssize_t iobuf_read_tls(struct iobuf *, void *); +ssize_t iobuf_read_tls(struct iobuf *, struct tls *); size_t iobuf_queued(struct iobuf *); void* iobuf_reserve(struct iobuf *, size_t); @@ -62,6 +63,6 @@ int iobuf_queuev(struct iobuf *, const struct iovec *, int); int iobuf_fqueue(struct iobuf *, const char *, ...); int iobuf_vfqueue(struct iobuf *, const char *, va_list); int iobuf_flush(struct iobuf *, int); -int iobuf_flush_tls(struct iobuf *, void *); +int iobuf_flush_tls(struct iobuf *, struct tls *); ssize_t iobuf_write(struct iobuf *, int); -ssize_t iobuf_write_tls(struct iobuf *, void *); +ssize_t iobuf_write_tls(struct iobuf *, struct tls *); diff --git a/usr.sbin/smtpd/ioev.c b/usr.sbin/smtpd/ioev.c index 8ae0bc30d58..109a2c3c8ba 100644 --- a/usr.sbin/smtpd/ioev.c +++ b/usr.sbin/smtpd/ioev.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ioev.c,v 1.43 2021/01/23 16:11:11 rob Exp $ */ +/* $OpenBSD: ioev.c,v 1.44 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2012 Eric Faurot * @@ -27,16 +27,14 @@ #include #include #include +#ifdef IO_TLS +#include +#endif #include #include "ioev.h" #include "iobuf.h" -#ifdef IO_TLS -#include -#include -#endif - enum { IO_STATE_NONE, IO_STATE_CONNECT, @@ -65,7 +63,9 @@ struct io { int flags; int state; struct event ev; - void *tls; + struct tls *tls; + char *name; + const char *error; /* only valid immediately on callback */ }; @@ -85,9 +85,7 @@ void io_frame_enter(const char *, struct io *, int); void io_frame_leave(struct io *); #ifdef IO_TLS -void ssl_error(const char *); /* XXX external */ - -static const char* io_tls_error(void); +void io_dispatch_handshake_tls(int, short, void *); void io_dispatch_accept_tls(int, short, void *); void io_dispatch_connect_tls(int, short, void *); void io_dispatch_read_tls(int, short, void *); @@ -111,10 +109,9 @@ io_strio(struct io *io) ssl[0] = '\0'; #ifdef IO_TLS if (io->tls) { - (void)snprintf(ssl, sizeof ssl, " tls=%s:%s:%d", - SSL_get_version(io->tls), - SSL_get_cipher_name(io->tls), - SSL_get_cipher_bits(io->tls, NULL)); + (void)snprintf(ssl, sizeof ssl, " tls=%s:%s", + tls_conn_version(io->tls), + tls_conn_cipher(io->tls)); } #endif @@ -168,7 +165,7 @@ io_set_nolinger(int fd) memset(&l, 0, sizeof(l)); if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1) - err(1, "io_set_linger:setsockopt()"); + err(1, "io_set_linger:setsockopt"); } /* @@ -272,7 +269,7 @@ io_free(struct io *io) current = NULL; #ifdef IO_TLS - SSL_free(io->tls); + tls_free(io->tls); io->tls = NULL; #endif @@ -283,6 +280,7 @@ io_free(struct io *io) io->sock = -1; } + free(io->name); iobuf_clear(&io->iobuf); free(io); } @@ -368,7 +366,7 @@ io_set_read(struct io *io) mode = io->flags & IO_RW; if (!(mode == 0 || mode == IO_WRITE)) - errx(1, "io_set_read(): full-duplex or reading"); + errx(1, "io_set_read: full-duplex or reading"); io->flags &= ~IO_RW; io->flags |= IO_READ; @@ -384,7 +382,7 @@ io_set_write(struct io *io) mode = io->flags & IO_RW; if (!(mode == 0 || mode == IO_READ)) - errx(1, "io_set_write(): full-duplex or writing"); + errx(1, "io_set_write: full-duplex or writing"); io->flags &= ~IO_RW; io->flags |= IO_WRITE; @@ -397,7 +395,7 @@ io_error(struct io *io) return io->error; } -void * +struct tls * io_tls(struct io *io) { return io->tls; @@ -807,57 +805,85 @@ io_dispatch_connect(int fd, short ev, void *humppa) } #ifdef IO_TLS - -static const char* -io_tls_error(void) +int +io_connect_tls(struct io *io, struct tls *tls, const char *hostname) { - static char buf[128]; - unsigned long e; + int mode; + + mode = io->flags & IO_RW; + if (mode != IO_WRITE) + errx(1, "io_connect_tls: expect IO_WRITE mode"); + + if (io->tls) + errx(1, "io_connect_tls: TLS already started"); - e = ERR_peek_last_error(); - if (e) { - ERR_error_string(e, buf); - return (buf); + if (hostname) { + if ((io->name = strdup(hostname)) == NULL) + err(1, "io_connect_tls"); } - return ("No TLS error"); + io->tls = tls; + io->state = IO_STATE_CONNECT_TLS; + io_reset(io, EV_WRITE, io_dispatch_connect_tls); + + return (0); } int -io_start_tls(struct io *io, void *tls) +io_accept_tls(struct io *io, struct tls *tls) { int mode; mode = io->flags & IO_RW; - if (mode == 0 || mode == IO_RW) - errx(1, "io_start_tls(): full-duplex or unset"); + if (mode != IO_READ) + errx(1, "io_accept_tls: expect IO_READ mode"); if (io->tls) - errx(1, "io_start_tls(): TLS already started"); + errx(1, "io_accept_tls: TLS already started"); io->tls = tls; + io->state = IO_STATE_ACCEPT_TLS; + io_reset(io, EV_READ, io_dispatch_accept_tls); + + return (0); +} - if (SSL_set_fd(io->tls, io->sock) == 0) { - ssl_error("io_start_tls:SSL_set_fd"); - return (-1); +void +io_dispatch_handshake_tls(int fd, short event, void *humppa) +{ + struct io *io = humppa; + int ret; + + io_frame_enter("io_dispatch_handshake_tls", io, event); + + if (event == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; } - if (mode == IO_WRITE) { - io->state = IO_STATE_CONNECT_TLS; - SSL_set_connect_state(io->tls); - io_reset(io, EV_WRITE, io_dispatch_connect_tls); - } else { - io->state = IO_STATE_ACCEPT_TLS; - SSL_set_accept_state(io->tls); - io_reset(io, EV_READ, io_dispatch_accept_tls); + if ((ret = tls_handshake(io->tls)) == 0) { + io->state = IO_STATE_UP; + io_callback(io, IO_TLSREADY); + goto leave; + } + if (ret == TLS_WANT_POLLIN) + io_reset(io, EV_READ, io_dispatch_handshake_tls); + else if (ret == TLS_WANT_POLLOUT) + io_reset(io, EV_WRITE, io_dispatch_handshake_tls); + else { + io->error = tls_error(io->tls); + io_callback(io, IO_ERROR); } - return (0); + leave: + io_frame_leave(io); + return; } void io_dispatch_accept_tls(int fd, short event, void *humppa) { struct io *io = humppa; + struct tls *cctx = NULL; int ret; io_frame_enter("io_dispatch_accept_tls", io, event); @@ -867,28 +893,17 @@ io_dispatch_accept_tls(int fd, short event, void *humppa) goto leave; } - if ((ret = SSL_accept(io->tls)) > 0) { - io->state = IO_STATE_UP; - io_callback(io, IO_TLSREADY); + if ((ret = tls_accept_socket(io->tls, &cctx, io->sock)) == 0) { + io->tls = cctx; + io_reset(io, EV_READ|EV_WRITE, io_dispatch_handshake_tls); goto leave; } + io->error = tls_error(io->tls); + io_callback(io, IO_ERROR); - switch (SSL_get_error(io->tls, ret)) { - case SSL_ERROR_WANT_READ: - io_reset(io, EV_READ, io_dispatch_accept_tls); - break; - case SSL_ERROR_WANT_WRITE: - io_reset(io, EV_WRITE, io_dispatch_accept_tls); - break; - default: - io->error = io_tls_error(); - ssl_error("io_dispatch_accept_tls:SSL_accept"); - io_callback(io, IO_ERROR); - break; - } - - leave: + leave: io_frame_leave(io); + return; } void @@ -904,27 +919,15 @@ io_dispatch_connect_tls(int fd, short event, void *humppa) goto leave; } - if ((ret = SSL_connect(io->tls)) > 0) { - io->state = IO_STATE_UP; - io_callback(io, IO_TLSREADY); + if ((ret = tls_connect_socket(io->tls, io->sock, io->name)) == 0) { + io_reset(io, EV_READ|EV_WRITE, io_dispatch_handshake_tls); goto leave; } - switch (SSL_get_error(io->tls, ret)) { - case SSL_ERROR_WANT_READ: - io_reset(io, EV_READ, io_dispatch_connect_tls); - break; - case SSL_ERROR_WANT_WRITE: - io_reset(io, EV_WRITE, io_dispatch_connect_tls); - break; - default: - io->error = io_tls_error(); - ssl_error("io_dispatch_connect_ssl:SSL_connect"); - io_callback(io, IO_TLSERROR); - break; - } + io->error = tls_error(io->tls); + io_callback(io, IO_ERROR); - leave: + leave: io_frame_leave(io); } @@ -932,7 +935,7 @@ void io_dispatch_read_tls(int fd, short event, void *humppa) { struct io *io = humppa; - int n, saved_errno; + int n; io_frame_enter("io_dispatch_read_tls", io, event); @@ -943,7 +946,7 @@ io_dispatch_read_tls(int fd, short event, void *humppa) again: iobuf_normalize(&io->iobuf); - switch ((n = iobuf_read_tls(&io->iobuf, (SSL*)io->tls))) { + switch ((n = iobuf_read_tls(&io->iobuf, io->tls))) { case IOBUF_WANT_READ: io_reset(io, EV_READ, io_dispatch_read_tls); break; @@ -954,20 +957,13 @@ again: io_callback(io, IO_DISCONNECTED); break; case IOBUF_ERROR: - saved_errno = errno; - io->error = strerror(errno); - errno = saved_errno; - io_callback(io, IO_ERROR); - break; - case IOBUF_TLSERROR: - io->error = io_tls_error(); - ssl_error("io_dispatch_read_tls:SSL_read"); + io->error = tls_error(io->tls); io_callback(io, IO_ERROR); break; default: io_debug("io_dispatch_read_tls(...) -> r=%d\n", n); io_callback(io, IO_DATAIN); - if (current == io && IO_READING(io) && SSL_pending(io->tls)) + if (current == io && IO_READING(io)) goto again; } @@ -979,7 +975,7 @@ void io_dispatch_write_tls(int fd, short event, void *humppa) { struct io *io = humppa; - int n, saved_errno; + int n; size_t w2, w; io_frame_enter("io_dispatch_write_tls", io, event); @@ -990,7 +986,7 @@ io_dispatch_write_tls(int fd, short event, void *humppa) } w = io_queued(io); - switch ((n = iobuf_write_tls(&io->iobuf, (SSL*)io->tls))) { + switch ((n = iobuf_write_tls(&io->iobuf, io->tls))) { case IOBUF_WANT_READ: io_reset(io, EV_READ, io_dispatch_write_tls); break; @@ -1001,14 +997,7 @@ io_dispatch_write_tls(int fd, short event, void *humppa) io_callback(io, IO_DISCONNECTED); break; case IOBUF_ERROR: - saved_errno = errno; - io->error = strerror(errno); - errno = saved_errno; - io_callback(io, IO_ERROR); - break; - case IOBUF_TLSERROR: - io->error = io_tls_error(); - ssl_error("io_dispatch_write_tls:SSL_write"); + io->error = tls_error(io->tls); io_callback(io, IO_ERROR); break; default: @@ -1053,7 +1042,7 @@ io_reload_tls(struct io *io) return; /* paused */ break; default: - errx(1, "io_reload_tls(): bad state"); + errx(1, "io_reload_tls: bad state"); } io_reset(io, ev, dispatch); diff --git a/usr.sbin/smtpd/ioev.h b/usr.sbin/smtpd/ioev.h index f155a7fc23c..96b9e5c44e2 100644 --- a/usr.sbin/smtpd/ioev.h +++ b/usr.sbin/smtpd/ioev.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ioev.h,v 1.18 2019/09/11 04:19:19 martijn Exp $ */ +/* $OpenBSD: ioev.h,v 1.19 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2012 Eric Faurot * @@ -18,7 +18,6 @@ enum { IO_CONNECTED = 0, /* connection successful */ IO_TLSREADY, /* TLS started successfully */ - IO_TLSERROR, /* XXX - needs more work */ IO_DATAIN, /* new data in input buffer */ IO_LOWAT, /* output queue running low */ IO_DISCONNECTED, /* error? */ @@ -30,6 +29,7 @@ enum { #define IO_OUT 0x02 struct io; +struct tls; void io_set_nonblocking(int); void io_set_nolinger(int); @@ -46,11 +46,12 @@ void io_pause(struct io *, int); void io_resume(struct io *, int); void io_reload(struct io *); int io_connect(struct io *, const struct sockaddr *, const struct sockaddr *); -int io_start_tls(struct io *, void *); +int io_connect_tls(struct io *, struct tls *, const char *); +int io_accept_tls(struct io *, struct tls *); const char* io_strio(struct io *); const char* io_strevent(int); const char* io_error(struct io *); -void* io_tls(struct io *); +struct tls* io_tls(struct io *); int io_fileno(struct io *); int io_paused(struct io *, int); diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c index ea79e2dc6ec..74dd7c71ba0 100644 --- a/usr.sbin/smtpd/mta.c +++ b/usr.sbin/smtpd/mta.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta.c,v 1.234 2019/12/21 10:34:07 gilles Exp $ */ +/* $OpenBSD: mta.c,v 1.235 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard @@ -38,10 +38,12 @@ #include #include #include +#include #include #include "smtpd.h" #include "log.h" +#include "ssl.h" #define MAXERROR_PER_ROUTE 4 @@ -57,6 +59,7 @@ #define RELAY_ONHOLD 0x01 #define RELAY_HOLDQ 0x02 +static void mta_setup_dispatcher(struct dispatcher *); static void mta_handle_envelope(struct envelope *, const char *); static void mta_query_smarthost(struct envelope *); static void mta_on_smarthost(struct envelope *, const char *); @@ -138,6 +141,12 @@ int mta_is_blocked(struct mta_source *, char *); static int mta_block_cmp(const struct mta_block *, const struct mta_block *); SPLAY_PROTOTYPE(mta_block_tree, mta_block, entry, mta_block_cmp); +/* + * This function is not publicy exported because it is a hack until libtls + * has a proper privsep setup + */ +void tls_config_use_fake_private_key(struct tls_config *config); + static struct mta_relay_tree relays; static struct mta_domain_tree domains; static struct mta_host_tree hosts; @@ -463,6 +472,70 @@ mta_imsg(struct mproc *p, struct imsg *imsg) void mta_postfork(void) { + struct dispatcher *dispatcher; + const char *key; + void *iter; + + iter = NULL; + while (dict_iter(env->sc_dispatchers, &iter, &key, (void **)&dispatcher)) { + log_debug("%s: %s", __func__, key); + mta_setup_dispatcher(dispatcher); + } +} + +static void +mta_setup_dispatcher(struct dispatcher *dispatcher) +{ + struct dispatcher_remote *remote; + static const char *dheparams[] = { "none", "auto", "legacy" }; + struct tls_config *config; + struct pki *pki; + struct ca *ca; + + if (dispatcher->type != DISPATCHER_REMOTE) + return; + + remote = &dispatcher->u.remote; + + if ((config = tls_config_new()) == NULL) + fatal("smtpd: tls_config_new"); + + if (env->sc_tls_ciphers) { + if (tls_config_set_ciphers(config, env->sc_tls_ciphers) == -1) + err(1, "%s", tls_config_error(config)); + } + + if (remote->pki) { + pki = dict_get(env->sc_pki_dict, remote->pki); + if (pki == NULL) + err(1, "client pki \"%s\" not found ", remote->pki); + + tls_config_set_dheparams(config, dheparams[pki->pki_dhe]); + tls_config_use_fake_private_key(config); + if (tls_config_set_keypair_mem(config, pki->pki_cert, + pki->pki_cert_len, NULL, 0) == -1) + fatal("tls_config_set_keypair_mem"); + } + + if (remote->ca) { + ca = dict_get(env->sc_ca_dict, remote->ca); + if (tls_config_set_ca_mem(config, ca->ca_cert, ca->ca_cert_len) + == -1) + fatal("tls_config_set_ca_mem"); + } + else if (tls_config_set_ca_file(config, tls_default_ca_cert_file()) + == -1) + fatal("tls_config_set_ca_file"); + + if (remote->tls_noverify) { + tls_config_insecure_noverifycert(config); + tls_config_insecure_noverifyname(config); + tls_config_insecure_noverifytime(config); + } + else + tls_config_verify(config); + + remote->tls_config = config; } void diff --git a/usr.sbin/smtpd/mta_session.c b/usr.sbin/smtpd/mta_session.c index 6f3917b5ffd..2445378af72 100644 --- a/usr.sbin/smtpd/mta_session.c +++ b/usr.sbin/smtpd/mta_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta_session.c,v 1.138 2020/12/21 11:44:07 martijn Exp $ */ +/* $OpenBSD: mta_session.c,v 1.139 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard @@ -43,6 +43,7 @@ #include #include #include +#include #include #include "smtpd.h" @@ -153,11 +154,8 @@ static void mta_send(struct mta_session *, char *, ...); static ssize_t mta_queue_data(struct mta_session *); static void mta_response(struct mta_session *, char *); static const char * mta_strstate(int); -static void mta_cert_init(struct mta_session *); -static void mta_cert_init_cb(void *, int, const char *, const void *, size_t); -static void mta_cert_verify(struct mta_session *); -static void mta_cert_verify_cb(void *, int); -static void mta_tls_verified(struct mta_session *); +static void mta_tls_init(struct mta_session *); +static void mta_tls_started(struct mta_session *); static struct mta_session *mta_tree_pop(struct tree *, uint64_t); static const char * dsn_strret(enum dsn_ret); static const char * dsn_strnotify(uint8_t); @@ -974,7 +972,7 @@ mta_response(struct mta_session *s, char *line) return; } - mta_cert_init(s); + mta_tls_init(s); break; case MTA_AUTH_PLAIN: @@ -1229,7 +1227,7 @@ mta_io(struct io *io, int evt, void *arg) if (s->use_smtps) { io_set_write(io); - mta_cert_init(s); + mta_tls_init(s); } else { mta_enter_state(s, MTA_BANNER); @@ -1239,13 +1237,14 @@ mta_io(struct io *io, int evt, void *arg) case IO_TLSREADY: log_info("%016"PRIx64" mta tls ciphers=%s", - s->id, ssl_to_text(io_tls(s->io))); + s->id, tls_to_text(io_tls(s->io))); s->flags |= MTA_TLS; + if (!s->relay->dispatcher->u.remote.tls_noverify) + s->flags |= MTA_TLS_VERIFIED; + mta_tls_started(s); mta_report_link_tls(s, - ssl_to_text(io_tls(s->io))); - - mta_cert_verify(s); + tls_to_text(io_tls(s->io))); break; case IO_DATAIN: @@ -1378,7 +1377,6 @@ mta_io(struct io *io, int evt, void *arg) break; case IO_ERROR: - case IO_TLSERROR: log_debug("debug: mta: %p: IO error: %s", s, io_error(io)); if (s->state == MTA_STARTTLS && s->use_smtp_tls) { @@ -1579,152 +1577,42 @@ mta_error(struct mta_session *s, const char *fmt, ...) } static void -mta_cert_init(struct mta_session *s) +mta_tls_init(struct mta_session *s) { - const char *name; - int fallback; - - if (s->relay->pki_name) { - name = s->relay->pki_name; - fallback = 0; - } - else { - name = s->helo; - fallback = 1; - } + struct tls_config *tls_config; + struct tls *tls; - if (cert_init(name, fallback, mta_cert_init_cb, s)) { - tree_xset(&wait_tls_init, s->id, s); - s->flags |= MTA_WAIT; - } -} - -static void -mta_cert_init_cb(void *arg, int status, const char *name, const void *cert, - size_t cert_len) -{ - struct mta_session *s = arg; - void *ssl; - char *xname = NULL, *xcert = NULL; - union { - struct in_addr in4; - struct in6_addr in6; - } addrbuf; - - if (s->flags & MTA_WAIT) - mta_tree_pop(&wait_tls_init, s->id); - - if (status == CA_FAIL && s->relay->pki_name) { - log_info("%016"PRIx64" mta closing reason=ca-failure", s->id); + if ((tls = tls_client()) == NULL) { + log_info("%016"PRIx64" mta closing reason=tls-failure", s->id); mta_free(s); return; } - if (name) - xname = xstrdup(name); - if (cert) - xcert = xmemdup(cert, cert_len); - ssl = ssl_mta_init(xname, xcert, cert_len, env->sc_tls_ciphers); - free(xname); - free(xcert); - if (ssl == NULL) - fatal("mta: ssl_mta_init"); - - /* - * RFC4366 (SNI): Literal IPv4 and IPv6 addresses are not - * permitted in "HostName". - */ - if (s->relay->domain->as_host == 1) { - if (inet_pton(AF_INET, s->relay->domain->name, &addrbuf) != 1 && - inet_pton(AF_INET6, s->relay->domain->name, &addrbuf) != 1) { - log_debug("%016"PRIx64" mta tls setting SNI name=%s", - s->id, s->relay->domain->name); - if (SSL_set_tlsext_host_name(ssl, s->relay->domain->name) == 0) - log_warnx("%016"PRIx64" mta tls setting SNI failed", - s->id); - } - } - - io_start_tls(s->io, ssl); -} - -static void -mta_cert_verify(struct mta_session *s) -{ - const char *name; - int fallback; - - if (s->relay->ca_name) { - name = s->relay->ca_name; - fallback = 0; - } - else { - name = s->helo; - fallback = 1; - } - - if (cert_verify(io_tls(s->io), name, fallback, mta_cert_verify_cb, s)) { - tree_xset(&wait_tls_verify, s->id, s); - io_pause(s->io, IO_IN); - s->flags |= MTA_WAIT; - } -} - -static void -mta_cert_verify_cb(void *arg, int status) -{ - struct mta_session *s = arg; - int match, resume = 0; - X509 *cert; - - if (s->flags & MTA_WAIT) { - mta_tree_pop(&wait_tls_verify, s->id); - resume = 1; - } - - if (status == CERT_OK) { - cert = SSL_get_peer_certificate(io_tls(s->io)); - if (!cert) - status = CERT_NOCERT; - else { - match = 0; - (void)ssl_check_name(cert, s->mxname, &match); - X509_free(cert); - if (!match) { - log_info("%016"PRIx64" mta " - "ssl_check_name: no match for '%s' in cert", - s->id, s->mxname); - status = CERT_INVALID; - } - } - } - - if (status == CERT_OK) - s->flags |= MTA_TLS_VERIFIED; - else if (s->relay->flags & RELAY_TLS_VERIFY) { - errno = 0; - mta_error(s, "SSL certificate check failed"); + tls_config = s->relay->dispatcher->u.remote.tls_config; + if (tls_configure(tls, tls_config) == -1) { + log_info("%016"PRIx64" mta closing reason=tls-failure", s->id); + tls_free(tls); mta_free(s); return; } - mta_tls_verified(s); - if (resume) - io_resume(s->io, IO_IN); + io_connect_tls(s->io, tls, s->route->dst->ptrname); } static void -mta_tls_verified(struct mta_session *s) +mta_tls_started(struct mta_session *s) { - X509 *x; - - x = SSL_get_peer_certificate(io_tls(s->io)); - if (x) { - log_info("%016"PRIx64" mta " - "server-cert-check result=\"%s\"", - s->id, - (s->flags & MTA_TLS_VERIFIED) ? "success" : "failure"); - X509_free(x); + if (tls_peer_cert_provided(io_tls(s->io))) { + log_info("%016"PRIx64" mta " + "cert-check result=\"%s\" fingerprint=\"%s\"", + s->id, + (s->flags & MTA_TLS_VERIFIED) ? "valid" : "unverified", + tls_peer_cert_hash(io_tls(s->io))); + } + else { + log_info("%016"PRIx64" smtp " + "cert-check result=\"no certificate presented\"", + s->id); } if (s->use_smtps) { diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y index c2022bdbeae..40f78be84f7 100644 --- a/usr.sbin/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.284 2021/01/23 16:11:11 rob Exp $ */ +/* $OpenBSD: parse.y,v 1.285 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade @@ -128,13 +128,15 @@ enum listen_options { LO_PROXY = 0x008000, }; +#define PKI_MAX 32 static struct listen_opts { char *ifx; int family; in_port_t port; uint16_t ssl; char *filtername; - char *pki; + char *pki[PKI_MAX]; + int pkicount; char *ca; uint16_t auth; struct table *authtable; @@ -2316,12 +2318,11 @@ opt_if_listen : INET4 { listen_opts.ssl = F_STARTTLS|F_STARTTLS_REQUIRE|F_TLS_VERIFY; } | PKI STRING { - if (listen_opts.options & LO_PKI) { - yyerror("pki already specified"); + if (listen_opts.pkicount == PKI_MAX) { + yyerror("too many pki specified"); YYERROR; } - listen_opts.options |= LO_PKI; - listen_opts.pki = $2; + listen_opts.pki[listen_opts.pkicount++] = $2; } | CA STRING { if (listen_opts.options & LO_CA) { @@ -3221,8 +3222,10 @@ create_if_listener(struct listen_opts *lo) if (lo->auth != 0 && !lo->ssl) errx(1, "invalid listen option: auth requires tls/smtps"); - if (lo->pki && !lo->ssl) + if (lo->pkicount && !lo->ssl) errx(1, "invalid listen option: pki requires tls/smtps"); + if (lo->pkicount == 0 && lo->ssl) + errx(1, "invalid listen option: pki required for tls/smtps"); flags = lo->flags; @@ -3259,6 +3262,8 @@ create_if_listener(struct listen_opts *lo) static void config_listener(struct listener *h, struct listen_opts *lo) { + int i; + h->fd = -1; h->port = lo->port; h->flags = lo->flags; @@ -3273,17 +3278,19 @@ config_listener(struct listener *h, struct listen_opts *lo) sizeof(h->filter_name)); } - h->pki_name[0] = '\0'; - if (lo->authtable != NULL) (void)strlcpy(h->authtable, lo->authtable->t_name, sizeof(h->authtable)); - if (lo->pki != NULL) { - if (!lowercase(h->pki_name, lo->pki, sizeof(h->pki_name))) { - log_warnx("pki name too long: %s", lo->pki); - fatalx(NULL); - } - if (dict_get(conf->sc_pki_dict, h->pki_name) == NULL) { - log_warnx("pki name not found: %s", lo->pki); + + h->pkicount = lo->pkicount; + if (h->pkicount) { + h->pki = calloc(h->pkicount, sizeof(*h->pki)); + if (h->pki == NULL) + fatal("calloc"); + } + for (i = 0; i < lo->pkicount; i++) { + h->pki[i] = dict_get(conf->sc_pki_dict, lo->pki[i]); + if (h->pki[i] == NULL) { + log_warnx("pki name not found: %s", lo->pki[i]); fatalx(NULL); } } diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c index 35776c4f3a2..171025d0216 100644 --- a/usr.sbin/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp.c,v 1.166 2019/08/10 16:07:01 gilles Exp $ */ +/* $OpenBSD: smtp.c,v 1.167 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -50,7 +51,7 @@ static void smtp_dropped(struct listener *, int, const struct sockaddr_storage * static int smtp_enqueue(void); static int smtp_can_accept(void); static void smtp_setup_listeners(void); -static int smtp_sni_callback(SSL *, int *, void *); +static void smtp_setup_listener_tls(struct listener *); int proxy_session(struct listener *listener, int sock, @@ -62,6 +63,11 @@ proxy_session(struct listener *listener, int sock, static void smtp_accepted(struct listener *, int, const struct sockaddr_storage *, struct io *); +/* + * This function are not publicy exported because it is a hack until libtls + * has a proper privsep setup + */ +void tls_config_use_fake_private_key(struct tls_config *config); #define SMTP_FD_RESERVE 5 static size_t sessions; @@ -145,6 +151,10 @@ smtp_setup_listeners(void) } fatal("smtpd: socket"); } + + if (l->flags & F_SSL) + smtp_setup_listener_tls(l); + opt = 1; if (setsockopt(l->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) @@ -154,20 +164,78 @@ smtp_setup_listeners(void) } } +static void +smtp_setup_listener_tls(struct listener *l) +{ + static const char *dheparams[] = { "none", "auto", "legacy" }; + struct tls_config *config; + struct pki *pki; + struct ca *ca; + int i; + + if ((config = tls_config_new()) == NULL) + fatal("smtpd: tls_config_new"); + + if (env->sc_tls_ciphers && + tls_config_set_ciphers(config, env->sc_tls_ciphers) == -1) + err(1, "%s", tls_config_error(config)); + + pki = l->pki[0]; + if (pki == NULL) + fatal("no pki defined"); + + if (tls_config_set_dheparams(config, dheparams[pki->pki_dhe]) == -1) + fatal("tls_config_set_dheparams"); + + tls_config_use_fake_private_key(config); + for (i = 0; i < l->pkicount; i++) { + pki = l->pki[i]; + if (i == 0) { + if (tls_config_set_keypair_mem(config, pki->pki_cert, + pki->pki_cert_len, NULL, 0) == -1) + fatal("tls_config_set_keypair_mem"); + } else { + if (tls_config_add_keypair_mem(config, pki->pki_cert, + pki->pki_cert_len, NULL, 0) == -1) + fatal("tls_config_add_keypair_mem"); + } + } + free(l->pki); + l->pkicount = 0; + + if (l->ca_name[0]) { + ca = dict_get(env->sc_ca_dict, l->ca_name); + if (tls_config_set_ca_mem(config, ca->ca_cert, ca->ca_cert_len) + == -1) + fatal("tls_config_set_ca_mem"); + } + else if (tls_config_set_ca_file(config, tls_default_ca_cert_file()) + == -1) + fatal("tls_config_set_ca_file"); + + if (l->flags & F_TLS_VERIFY) + tls_config_verify_client(config); + else + tls_config_verify_client_optional(config); + + l->tls = tls_server(); + if (l->tls == NULL) + fatal("tls_server"); + if (tls_configure(l->tls, config) == -1) { + fatal("tls_configure: %s", tls_error(l->tls)); + } + tls_config_free(config); +} + + static void smtp_setup_events(void) { struct listener *l; - struct pki *pki; - SSL_CTX *ssl_ctx; - void *iter; - const char *k; TAILQ_FOREACH(l, env->sc_listeners, entry) { - log_debug("debug: smtp: listen on %s port %d flags 0x%01x" - " pki \"%s\"" - " ca \"%s\"", ss_to_text(&l->ss), ntohs(l->port), - l->flags, l->pki_name, l->ca_name); + log_debug("debug: smtp: listen on %s port %d flags 0x%01x", + ss_to_text(&l->ss), ntohs(l->port), l->flags); io_set_nonblocking(l->fd); if (listen(l->fd, SMTPD_BACKLOG) == -1) @@ -178,14 +246,6 @@ smtp_setup_events(void) event_add(&l->ev, NULL); } - iter = NULL; - while (dict_iter(env->sc_pki_dict, &iter, &k, (void **)&pki)) { - if (!ssl_setup((SSL_CTX **)&ssl_ctx, pki, smtp_sni_callback, - env->sc_tls_ciphers)) - fatal("smtp_setup_events: ssl_setup failure"); - dict_xset(env->sc_ssl_dict, k, ssl_ctx); - } - purge_config(PURGE_PKI_KEYS); maxsessions = (getdtablesize() - getdtablecount()) / 2 - SMTP_FD_RESERVE; @@ -319,22 +379,6 @@ smtp_collect(void) } } -static int -smtp_sni_callback(SSL *ssl, int *ad, void *arg) -{ - const char *sn; - void *ssl_ctx; - - sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - if (sn == NULL) - return SSL_TLSEXT_ERR_NOACK; - ssl_ctx = dict_get(env->sc_ssl_dict, sn); - if (ssl_ctx == NULL) - return SSL_TLSEXT_ERR_NOACK; - SSL_set_SSL_CTX(ssl, ssl_ctx); - return SSL_TLSEXT_ERR_OK; -} - static void smtp_accepted(struct listener *listener, int sock, const struct sockaddr_storage *ss, struct io *io) { diff --git a/usr.sbin/smtpd/smtp.h b/usr.sbin/smtpd/smtp.h index dc91d878940..11ab6cbedbe 100644 --- a/usr.sbin/smtpd/smtp.h +++ b/usr.sbin/smtpd/smtp.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp.h,v 1.3 2019/09/02 20:05:21 eric Exp $ */ +/* $OpenBSD: smtp.h,v 1.4 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2018 Eric Faurot @@ -46,6 +46,7 @@ struct smtp_params { /* TLS options */ int tls_req; /* requested TLS mode */ int tls_verify; /* need valid server certificate */ + const char *tls_servname; /* SNI */ /* SMTP options */ int lmtp; /* use LMTP protocol */ diff --git a/usr.sbin/smtpd/smtp/Makefile b/usr.sbin/smtpd/smtp/Makefile index fc47fa03935..db5f178312f 100644 --- a/usr.sbin/smtpd/smtp/Makefile +++ b/usr.sbin/smtpd/smtp/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.3 2019/09/18 11:26:30 eric Exp $ +# $OpenBSD: Makefile,v 1.4 2021/03/05 12:37:32 eric Exp $ .PATH: ${.CURDIR}/.. @@ -17,7 +17,7 @@ SRCS+= ssl_verify.c CPPFLAGS+= -DIO_TLS -LDADD+= -levent -lutil -lssl -lcrypto -lm -lz -DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} +LDADD+= -levent -lutil -ltls -lssl -lcrypto -lm -lz +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} .include diff --git a/usr.sbin/smtpd/smtp_client.c b/usr.sbin/smtpd/smtp_client.c index 8e146e1b7c2..372e50ac51f 100644 --- a/usr.sbin/smtpd/smtp_client.c +++ b/usr.sbin/smtpd/smtp_client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp_client.c,v 1.14 2020/04/24 11:34:07 eric Exp $ */ +/* $OpenBSD: smtp_client.c,v 1.15 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2018 Eric Faurot @@ -187,7 +187,7 @@ smtp_cert_verified(struct smtp_client *proto, int verified) void smtp_set_tls(struct smtp_client *proto, void *ctx) { - io_start_tls(proto->io, ctx); + io_connect_tls(proto->io, ctx, proto->params.tls_servname); } void @@ -624,8 +624,13 @@ smtp_client_io(struct io *io, int evt, void *arg) case IO_TLSREADY: proto->flags |= FLAG_TLS; - io_pause(proto->io, IO_IN); - smtp_verify_server_cert(proto->tag, proto, io_tls(proto->io)); + if (proto->state == STATE_INIT) + smtp_client_state(proto, STATE_BANNER); + else { + /* Clear extensions before re-issueing an EHLO command. */ + proto->ext = 0; + smtp_client_state(proto, STATE_EHLO); + } break; case IO_DATAIN: @@ -649,10 +654,6 @@ smtp_client_io(struct io *io, int evt, void *arg) smtp_client_abort(proto, FAIL_CONN, io_error(io)); break; - case IO_TLSERROR: - smtp_client_abort(proto, FAIL_CONN, io_error(io)); - break; - case IO_DISCONNECTED: smtp_client_abort(proto, FAIL_CONN, io_error(io)); break; diff --git a/usr.sbin/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c index 469f161efa3..96de6dc2ae8 100644 --- a/usr.sbin/smtpd/smtp_session.c +++ b/usr.sbin/smtpd/smtp_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp_session.c,v 1.428 2020/12/21 11:44:07 martijn Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.429 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -184,7 +185,8 @@ static void smtp_getnameinfo_cb(void *, int, const char *, const char *); static void smtp_getaddrinfo_cb(void *, int, struct addrinfo *); static void smtp_connected(struct smtp_session *); static void smtp_send_banner(struct smtp_session *); -static void smtp_tls_verified(struct smtp_session *); +static void smtp_tls_init(struct smtp_session *); +static void smtp_tls_started(struct smtp_session *); static void smtp_io(struct io *, int, void *); static void smtp_enter_state(struct smtp_session *, int); static void smtp_reply(struct smtp_session *, char *, ...); @@ -193,10 +195,6 @@ static void smtp_rfc4954_auth_plain(struct smtp_session *, char *); static void smtp_rfc4954_auth_login(struct smtp_session *, char *); static void smtp_free(struct smtp_session *, const char *); static const char *smtp_strstate(int); -static void smtp_cert_init(struct smtp_session *); -static void smtp_cert_init_cb(void *, int, const char *, const void *, size_t); -static void smtp_cert_verify(struct smtp_session *); -static void smtp_cert_verify_cb(void *, int); static void smtp_auth_failure_pause(struct smtp_session *); static void smtp_auth_failure_resume(int, short, void *); @@ -1066,17 +1064,26 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) } static void -smtp_tls_verified(struct smtp_session *s) +smtp_tls_init(struct smtp_session *s) { - X509 *x; + io_set_read(s->io); + io_accept_tls(s->io, s->listener->tls); +} - x = SSL_get_peer_certificate(io_tls(s->io)); - if (x) { +static void +smtp_tls_started(struct smtp_session *s) +{ + if (tls_peer_cert_provided(io_tls(s->io))) { log_info("%016"PRIx64" smtp " - "client-cert-check result=\"%s\"", + "cert-check result=\"%s\" fingerprint=\"%s\"", s->id, - (s->flags & SF_VERIFIED) ? "success" : "failure"); - X509_free(x); + (s->flags & SF_VERIFIED) ? "verified" : "unchecked", + tls_peer_cert_hash(io_tls(s->io))); + } + else { + log_info("%016"PRIx64" smtp " + "cert-check result=\"no certificate presented\"", + s->id); } if (s->listener->flags & F_SMTPS) { @@ -1105,14 +1112,16 @@ smtp_io(struct io *io, int evt, void *arg) case IO_TLSREADY: log_info("%016"PRIx64" smtp tls ciphers=%s", - s->id, ssl_to_text(io_tls(s->io))); + s->id, tls_to_text(io_tls(s->io))); - smtp_report_link_tls(s, ssl_to_text(io_tls(s->io))); + smtp_report_link_tls(s, tls_to_text(io_tls(s->io))); s->flags |= SF_SECURE; + if (s->listener->flags & F_TLS_VERIFY) + s->flags |= SF_VERIFIED; s->helo[0] = '\0'; - smtp_cert_verify(s); + smtp_tls_started(s); break; case IO_DATAIN: @@ -1193,7 +1202,7 @@ smtp_io(struct io *io, int evt, void *arg) /* Wait for the client to start tls */ if (s->state == STATE_TLS) { - smtp_cert_init(s); + smtp_tls_init(s); break; } @@ -2071,7 +2080,7 @@ static void smtp_proceed_connected(struct smtp_session *s) { if (s->listener->flags & F_SMTPS) - smtp_cert_init(s); + smtp_tls_init(s); else smtp_send_banner(s); } @@ -2260,112 +2269,6 @@ smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args, return (1); } -static void -smtp_cert_init(struct smtp_session *s) -{ - const char *name; - int fallback; - - if (s->listener->pki_name[0]) { - name = s->listener->pki_name; - fallback = 0; - } - else { - name = s->smtpname; - fallback = 1; - } - - if (cert_init(name, fallback, smtp_cert_init_cb, s)) - tree_xset(&wait_ssl_init, s->id, s); -} - -static void -smtp_cert_init_cb(void *arg, int status, const char *name, const void *cert, - size_t cert_len) -{ - struct smtp_session *s = arg; - void *ssl, *ssl_ctx; - - tree_pop(&wait_ssl_init, s->id); - - if (status == CA_FAIL) { - log_info("%016"PRIx64" smtp disconnected " - "reason=ca-failure", - s->id); - smtp_free(s, "CA failure"); - return; - } - - ssl_ctx = dict_get(env->sc_ssl_dict, name); - ssl = ssl_smtp_init(ssl_ctx, s->listener->flags & F_TLS_VERIFY); - io_set_read(s->io); - io_start_tls(s->io, ssl); -} - -static void -smtp_cert_verify(struct smtp_session *s) -{ - const char *name; - int fallback; - - if (s->listener->ca_name[0]) { - name = s->listener->ca_name; - fallback = 0; - } - else { - name = s->smtpname; - fallback = 1; - } - - if (cert_verify(io_tls(s->io), name, fallback, smtp_cert_verify_cb, s)) { - tree_xset(&wait_ssl_verify, s->id, s); - io_pause(s->io, IO_IN); - } -} - -static void -smtp_cert_verify_cb(void *arg, int status) -{ - struct smtp_session *s = arg; - const char *reason = NULL; - int resume; - - resume = tree_pop(&wait_ssl_verify, s->id) != NULL; - - switch (status) { - case CERT_OK: - reason = "cert-ok"; - s->flags |= SF_VERIFIED; - break; - case CERT_NOCA: - reason = "no-ca"; - break; - case CERT_NOCERT: - reason = "no-client-cert"; - break; - case CERT_INVALID: - reason = "cert-invalid"; - break; - default: - reason = "cert-check-failed"; - break; - } - - log_debug("smtp: %p: smtp_cert_verify_cb: %s", s, reason); - - if (!(s->flags & SF_VERIFIED) && (s->listener->flags & F_TLS_VERIFY)) { - log_info("%016"PRIx64" smtp disconnected " - " reason=%s", s->id, - reason); - smtp_free(s, "SSL certificate check failed"); - return; - } - - smtp_tls_verified(s); - if (resume) - io_resume(s->io, IO_IN); -} - static void smtp_auth_failure_resume(int fd, short event, void *p) { @@ -2844,7 +2747,6 @@ static void smtp_message_begin(struct smtp_tx *tx) { struct smtp_session *s; - X509 *x; int (*m_printf)(struct smtp_tx *, const char *, ...); m_printf = smtp_message_printf; @@ -2879,13 +2781,11 @@ smtp_message_begin(struct smtp_tx *tx) tx->msgid); if (s->flags & SF_SECURE) { - x = SSL_get_peer_certificate(io_tls(s->io)); m_printf(tx, " (%s:%s:%d:%s)", - SSL_get_version(io_tls(s->io)), - SSL_get_cipher_name(io_tls(s->io)), - SSL_get_cipher_bits(io_tls(s->io), NULL), - (s->flags & SF_VERIFIED) ? "YES" : (x ? "FAIL" : "NO")); - X509_free(x); + tls_conn_version(io_tls(s->io)), + tls_conn_cipher(io_tls(s->io)), + tls_conn_cipher_strength(io_tls(s->io)), + (s->flags & SF_VERIFIED) ? "YES" : "NO"); if (s->listener->flags & F_RECEIVEDAUTH) { m_printf(tx, " auth=%s", diff --git a/usr.sbin/smtpd/smtpc.c b/usr.sbin/smtpd/smtpc.c index ec77e1593f8..fd0f8ef3652 100644 --- a/usr.sbin/smtpd/smtpc.c +++ b/usr.sbin/smtpd/smtpc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpc.c,v 1.13 2020/12/29 12:17:54 jmc Exp $ */ +/* $OpenBSD: smtpc.c,v 1.14 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2018 Eric Faurot @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -48,8 +49,7 @@ static struct addrinfo *res0, *ai; static struct smtp_params params; static struct smtp_mail mail; static const char *servname = NULL; - -static SSL_CTX *ssl_ctx; +static struct tls_config *tls_config; static void usage(void) @@ -156,16 +156,20 @@ main(int argc, char **argv) mail.rcptcount = argc; } - ssl_init(); + tls_init(); event_init(); - ssl_ctx = ssl_ctx_create(NULL, NULL, 0, NULL); - if (!SSL_CTX_load_verify_locations(ssl_ctx, - X509_get_default_cert_file(), NULL)) - fatal("SSL_CTX_load_verify_locations"); - if (!SSL_CTX_set_ssl_version(ssl_ctx, SSLv23_client_method())) - fatal("SSL_CTX_set_ssl_version"); - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE , NULL); + tls_config = tls_config_new(); + if (tls_config == NULL) + fatal("tls_config_new"); + if (tls_config_set_ca_file(tls_config, tls_default_ca_cert_file()) == -1) + fatal("tls_set_ca_file"); + if (!params.tls_verify) { + tls_config_insecure_noverifycert(tls_config); + tls_config_insecure_noverifyname(tls_config); + tls_config_insecure_noverifytime(tls_config); + } else + tls_config_verify(tls_config); if (pledge("stdio inet dns tmppath", NULL) == -1) fatal("pledge"); @@ -282,6 +286,7 @@ parse_server(char *server) if (servname == NULL) servname = host; + params.tls_servname = servname; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; @@ -399,11 +404,16 @@ smtp_verify_server_cert(void *tag, struct smtp_client *proto, void *ctx) void smtp_require_tls(void *tag, struct smtp_client *proto) { - SSL *ssl = NULL; + struct tls *tls; + + tls = tls_client(); + if (tls == NULL) + fatal("tls_client"); + + if (tls_configure(tls, tls_config) == -1) + fatal("tls_configure"); - if ((ssl = SSL_new(ssl_ctx)) == NULL) - fatal("SSL_new"); - smtp_set_tls(proto, ssl); + smtp_set_tls(proto, tls); } void diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index ee9a77157bc..329f6d8673b 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.c,v 1.336 2020/12/31 08:27:15 martijn Exp $ */ +/* $OpenBSD: smtpd.c,v 1.337 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -609,7 +610,7 @@ main(int argc, char *argv[]) env->sc_opts |= opts; - ssl_init(); + tls_init(); if (parse_config(conf, conffile, opts)) exit(1); diff --git a/usr.sbin/smtpd/smtpd.conf.5 b/usr.sbin/smtpd/smtpd.conf.5 index 20ac8b55e83..6359cc8434a 100644 --- a/usr.sbin/smtpd/smtpd.conf.5 +++ b/usr.sbin/smtpd/smtpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: smtpd.conf.5,v 1.257 2021/02/13 07:59:54 jmc Exp $ +.\" $OpenBSD: smtpd.conf.5,v 1.258 2021/03/05 12:37:32 eric Exp $ .\" .\" Copyright (c) 2008 Janne Johansson .\" Copyright (c) 2009 Jacek Masiulaniec @@ -17,7 +17,7 @@ .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" -.Dd $Mdocdate: February 13 2021 $ +.Dd $Mdocdate: March 5 2021 $ .Dt SMTPD.CONF 5 .Os .Sh NAME @@ -345,17 +345,9 @@ sending a single warning after four hours. .It Ic ca Ar caname Cm cert Ar cafile Associate the Certificate Authority (CA) certificate file .Ar cafile -with host -.Ar caname , -and use that file as the CA certificate for that host. -.Ar caname -is the server's name, -derived from the default hostname -or set using either -.Pa /etc/mail/mailname -or using the -.Ic hostname -directive. +with ca entry +.Ar caname . +The ca entry can be referenced in listener rules and relay actions. .It Ic filter Ar chain-name Ic chain Brq Ar filter-name Op , Ar ... Register a chain of filters .Ar chain-name , @@ -479,6 +471,8 @@ use the certificate associated with .Ic pki directive) to prove a mail server's identity. +This option can be used multiple times to provide alternate +certificates for SNI. .It Cm port Op Ar port Listen on the given .Ar port @@ -800,21 +794,10 @@ The default is 100. .It Ic pki Ar pkiname Cm cert Ar certfile Associate certificate file .Ar certfile -with host -.Ar pkiname , -and use that file to prove the identity of the mail server to clients. -.Ar pkiname -is the server's name, -derived from the default hostname -or set using either -.Pa /etc/mail/mailname -or using the -.Ic hostname -directive. -If a fallback certificate or SNI is wanted, the -.Sq * -wildcard may be used as +with pki entry .Ar pkiname . +The pki entry defines a keypair configuration that can be referenced +in listener rules and relay actions. .Pp A certificate chain may be created by appending one or many certificates, including a Certificate Authority certificate, @@ -825,10 +808,10 @@ The creation of certificates is documented in .It Ic pki Ar pkiname Cm key Ar keyfile Associate the key located in .Ar keyfile -with host +with pki entry .Ar pkiname . .It Ic pki Ar pkiname Cm dhe Ar params -Specify the DHE parameters to use for DHE cipher suites with host +Specify the DHE parameters to use for DHE cipher suites with pki entry .Ar pkiname . Valid parameter values are .Cm none , diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index 487573da170..c0e4878893f 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.661 2021/01/19 09:16:20 claudio Exp $ */ +/* $OpenBSD: smtpd.h,v 1.662 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade @@ -542,6 +542,10 @@ struct listener { TAILQ_ENTRY(listener) entry; int local; /* there must be a better way */ + + struct tls *tls; + struct pki **pki; + int pkicount; }; struct smtpd { @@ -1176,6 +1180,7 @@ struct dispatcher_remote { char *source; + struct tls_config *tls_config; char *ca; char *pki; @@ -1690,6 +1695,7 @@ const char *rule_to_text(struct rule *); const char *sockaddr_to_text(const struct sockaddr *); const char *mailaddr_to_text(const struct mailaddr *); const char *expandnode_to_text(struct expandnode *); +const char *tls_to_text(struct tls *); /* util.c */ diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index 5a0d0993208..704078f2ce5 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.110 2020/12/31 08:27:15 martijn Exp $ +# $OpenBSD: Makefile,v 1.111 2021/03/05 12:37:32 eric Exp $ .PATH: ${.CURDIR}/.. @@ -82,8 +82,8 @@ SRCS+= stat_ramstat.c MAN= sendmail.8 smtpd.8 smtpd.conf.5 table.5 BINDIR= /usr/sbin -LDADD+= -levent -lutil -lssl -lcrypto -lm -lz -DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} +LDADD+= -levent -lutil -ltls -lssl -lcrypto -lm -lz +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} CFLAGS+= -fstack-protector-all CFLAGS+= -I${.CURDIR}/.. diff --git a/usr.sbin/smtpd/ssl.c b/usr.sbin/smtpd/ssl.c index bd18ad61ec8..c4ec69aba2a 100644 --- a/usr.sbin/smtpd/ssl.c +++ b/usr.sbin/smtpd/ssl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssl.c,v 1.93 2019/06/05 06:40:13 gilles Exp $ */ +/* $OpenBSD: ssl.c,v 1.94 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard @@ -450,3 +450,59 @@ ssl_ctx_fake_private_key(SSL_CTX *ctx, const void *data, size_t datalen, return (ret); } + +static void +hash_x509(X509 *cert, char *hash, size_t hashlen) +{ + static const char hex[] = "0123456789abcdef"; + size_t off; + char digest[EVP_MAX_MD_SIZE]; + int dlen, i; + + if (X509_pubkey_digest(cert, EVP_sha256(), digest, &dlen) != 1) + fatalx("%s: X509_pubkey_digest failed", __func__); + + if (hashlen < 2 * dlen + sizeof("SHA256:")) + fatalx("%s: hash buffer to small", __func__); + + off = strlcpy(hash, "SHA256:", hashlen); + + for (i = 0; i < dlen; i++) { + hash[off++] = hex[(digest[i] >> 4) & 0x0f]; + hash[off++] = hex[digest[i] & 0x0f]; + } + hash[off] = 0; +} + +char * +ssl_pubkey_hash(const char *buf, off_t len) +{ +#define TLS_CERT_HASH_SIZE 128 + BIO *in; + X509 *x509 = NULL; + char *hash = NULL; + + if ((in = BIO_new_mem_buf(buf, len)) == NULL) { + log_warnx("%s: BIO_new_mem_buf failed", __func__); + return NULL; + } + + if ((x509 = PEM_read_bio_X509(in, NULL, NULL, NULL)) == NULL) { + log_warnx("%s: PEM_read_bio_X509 failed", __func__); + goto fail; + } + + if ((hash = malloc(TLS_CERT_HASH_SIZE)) == NULL) { + log_warn("%s: malloc", __func__); + goto fail; + } + hash_x509(x509, hash, TLS_CERT_HASH_SIZE); + +fail: + BIO_free(in); + + if (x509) + X509_free(x509); + + return hash; +} diff --git a/usr.sbin/smtpd/ssl.h b/usr.sbin/smtpd/ssl.h index 1c0dc072d11..9ee1b849999 100644 --- a/usr.sbin/smtpd/ssl.h +++ b/usr.sbin/smtpd/ssl.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ssl.h,v 1.21 2019/09/18 11:26:30 eric Exp $ */ +/* $OpenBSD: ssl.h,v 1.22 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2013 Gilles Chehade * @@ -15,6 +15,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include + #define SSL_CIPHERS "HIGH:!aNULL:!MD5" #define SSL_SESSION_TIMEOUT 300 @@ -62,6 +64,7 @@ int ssl_load_pkey(const void *, size_t, char *, off_t, X509 **, EVP_PKEY **); int ssl_ctx_fake_private_key(SSL_CTX *, const void *, size_t, char *, off_t, X509 **, EVP_PKEY **); +char *ssl_pubkey_hash(const char *, off_t); /* ssl_privsep.c */ int ssl_by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char **); diff --git a/usr.sbin/smtpd/to.c b/usr.sbin/smtpd/to.c index 11cdf59d7b8..8a285f666c6 100644 --- a/usr.sbin/smtpd/to.c +++ b/usr.sbin/smtpd/to.c @@ -1,4 +1,4 @@ -/* $OpenBSD: to.c,v 1.45 2021/01/19 09:16:20 claudio Exp $ */ +/* $OpenBSD: to.c,v 1.46 2021/03/05 12:37:32 eric Exp $ */ /* * Copyright (c) 2009 Jacek Masiulaniec @@ -43,6 +43,9 @@ #include #include #include +#if IO_TLS +#include +#endif #include #include "smtpd.h" @@ -795,3 +798,18 @@ alias_is_error(struct expandnode *alias, const char *line, size_t len) alias->type = EXPAND_ERROR; return 1; } + +#if IO_TLS +const char * +tls_to_text(struct tls *tls) +{ + static char buf[256]; + + (void)snprintf(buf, sizeof buf, "%s:%s:%d", + tls_conn_version(tls), + tls_conn_cipher(tls), + tls_conn_cipher_strength(tls)); + + return (buf); +} +#endif