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).
This commit is contained in:
Omar Polo 2023-06-11 11:03:59 +00:00
parent f81a97b356
commit 86693a33ab
9 changed files with 1064 additions and 26 deletions

View File

@ -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

118
config.c
View File

@ -22,6 +22,8 @@
#include <limits.h>
#include <string.h>
#include <openssl/pem.h>
#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
@ -168,6 +180,38 @@ config_open_send(struct privsep *ps, enum privsep_procid id, int type,
return config_send_file(ps, id, type, fd, NULL, 0);
}
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)
{
@ -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)

778
crypto.c Normal file
View File

@ -0,0 +1,778 @@
/*
* Copyright (c) 2023 Omar Polo <op@omarpolo.com>
* Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
* Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
*
* 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 <string.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/engine.h>
#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();
}

25
gmid.c
View File

@ -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,16 +330,20 @@ 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)
return -1;
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;
}
@ -420,6 +426,23 @@ main_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg)
return 0;
}
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)
{

22
gmid.h
View File

@ -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;
@ -328,6 +338,10 @@ enum imsg_type {
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);

1
proc.h
View File

@ -36,6 +36,7 @@ struct imsgev {
enum privsep_procid {
PROC_PARENT,
PROC_SERVER,
PROC_CRYPTO,
PROC_LOGGER,
PROC_MAX,
};

View File

@ -35,6 +35,13 @@ sandbox_server_process(void)
fatal("pledge");
}
void
sandbox_crypto_process(void)
{
if (pledge("stdio recvfd", NULL) == -1)
fatal("pledge");
}
void
sandbox_logger_process(void)
{
@ -58,6 +65,12 @@ sandbox_server_process(void)
return;
}
void
sandbox_crypto_process(void)
{
return;
}
void
sandbox_logger_process(void)
{

View File

@ -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, void *arg)
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,6 +1532,13 @@ server_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
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)
{

87
utils.c
View File

@ -1,5 +1,7 @@
/*
* Copyright (c) 2021 Omar Polo <op@omarpolo.com>
* Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
* Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org>
*
* 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 <string.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/x509_vfy.h>
#include <openssl/x509v3.h>
@ -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)
{