Commit Diff


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 <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
@@ -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 <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();
+}
+
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 <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)
 {