Download raw body.
Relayd doesn't like ecdsa
Hello,
Mischa <bsdnl@mlst.nl> wrote:
> On 2026-04-23 14:25, Theo Buehler wrote:
> > On Thu, Apr 23, 2026 at 02:07:45PM +0200, Mischa wrote:
> >> Hi All,
> >>
> >> When using edcsa within acme-client.conf, relayd is unable to use the
> >> key/cert, it seems to be looking for an RSA key/cert specifically. Is
> >> there
> >> a way to go around this?
> >
> > No. The privsep stuff has only RSA wired up. Someone motivated could
> > probably crib from smtpd's ca.c.
>
> I wish I had the skilzzz. :/
> Willing to incentivize where possible. :)
some time ago while working on smtpd's ca.c I wrote an implementation
for relayd, mostly to validate my understanding. I was too scared to
share it, I don't use relayd normally, and I try to stay a little bit
away from it in general. (sorry, I found it confusing!)
Anyway, I tried to resurrect the diff. It works for me with a stupid
small config and an ec key generated with:
key=...
pem=...
openssl ecparam -name secp384r1 -genkey -noout -out "${key}"
openssl req -new -x509 -key "${key}" -out "${pem}" -days 365 \
-nodes -subj "/CN=localhost"
can you give it a spin? there are chances it might work =)
I don't like how we reuse the cko struct in ca_dispatch_relay(), but
that's what was already done in the RSA case.
diff /usr/src
path + /usr/src
commit - e268a8ba09fa63295bce8b5f024a710203085e2a
blob - c4f527fa96f5b64a702d682f9d59ef92cf46d9d4
file + usr.sbin/relayd/ca.c
--- usr.sbin/relayd/ca.c
+++ usr.sbin/relayd/ca.c
@@ -222,9 +222,11 @@ ca_dispatch_relay(int fd, struct privsep_proc *p, stru
struct ctl_keyop cko;
EVP_PKEY *pkey;
RSA *rsa;
+ EC_KEY *ecdsa;
u_char *from = NULL, *to = NULL;
struct iovec iov[2];
- int c = 0;
+ int ret, c = 0;
+ unsigned int len;
switch (imsg->hdr.type) {
case IMSG_CA_PRIVENC:
@@ -291,6 +293,55 @@ ca_dispatch_relay(int fd, struct privsep_proc *p, stru
free(to);
RSA_free(rsa);
break;
+
+ case IMSG_CA_ECDSA_SIGN:
+ IMSG_SIZE_CHECK(imsg, (&cko));
+ bcopy(imsg->data, &cko, sizeof(cko));
+ if (cko.cko_proc > env->sc_conf.prefork_relay)
+ fatalx("%s: invalid relay proc", __func__);
+ if (IMSG_DATA_SIZE(imsg) != (sizeof(cko) + cko.cko_flen))
+ fatalx("%s: invalid key operation", __func__);
+ from = (u_char *)imsg->data + sizeof(cko);
+
+ 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->hdr.type, -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("ca_imsg: calloc");
+ ret = ECDSA_sign(0, from, cko.cko_flen,
+ to, &len, ecdsa);
+
+ iov[c].iov_base = &cko;
+ iov[c++].iov_len = sizeof(cko);
+ if (ret > 0) {
+ cko.cko_tlen = len;
+ iov[c].iov_base = to;
+ iov[c++].iov_len = len;
+ }
+
+ if (proc_composev_imsg(env->sc_ps, PROC_RELAY, cko.cko_proc,
+ imsg->hdr.type, -1, -1, iov, c) == -1)
+ log_warn("%s: proc_composev_imsg", __func__);
+
+ free(to);
+ EC_KEY_free(ecdsa);
+ break;
+
default:
return -1;
}
@@ -440,14 +491,11 @@ rsae_priv_dec(int flen, const u_char *from, u_char *to
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;
@@ -477,3 +525,186 @@ ca_engine_init(struct relayd *x_env)
RSA_meth_free(rsae_method);
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 privsep *ps = env->sc_ps;
+ struct pollfd pfd[1];
+ struct ctl_keyop cko;
+ struct iovec iov[2];
+ struct imsgbuf *ibuf;
+ 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 (0);
+
+ iev = proc_iev(ps, PROC_CA, ps->ps_instance);
+ ibuf = &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(ibuf, IMSG_CA_ECDSA_SIGN, 0, 0, -1, iov, cnt) == -1) {
+ log_warn("%s: imsg_composev", __func__);
+ return NULL;
+ }
+ if (imsgbuf_flush(ibuf) == -1) {
+ log_warn("%s: imsgbuf_flush", __func__);
+ return NULL;
+ }
+
+ pfd[0].fd = ibuf->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(ibuf)) == -1)
+ fatalx("imsgbuf_read");
+ if (n == 0)
+ fatalx("pipe closed");
+
+ while (!done) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatalx("imsg_get error");
+ if (n == 0)
+ break;
+
+ IMSG_SIZE_CHECK(&imsg, (&cko));
+ memcpy(&cko, imsg.data, sizeof(cko));
+
+ /*
+ * 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.hdr.type != 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 (IMSG_DATA_SIZE(&imsg) !=
+ (sizeof(cko) + cko.cko_tlen))
+ fatalx("data size");
+ toptr = (u_char *)imsg.data + sizeof(cko);
+ d2i_ECDSA_SIG(&sig, (const unsigned char **)&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();
+}
commit - e268a8ba09fa63295bce8b5f024a710203085e2a
blob - a5363989f4b4cbcca6c899b12de4ccd3cdf4dfcb
file + usr.sbin/relayd/relayd.h
--- usr.sbin/relayd/relayd.h
+++ usr.sbin/relayd/relayd.h
@@ -1005,6 +1005,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
@@ -1289,6 +1290,7 @@ void script_done(struct relayd *, struct ctl_script *
int script_exec(struct relayd *, struct ctl_script *);
/* ssl.c */
+void ssl_error(const char *);
char *ssl_load_key(struct relayd *, const char *, off_t *, char *);
uint8_t *ssl_update_certificate(const uint8_t *, size_t, EVP_PKEY *,
EVP_PKEY *, X509 *, size_t *);
commit - e268a8ba09fa63295bce8b5f024a710203085e2a
blob - 19950b89e56aec83e15bc9635293bd078f331e45
file + usr.sbin/relayd/ssl.c
--- usr.sbin/relayd/ssl.c
+++ usr.sbin/relayd/ssl.c
@@ -46,6 +46,18 @@ ssl_password_cb(char *buf, int size, int rwflag, void
return (len);
}
+void
+ssl_error(const char *where)
+{
+ unsigned long code;
+ char errbuf[128];
+
+ for (; (code = ERR_get_error()) != 0 ;) {
+ ERR_error_string_n(code, errbuf, sizeof(errbuf));
+ log_debug("debug: SSL library error: %s: %s", where, errbuf);
+ }
+}
+
char *
ssl_load_key(struct relayd *env, const char *name, off_t *len, char *pass)
{
@@ -181,6 +193,7 @@ ssl_load_pkey(char *buf, off_t len, X509 **x509ptr, EV
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) {
@@ -196,21 +209,47 @@ ssl_load_pkey(char *buf, off_t len, X509 **x509ptr, EV
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 */
+ RSA_free(rsa);
+ EC_KEY_free(eckey);
*pkeyptr = pkey;
if (x509ptr != NULL)
*x509ptr = x509;
Relayd doesn't like ecdsa