Download raw body.
Relayd doesn't like ecdsa
On Mon Jun 15, 2026 at 04:09:11PM +0200, Rafael Sadowski wrote:
> On Wed Jun 03, 2026 at 12:45:27PM +0200, Mischa wrote:
> > Hi Rafael,
> >
> > Not sure if this is relevant in this thread but...
> > I got this message yesterday and the relayd process stopped.
> > This is with the initial patch which Omar provided, running on 7.9.
> >
> > Jun 2 21:18:49 obsdams relayd[69181]: ecdsae_send_enc_imsg: priv ecdsa poll
> > timeout, keyop #29f3
> > Jun 2 21:18:49 obsdams relayd[69181]: fatal in relay: proc_dispatch: relay
> > 1 got invalid imsg 61 peerid -1 from ca 1
> > Jun 2 21:18:49 obsdams relayd[33605]: lost child: pid 69181 exited
> > abnormally
> > Jun 2 21:18:50 obsdams relayd[33597]: ecdsae_send_enc_imsg: imsgbuf_flush:
> > Broken pipe
> >
> > Mischa
> >
>
> I think you ran into the RELAY_TLS_PRIV_TIMEOUT (hard-coded 1sec) and
> Omas's diff do not handle IMSG_CA_ECDSA_SIGN in relay_dispatch_ca
> properly:
>
> diff --git a/relay.c b/relay.c
> index 2f6ec5d..0c6dae0 100644
> --- a/relay.c
> +++ b/relay.c
> @@ -1980,6 +1980,7 @@ relay_dispatch_ca(int fd, struct privsep_proc *p, struct imsg *imsg)
> switch (imsg_get_type(imsg)) {
> case IMSG_CA_PRIVENC:
> case IMSG_CA_PRIVDEC:
> + case IMSG_CA_ECDSA_SIGN:
> log_warnx("%s: priv%s result after timeout", __func__,
> imsg_get_type(imsg) == IMSG_CA_PRIVENC ? "enc" : "dec");
> return (0);
>
Of course this was quick and dirty below is a better solution. Full diff
if you want to test it.
https://rsadowski.gothub.org/?action=summary&headref=feature/ecdsa&path=relayd.git
diff --git a/ca.c b/ca.c
index 6ce8626..0fb4877 100644
--- a/ca.c
+++ b/ca.c
@@ -223,9 +223,11 @@ ca_dispatch_relay(int fd, struct privsep_proc *p, struct imsg *imsg)
struct ctl_keyop cko;
EVP_PKEY *pkey;
RSA *rsa;
+ EC_KEY *ecdsa;
u_char *to = NULL;
struct iovec iov[2];
- int c = 0;
+ int ret = 0, c = 0;
+ unsigned int len;
switch (imsg_get_type(imsg)) {
case IMSG_CA_PRIVENC:
@@ -299,6 +301,64 @@ ca_dispatch_relay(int fd, struct privsep_proc *p, struct imsg *imsg)
free(to);
RSA_free(rsa);
break;
+
+ case IMSG_CA_ECDSA_SIGN:
+ if (imsg_get_ibuf(imsg, &ibuf) == -1) {
+ log_warn("%s: imsg_get_ibuf", __func__);
+ return (-1);
+ }
+
+ if (ibuf_get(&ibuf, &cko, sizeof(cko)) == -1) {
+ log_warn("%s: ibuf_get", __func__);
+ return (-1);
+ }
+
+ if (cko.cko_proc > env->sc_conf.prefork_relay)
+ fatalx("%s: invalid relay proc", __func__);
+ if (ibuf_size(&ibuf) != (size_t)cko.cko_flen)
+ fatalx("%s: invalid key operation", __func__);
+
+ if ((pkey = pkey_find(env, cko.cko_hash)) == NULL) {
+ log_warnx("%s: invalid relay hash '%s'",
+ __func__, cko.cko_hash);
+ /* Signal failure to the waiting relay worker. */
+ cko.cko_tlen = -1;
+ iov[c].iov_base = &cko;
+ iov[c++].iov_len = sizeof(cko);
+ if (proc_composev_imsg(env->sc_ps, PROC_RELAY,
+ cko.cko_proc, imsg_get_type(imsg), -1, -1, iov,
+ c) == -1)
+ log_warn("%s: proc_composev_imsg", __func__);
+ break;
+ }
+
+ if ((ecdsa = EVP_PKEY_get1_EC_KEY(pkey)) == NULL)
+ fatalx("%s: invalid relay key", __func__);
+
+ len = ECDSA_size(ecdsa);
+
+ if ((to = calloc(1, len)) == NULL)
+ fatalx("%s: calloc", __func__);
+
+ ret = ECDSA_sign(0, ibuf_data(&ibuf), ibuf_size(&ibuf), to,
+ &len, ecdsa);
+
+ cko.cko_tlen = (ret > 0) ? len : -1;
+ iov[c].iov_base = &cko;
+ iov[c++].iov_len = sizeof(cko);
+ if (ret > 0) {
+ iov[c].iov_base = to;
+ iov[c++].iov_len = len;
+ }
+
+ if (proc_composev_imsg(env->sc_ps, PROC_RELAY, cko.cko_proc,
+ imsg_get_type(imsg), -1, -1, iov, c) == -1)
+ log_warn("%s: proc_composev_imsg", __func__);
+
+ free(to);
+ EC_KEY_free(ecdsa);
+ break;
+
default:
return -1;
}
@@ -389,7 +449,7 @@ rsae_send_imsg(int flen, const u_char *from, u_char *to, RSA *rsa,
while (!done) {
if ((n = imsgbuf_get(imsgbuf, &imsg)) == -1)
- fatalx("imsg_get error");
+ fatalx("imsgbuf_get error");
if (n == 0)
break;
@@ -455,14 +515,11 @@ rsae_priv_dec(int flen, const u_char *from, u_char *to, RSA *rsa, int padding)
return rsae_send_imsg(flen, from, to, rsa, padding, IMSG_CA_PRIVDEC);
}
-void
-ca_engine_init(struct relayd *x_env)
+static void
+rsa_engine_init(void)
{
const char *errstr;
- if (env == NULL)
- env = x_env;
-
if (rsa_default != NULL)
return;
@@ -494,3 +551,196 @@ ca_engine_init(struct relayd *x_env)
ssl_error(errstr);
fatalx("%s: %s", __func__, errstr);
}
+
+/*
+ * ECDSA privsep engine (called from unprivileged processes)
+ */
+
+const EC_KEY_METHOD *ecdsa_default = NULL;
+
+static EC_KEY_METHOD *ecdsae_method = NULL;
+
+static ECDSA_SIG *
+ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len,
+ const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey)
+{
+ struct ibuf ibuf;
+ struct privsep *ps = env->sc_ps;
+ struct pollfd pfd[1];
+ struct ctl_keyop cko;
+ struct iovec iov[2];
+ struct imsgbuf *imsgbuf;
+ struct imsgev *iev;
+ struct imsg imsg;
+ int n, done = 0, cnt = 0;
+ const u_char *toptr;
+ static u_int seq = 0;
+
+ char *hash;
+ ECDSA_SIG *sig = NULL;
+
+ if ((hash = EC_KEY_get_ex_data(eckey, 0)) == NULL)
+ return (NULL);
+
+ iev = proc_iev(ps, PROC_CA, ps->ps_instance);
+ imsgbuf = &iev->ibuf;
+
+ /*
+ * XXX this could be nicer...
+ */
+
+ memset(&cko, 0, sizeof(cko));
+ (void)strlcpy(cko.cko_hash, hash, sizeof(cko.cko_hash));
+ cko.cko_proc = ps->ps_instance;
+ cko.cko_flen = dgst_len;
+ cko.cko_cookie = seq++;
+
+ iov[cnt].iov_base = &cko;
+ iov[cnt++].iov_len = sizeof(cko);
+ iov[cnt].iov_base = (void *)(uintptr_t)dgst;
+ iov[cnt++].iov_len = dgst_len;
+
+ /*
+ * Send a synchronous imsg because we cannot defer the ECDSA
+ * operation in OpenSSL's engine layer.
+ */
+ if (imsg_composev(imsgbuf, IMSG_CA_ECDSA_SIGN, 0, 0, -1, iov, cnt) ==
+ -1) {
+ log_warn("%s: imsg_composev", __func__);
+ return (NULL);
+ }
+ if (imsgbuf_flush(imsgbuf) == -1) {
+ log_warn("%s: imsgbuf_flush", __func__);
+ return (NULL);
+ }
+
+ pfd[0].fd = imsgbuf->fd;
+ pfd[0].events = POLLIN;
+
+ while (!done) {
+ switch (poll(pfd, 1, RELAY_TLS_PRIV_TIMEOUT)) {
+ case -1:
+ if (errno != EINTR)
+ fatal("%s: poll", __func__);
+ continue;
+ case 0:
+ log_warnx("%s: priv ecdsa poll timeout, keyop #%x",
+ __func__,
+ cko.cko_cookie);
+ return (NULL);
+ default:
+ break;
+ }
+
+ if ((n = imsgbuf_read(imsgbuf)) == -1)
+ fatalx("imsgbuf_read");
+ if (n == 0)
+ fatalx("pipe closed");
+
+ while (!done) {
+ if ((n = imsgbuf_get(imsgbuf, &imsg)) == -1)
+ fatalx("imsgbuf_get error");
+ if (n == 0)
+ break;
+
+ if (imsg_get_ibuf(&imsg, &ibuf) == -1) {
+ log_warn("%s: imsg_get_ibuf", __func__);
+ imsg_free(&imsg);
+ return (NULL);
+ }
+
+ if (ibuf_get(&ibuf, &cko, sizeof(cko)) == -1) {
+ log_warn("%s: ibuf_get", __func__);
+ imsg_free(&imsg);
+ return (NULL);
+ }
+
+ /*
+ * Due to earlier timed out requests, there may be
+ * responses that need to be skipped.
+ */
+ if (cko.cko_cookie != seq - 1) {
+ log_warnx(
+ "%s: priv ecdsa obsolete keyop #%x",
+ __func__,
+ cko.cko_cookie);
+ imsg_free(&imsg);
+ continue;
+ }
+
+ if (imsg_get_type(&imsg) != IMSG_CA_ECDSA_SIGN)
+ fatalx("invalid response");
+
+ if (cko.cko_tlen == -1) {
+ log_warnx("%s: priv ecdsa failed for key %s",
+ __func__, cko.cko_hash);
+ } else if (cko.cko_tlen > 0) {
+ if (ibuf_size(&ibuf) != (size_t)cko.cko_tlen)
+ fatalx("data size");
+ toptr = ibuf_data(&ibuf);
+ d2i_ECDSA_SIG(&sig, &toptr, cko.cko_tlen);
+ }
+ 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 *);
+
+ DPRINTF("%s:%d", __func__, __LINE__);
+ 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 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
+ca_engine_init(struct relayd *x_env)
+{
+ if (env == NULL)
+ env = x_env;
+
+ rsa_engine_init();
+ ecdsa_engine_init();
+}
diff --git a/relay.c b/relay.c
index 2f6ec5d..8db6c32 100644
--- a/relay.c
+++ b/relay.c
@@ -1978,6 +1978,9 @@ int
relay_dispatch_ca(int fd, struct privsep_proc *p, struct imsg *imsg)
{
switch (imsg_get_type(imsg)) {
+ case IMSG_CA_ECDSA_SIGN:
+ log_warnx("%s: priv result after timeout", __func__);
+ return (0);
case IMSG_CA_PRIVENC:
case IMSG_CA_PRIVDEC:
log_warnx("%s: priv%s result after timeout", __func__,
diff --git a/relayd.h b/relayd.h
index c772300..dd666b6 100644
--- a/relayd.h
+++ b/relayd.h
@@ -1001,6 +1001,7 @@ enum imsg_type {
IMSG_CFG_DONE,
IMSG_CA_PRIVENC,
IMSG_CA_PRIVDEC,
+ IMSG_CA_ECDSA_SIGN,
IMSG_SESS_PUBLISH, /* from relay to pfe */
IMSG_SESS_UNPUBLISH,
IMSG_TLSTICKET_REKEY
diff --git a/ssl.c b/ssl.c
index 77f4dd1..c635648 100644
--- a/ssl.c
+++ b/ssl.c
@@ -189,6 +189,7 @@ ssl_load_pkey(char *buf, off_t len, X509 **x509ptr, EVP_PKEY **pkeyptr)
X509 *x509 = NULL;
EVP_PKEY *pkey = NULL;
RSA *rsa = NULL;
+ EC_KEY *eckey = NULL;
char *hash = NULL;
if ((in = BIO_new_mem_buf(buf, len)) == NULL) {
@@ -204,21 +205,47 @@ ssl_load_pkey(char *buf, off_t len, X509 **x509ptr, EVP_PKEY **pkeyptr)
log_warnx("%s: X509_get_pubkey failed", __func__);
goto fail;
}
- if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) {
- log_warnx("%s: failed to extract RSA", __func__);
- goto fail;
- }
if ((hash = malloc(TLS_CERT_HASH_SIZE)) == NULL) {
log_warn("%s: allocate hash failed", __func__);
goto fail;
}
hash_x509(x509, hash, TLS_CERT_HASH_SIZE);
- if (RSA_set_ex_data(rsa, 0, hash) != 1) {
- log_warnx("%s: failed to set hash as exdata", __func__);
+
+ switch (EVP_PKEY_id(pkey)) {
+ case EVP_PKEY_RSA:
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) {
+ log_warnx("%s: failed to extract RSA", __func__);
+ goto fail;
+ }
+ if (RSA_set_ex_data(rsa, 0, hash) != 1) {
+ log_warnx("%s: failed to set hash as exdata", __func__);
+ goto fail;
+ }
+ break;
+ case EVP_PKEY_EC:
+ if ((eckey = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) {
+ log_warnx("%s: failed to set extract EC key", __func__);
+ goto fail;
+ }
+ if (EC_KEY_set_ex_data(eckey, 0, hash) == 0) {
+ log_warnx("%s: failed to set hash as exdata", __func__);
+ goto fail;
+ }
+
+ /* Reset the key to work around caching in OpenSSL 3. */
+ if (EVP_PKEY_set1_EC_KEY(pkey, eckey) == 0) {
+ log_warnx("%s: failed to set EC key", __func__);
+ goto fail;
+ }
+ break;
+ default:
+ log_warnx("%s: incorrect key type", __func__);
goto fail;
}
- RSA_free(rsa); /* dereference, will be cleaned up with pkey */
+ /* dereference, will be cleaned up with pkey */
+ RSA_free(rsa);
+ EC_KEY_free(eckey);
*pkeyptr = pkey;
if (x509ptr != NULL)
*x509ptr = x509;
Relayd doesn't like ecdsa