From: Rafael Sadowski Subject: relayd: support explicit paths for keypair To: tech@openbsd.org Cc: "Kirill A. Korinsky" Date: Thu, 19 Feb 2026 21:28:17 +0100 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]]]. 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);