commit 86693a33abd5e8c31530adb3045c9f4664d4d6c9 from: Omar Polo date: Sun Jun 11 11:03:59 2023 UTC add a privsep crypto engine Incorporate the OpenSMTPD' privsep crypto engine. The idea behind it is to never load the certificate' private keys in a networked process, instead they are loaded in a separate process (the `crypto' one) which signs payloads on the behalf of the server processes. This way, we greatly reduce the risk of leaking the certificate' private key should the server process be compromised. This currently compiles only on LibreSSL (portable fix is in the way). commit - f81a97b3569478a36e5cbe95229efd1b831b7a7b commit + 86693a33abd5e8c31530adb3045c9f4664d4d6c9 blob - 6872ae738dce80e2acf9f027ced5244c93ddcf34 blob + 0b73090e43c8e6c5295f33323c4c803c1cbef5c2 --- Makefile +++ Makefile @@ -18,14 +18,14 @@ # all. TESTS= -GMID_SRCS = gmid.c config.c dirs.c fcgi.c iri.c log.c logger.c mime.c \ - proc.c proxy.c puny.c sandbox.c server.c utf8.c utils.c \ - y.tab.c +GMID_SRCS = gmid.c config.c crypto.c dirs.c fcgi.c iri.c log.c \ + logger.c mime.c proc.c proxy.c puny.c sandbox.c \ + server.c utf8.c utils.c y.tab.c GMID_OBJS = ${GMID_SRCS:.c=.o} ${COBJS} -GE_SRCS = ge.c config.c dirs.c fcgi.c iri.c log.c mime.c proc.c \ - proxy.c puny.c sandbox.c server.c utf8.c utils.c +GE_SRCS = ge.c config.c crypto.c dirs.c fcgi.c iri.c log.c mime.c \ + proc.c proxy.c puny.c sandbox.c server.c utf8.c utils.c GE_OBJS = ${GE_SRCS:.c=.o} ${COBJS} @@ -110,9 +110,10 @@ uninstall: DISTFILES = .cirrus.yml .dockerignore .gitignore ChangeLog LICENSE \ Makefile README.md config.c configure configure.local.example \ - dirs.c fcgi.c ge.1 ge.c gg.1 gg.c gmid.8 gmid.c gmid.conf.5 \ - gmid.h iri.c log.c log.h logger.c mime.c parse.y proxy.c \ - puny.c sandbox.c server.c utf8.c utils.c y.tab.c + crypto.c dirs.c fcgi.c ge.1 ge.c gg.1 gg.c gmid.8 gmid.c \ + gmid.conf.5 gmid.h iri.c log.c log.h logger.c mime.c \ + parse.y proxy.c puny.c sandbox.c server.c utf8.c utils.c \ + y.tab.c dist: ${DISTNAME}.sha256 blob - de266c42b8d60b2c4a929990edac3a357eb720ad blob + 251095ccaad5a57252183e4a64fefaa6d140f5ee --- config.c +++ config.c @@ -22,6 +22,8 @@ #include #include +#include + #include "log.h" #include "proc.h" @@ -34,6 +36,7 @@ config_new(void) TAILQ_INIT(&conf->fcgi); TAILQ_INIT(&conf->hosts); + TAILQ_INIT(&conf->pkis); conf->port = 1965; conf->ipv6 = 0; @@ -59,6 +62,7 @@ config_purge(struct conf *conf) struct proxy *p, *tp; struct envlist *e, *te; struct alist *a, *ta; + struct pki *pki, *tpki; ps = conf->ps; @@ -122,6 +126,13 @@ config_purge(struct conf *conf) free(h); } + TAILQ_FOREACH_SAFE(pki, &conf->pkis, pkis, tpki) { + TAILQ_REMOVE(&conf->pkis, pki, pkis); + free(pki->hash); + EVP_PKEY_free(pki->pkey); + free(pki); + } + memset(conf, 0, sizeof(*conf)); conf->ps = ps; @@ -130,6 +141,7 @@ config_purge(struct conf *conf) init_mime(&conf->mime); TAILQ_INIT(&conf->fcgi); TAILQ_INIT(&conf->hosts); + TAILQ_INIT(&conf->pkis); } static int @@ -169,6 +181,38 @@ config_open_send(struct privsep *ps, enum privsep_proc } static int +config_send_kp(struct privsep *ps, int cert_type, int key_type, + const char *cert, const char *key) +{ + int fd, d; + + log_debug("sending %s", cert); + if ((fd = open(cert, O_RDONLY)) == -1) + fatal("can't open %s", cert); + if ((d = dup(fd)) == -1) + fatal("fd"); + + if (config_send_file(ps, PROC_SERVER, cert_type, fd, NULL, 0) == -1) { + close(d); + return -1; + } + if (config_send_file(ps, PROC_CRYPTO, cert_type, d, NULL, 0) == -1) + return -1; + + log_debug("sending %s", key); + if ((fd = open(key, O_RDONLY)) == -1) + return -1; + if (config_send_file(ps, PROC_CRYPTO, key_type, fd, NULL, 0) == -1) + return -1; + + if (proc_flush_imsg(ps, PROC_SERVER, -1) == -1) + return -1; + if (proc_flush_imsg(ps, PROC_CRYPTO, -1) == -1) + return -1; + return 0; +} + +static int make_socket(int port, int family) { int sock, v; @@ -261,7 +305,6 @@ config_send(struct conf *conf) struct envlist *e; struct alist *a; size_t i; - int fd; for (i = 0; i < conf->mime.len; ++i) { m = &conf->mime.t[i]; @@ -308,18 +351,8 @@ config_send(struct conf *conf) &vcopy, sizeof(vcopy)) == -1) return -1; - log_debug("sending certificate %s", h->cert_path); - if ((fd = open(h->cert_path, O_RDONLY)) == -1) - fatal("can't open %s", h->cert_path); - if (config_send_file(ps, PROC_SERVER, IMSG_RECONF_CERT, fd, - NULL, 0) == -1) - return -1; - - log_debug("sending key %s", h->key_path); - if ((fd = open(h->key_path, O_RDONLY)) == -1) - fatal("can't open %s", h->key_path); - if (config_send_file(ps, PROC_SERVER, IMSG_RECONF_KEY, fd, - NULL, 0) == -1) + if (config_send_kp(ps, IMSG_RECONF_CERT, IMSG_RECONF_KEY, + h->cert_path, h->key_path) == -1) return -1; if (h->ocsp_path != NULL) { @@ -394,12 +427,14 @@ config_send(struct conf *conf) fd, &pcopy, sizeof(pcopy)) == -1) return -1; - if (p->cert_path != NULL && - config_open_send(ps, PROC_SERVER, - IMSG_RECONF_PROXY_CERT, p->cert_path) == -1) + if (proc_flush_imsg(ps, PROC_SERVER, -1) == -1) return -1; - if (p->key_path != NULL && + if (p->cert_path == NULL || p->key_path == NULL) + continue; + + if (config_open_send(ps, PROC_SERVER, + IMSG_RECONF_PROXY_CERT, p->cert_path) == -1 || config_open_send(ps, PROC_SERVER, IMSG_RECONF_PROXY_KEY, p->key_path) == -1) return -1; @@ -446,6 +481,51 @@ load_file(int fd, uint8_t **data, size_t *len) return 0; } +static int +config_crypto_recv_kp(struct conf *conf, struct imsg *imsg) +{ + static struct pki *pki; + uint8_t *d; + size_t len; + + /* XXX: check for duplicates */ + + if (imsg->fd == -1) + fatalx("no fd for imsg %d", imsg->hdr.type); + + switch (imsg->hdr.type) { + case IMSG_RECONF_CERT: + if (pki != NULL) + fatalx("imsg in wrong order; pki is not NULL"); + if ((pki = calloc(1, sizeof(*pki))) == NULL) + fatal("calloc"); + if (load_file(imsg->fd, &d, &len) == -1) + fatalx("can't load file"); + if ((pki->hash = ssl_pubkey_hash(d, len)) == NULL) + fatalx("failed to compute cert hash"); + free(d); + TAILQ_INSERT_TAIL(&conf->pkis, pki, pkis); + break; + + case IMSG_RECONF_KEY: + if (pki == NULL) + fatalx("got key without cert beforehand %d", + imsg->hdr.type); + if (load_file(imsg->fd, &d, &len) == -1) + fatalx("failed to load private key"); + if ((pki->pkey = ssl_load_pkey(d, len)) == NULL) + fatalx("failed load private key"); + free(d); + pki = NULL; + break; + + default: + return -1; + } + + return 0; +} + int config_recv(struct conf *conf, struct imsg *imsg) { @@ -533,6 +613,8 @@ config_recv(struct conf *conf, struct imsg *imsg) case IMSG_RECONF_CERT: log_debug("receiving cert"); + if (privsep_process == PROC_CRYPTO) + return config_crypto_recv_kp(conf, imsg); if (h == NULL) fatalx("recv'd cert without host"); if (h->cert != NULL) @@ -546,6 +628,8 @@ config_recv(struct conf *conf, struct imsg *imsg) case IMSG_RECONF_KEY: log_debug("receiving key"); + if (privsep_process == PROC_CRYPTO) + return config_crypto_recv_kp(conf, imsg); if (h == NULL) fatalx("recv'd key without host"); if (h->key != NULL) blob - /dev/null blob + 9b4f060e60aa901ccc1b3f0e35bd1185c67037d2 (mode 644) --- /dev/null +++ crypto.c @@ -0,0 +1,778 @@ +/* + * Copyright (c) 2023 Omar Polo + * Copyright (c) 2014 Reyk Floeter + * Copyright (c) 2012 Gilles Chehade + * + * 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 "gmid.h" + +#include + +#include +#include +#include + +#include "log.h" +#include "proc.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +static void crypto_init(struct privsep *, struct privsep_proc *, void *); +static int crypto_dispatch_parent(int, struct privsep_proc *, struct imsg *); +static int crypto_dispatch_server(int, struct privsep_proc *, struct imsg *); + +static struct privsep_proc procs[] = { + { "parent", PROC_PARENT, crypto_dispatch_parent }, + { "server", PROC_SERVER, crypto_dispatch_server }, +}; + +struct imsg_crypto_req { + uint64_t id; + char hash[TLS_CERT_HASH_SIZE]; + size_t flen; + size_t tlen; + int padding; + /* followed by flen bytes of `from'. */ +}; + +struct imsg_crypto_res { + uint64_t id; + int ret; + size_t len; + /* followed by len bytes of reply */ +}; + +static uint64_t reqid; +static struct conf *conf; + +void +crypto(struct privsep *ps, struct privsep_proc *p) +{ + proc_run(ps, p, procs, nitems(procs), crypto_init, NULL); +} + +static void +crypto_init(struct privsep *ps, struct privsep_proc *p, void *arg) +{ +#if 0 + static volatile int attached; + while (!attached) sleep(1); +#endif + + conf = ps->ps_env; + + sandbox_crypto_process(); +} + +static int +crypto_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_RECONF_START: + case IMSG_RECONF_CERT: + case IMSG_RECONF_KEY: + case IMSG_RECONF_END: + if (config_recv(conf, imsg) == -1) + return -1; + break; + default: + return -1; + } + + return 0; +} + +static EVP_PKEY * +get_pkey(const char *hash) +{ + struct pki *pki; + + TAILQ_FOREACH(pki, &conf->pkis, pkis) { + if (!strcmp(pki->hash, hash)) + return pki->pkey; + } + + return NULL; +} + +static int +crypto_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + struct privsep *ps = p->p_ps; + RSA *rsa; + EC_KEY *ecdsa; + EVP_PKEY *pkey; + struct imsg_crypto_req req; + struct imsg_crypto_res res; + struct iovec iov[2]; + const void *from; + unsigned char *to; + size_t datalen; + int n, len, ret; + + datalen = IMSG_DATA_SIZE(imsg); + + switch (imsg->hdr.type) { + case IMSG_CRYPTO_RSA_PRIVENC: + case IMSG_CRYPTO_RSA_PRIVDEC: + if (datalen < sizeof(req)) + fatalx("size mismatch for imsg %d", imsg->hdr.type); + memcpy(&req, imsg->data, sizeof(req)); + if (datalen != sizeof(req) + req.flen) + fatalx("size mismatch for imsg %d", imsg->hdr.type); + from = imsg->data + sizeof(req); + + if ((pkey = get_pkey(req.hash)) == NULL || + (rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) + fatalx("invalid pkey hash"); + + if ((to = calloc(1, req.tlen)) == NULL) + fatal("calloc"); + + switch (imsg->hdr.type) { + case IMSG_CRYPTO_RSA_PRIVENC: + ret = RSA_private_encrypt(req.flen, from, + to, rsa, req.padding); + break; + case IMSG_CRYPTO_RSA_PRIVDEC: + ret = RSA_private_decrypt(req.flen, from, + to, rsa, req.padding); + break; + } + + memset(&res, 0, sizeof(res)); + res.id = req.id; + res.ret = ret; + + memset(&iov, 0, sizeof(iov)); + n = 0; + iov[n].iov_base = &res; + iov[n].iov_len = sizeof(res); + n++; + + if (ret > 0) { + res.len = ret; + iov[n].iov_base = to; + iov[n].iov_len = ret; + n++; + } + + log_debug("replying to server #%d", imsg->hdr.pid); + if (proc_composev_imsg(ps, PROC_SERVER, imsg->hdr.pid - 1, + imsg->hdr.type, 0, -1, iov, n) == -1) + fatal("proc_composev_imsg"); + + if (proc_flush_imsg(ps, PROC_SERVER, imsg->hdr.pid - 1) == -1) + fatal("proc_flush_imsg"); + + free(to); + RSA_free(rsa); + break; + + case IMSG_CRYPTO_ECDSA_SIGN: + if (datalen < sizeof(req)) + fatalx("size mismatch for imsg %d", imsg->hdr.type); + memcpy(&req, imsg->data, sizeof(req)); + if (datalen != sizeof(req) + req.flen) + fatalx("size mismatch for imsg %d", imsg->hdr.type); + from = imsg->data + sizeof(req); + + if ((pkey = get_pkey(req.hash)) == NULL || + (ecdsa = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) + fatalx("invalid pkey hash"); + + len = ECDSA_size(ecdsa); + if ((to = calloc(1, len)) == NULL) + fatal("calloc"); + ret = ECDSA_sign(0, from, req.flen, to, &len, ecdsa); + + memset(&res, 0, sizeof(res)); + res.id = req.id; + res.ret = ret; + + memset(&iov, 0, sizeof(iov)); + n = 0; + iov[0].iov_base = &res; + iov[1].iov_len = sizeof(res); + n++; + + if (ret > 0) { + res.len = len; + iov[n].iov_base = to; + iov[n].iov_len = len; + n++; + } + + log_debug("replying to server #%d", imsg->hdr.pid); + if (proc_composev_imsg(ps, PROC_SERVER, imsg->hdr.pid - 1, + imsg->hdr.type, 0, -1, iov, n) == -1) + fatal("proc_composev_imsg"); + + if (proc_flush_imsg(ps, PROC_SERVER, imsg->hdr.pid - 1) == -1) + fatal("proc_flush_imsg"); + + free(to); + EC_KEY_free(ecdsa); + break; + + default: + return -1; + } + + return 0; +} + + +/* + * RSA privsep engine (called from unprivileged processes) + */ + +static const RSA_METHOD *rsa_default; +static RSA_METHOD *rsae_method; + +static int +rsae_send_imsg(int flen, const unsigned char *from, unsigned char *to, + RSA *rsa, int padding, unsigned int cmd) +{ + struct imsg_crypto_req req; + struct iovec iov[2]; + struct imsg_crypto_res res; + struct imsgev *iev; + struct privsep_proc *p; + struct privsep *ps = conf->ps; + struct imsgbuf *ibuf; + struct imsg imsg; + int ret = 0; + int n, done = 0; + const void *toptr; + char *hash; + size_t datalen; + + if ((hash = RSA_get_ex_data(rsa, 0)) == NULL) + return (0); + + /* + * Send a synchronous imsg because we cannot defer the RSA + * operation in OpenSSL's engine layer. + */ + memset(&req, 0, sizeof(req)); + req.id = ++reqid; + if (strlcpy(req.hash, hash, sizeof(req.hash)) >= sizeof(req.hash)) + fatalx("%s: hash too long (%zu)", __func__, strlen(hash)); + req.flen = flen; + req.tlen = RSA_size(rsa); + req.padding = padding; + + memset(&iov, 0, sizeof(iov)); + iov[0].iov_base = &req; + iov[0].iov_len = sizeof(req); + iov[1].iov_base = (void *)from; + iov[1].iov_len = flen; + + if (proc_composev(ps, PROC_CRYPTO, cmd, iov, 2) == -1) + fatal("proc_composev"); + + if (proc_flush_imsg(ps, PROC_CRYPTO, -1) == -1) + fatal("proc_flush_imsg"); + + iev = ps->ps_ievs[PROC_CRYPTO]; + p = iev->proc; + ibuf = &iev->ibuf; + + while (!done) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatalx("imsg_read"); + if (n == 0) + fatalx("pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatalx("imsg_get error"); + if (n == 0) + break; + +#if DEBUG > 1 + log_debug( + "%s: %s %d got imsg %d peerid %d from %s %d", + __func__, title, 1, imsg.hdr.type, + imsg.hdr.peerid, "crypto", imsg.hdr.pid); +#endif + + if ((p->p_cb)(ibuf->fd, p, &imsg) == 0) { + /* Message was handled by the callback */ + imsg_free(&imsg); + continue; + } + + switch (imsg.hdr.type) { + case IMSG_CRYPTO_RSA_PRIVENC: + case IMSG_CRYPTO_RSA_PRIVDEC: + break; + default: + fatalx("%s: %s %d got invalid imsg %d" + " peerid %d from %s %d", + __func__, "server", ps->ps_instance + 1, + imsg.hdr.type, imsg.hdr.peerid, + "crypto", imsg.hdr.pid); + } + + datalen = IMSG_DATA_SIZE(&imsg); + if (datalen < sizeof(res)) + fatalx("size mismatch for imsg %d", + imsg.hdr.type); + memcpy(&res, imsg.data, sizeof(res)); + if (datalen != sizeof(res) + res.ret) + fatalx("size mismatch for imsg %d", + imsg.hdr.type); + ret = res.ret; + toptr = imsg.data + sizeof(res); + + if (res.id != reqid) + fatalx("invalid response id" + " got %llu, want %llu", res.id, reqid); + if (res.ret > 0) + memcpy(to, toptr, res.len); + + log_warnx("the return is %d", ret); + + done = 1; + + imsg_free(&imsg); + } + } + imsg_event_add(iev); + + return (ret); +} + +static int +rsae_pub_enc(int flen,const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s", __func__); + return (RSA_meth_get_pub_enc(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_pub_dec(int flen,const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s", __func__); + return (RSA_meth_get_pub_dec(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s", __func__); + if (RSA_get_ex_data(rsa, 0) != NULL) + return (rsae_send_imsg(flen, from, to, rsa, padding, + IMSG_CRYPTO_RSA_PRIVENC)); + return (RSA_meth_get_priv_enc(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s", __func__); + if (RSA_get_ex_data(rsa, 0) != NULL) + return (rsae_send_imsg(flen, from, to, rsa, padding, + IMSG_CRYPTO_RSA_PRIVDEC)); + + return (RSA_meth_get_priv_dec(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_mod_exp(BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx) +{ + log_debug("debug: %s", __func__); + return (RSA_meth_get_mod_exp(rsa_default)(r0, I, rsa, ctx)); +} + +static int +rsae_bn_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx) +{ + log_debug("debug: %s", __func__); + return (RSA_meth_get_bn_mod_exp(rsa_default)(r, a, p, m, ctx, m_ctx)); +} + +static int +rsae_init(RSA *rsa) +{ + log_debug("debug: %s", __func__); + if (RSA_meth_get_init(rsa_default) == NULL) + return (1); + return (RSA_meth_get_init(rsa_default)(rsa)); +} + +static int +rsae_finish(RSA *rsa) +{ + log_debug("debug: %s", __func__); + if (RSA_meth_get_finish(rsa_default) == NULL) + return (1); + return (RSA_meth_get_finish(rsa_default)(rsa)); +} + +static int +rsae_keygen(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb) +{ + log_debug("debug: %s", __func__); + return (RSA_meth_get_keygen(rsa_default)(rsa, bits, e, cb)); +} + + +/* + * ECDSA privsep engine (called from unprivileged processes) + */ + +static const EC_KEY_METHOD *ecdsa_default; +static EC_KEY_METHOD *ecdsae_method; + +static ECDSA_SIG * +ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, + const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) +{ + ECDSA_SIG *sig = NULL; + struct imsg_crypto_req req; + struct iovec iov[2]; + struct imsg_crypto_res res; + struct imsgev *iev; + struct privsep_proc *p; + struct privsep *ps = conf->ps; + struct imsgbuf *ibuf; + struct imsg imsg; + int n, done = 0; + const void *toptr; + char *hash; + size_t datalen; + + if ((hash = EC_KEY_get_ex_data(eckey, 0)) == NULL) + return (0); + + /* + * Send a synchronous imsg because we cannot defer the RSA + * operation in OpenSSL's engine layer. + */ + memset(&req, 0, sizeof(req)); + req.id = reqid++; + if (strlcpy(req.hash, hash, sizeof(req.hash)) >= sizeof(req.hash)) + fatalx("%s: hash too long (%zu)", __func__, strlen(hash)); + req.flen = dgst_len; + + memset(&iov, 0, sizeof(iov)); + iov[0].iov_base = &req; + iov[0].iov_len = sizeof(req); + iov[1].iov_base = (void *)dgst; + iov[1].iov_len = dgst_len; + + if (proc_composev(ps, PROC_CRYPTO, IMSG_CRYPTO_ECDSA_SIGN, iov, 2) == -1) + fatal("proc_composev"); + + if (proc_flush_imsg(ps, PROC_CRYPTO, -1) == -1) + fatal("proc_flush_imsg"); + + iev = ps->ps_ievs[PROC_CRYPTO]; + p = iev->proc; + ibuf = &iev->ibuf; + + while (!done) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatalx("imsg_read"); + if (n == 0) + fatalx("pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatalx("imsg_get error"); + if (n == 0) + break; + +#if DEBUG > 1 + log_debug( + "%s: %s %d got imsg %d peerid %d from %s %d", + __func__, title, 1, imsg.hdr.type, + imsg.hdr.peerid, "crypto", imsg.hdr.pid); +#endif + + if (crypto_dispatch_server(ibuf->fd, p, &imsg) == 0) { + /* Message was handled by the callback */ + imsg_free(&imsg); + continue; + } + + if (imsg.hdr.type != IMSG_CRYPTO_ECDSA_SIGN) + fatalx("%s: %s %d got invalid imsg %d" + " peerid %d from %s %d", + __func__, "server", ps->ps_instance + 1, + imsg.hdr.type, imsg.hdr.peerid, + "crypto", imsg.hdr.pid); + + datalen = IMSG_DATA_SIZE(&imsg); + if (datalen < sizeof(res)) + fatalx("size mismatch for imsg %d", + imsg.hdr.type); + memcpy(&res, imsg.data, sizeof(res)); + if (datalen != sizeof(res) + res.ret) + fatalx("size mismatch for imsg %d", + imsg.hdr.type); + toptr = imsg.data + sizeof(res); + + if (res.id != reqid) + fatalx("invalid response id"); + if (res.ret > 0) { + d2i_ECDSA_SIG(&sig, + (const unsigned char **)&toptr, res.len); + } + + done = 1; + + imsg_free(&imsg); + } + } + imsg_event_add(iev); + + return (sig); +} + +static int +ecdsae_keygen(EC_KEY *eckey) +{ + int (*keygen)(EC_KEY *); + + log_debug("debug: %s", __func__); + EC_KEY_METHOD_get_keygen(ecdsa_default, &keygen); + return (keygen(eckey)); +} + +static int +ecdsae_compute_key(void *out, size_t outlen, const EC_POINT *pub_key, + EC_KEY *ecdh, void *(*kdf)(const void *, size_t, void *, size_t *)) +{ + int (*ckey)(void *, size_t, const EC_POINT *, EC_KEY *, + void *(*)(const void *, size_t, void *, size_t *)); + + log_debug("debug: %s", __func__); + EC_KEY_METHOD_get_compute_key(ecdsa_default, &ckey); + return (ckey(out, outlen, pub_key, ecdh, kdf)); +} + +static int +ecdsae_sign(int type, const unsigned char *dgst, int dlen, unsigned char *sig, + unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *eckey) +{ + int (*sign)(int, const unsigned char *, int, unsigned char *, + unsigned int *, const BIGNUM *, const BIGNUM *, EC_KEY *); + + log_debug("debug: %s", __func__); + EC_KEY_METHOD_get_sign(ecdsa_default, &sign, NULL, NULL); + return (sign(type, dgst, dlen, sig, siglen, kinv, r, eckey)); +} + +static ECDSA_SIG * +ecdsae_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv, + const BIGNUM *rp, EC_KEY *eckey) +{ + ECDSA_SIG *(*psign_sig)(const unsigned char *, int, const BIGNUM *, + const BIGNUM *, EC_KEY *); + + log_debug("debug: %s", __func__); + if (EC_KEY_get_ex_data(eckey, 0) != NULL) + return (ecdsae_send_enc_imsg(dgst, dgst_len, inv, rp, eckey)); + EC_KEY_METHOD_get_sign(ecdsa_default, NULL, NULL, &psign_sig); + return (psign_sig(dgst, dgst_len, inv, rp, eckey)); +} + +static int +ecdsae_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, BIGNUM **r) +{ + int (*psign_setup)(EC_KEY *, BN_CTX *, BIGNUM **, BIGNUM **); + + log_debug("debug: %s", __func__); + EC_KEY_METHOD_get_sign(ecdsa_default, NULL, &psign_setup, NULL); + return (psign_setup(eckey, ctx, kinv, r)); +} + +static int +ecdsae_verify(int type, const unsigned char *dgst, int dgst_len, + const unsigned char *sigbuf, int sig_len, EC_KEY *eckey) +{ + int (*verify)(int, const unsigned char *, int, const unsigned char *, + int, EC_KEY *); + + log_debug("debug: %s", __func__); + EC_KEY_METHOD_get_verify(ecdsa_default, &verify, NULL); + return (verify(type, dgst, dgst_len, sigbuf, sig_len, eckey)); +} + +static int +ecdsae_do_verify(const unsigned char *dgst, int dgst_len, + const ECDSA_SIG *sig, EC_KEY *eckey) +{ + int (*pverify_sig)(const unsigned char *, int, const ECDSA_SIG *, + EC_KEY *); + + log_debug("debug: %s", __func__); + EC_KEY_METHOD_get_verify(ecdsa_default, NULL, &pverify_sig); + return (pverify_sig(dgst, dgst_len, sig, eckey)); +} + + +/* + * Initialize the two engines. + */ + +static void +rsa_engine_init(void) +{ + ENGINE *e; + const char *errstr, *name; + + if ((rsae_method = RSA_meth_new("RSA privsep engine", 0)) == NULL) { + errstr = "RSA_meth_new"; + goto fail; + } + + RSA_meth_set_pub_enc(rsae_method, rsae_pub_enc); + RSA_meth_set_pub_dec(rsae_method, rsae_pub_dec); + RSA_meth_set_priv_enc(rsae_method, rsae_priv_enc); + RSA_meth_set_priv_dec(rsae_method, rsae_priv_dec); + RSA_meth_set_mod_exp(rsae_method, rsae_mod_exp); + RSA_meth_set_bn_mod_exp(rsae_method, rsae_bn_mod_exp); + RSA_meth_set_init(rsae_method, rsae_init); + RSA_meth_set_finish(rsae_method, rsae_finish); + RSA_meth_set_keygen(rsae_method, rsae_keygen); + + if ((e = ENGINE_get_default_RSA()) == NULL) { + if ((e = ENGINE_new()) == NULL) { + errstr = "ENGINE_new"; + goto fail; + } + if (!ENGINE_set_name(e, RSA_meth_get0_name(rsae_method))) { + errstr = "ENGINE_set_name"; + goto fail; + } + if ((rsa_default = RSA_get_default_method()) == NULL) { + errstr = "RSA_get_default_method"; + goto fail; + } + } else if ((rsa_default = ENGINE_get_RSA(e)) == NULL) { + errstr = "ENGINE_get_RSA"; + goto fail; + } + + if ((name = ENGINE_get_name(e)) == NULL) + name = "unknown RSA engine"; + + log_debug("debug: %s: using %s", __func__, name); + + if (RSA_meth_get_mod_exp(rsa_default) == NULL) + RSA_meth_set_mod_exp(rsae_method, NULL); + if (RSA_meth_get_bn_mod_exp(rsa_default) == NULL) + RSA_meth_set_bn_mod_exp(rsae_method, NULL); + if (RSA_meth_get_keygen(rsa_default) == NULL) + RSA_meth_set_keygen(rsae_method, NULL); + RSA_meth_set_flags(rsae_method, + RSA_meth_get_flags(rsa_default) | RSA_METHOD_FLAG_NO_CHECK); + RSA_meth_set0_app_data(rsae_method, + RSA_meth_get0_app_data(rsa_default)); + + if (!ENGINE_set_RSA(e, rsae_method)) { + errstr = "ENGINE_set_RSA"; + goto fail; + } + if (!ENGINE_set_default_RSA(e)) { + errstr = "ENGINE_set_default_RSA"; + goto fail; + } + + return; + + fail: + ssl_error(errstr); + fatalx("%s", errstr); +} + +static void +ecdsa_engine_init(void) +{ + ENGINE *e; + const char *errstr, *name; + + if ((ecdsae_method = EC_KEY_METHOD_new(NULL)) == NULL) { + errstr = "EC_KEY_METHOD_new"; + goto fail; + } + + EC_KEY_METHOD_set_keygen(ecdsae_method, ecdsae_keygen); + EC_KEY_METHOD_set_compute_key(ecdsae_method, ecdsae_compute_key); + EC_KEY_METHOD_set_sign(ecdsae_method, ecdsae_sign, ecdsae_sign_setup, + ecdsae_do_sign); + EC_KEY_METHOD_set_verify(ecdsae_method, ecdsae_verify, + ecdsae_do_verify); + + if ((e = ENGINE_get_default_EC()) == NULL) { + if ((e = ENGINE_new()) == NULL) { + errstr = "ENGINE_new"; + goto fail; + } + if (!ENGINE_set_name(e, "ECDSA privsep engine")) { + errstr = "ENGINE_set_name"; + goto fail; + } + if ((ecdsa_default = EC_KEY_get_default_method()) == NULL) { + errstr = "EC_KEY_get_default_method"; + goto fail; + } + } else if ((ecdsa_default = ENGINE_get_EC(e)) == NULL) { + errstr = "ENGINE_get_EC"; + goto fail; + } + + if ((name = ENGINE_get_name(e)) == NULL) + name = "unknown ECDSA engine"; + + log_debug("debug: %s: using %s", __func__, name); + + if (!ENGINE_set_EC(e, ecdsae_method)) { + errstr = "ENGINE_set_EC"; + goto fail; + } + if (!ENGINE_set_default_EC(e)) { + errstr = "ENGINE_set_default_EC"; + goto fail; + } + + return; + + fail: + ssl_error(errstr); + fatalx("%s", errstr); +} + +void +crypto_engine_init(struct conf *c) +{ + conf = c; + + rsa_engine_init(); + ecdsa_engine_init(); +} + blob - 463b7ae222bd2bcef0c091b702e42cb5c436dcb8 blob + 9ba75c76478d6836d8e81d5810ac9ff01f5541ba --- gmid.c +++ gmid.c @@ -42,12 +42,14 @@ static void main_configure_done(struct conf *); static void main_reload(struct conf *); static void main_sig_handler(int, short, void *); static int main_dispatch_server(int, struct privsep_proc *, struct imsg *); +static int main_dispatch_crypto(int, struct privsep_proc *, struct imsg *); static int main_dispatch_logger(int, struct privsep_proc *, struct imsg *); static void __dead main_shutdown(struct conf *); static void main_print_conf(struct conf *); static struct privsep_proc procs[] = { { "server", PROC_SERVER, main_dispatch_server, server }, + { "crypto", PROC_CRYPTO, main_dispatch_crypto, crypto }, { "logger", PROC_LOGGER, main_dispatch_logger, logger }, }; @@ -328,9 +330,11 @@ main_configure(struct conf *conf) { struct privsep *ps = conf->ps; - conf->reload = conf->prefork; + conf->reload = conf->prefork + 1; /* servers, crypto */ if (proc_compose(ps, PROC_SERVER, IMSG_RECONF_START, NULL, 0) == -1) + return -1; + if (proc_compose(ps, PROC_CRYPTO, IMSG_RECONF_START, NULL, 0) == -1) return -1; if (config_send(conf) == -1) @@ -338,6 +342,8 @@ main_configure(struct conf *conf) if (proc_compose(ps, PROC_SERVER, IMSG_RECONF_END, NULL, 0) == -1) return -1; + if (proc_compose(ps, PROC_CRYPTO, IMSG_RECONF_END, NULL, 0) == -1) + return -1; return 0; } @@ -421,6 +427,23 @@ main_dispatch_server(int fd, struct privsep_proc *p, s } static int +main_dispatch_crypto(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + struct privsep *ps = p->p_ps; + struct conf *conf = ps->ps_env; + + switch (imsg->hdr.type) { + case IMSG_RECONF_DONE: + main_configure_done(conf); + break; + default: + return -1; + } + + return 0; +} + +static int main_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg) { struct privsep *ps = p->p_ps; blob - 847af9e1cf68acbe1a5199b28428a8e036f588c7 blob + 353c31d7603e7ca52cc134d18fc646286ac7e513 --- gmid.h +++ gmid.h @@ -82,6 +82,8 @@ #define PROC_MAX_INSTANCES 16 +#define TLS_CERT_HASH_SIZE 128 + /* forward declaration */ struct privsep; struct privsep_proc; @@ -209,6 +211,13 @@ struct mime { size_t cap; }; +TAILQ_HEAD(pkihead, pki); +struct pki { + char *hash; + EVP_PKEY *pkey; + TAILQ_ENTRY(pki) pkis; +}; + struct conf { struct privsep *ps; int port; @@ -227,6 +236,7 @@ struct conf { struct fcgihead fcgi; struct vhosthead hosts; + struct pkihead pkis; }; extern const char *config_path; @@ -327,6 +337,10 @@ enum imsg_type { IMSG_RECONF_PROXY_KEY, IMSG_RECONF_END, IMSG_RECONF_DONE, + + IMSG_CRYPTO_RSA_PRIVENC, + IMSG_CRYPTO_RSA_PRIVDEC, + IMSG_CRYPTO_ECDSA_SIGN, IMSG_CTL_PROCFD, }; @@ -344,6 +358,10 @@ void config_purge(struct conf *); int config_send(struct conf *); int config_recv(struct conf *, struct imsg *); +/* crypto.c */ +void crypto(struct privsep *, struct privsep_proc *); +void crypto_engine_init(struct conf *); + /* parse.y */ void yyerror(const char*, ...); int parse_conf(struct conf *, const char*); @@ -398,6 +416,7 @@ void fcgi_req(struct client *); /* sandbox.c */ void sandbox_main_process(void); void sandbox_server_process(void); +void sandbox_crypto_process(void); void sandbox_logger_process(void); /* utf8.c */ @@ -431,6 +450,9 @@ void *xcalloc(size_t, size_t); void gen_certificate(const char*, const char*, const char*); X509_STORE *load_ca(int); int validate_against_ca(X509_STORE*, const uint8_t*, size_t); +void ssl_error(const char *); +char *ssl_pubkey_hash(const char *, size_t); +EVP_PKEY *ssl_load_pkey(const char *, size_t); struct vhost *new_vhost(void); struct location *new_location(void); struct proxy *new_proxy(void); blob - 4ebacd18de758fca8b539a228d69181f2002a474 blob + f0def37e0aa277ee96bb187dcd22055aa343b7ff --- proc.h +++ proc.h @@ -36,6 +36,7 @@ struct imsgev { enum privsep_procid { PROC_PARENT, PROC_SERVER, + PROC_CRYPTO, PROC_LOGGER, PROC_MAX, }; blob - 6d68b0f734994ca77ccdb0f8433265a1780cfdba blob + c1894463b961560587ab30220cfe898d86ac5e98 --- sandbox.c +++ sandbox.c @@ -36,6 +36,13 @@ sandbox_server_process(void) } void +sandbox_crypto_process(void) +{ + if (pledge("stdio recvfd", NULL) == -1) + fatal("pledge"); +} + +void sandbox_logger_process(void) { if (pledge("stdio recvfd", NULL) == -1) @@ -59,6 +66,12 @@ sandbox_server_process(void) } void +sandbox_crypto_process(void) +{ + return; +} + +void sandbox_logger_process(void) { return; blob - 483dd0b144ea9515d70728c7c5b9f4877acf545a blob + b29249f26ffb61c551fad6b2bdcae9e9c7b192d3 --- server.c +++ server.c @@ -46,6 +46,12 @@ static int has_siginfo; int connected_clients; +/* + * 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 *); + static inline int matches(const char*, const char*); static int check_path(struct client*, const char*, int*); @@ -73,10 +79,12 @@ static void client_close_ev(int, short, void *); static void handle_siginfo(int, short, void*); static int server_dispatch_parent(int, struct privsep_proc *, struct imsg *); +static int server_dispatch_crypto(int, struct privsep_proc *, struct imsg *); static int server_dispatch_logger(int, struct privsep_proc *, struct imsg *); static struct privsep_proc procs[] = { { "parent", PROC_PARENT, server_dispatch_parent }, + { "crypto", PROC_CRYPTO, server_dispatch_crypto }, { "logger", PROC_LOGGER, server_dispatch_logger }, }; @@ -1384,6 +1392,13 @@ setup_tls(struct conf *conf) if ((tlsconf = tls_config_new()) == NULL) fatal("tls_config_new"); + /* + * ge doesn't use the privsep crypto engine; it doesn't use + * privsep at all so `ps' is NULL. + */ + if (conf->ps != NULL) + tls_config_use_fake_private_key(tlsconf); + /* optionally accept client certs, but don't try to verify them */ tls_config_verify_client_optional(tlsconf); tls_config_insecure_noverifycert(tlsconf); @@ -1455,6 +1470,13 @@ server_init(struct privsep *ps, struct privsep_proc *p signal_add(&sigusr2, NULL); sandbox_server_process(); + + /* + * ge doesn't use the privsep crypto engine; it doesn't use + * privsep at all so `ps' is NULL. + */ + if (ps != NULL) + crypto_engine_init(ps->ps_env); } int @@ -1510,7 +1532,14 @@ server_dispatch_parent(int fd, struct privsep_proc *p, return 0; } + static int +server_dispatch_crypto(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + return -1; +} + +static int server_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg) { return -1; blob - b8f09c78a7371abd6765f332139af72ee4e58c67 blob + c5f91a1bb89b4be42a239f0cb81b5d97e78e50e7 --- utils.c +++ utils.c @@ -1,5 +1,7 @@ /* * Copyright (c) 2021 Omar Polo + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2008 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,6 +22,7 @@ #include #include +#include #include #include #include @@ -246,6 +249,90 @@ end: return ret; } +void +ssl_error(const char *where) +{ + unsigned long code; + char errbuf[128]; + + while ((code = ERR_get_error()) != 0) { + ERR_error_string_n(code, errbuf, sizeof(errbuf)); + log_debug("debug: SSL library error: %s: %s", where, errbuf); + } +} + +char * +ssl_pubkey_hash(const char *buf, size_t len) +{ + static const char hex[] = "0123456789abcdef"; + BIO *in; + X509 *x509 = NULL; + char *hash = NULL; + size_t off; + char digest[EVP_MAX_MD_SIZE]; + int dlen, i; + + 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__); + ssl_error("PEM_read_bio_X509"); + goto fail; + } + + if ((hash = malloc(TLS_CERT_HASH_SIZE)) == NULL) { + log_warn("%s: malloc", __func__); + goto fail; + } + + if (X509_pubkey_digest(x509, EVP_sha256(), digest, &dlen) != 1) { + log_warnx("%s: X509_pubkey_digest failed", __func__); + ssl_error("X509_pubkey_digest"); + free(hash); + hash = NULL; + goto fail; + } + + if (TLS_CERT_HASH_SIZE < 2 * dlen + sizeof("SHA256:")) + fatalx("%s: hash buffer too small", __func__); + + off = strlcpy(hash, "SHA256:", TLS_CERT_HASH_SIZE); + for (i = 0; i < dlen; ++i) { + hash[off++] = hex[(digest[i] >> 4) & 0xf]; + hash[off++] = hex[digest[i] & 0xf]; + } + hash[off] = '\0'; + + fail: + BIO_free(in); + if (x509) + X509_free(x509); + return hash; +} + +EVP_PKEY * +ssl_load_pkey(const char *buf, size_t len) +{ + BIO *in; + EVP_PKEY *pkey; + + if ((in = BIO_new_mem_buf(buf, len)) == NULL) { + log_warnx("%s: BIO_new_mem_buf failed", __func__); + return NULL; + } + + if ((pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL)) == NULL) { + log_warnx("%s: PEM_read_bio_PrivateKey failed", __func__); + ssl_error("PEM_read_bio_PrivateKey"); + } + + BIO_free(in); + return pkey; +} + struct vhost * new_vhost(void) {