From: Rafael Sadowski Subject: Re: relayd: support explicit paths for keypair To: tech@openbsd.org Cc: "Kirill A. Korinsky" Date: Wed, 18 Mar 2026 17:47:42 +0100 On Thu Feb 19, 2026 at 09:28:17PM +0100, Rafael Sadowski wrote: > The following diff extends the keypair keyword in relayd.conf to allow > explicit path specifications for certificates, private keys, and OCSP > staple files. > > Currently, relayd relies on a fixed lookup logic, searching for TLS > crt/key in /etc/ssl and /etc/ssl/private based on the keypair name and > port. > > That has always annoyed me, since all other applications must comply > with the naming convention of relayd. > > The idea is simple, the keypair statement now supports optional > certificate, key, and ocsp keywords followed by a path: > > keypair name [certificate path [key path [ocsp path]]]. Is anyone interested? > > > diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y > index fcdfb8e92e3..0cec3557657 100644 > --- a/usr.sbin/relayd/parse.y > +++ b/usr.sbin/relayd/parse.y > @@ -139,6 +139,7 @@ int relay_id(struct relay *); > struct relay *relay_inherit(struct relay *, struct relay *); > int getservice(char *); > int is_if_in_group(const char *, const char *); > +static struct keyname* proto_keyname(char *); > > typedef struct { > union { > @@ -1336,20 +1337,108 @@ tlsflags : SESSION TICKETS { proto->tickets = 1; } > free($3); > } > | KEYPAIR STRING { > - struct keyname *name; > + struct keyname *kname = NULL; > > if (strlen($2) >= PATH_MAX) { > yyerror("keypair name too long"); > free($2); > YYERROR; > } > - if ((name = calloc(1, sizeof(*name))) == NULL) { > - yyerror("calloc"); > + > + if ((kname = proto_keyname($2)) == NULL) { > free($2); > YYERROR; > } > - name->name = $2; > - TAILQ_INSERT_TAIL(&proto->tlscerts, name, entry); > + free($2); > + } > + | KEYPAIR STRING CERTIFICATE STRING { > + struct keyname *kname = NULL; > + > + if (strlen($2) >= PATH_MAX) { > + yyerror("keypair name too long"); > + free($2); > + YYERROR; > + } > + > + if ((kname = proto_keyname($2)) == NULL) { > + free($2); > + YYERROR; > + } > + > + if (strlen($4) >= PATH_MAX) { > + yyerror("keypair cert too long"); > + free($4); > + YYERROR; > + } > + if (strlcpy(kname->certificate, $4, > + sizeof(kname->certificate)) >= > + sizeof(kname->certificate)) { > + yyerror("keypair certificate truncated"); > + free($4); > + YYERROR; > + } > + free($2); > + free($4); > + } > + | KEYPAIR STRING KEY STRING { > + struct keyname *kname = NULL; > + > + if (strlen($2) >= PATH_MAX) { > + yyerror("keypair name too long"); > + free($2); > + YYERROR; > + } > + > + if ((kname = proto_keyname($2)) == NULL) { > + free($2); > + YYERROR; > + } > + > + if (strlen($4) >= PATH_MAX) { > + yyerror("keypair certificate key too long"); > + free($4); > + YYERROR; > + } > + > + if (strlcpy(kname->key, $4, > + sizeof(kname->key)) >= > + sizeof(kname->key)) { > + yyerror("keypair certificate key truncated"); > + free($4); > + YYERROR; > + } > + free($2); > + free($4); > + } > + | KEYPAIR STRING OCSP STRING { > + struct keyname *kname = NULL; > + > + if (strlen($2) >= PATH_MAX) { > + yyerror("keypair name too long"); > + free($2); > + YYERROR; > + } > + > + if ((kname = proto_keyname($2)) == NULL) { > + free($2); > + YYERROR; > + } > + > + if (strlen($4) >= PATH_MAX) { > + yyerror("keypair ocsp file too long"); > + free($4); > + YYERROR; > + } > + > + if (strlcpy(kname->ocsp, $4, > + sizeof(kname->ocsp)) >= > + sizeof(kname->ocsp)) { > + yyerror("ocsp truncated"); > + free($4); > + YYERROR; > + } > + free($2); > + free($4); > } > | CLIENT CA STRING { > if (strlcpy(proto->tlsclientca, $3, > @@ -1844,7 +1933,7 @@ relay : RELAY STRING { > } '{' optnl relayopts_l '}' { > struct relay *r; > struct relay_config *rlconf = &rlay->rl_conf; > - struct keyname *name; > + struct keyname *kname; > > if (relay_findbyname(conf, rlconf->name) != NULL || > relay_findbyaddr(conf, rlconf) != NULL) { > @@ -1882,11 +1971,11 @@ relay : RELAY STRING { > rlay->rl_conf.name); > YYERROR; > } > - TAILQ_FOREACH(name, &rlay->rl_proto->tlscerts, entry) { > + TAILQ_FOREACH(kname, &rlay->rl_proto->tlscerts, entry) { > if (relay_load_certfiles(conf, > - rlay, name->name) == -1) { > + rlay, kname) == -1) { > yyerror("cannot load keypair %s" > - " for relay %s", name->name, > + " for relay %s", kname->name, > rlay->rl_conf.name); > YYERROR; > } > @@ -3530,3 +3619,27 @@ end: > close(s); > return (ret); > } > + > +struct keyname* > +proto_keyname(char * name) > +{ > + struct keyname *kname = NULL, *key; > + if (name == NULL) > + return NULL; > + > + TAILQ_FOREACH(key, &proto->tlscerts, entry) { > + if (strcmp(key->name, name) == 0) > + return key; > + } > + > + if ((kname = calloc(1, sizeof(*kname))) == NULL) { > + return NULL; > + } > + > + if ((kname->name = strdup(name)) == NULL) { > + free(kname); > + return NULL; > + } > + TAILQ_INSERT_TAIL(&proto->tlscerts, kname, entry); > + return kname; > +} > diff --git a/usr.sbin/relayd/relayd.c b/usr.sbin/relayd/relayd.c > index 612dda56920..311becdcc47 100644 > --- a/usr.sbin/relayd/relayd.c > +++ b/usr.sbin/relayd/relayd.c > @@ -1334,7 +1334,7 @@ relay_load_fd(int fd, off_t *len) > } > > int > -relay_load_certfiles(struct relayd *env, struct relay *rlay, const char *name) > +relay_load_certfiles(struct relayd *env, struct relay *rlay, const struct keyname *name) > { > char certfile[PATH_MAX]; > char hbuf[PATH_MAX]; > @@ -1384,12 +1384,17 @@ relay_load_certfiles(struct relayd *env, struct relay *rlay, const char *name) > print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL) > goto fail; > else if (name != NULL && > - strlcpy(hbuf, name, sizeof(hbuf)) >= sizeof(hbuf)) > + strlcpy(hbuf, name->name, sizeof(hbuf)) >= sizeof(hbuf)) > goto fail; > > - if (snprintf(certfile, sizeof(certfile), > - "/etc/ssl/%s:%u.crt", hbuf, useport) == -1) > - goto fail; > + if (name != NULL && strcmp(name->certificate,"") != 0) { > + strlcpy(certfile, name->certificate, sizeof(certfile)); > + } > + else { > + if (snprintf(certfile, sizeof(certfile), > + "/etc/ssl/%s:%u.crt", hbuf, useport) == -1) > + goto fail; > + } > if ((cert_fd = open(certfile, O_RDONLY)) == -1) { > if (snprintf(certfile, sizeof(certfile), > "/etc/ssl/%s.crt", hbuf) == -1) > @@ -1400,27 +1405,37 @@ relay_load_certfiles(struct relayd *env, struct relay *rlay, const char *name) > } > log_debug("%s: using certificate %s", __func__, certfile); > > - if (useport) { > - if (snprintf(certfile, sizeof(certfile), > - "/etc/ssl/private/%s:%u.key", hbuf, useport) == -1) > - goto fail; > - } else { > - if (snprintf(certfile, sizeof(certfile), > - "/etc/ssl/private/%s.key", hbuf) == -1) > - goto fail; > + if (name != NULL && strcmp(name->key,"") != 0) { > + strlcpy(certfile, name->key, sizeof(certfile)); > + } > + else { > + if (useport) { > + if (snprintf(certfile, sizeof(certfile), > + "/etc/ssl/private/%s:%u.key", hbuf, useport) == -1) > + goto fail; > + } else { > + if (snprintf(certfile, sizeof(certfile), > + "/etc/ssl/private/%s.key", hbuf) == -1) > + goto fail; > + } > } > if ((key_fd = open(certfile, O_RDONLY)) == -1) > goto fail; > log_debug("%s: using private key %s", __func__, certfile); > > - if (useport) { > - if (snprintf(certfile, sizeof(certfile), > - "/etc/ssl/%s:%u.ocsp", hbuf, useport) == -1) > - goto fail; > - } else { > - if (snprintf(certfile, sizeof(certfile), > - "/etc/ssl/%s.ocsp", hbuf) == -1) > - goto fail; > + if (name != NULL && strcmp(name->ocsp,"") != 0) { > + strlcpy(certfile, name->ocsp, sizeof(certfile)); > + } > + else { > + if (useport) { > + if (snprintf(certfile, sizeof(certfile), > + "/etc/ssl/%s:%u.ocsp", hbuf, useport) == -1) > + goto fail; > + } else { > + if (snprintf(certfile, sizeof(certfile), > + "/etc/ssl/%s.ocsp", hbuf) == -1) > + goto fail; > + } > } > if ((ocsp_fd = open(certfile, O_RDONLY)) != -1) > log_debug("%s: using OCSP staple file %s", __func__, certfile); > diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/relayd.conf.5 > index 5ae11c5ac01..628a12e46ea 100644 > --- a/usr.sbin/relayd/relayd.conf.5 > +++ b/usr.sbin/relayd/relayd.conf.5 > @@ -990,8 +990,10 @@ is omitted, > is used. > The default is > .Ic no edh . > -.It Ic keypair Ar name > -The relay will attempt to look up a private key in > +.It Ic keypair Ar name Op Ic certificate Ar path Op Ic key Ar path Op Ic ocsp Ar path > +The relay will attempt to look up the TLS assets associated with > +.Ar name . > +By default, it searches for a private key in > .Pa /etc/ssl/private/name:port.key > and a public certificate in > .Pa /etc/ssl/name:port.crt , > @@ -1002,6 +1004,16 @@ If these files are not present, the relay will continue to look in > .Pa /etc/ssl/private/name.key > and > .Pa /etc/ssl/name.crt . > +.Pp > +If the > +.Ic certificate , > +.Ic key , > +or > +.Ic ocsp > +keywords are followed by an explicit > +.Ar path , > +that file will be used instead of the default location. > +.Pp > This option can be specified multiple times for TLS Server Name Indication. > If not specified, > a keypair will be loaded using the specified IP address of the relay as > @@ -1010,8 +1022,10 @@ See > .Xr ssl 8 > for details about TLS server certificates. > .Pp > -An optional OCSP staple file will be used during TLS handshakes with > -this server if it is found as a non-empty file in > +An optional OCSP staple file will be used during TLS handshakes. > +If no explicit > +.Ic ocsp Ar path > +is given, it will be searched as a non-empty file in > .Pa /etc/ssl/name:port.ocsp > or > .Pa /etc/ssl/name.ocsp . > diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h > index 3b5c3987f93..7c62bf08054 100644 > --- a/usr.sbin/relayd/relayd.h > +++ b/usr.sbin/relayd/relayd.h > @@ -724,6 +724,9 @@ struct relay_ticket_key { > struct keyname { > TAILQ_ENTRY(keyname) entry; > char *name; > + char certificate[PATH_MAX]; > + char key[PATH_MAX]; > + char ocsp[PATH_MAX]; > }; > TAILQ_HEAD(keynamelist, keyname); > > @@ -1321,7 +1324,7 @@ struct relay_cert *cert_add(struct relayd *, objid_t); > struct relay_cert *cert_find(struct relayd *, objid_t); > char *relay_load_fd(int, off_t *); > int relay_load_certfiles(struct relayd *, struct relay *, > - const char *); > + const struct keyname *); > int expand_string(char *, size_t, const char *, const char *); > void translate_string(char *); > void purge_key(char **, off_t); >