/* * 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 "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_get_type(imsg)) { 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 = NULL; EC_KEY *ecdsa = NULL; EVP_PKEY *pkey; struct imsg_crypto_req req; struct imsg_crypto_res res; struct ibuf ibuf; struct iovec iov[2]; const void *from; unsigned char *to; int n, ret, type; unsigned int len; pid_t pid; if (imsg_get_ibuf(imsg, &ibuf) == -1) fatalx("%s: couldn't get an ibuf", __func__); pid = imsg_get_pid(imsg); switch (type = imsg_get_type(imsg)) { case IMSG_CRYPTO_RSA_PRIVENC: case IMSG_CRYPTO_RSA_PRIVDEC: if (ibuf_get(&ibuf, &req, sizeof(req)) == -1 || ibuf_size(&ibuf) != req.flen) fatalx("size mismatch for imsg %d", type); from = ibuf_data(&ibuf); 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"); if (type == IMSG_CRYPTO_RSA_PRIVENC) ret = RSA_private_encrypt(req.flen, from, to, rsa, req.padding); else ret = RSA_private_decrypt(req.flen, from, to, rsa, req.padding); 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", pid); if (proc_composev_imsg(ps, PROC_SERVER, pid - 1, type, 0, -1, iov, n) == -1) fatal("proc_composev_imsg"); if (proc_flush_imsg(ps, PROC_SERVER, pid - 1) == -1) fatal("proc_flush_imsg"); free(to); RSA_free(rsa); break; case IMSG_CRYPTO_ECDSA_SIGN: if (ibuf_get(&ibuf, &req, sizeof(req)) == -1 || ibuf_size(&ibuf) != req.flen) fatalx("size mismatch for imsg %d", type); from = ibuf_data(&ibuf); 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[0].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", pid); if (proc_composev_imsg(ps, PROC_SERVER, pid - 1, type, 0, -1, iov, n) == -1) fatal("proc_composev_imsg"); if (proc_flush_imsg(ps, PROC_SERVER, 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 *imsgbuf; struct imsg imsg; struct ibuf ibuf; int ret = 0; int n, done = 0; const void *toptr; char *hash; 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; imsgbuf = &iev->ibuf; while (!done) { if ((n = imsgbuf_read(imsgbuf)) == -1) fatalx("imsg_read"); if (n == 0) fatalx("pipe closed"); while (!done) { if ((n = imsg_get(imsgbuf, &imsg)) == -1) fatalx("imsg_get error"); if (n == 0) break; #if DEBUG > 1 log_debug( "%s: %s %d got imsg %d id %d from %s %d", __func__, title, 1, imsg_get_type(&imsg), imsg_get_id(&imsg), "crypto", imsg_get_pid(&imsg)); #endif if ((p->p_cb)(imsgbuf->fd, p, &imsg) == 0) { /* Message was handled by the callback */ imsg_free(&imsg); continue; } switch (imsg_get_type(&imsg)) { case IMSG_CRYPTO_RSA_PRIVENC: case IMSG_CRYPTO_RSA_PRIVDEC: break; default: fatalx("%s: %s %d got invalid imsg %d" " id %d from %s %d", __func__, "server", ps->ps_instance + 1, imsg_get_type(&imsg), imsg_get_id(&imsg), "crypto", imsg_get_pid(&imsg)); } if (imsg_get_ibuf(&imsg, &ibuf) == -1 || ibuf_get(&ibuf, &res, sizeof(res)) == -1 || (int)ibuf_size(&ibuf) != res.ret) fatalx("size mismatch for imsg %d", imsg.hdr.type); ret = res.ret; toptr = ibuf_data(&ibuf); if (res.id != reqid) fatalx("invalid id; got %llu, want %llu", (unsigned long long)res.id, (unsigned long long)reqid); if (res.ret > 0) memcpy(to, toptr, res.len); done = 1; imsg_free(&imsg); } } imsg_event_add(iev); return (ret); } 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)); } /* * 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 *imsgbuf; struct imsg imsg; struct ibuf ibuf; int n, done = 0; const void *toptr; char *hash; 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; imsgbuf = &iev->ibuf; while (!done) { if ((n = imsgbuf_read(imsgbuf)) == -1) fatalx("imsg_read"); if (n == 0) fatalx("pipe closed"); while (!done) { if ((n = imsg_get(imsgbuf, &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 (imsg.hdr.type != IMSG_CRYPTO_ECDSA_SIGN && crypto_dispatch_server(imsgbuf->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); if (imsg_get_ibuf(&imsg, &ibuf) == -1 || ibuf_get(&ibuf, &res, sizeof(res)) == -1 || ibuf_size(&ibuf) != res.len) fatalx("size mismatch for imsg %d", imsg.hdr.type); toptr = ibuf_data(&ibuf); 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 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)); } /* * Initialize the two engines. */ static void rsa_engine_init(void) { const char *errstr; if ((rsa_default = RSA_get_default_method()) == NULL) { errstr = "RSA_get_default_method"; goto fail; } if ((rsae_method = RSA_meth_dup(rsa_default)) == NULL) { errstr = "RSA_meth_dup"; goto fail; } RSA_meth_set_priv_enc(rsae_method, rsae_priv_enc); RSA_meth_set_priv_dec(rsae_method, rsae_priv_dec); 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)); RSA_set_default_method(rsae_method); return; fail: ssl_error(errstr); fatalx("%s", errstr); } static void ecdsa_engine_init(void) { int (*sign)(int, const unsigned char *, int, unsigned char *, unsigned int *, const BIGNUM *, const BIGNUM *, EC_KEY *); int (*sign_setup)(EC_KEY *, BN_CTX *, BIGNUM **, BIGNUM **); const char *errstr; if ((ecdsa_default = EC_KEY_get_default_method()) == NULL) { errstr = "EC_KEY_get_default_method"; goto fail; } if ((ecdsae_method = EC_KEY_METHOD_new(ecdsa_default)) == NULL) { errstr = "EC_KEY_METHOD_new"; goto fail; } EC_KEY_METHOD_get_sign(ecdsa_default, &sign, &sign_setup, NULL); EC_KEY_METHOD_set_sign(ecdsae_method, sign, sign_setup, ecdsae_do_sign); EC_KEY_set_default_method(ecdsae_method); return; fail: ssl_error(errstr); fatalx("%s", errstr); } void crypto_engine_init(struct conf *c) { conf = c; rsa_engine_init(); ecdsa_engine_init(); }