From d35095c2f73e3ffac865380bdef835293d682244 Mon Sep 17 00:00:00 2001 From: eric Date: Fri, 7 Dec 2018 08:05:59 +0000 Subject: [PATCH] Refactor certificate initialization and verification. Factorize code duplicated in smtp_session.c and mta_session.c Implement a simple callback interface, with proper request management and simplified imsg protocol. Only add the necessary parts for now. Exisiting code path will be adapted later. input from gilles@ sunil@ ok gilles@ --- usr.sbin/smtpd/cert.c | 407 ++++++++++++++++++++++++++++++++++ usr.sbin/smtpd/lka.c | 8 +- usr.sbin/smtpd/pony.c | 7 +- usr.sbin/smtpd/smtpd.c | 6 +- usr.sbin/smtpd/smtpd.h | 15 +- usr.sbin/smtpd/smtpd/Makefile | 3 +- 6 files changed, 441 insertions(+), 5 deletions(-) create mode 100644 usr.sbin/smtpd/cert.c diff --git a/usr.sbin/smtpd/cert.c b/usr.sbin/smtpd/cert.c new file mode 100644 index 00000000000..a716004414f --- /dev/null +++ b/usr.sbin/smtpd/cert.c @@ -0,0 +1,407 @@ +/* $OpenBSD: cert.c,v 1.1 2018/12/07 08:05:59 eric Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "smtpd.h" +#include "ssl.h" + +#define p_cert p_lka + +struct request { + SPLAY_ENTRY(request) entry; + uint32_t id; + void (*cb_get_certificate)(void *, int, const char *, + const void *, size_t); + void (*cb_verify)(void *, int); + void *arg; +}; + +#define MAX_CERTS 16 +#define MAX_CERT_LEN (MAX_IMSGSIZE - (IMSG_HEADER_SIZE + sizeof(size_t))) + +struct session { + SPLAY_ENTRY(session) entry; + uint32_t id; + struct mproc *proc; + char *cert[MAX_CERTS]; + size_t cert_len[MAX_CERTS]; + int cert_count; +}; + +SPLAY_HEAD(cert_reqtree, request); +SPLAY_HEAD(cert_sestree, session); + +static int request_cmp(struct request *, struct request *); +static int session_cmp(struct session *, struct session *); +SPLAY_PROTOTYPE(cert_reqtree, request, entry, request_cmp); +SPLAY_PROTOTYPE(cert_sestree, session, entry, session_cmp); + +static void cert_do_verify(struct session *, const char *, int); +static int cert_X509_verify(struct session *, const char *, const char *); + +static struct cert_reqtree reqs = SPLAY_INITIALIZER(&reqs); +static struct cert_sestree sess = SPLAY_INITIALIZER(&sess); + +int +cert_init(const char *name, int fallback, void (*cb)(void *, int, + const char *, const void *, size_t), void *arg) +{ + struct request *req; + + req = calloc(1, sizeof(*req)); + if (req == NULL) + return -1; + while (req->id == 0 || SPLAY_FIND(cert_reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_get_certificate = cb; + req->arg = arg; + SPLAY_INSERT(cert_reqtree, &reqs, req); + + m_create(p_cert, IMSG_CERT_INIT, req->id, 0, -1); + m_add_string(p_cert, name); + m_add_int(p_cert, fallback); + m_close(p_cert); + + return 0; +} + +int +cert_verify(const void *ssl, const char *name, int fallback, + void (*cb)(void *, int), void *arg) +{ + struct request *req; + X509 *x; + STACK_OF(X509) *xchain; + unsigned char *cert_der[MAX_CERTS]; + int cert_len[MAX_CERTS]; + int i, cert_count, res; + + res = -1; + memset(cert_der, 0, sizeof(cert_der)); + + req = calloc(1, sizeof(*req)); + if (req == NULL) + return -1; + while (req->id == 0 || SPLAY_FIND(cert_reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_verify = cb; + req->arg = arg; + SPLAY_INSERT(cert_reqtree, &reqs, req); + + cert_count = 1; + if ((xchain = SSL_get_peer_cert_chain(ssl))) { + cert_count += sk_X509_num(xchain); + if (cert_count > MAX_CERTS) { + log_warnx("warn: certificate chain too long"); + goto end; + } + } + + for (i = 0; i < cert_count; ++i) { + if (i == 0) + x = SSL_get_peer_certificate(ssl); + else + x = sk_X509_value(xchain, i - 1); + + if (x == NULL) { + log_warnx("warn: failed to retreive certificate"); + goto end; + } + + cert_len[i] = i2d_X509(x, &cert_der[i]); + if (i == 0) + X509_free(x); + + if (cert_len[i] < 0) { + log_warnx("warn: failed to encode certificate"); + goto end; + } + + log_debug("debug: certificate %i: len=%d", i, cert_len[i]); + if (cert_len[i] > (int)MAX_CERT_LEN) { + log_warnx("warn: certificate too long"); + goto end; + } + } + + /* Send the cert chain, one cert at a time */ + for (i = 0; i < cert_count; ++i) { + m_create(p_cert, IMSG_CERT_CERTIFICATE, req->id, 0, -1); + m_add_data(p_cert, cert_der[i], cert_len[i]); + m_close(p_cert); + } + + /* Tell lookup process that it can start verifying, we're done */ + m_create(p_cert, IMSG_CERT_VERIFY, req->id, 0, -1); + m_add_string(p_cert, name); + m_add_int(p_cert, fallback); + m_close(p_cert); + + res = 0; + + end: + for (i = 0; i < MAX_CERTS; ++i) + free(cert_der[i]); + + if (res == -1) { + SPLAY_REMOVE(cert_reqtree, &reqs, req); + free(req); + } + + return res; +} + + +void +cert_dispatch_request(struct mproc *proc, struct imsg *imsg) +{ + struct pki *pki; + struct session key, *s; + const char *name; + const void *data; + size_t datalen; + struct msg m; + uint32_t reqid; + char buf[LINE_MAX]; + int fallback; + + reqid = imsg->hdr.peerid; + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_CERT_INIT: + m_get_string(&m, &name); + m_get_int(&m, &fallback); + m_end(&m); + + xlowercase(buf, name, sizeof(buf)); + log_debug("debug: looking up pki \"%s\"", buf); + pki = dict_get(env->sc_pki_dict, buf); + if (pki == NULL && fallback) + pki = dict_get(env->sc_pki_dict, "*"); + + m_create(proc, IMSG_CERT_INIT, reqid, 0, -1); + if (pki) { + m_add_int(proc, CA_OK); + m_add_string(proc, pki->pki_name); + m_add_data(proc, pki->pki_cert, pki->pki_cert_len); + } else { + m_add_int(proc, CA_FAIL); + m_add_string(proc, NULL); + m_add_data(proc, NULL, 0); + } + m_close(proc); + return; + + case IMSG_CERT_CERTIFICATE: + m_get_data(&m, &data, &datalen); + m_end(&m); + + key.id = reqid; + key.proc = proc; + s = SPLAY_FIND(cert_sestree, &sess, &key); + if (s == NULL) { + s = calloc(1, sizeof(*s)); + s->proc = proc; + s->id = reqid; + SPLAY_INSERT(cert_sestree, &sess, s); + } + + if (s->cert_count == MAX_CERTS) + fatalx("%s: certificate chain too long", __func__); + + s->cert[s->cert_count] = xmemdup(data, datalen); + s->cert_len[s->cert_count] = datalen; + s->cert_count++; + return; + + case IMSG_CERT_VERIFY: + m_get_string(&m, &name); + m_get_int(&m, &fallback); + m_end(&m); + + key.id = reqid; + key.proc = proc; + s = SPLAY_FIND(cert_sestree, &sess, &key); + if (s == NULL) + fatalx("%s: no certificate", __func__); + + SPLAY_REMOVE(cert_sestree, &sess, s); + cert_do_verify(s, name, fallback); + return; + + default: + fatalx("%s: %s", __func__, imsg_to_str(imsg->hdr.type)); + } +} + +void +cert_dispatch_result(struct mproc *proc, struct imsg *imsg) +{ + struct request key, *req; + struct msg m; + const void *cert; + const char *name; + size_t cert_len; + int res; + + key.id = imsg->hdr.peerid; + req = SPLAY_FIND(cert_reqtree, &reqs, &key); + if (req == NULL) + fatalx("%s: unknown request %08x", __func__, imsg->hdr.peerid); + + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_CERT_INIT: + m_get_int(&m, &res); + m_get_string(&m, &name); + m_get_data(&m, &cert, &cert_len); + m_end(&m); + SPLAY_REMOVE(cert_reqtree, &reqs, req); + req->cb_get_certificate(req->arg, res, name, cert, cert_len); + free(req); + break; + + case IMSG_CERT_VERIFY: + m_get_int(&m, &res); + m_end(&m); + SPLAY_REMOVE(cert_reqtree, &reqs, req); + req->cb_verify(req->arg, res); + free(req); + break; + } +} + +static void +cert_do_verify(struct session *s, const char *name, int fallback) +{ + struct ca *ca; + const char *cafile; + int i, res; + + ca = dict_get(env->sc_ca_dict, name); + if (ca == NULL) + if (fallback) + ca = dict_get(env->sc_ca_dict, "*"); + cafile = ca ? ca->ca_cert_file : CA_FILE; + + if (ca == NULL && !fallback) + res = CA_FAIL; + else if (!cert_X509_verify(s, cafile, NULL)) + res = CA_FAIL; + else + res = CA_OK; + + for (i = 0; i < s->cert_count; ++i) + free(s->cert[i]); + + m_create(s->proc, IMSG_CERT_VERIFY, s->id, 0, -1); + m_add_int(s->proc, res); + m_close(s->proc); + + free(s); +} + +static int +cert_X509_verify(struct session *s, const char *CAfile, + const char *CRLfile) +{ + X509 *x509; + X509 *x509_tmp; + STACK_OF(X509) *x509_chain; + const unsigned char *d2i; + int i, ret = 0; + const char *errstr; + + x509 = NULL; + x509_tmp = NULL; + x509_chain = NULL; + + d2i = s->cert[0]; + if (d2i_X509(&x509, &d2i, s->cert_len[0]) == NULL) { + x509 = NULL; + goto end; + } + + if (s->cert_count > 1) { + x509_chain = sk_X509_new_null(); + for (i = 1; i < s->cert_count; ++i) { + d2i = s->cert[i]; + if (d2i_X509(&x509_tmp, &d2i, s->cert_len[i]) == NULL) + goto end; + sk_X509_insert(x509_chain, x509_tmp, i); + x509_tmp = NULL; + } + } + if (!ca_X509_verify(x509, x509_chain, CAfile, NULL, &errstr)) + log_debug("debug: X509 verify: %s", errstr); + else + ret = 1; + +end: + X509_free(x509); + X509_free(x509_tmp); + if (x509_chain) + sk_X509_pop_free(x509_chain, X509_free); + + return ret; +} + +static int +request_cmp(struct request *a, struct request *b) +{ + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + return 0; +} + +SPLAY_GENERATE(cert_reqtree, request, entry, request_cmp); + +static int +session_cmp(struct session *a, struct session *b) +{ + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + if (a->proc < b->proc) + return -1; + if (a->proc > b->proc) + return 1; + return 0; +} + +SPLAY_GENERATE(cert_sestree, session, entry, session_cmp); diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c index c898d74c6e4..24141e24407 100644 --- a/usr.sbin/smtpd/lka.c +++ b/usr.sbin/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.218 2018/12/06 16:05:04 gilles Exp $ */ +/* $OpenBSD: lka.c,v 1.219 2018/12/07 08:05:59 eric Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard @@ -107,6 +107,12 @@ lka_imsg(struct mproc *p, struct imsg *imsg) resolver_dispatch_request(p, imsg); return; + case IMSG_CERT_INIT: + case IMSG_CERT_CERTIFICATE: + case IMSG_CERT_VERIFY: + cert_dispatch_request(p, imsg); + return; + case IMSG_MTA_DNS_HOST: case IMSG_MTA_DNS_MX: case IMSG_MTA_DNS_MX_PREFERENCE: diff --git a/usr.sbin/smtpd/pony.c b/usr.sbin/smtpd/pony.c index 4018c8f8a1d..92d10cdf39c 100644 --- a/usr.sbin/smtpd/pony.c +++ b/usr.sbin/smtpd/pony.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pony.c,v 1.23 2018/12/06 12:09:50 gilles Exp $ */ +/* $OpenBSD: pony.c,v 1.24 2018/12/07 08:05:59 eric Exp $ */ /* * Copyright (c) 2014 Gilles Chehade @@ -63,6 +63,11 @@ pony_imsg(struct mproc *p, struct imsg *imsg) resolver_dispatch_result(p, imsg); return; + case IMSG_CERT_INIT: + case IMSG_CERT_VERIFY: + cert_dispatch_result(p, imsg); + return; + case IMSG_CONF_START: return; case IMSG_CONF_END: diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index ef0f66ddfe9..0a07c664497 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.c,v 1.308 2018/12/06 12:09:50 gilles Exp $ */ +/* $OpenBSD: smtpd.c,v 1.309 2018/12/07 08:05:59 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade @@ -1903,6 +1903,10 @@ imsg_to_str(int type) CASE(IMSG_GETADDRINFO_END); CASE(IMSG_GETNAMEINFO); + CASE(IMSG_CERT_INIT); + CASE(IMSG_CERT_CERTIFICATE); + CASE(IMSG_CERT_VERIFY); + CASE(IMSG_SETUP_KEY); CASE(IMSG_SETUP_PEER); CASE(IMSG_SETUP_DONE); diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index e02201e4349..5f1f9518478 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.580 2018/12/06 16:05:04 gilles Exp $ */ +/* $OpenBSD: smtpd.h,v 1.581 2018/12/07 08:05:59 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade @@ -202,6 +202,10 @@ enum imsg_type { IMSG_GETADDRINFO_END, IMSG_GETNAMEINFO, + IMSG_CERT_INIT, + IMSG_CERT_CERTIFICATE, + IMSG_CERT_VERIFY, + IMSG_SETUP_KEY, IMSG_SETUP_PEER, IMSG_SETUP_DONE, @@ -1226,6 +1230,15 @@ void ca_imsg(struct mproc *, struct imsg *); void ca_init(void); void ca_engine_init(void); + +/* cert.c */ +int cert_init(const char *, int, + void (*)(void *, int, const char *, const void *, size_t), void *); +int cert_verify(const void *, const char *, int, void (*)(void *, int), void *); +void cert_dispatch_request(struct mproc *, struct imsg *); +void cert_dispatch_result(struct mproc *, struct imsg *); + + /* compress_backend.c */ struct compress_backend *compress_backend_lookup(const char *); size_t compress_chunk(void *, size_t, void *, size_t); diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index 568e908d4df..e55772c7ca4 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.98 2018/11/30 15:33:40 gilles Exp $ +# $OpenBSD: Makefile,v 1.99 2018/12/07 08:05:59 eric Exp $ .PATH: ${.CURDIR}/.. @@ -7,6 +7,7 @@ PROG= smtpd SRCS= aliases.c SRCS+= bounce.c SRCS+= ca.c +SRCS+= cert.c SRCS+= compress_backend.c SRCS+= config.c SRCS+= control.c -- 2.20.1