diff options
author | Gilles Chehade <gilles@cvs.openbsd.org> | 2019-06-05 06:40:14 +0000 |
---|---|---|
committer | Gilles Chehade <gilles@cvs.openbsd.org> | 2019-06-05 06:40:14 +0000 |
commit | c721df2cebecf2c16f27d599c4712cf6dcb98ba5 (patch) | |
tree | e23e837842071a6efc0f32528f0264c68e7cd359 | |
parent | 0f036f0658db45187a756f457df61b42251b0f6f (diff) |
a long long time ago, in a galaxy quite close actually, reyk@ introduced an
RSA privsep engine to isolate private keys in the ca process. ECDSA support
in smtpd is become a frequent request so here's an ECDSA privsep engine and
the code required for smtpd to load ECDSA certificates and use them.
-rw-r--r-- | usr.sbin/smtpd/ca.c | 263 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.c | 7 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 7 | ||||
-rw-r--r-- | usr.sbin/smtpd/ssl.c | 14 |
4 files changed, 262 insertions, 29 deletions
diff --git a/usr.sbin/smtpd/ca.c b/usr.sbin/smtpd/ca.c index 8be8796508e..142926cb3b1 100644 --- a/usr.sbin/smtpd/ca.c +++ b/usr.sbin/smtpd/ca.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ca.c,v 1.32 2019/05/24 15:34:05 gilles Exp $ */ +/* $OpenBSD: ca.c,v 1.33 2019/06/05 06:40:13 gilles Exp $ */ /* * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org> @@ -34,6 +34,7 @@ #include <openssl/ssl.h> #include <openssl/pem.h> #include <openssl/evp.h> +#include <openssl/ecdsa.h> #include <openssl/rsa.h> #include <openssl/engine.h> #include <openssl/err.h> @@ -61,7 +62,14 @@ static int rsae_init(RSA *); static int rsae_finish(RSA *); static int rsae_keygen(RSA *, int, BIGNUM *, BN_GENCB *); -static uint64_t rsae_reqid = 0; +static ECDSA_SIG *ecdsae_do_sign(const unsigned char *, int, const BIGNUM *, + const BIGNUM *, EC_KEY *); +static int ecdsae_sign_setup(EC_KEY *, BN_CTX *, BIGNUM **, BIGNUM **); +static int ecdsae_do_verify(const unsigned char *, int, const ECDSA_SIG *, + EC_KEY *); + + +static uint64_t reqid = 0; static void ca_shutdown(void) @@ -215,12 +223,14 @@ end: void ca_imsg(struct mproc *p, struct imsg *imsg) { - RSA *rsa; + RSA *rsa = NULL; + EC_KEY *ecdsa = NULL; const void *from = NULL; unsigned char *to = NULL; struct msg m; const char *pkiname; size_t flen, tlen, padding; + int buf_len; struct pki *pki; int ret = 0; uint64_t id; @@ -253,8 +263,8 @@ ca_imsg(struct mproc *p, struct imsg *imsg) profiling = v; return; - case IMSG_CA_PRIVENC: - case IMSG_CA_PRIVDEC: + case IMSG_CA_RSA_PRIVENC: + case IMSG_CA_RSA_PRIVDEC: m_msg(&m, imsg); m_get_id(&m, &id); m_get_string(&m, &pkiname); @@ -272,11 +282,11 @@ ca_imsg(struct mproc *p, struct imsg *imsg) fatalx("ca_imsg: calloc"); switch (imsg->hdr.type) { - case IMSG_CA_PRIVENC: + case IMSG_CA_RSA_PRIVENC: ret = RSA_private_encrypt(flen, from, to, rsa, padding); break; - case IMSG_CA_PRIVDEC: + case IMSG_CA_RSA_PRIVDEC: ret = RSA_private_decrypt(flen, from, to, rsa, padding); break; @@ -291,7 +301,32 @@ ca_imsg(struct mproc *p, struct imsg *imsg) free(to); RSA_free(rsa); + return; + + case IMSG_CA_ECDSA_SIGN: + m_msg(&m, imsg); + m_get_id(&m, &id); + m_get_string(&m, &pkiname); + m_get_data(&m, &from, &flen); + m_end(&m); + + pki = dict_get(env->sc_pki_dict, pkiname); + if (pki == NULL || pki->pki_pkey == NULL || + (ecdsa = EVP_PKEY_get1_EC_KEY(pki->pki_pkey)) == NULL) + fatalx("ca_imsg: invalid pki"); + buf_len = ECDSA_size(ecdsa); + if ((to = calloc(1, buf_len)) == NULL) + fatalx("ca_imsg: calloc"); + ret = ECDSA_sign(0, from, flen, to, &buf_len, ecdsa); + m_create(p, imsg->hdr.type, 0, 0, -1); + m_add_id(p, id); + m_add_int(p, ret); + if (ret > 0) + m_add_data(p, to, (size_t)buf_len); + m_close(p); + free(to); + EC_KEY_free(ecdsa); return; } @@ -328,8 +363,8 @@ rsae_send_imsg(int flen, const unsigned char *from, unsigned char *to, * operation in OpenSSL's engine layer. */ m_create(p_ca, cmd, 0, 0, -1); - rsae_reqid++; - m_add_id(p_ca, rsae_reqid); + reqid++; + m_add_id(p_ca, reqid); m_add_string(p_ca, pkiname); m_add_data(p_ca, (const void *)from, (size_t)flen); m_add_size(p_ca, (size_t)RSA_size(rsa)); @@ -353,8 +388,8 @@ rsae_send_imsg(int flen, const unsigned char *from, unsigned char *to, log_imsg(PROC_PONY, PROC_CA, &imsg); switch (imsg.hdr.type) { - case IMSG_CA_PRIVENC: - case IMSG_CA_PRIVDEC: + case IMSG_CA_RSA_PRIVENC: + case IMSG_CA_RSA_PRIVDEC: break; default: /* Another imsg is queued up in the buffer */ @@ -365,7 +400,7 @@ rsae_send_imsg(int flen, const unsigned char *from, unsigned char *to, m_msg(&m, &imsg); m_get_id(&m, &id); - if (id != rsae_reqid) + if (id != reqid) fatalx("invalid response id"); m_get_int(&m, &ret); if (ret > 0) @@ -405,10 +440,9 @@ rsae_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) { log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - if (RSA_get_ex_data(rsa, 0) != NULL) { + if (RSA_get_ex_data(rsa, 0) != NULL) return (rsae_send_imsg(flen, from, to, rsa, padding, - IMSG_CA_PRIVENC)); - } + IMSG_CA_RSA_PRIVENC)); return (rsa_default->rsa_priv_enc(flen, from, to, rsa, padding)); } @@ -417,10 +451,10 @@ rsae_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) { log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - if (RSA_get_ex_data(rsa, 0) != NULL) { + if (RSA_get_ex_data(rsa, 0) != NULL) return (rsae_send_imsg(flen, from, to, rsa, padding, - IMSG_CA_PRIVDEC)); - } + IMSG_CA_RSA_PRIVDEC)); + return (rsa_default->rsa_priv_dec(flen, from, to, rsa, padding)); } @@ -464,8 +498,138 @@ rsae_keygen(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb) return (rsa_default->rsa_keygen(rsa, bits, e, cb)); } -void -ca_engine_init(void) + +/* + * ECDSA privsep engine (called from unprivileged processes) + */ + +const ECDSA_METHOD *ecdsa_default = NULL; + +static ECDSA_METHOD *ecdsae_method = NULL; + +ECDSA_METHOD * +ECDSA_METHOD_new_temporary(const char *name, int); + +ECDSA_METHOD * +ECDSA_METHOD_new_temporary(const char *name, int flags) +{ + ECDSA_METHOD *ecdsa; + + if ((ecdsa = calloc(1, sizeof (*ecdsa))) == NULL) + return NULL; + + if ((ecdsa->name = strdup(name)) == NULL) { + free(ecdsa); + return NULL; + } + + ecdsa->flags = flags; + return ecdsa; +} + +static ECDSA_SIG * +ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, + const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) +{ + int ret = 0; + struct imsgbuf *ibuf; + struct imsg imsg; + int n, done = 0; + const void *toptr; + char *pkiname; + size_t tlen; + struct msg m; + uint64_t id; + ECDSA_SIG *sig = NULL; + + if ((pkiname = ECDSA_get_ex_data(eckey, 0)) == NULL) + return (0); + + /* + * Send a synchronous imsg because we cannot defer the ECDSA + * operation in OpenSSL's engine layer. + */ + m_create(p_ca, IMSG_CA_ECDSA_SIGN, 0, 0, -1); + reqid++; + m_add_id(p_ca, reqid); + m_add_string(p_ca, pkiname); + m_add_data(p_ca, (const void *)dgst, (size_t)dgst_len); + m_flush(p_ca); + + ibuf = &p_ca->imsgbuf; + + 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; + + log_imsg(PROC_PONY, PROC_CA, &imsg); + + switch (imsg.hdr.type) { + case IMSG_CA_ECDSA_SIGN: + break; + default: + /* Another imsg is queued up in the buffer */ + pony_imsg(p_ca, &imsg); + imsg_free(&imsg); + continue; + } + + m_msg(&m, &imsg); + m_get_id(&m, &id); + if (id != reqid) + fatalx("invalid response id"); + m_get_int(&m, &ret); + if (ret > 0) + m_get_data(&m, &toptr, &tlen); + m_end(&m); + done = 1; + + if (ret > 0) + d2i_ECDSA_SIG(&sig, (const unsigned char **)&toptr, tlen); + imsg_free(&imsg); + } + } + mproc_event_add(p_ca); + + return (sig); +} + +ECDSA_SIG * +ecdsae_do_sign(const unsigned char *dgst, int dgst_len, + const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (ECDSA_get_ex_data(eckey, 0) != NULL) + return (ecdsae_send_enc_imsg(dgst, dgst_len, inv, rp, eckey)); + return (ecdsa_default->ecdsa_do_sign(dgst, dgst_len, inv, rp, eckey)); +} + +int +ecdsae_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, + BIGNUM **r) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (ecdsa_default->ecdsa_sign_setup(eckey, ctx, kinv, r)); +} + +int +ecdsae_do_verify(const unsigned char *dgst, int dgst_len, + const ECDSA_SIG *sig, EC_KEY *eckey) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (ecdsa_default->ecdsa_do_verify(dgst, dgst_len, sig, eckey)); +} + + +static void +rsa_engine_init(void) { ENGINE *e; const char *errstr, *name; @@ -531,3 +695,62 @@ ca_engine_init(void) ssl_error(errstr); fatalx("%s", errstr); } + +static void +ecdsa_engine_init(void) +{ + ENGINE *e; + const char *errstr, *name; + + if ((ecdsae_method = ECDSA_METHOD_new_temporary("ECDSA privsep engine", 0)) == NULL) + goto fail; + + ecdsae_method->ecdsa_do_sign = ecdsae_do_sign; + ecdsae_method->ecdsa_sign_setup = ecdsae_sign_setup; + ecdsae_method->ecdsa_do_verify = ecdsae_do_verify; + + if ((e = ENGINE_get_default_ECDSA()) == NULL) { + if ((e = ENGINE_new()) == NULL) { + errstr = "ENGINE_new"; + goto fail; + } + if (!ENGINE_set_name(e, ecdsae_method->name)) { + errstr = "ENGINE_set_name"; + goto fail; + } + if ((ecdsa_default = ECDSA_get_default_method()) == NULL) { + errstr = "ECDSA_get_default_method"; + goto fail; + } + } else if ((ecdsa_default = ENGINE_get_ECDSA(e)) == NULL) { + errstr = "ENGINE_get_ECDSA"; + goto fail; + } + + if ((name = ENGINE_get_name(e)) == NULL) + name = "unknown ECDSA engine"; + + log_debug("debug: %s: using %s", __func__, name); + + if (!ENGINE_set_ECDSA(e, ecdsae_method)) { + errstr = "ENGINE_set_ECDSA"; + goto fail; + } + if (!ENGINE_set_default_ECDSA(e)) { + errstr = "ENGINE_set_default_ECDSA"; + goto fail; + } + + return; + + fail: + ssl_error(errstr); + fatalx("%s", errstr); +} + +void +ca_engine_init(void) +{ + rsa_engine_init(); + ecdsa_engine_init(); +} diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index acf0f657031..2a32f81ce90 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.c,v 1.318 2019/03/31 03:36:18 yasuoka Exp $ */ +/* $OpenBSD: smtpd.c,v 1.319 2019/06/05 06:40:13 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -2021,8 +2021,9 @@ imsg_to_str(int type) CASE(IMSG_FILTER_SMTP_DATA_BEGIN); CASE(IMSG_FILTER_SMTP_DATA_END); - CASE(IMSG_CA_PRIVENC); - CASE(IMSG_CA_PRIVDEC); + CASE(IMSG_CA_RSA_PRIVENC); + CASE(IMSG_CA_RSA_PRIVDEC); + CASE(IMSG_CA_ECDSA_SIGN); default: (void)snprintf(buf, sizeof(buf), "IMSG_??? (%d)", type); diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index 46a4e6af4f6..7bc3691f23b 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.621 2019/04/08 08:22:32 eric Exp $ */ +/* $OpenBSD: smtpd.h,v 1.622 2019/06/05 06:40:13 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -327,8 +327,9 @@ enum imsg_type { IMSG_FILTER_SMTP_DATA_BEGIN, IMSG_FILTER_SMTP_DATA_END, - IMSG_CA_PRIVENC, - IMSG_CA_PRIVDEC + IMSG_CA_RSA_PRIVENC, + IMSG_CA_RSA_PRIVDEC, + IMSG_CA_ECDSA_SIGN, }; enum smtp_proc_type { diff --git a/usr.sbin/smtpd/ssl.c b/usr.sbin/smtpd/ssl.c index b88d134961b..bd18ad61ec8 100644 --- a/usr.sbin/smtpd/ssl.c +++ b/usr.sbin/smtpd/ssl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssl.c,v 1.92 2019/05/24 16:29:41 gilles Exp $ */ +/* $OpenBSD: ssl.c,v 1.93 2019/06/05 06:40:13 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -39,6 +39,7 @@ #include <openssl/engine.h> #include <openssl/err.h> #include <openssl/rsa.h> +#include <openssl/ecdsa.h> #include <openssl/dh.h> #include <openssl/bn.h> @@ -361,6 +362,7 @@ ssl_load_pkey(const void *data, size_t datalen, char *buf, off_t len, X509 *x509 = NULL; EVP_PKEY *pkey = NULL; RSA *rsa = NULL; + EC_KEY *eckey = NULL; void *exdata = NULL; if ((in = BIO_new_mem_buf(buf, len)) == NULL) { @@ -383,15 +385,20 @@ ssl_load_pkey(const void *data, size_t datalen, char *buf, off_t len, in = NULL; if (data != NULL && datalen) { - if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL || + if (((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL && + (eckey = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) || (exdata = malloc(datalen)) == NULL) { SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_EVP_LIB); goto fail; } memcpy(exdata, data, datalen); - RSA_set_ex_data(rsa, 0, exdata); + if (rsa) + RSA_set_ex_data(rsa, 0, exdata); + if (eckey) + ECDSA_set_ex_data(eckey, 0, exdata); RSA_free(rsa); /* dereference, will be cleaned up with pkey */ + EC_KEY_free(eckey); /* dereference, will be cleaned up with pkey */ } *x509ptr = x509; @@ -401,6 +408,7 @@ ssl_load_pkey(const void *data, size_t datalen, char *buf, off_t len, fail: RSA_free(rsa); + EC_KEY_free(eckey); BIO_free(in); EVP_PKEY_free(pkey); X509_free(x509); |