summaryrefslogtreecommitdiff
path: root/usr.sbin/smtpd/ca.c
diff options
context:
space:
mode:
authorGilles Chehade <gilles@cvs.openbsd.org>2019-06-05 06:40:14 +0000
committerGilles Chehade <gilles@cvs.openbsd.org>2019-06-05 06:40:14 +0000
commitc721df2cebecf2c16f27d599c4712cf6dcb98ba5 (patch)
treee23e837842071a6efc0f32528f0264c68e7cd359 /usr.sbin/smtpd/ca.c
parent0f036f0658db45187a756f457df61b42251b0f6f (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.
Diffstat (limited to 'usr.sbin/smtpd/ca.c')
-rw-r--r--usr.sbin/smtpd/ca.c263
1 files changed, 243 insertions, 20 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();
+}