Download raw body.
relayd: support explicit paths for keypair
On Sat May 09, 2026 at 06:13:05PM +0200, Kirill A. Korinsky wrote:
> On Sat, 09 May 2026 14:59:45 +0200,
> Rafael Sadowski <rafael@sizeofvoid.org> wrote:
> >
> > On Sun Mar 29, 2026 at 03:31:40PM +0200, Kirill A. Korinsky wrote:
> > > On Wed, 18 Mar 2026 17:47:42 +0100,
> > > Rafael Sadowski <rafael@sizeofvoid.org> wrote:
> > > >
> > > > 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?
> > > >
> > >
> > > I think this is worth to be introduced. We already has in smptd.conf:
> > >
> > > pki pkiname cert certfile
> > >
> > > pki pkiname key keyfile
> > >
> > > And this is the way to somehow stay alligned.
> > >
> > > Personally, I think we need also support of keypair keyword in smtpd.conf,
> > > but it is different diff.
> > >
> > > Not sure that keypair here is good name, maybe smtpd.conf way is cleaner.
> > >
> >
> > Here is a rework of my latest diff after feedback from tb@ (Thanks!).
> >
> > - KNF and syntax fixes
> > - Added PATH_MAX check in proto_keyname
> > - Added missing free($*)
> > - Added strlcpy truncation checks
> > - Use secure snprintf idiom from manpage
> >
> > OK?
> >
>
> Probably I do something wrong. I've tried config:
>
> table <httpd> { 127.0.0.1 }
>
> http protocol https {
> tls keypair xxx cert /etc/ssl/test.crt key /etc/ssl/private/test.key
This is the correct syntax:
tls keypair xxx cert /etc/ssl/test.crt
tls keypair xxx key /etc/ssl/private/test.key
> }
>
> relay https {
> listen on egress port 443 tls
> protocol https
>
> forward to <httpd> port 80
> }
>
> and it fails as:
>
> relayd.conf:4: syntax error
> relayd.conf:9: no such protocol: https
> no actions, nothing to do
>
>
> >
> > diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y
> > index 6c3d538ed4e..5282afda091 100644
> > --- a/usr.sbin/relayd/parse.y
> > +++ b/usr.sbin/relayd/parse.y
> > @@ -140,6 +140,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 {
> > @@ -1342,20 +1343,93 @@ 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");
> > + if ((kname = proto_keyname($2)) == NULL) {
> > free($2);
> > YYERROR;
> > }
> > - if ((name = calloc(1, sizeof(*name))) == NULL) {
> > - yyerror("calloc");
> > + free($2);
> > + }
> > + | KEYPAIR STRING CERTIFICATE STRING {
> > + struct keyname *kname = NULL;
> > +
> > + if ((kname = proto_keyname($2)) == NULL) {
> > + free($2);
> > + free($4);
> > + YYERROR;
> > + }
> > +
> > + if (strlen($4) >= PATH_MAX) {
> > + yyerror("keypair cert too long");
> > free($2);
> > + free($4);
> > + YYERROR;
> > + }
> > + if (strlcpy(kname->certificate, $4,
> > + sizeof(kname->certificate)) >=
> > + sizeof(kname->certificate)) {
> > + yyerror("keypair certificate truncated");
> > + free($2);
> > + free($4);
> > YYERROR;
> > }
> > - name->name = $2;
> > - TAILQ_INSERT_TAIL(&proto->tlscerts, name, entry);
> > + free($2);
> > + free($4);
> > + }
> > + | KEYPAIR STRING KEY STRING {
> > + struct keyname *kname = NULL;
> > +
> > + if ((kname = proto_keyname($2)) == NULL) {
> > + free($2);
> > + free($4);
> > + YYERROR;
> > + }
> > +
> > + if (strlen($4) >= PATH_MAX) {
> > + yyerror("keypair certificate key too long");
> > + free($2);
> > + free($4);
> > + YYERROR;
> > + }
> > +
> > + if (strlcpy(kname->key, $4,
> > + sizeof(kname->key)) >=
> > + sizeof(kname->key)) {
> > + yyerror("keypair certificate key truncated");
> > + free($2);
> > + free($4);
> > + YYERROR;
> > + }
> > + free($2);
> > + free($4);
> > + }
> > + | KEYPAIR STRING OCSP STRING {
> > + struct keyname *kname = NULL;
> > +
> > + if ((kname = proto_keyname($2)) == NULL) {
> > + free($2);
> > + free($4);
> > + YYERROR;
> > + }
> > +
> > + if (strlen($4) >= PATH_MAX) {
> > + yyerror("keypair ocsp file too long");
> > + free($2);
> > + free($4);
> > + YYERROR;
> > + }
> > +
> > + if (strlcpy(kname->ocsp, $4,
> > + sizeof(kname->ocsp)) >=
> > + sizeof(kname->ocsp)) {
> > + yyerror("ocsp truncated");
> > + free($2);
> > + free($4);
> > + YYERROR;
> > + }
> > + free($2);
> > + free($4);
> > }
> > | CLIENT CA STRING {
> > if (strlcpy(proto->tlsclientca, $3,
> > @@ -1850,7 +1924,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) {
> > @@ -1888,11 +1962,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;
> > }
> > @@ -3452,7 +3526,7 @@ relay_inherit(struct relay *ra, struct relay *rb)
> > goto err;
> > }
> > TAILQ_FOREACH(name, &rb->rl_proto->tlscerts, entry) {
> > - if (relay_load_certfiles(conf, rb, name->name) == -1) {
> > + if (relay_load_certfiles(conf, rb, name) == -1) {
> > yyerror("cannot load keypair %s for relay %s",
> > name->name, rb->rl_conf.name);
> > goto err;
> > @@ -3551,3 +3625,31 @@ end:
> > close(s);
> > return (ret);
> > }
> > +
> > +struct keyname*
> > +proto_keyname(char *name)
> > +{
> > + struct keyname *kname = NULL, *key;
> > +
> > + if (strlen(name) >= PATH_MAX) {
> > + yyerror("keypair name too long");
> > + 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 9eb1b452d81..e3667a15dae 100644
> > --- a/usr.sbin/relayd/relayd.c
> > +++ b/usr.sbin/relayd/relayd.c
> > @@ -1335,14 +1335,14 @@ 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];
> > struct protocol *proto = rlay->rl_proto;
> > struct relay_cert *cert;
> > int useport = htons(rlay->rl_conf.port);
> > - int cert_fd = -1, key_fd = -1, ocsp_fd = -1;
> > + int cert_fd = -1, key_fd = -1, ocsp_fd = -1, ret = 0;
> >
> > if (rlay->rl_conf.flags & F_TLSCLIENT) {
> > if (strlen(proto->tlsca) && rlay->rl_tls_ca_fd == -1) {
> > @@ -1385,15 +1385,29 @@ 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) {
> > + if (strlcpy(certfile, name->certificate, sizeof(certfile))
> > + >= sizeof(certfile)) {
> > + log_warnx("certificate truncated");
> > + goto fail;
> > + }
> > + }
> > + else {
> > + ret = snprintf(certfile, sizeof(certfile),
> > + "/etc/ssl/%s:%u.crt", hbuf, useport);
> > +
> > + if (ret < 0 || (size_t)ret >= sizeof(certfile))
> > + goto fail;
> > + }
> > if ((cert_fd = open(certfile, O_RDONLY)) == -1) {
> > - if (snprintf(certfile, sizeof(certfile),
> > - "/etc/ssl/%s.crt", hbuf) == -1)
> > +
> > + ret = snprintf(certfile, sizeof(certfile),
> > + "/etc/ssl/%s.crt", hbuf);
> > +
> > + if (ret < 0 || (size_t)ret >= sizeof(certfile))
> > goto fail;
> > if ((cert_fd = open(certfile, O_RDONLY)) == -1)
> > goto fail;
> > @@ -1401,27 +1415,56 @@ 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)
> > + if (name != NULL && strcmp(name->key, "") != 0) {
> > + if (strlcpy(certfile, name->key, sizeof(certfile))
> > + >= sizeof(certfile)) {
> > + log_warnx("certificate key truncated");
> > goto fail;
> > + }
> > + }
> > + else {
> > + if (useport) {
> > + ret = snprintf(certfile, sizeof(certfile),
> > + "/etc/ssl/private/%s:%u.key",
> > + hbuf, useport);
> > +
> > + if (ret < 0 || (size_t)ret >= sizeof(certfile))
> > + goto fail;
> > + } else {
> > + ret = snprintf(certfile, sizeof(certfile),
> > + "/etc/ssl/private/%s.key", hbuf);
> > +
> > + if (ret < 0 || (size_t)ret >= sizeof(certfile))
> > + 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)
> > + if (name != NULL && strcmp(name->ocsp, "") != 0) {
> > + if (strlcpy(certfile, name->ocsp, sizeof(certfile))
> > + >= sizeof(certfile)) {
> > + log_warnx("certificate ocsp truncated");
> > goto fail;
> > + }
> > +
> > + }
> > + else {
> > + if (useport) {
> > + ret = snprintf(certfile, sizeof(certfile),
> > + "/etc/ssl/%s:%u.ocsp",
> > + hbuf, useport);
> > +
> > + if (ret < 0 || (size_t)ret >= sizeof(certfile))
> > + goto fail;
> > + } else {
> > + ret = snprintf(certfile, sizeof(certfile),
> > + "/etc/ssl/%s.ocsp", hbuf);
> > +
> > + if (ret < 0 || (size_t)ret >= sizeof(certfile))
> > + 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 a63dd4e3a41..8803b57c73b 100644
> > --- a/usr.sbin/relayd/relayd.conf.5
> > +++ b/usr.sbin/relayd/relayd.conf.5
> > @@ -997,8 +997,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 cert 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 ,
> > @@ -1009,6 +1011,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 cert ,
> > +.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
> > @@ -1017,8 +1029,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 a5363989f4b..1756d9aec7a 100644
> > --- a/usr.sbin/relayd/relayd.h
> > +++ b/usr.sbin/relayd/relayd.h
> > @@ -726,6 +726,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);
> >
> > @@ -1323,7 +1326,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);
> >
>
> --
> wbr, Kirill
relayd: support explicit paths for keypair