Download raw body.
relayd: support explicit paths for keypair
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);
>
relayd: support explicit paths for keypair