Download raw body.
Relayd doesn't like ecdsa
Hi Rafael,
Patches applied on -current and running.
Looking good.
Mischa
On 2026-06-15 17:04, Rafael Sadowski wrote:
> 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