Hi,

this diff adds SNI support to relayd.

It is a bit big and I have to break it down, but I'm sending this
first version now to give people a chance to test.  The major
"infrastructure" change is that keypairs are not stored in relay
structs anymore but in a global list where each keypair ("cert") is
associated to a relay id.

relayd currently loads the keypair using the listen address as the
file name for the .key and .crt files.  I decided to keep this
behavior.  The new optional "tls keypair" argument allows to specify
one or more keypairs by name instead and they will be loaded as .key
and .crt files accordingly.

```relayd.conf
protocol foo {
        tls keypair "localhost"
        tls keypair "server"
        # Or: tls { keypair "localhost", keypair "server" }
}

relay assl {
        listen on 127.0.0.1 port 443 tls
        protocol foo
        forward to 199.233.217.205 port 80
}
```

Results in:
relay_load_certfiles: using certificate /etc/ssl/localhost.crt
relay_load_certfiles: using private key /etc/ssl/private/localhost.key
relay_load_certfiles: using certificate /etc/ssl/server.crt
relay_load_certfiles: using private key /etc/ssl/private/server.key

Connecting to relayd now gives you certificates based on the server
name, using the first one as the default.

Comments?

Reyk

Index: usr.sbin/relayd/ca.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/ca.c,v
retrieving revision 1.34
diff -u -p -u -p -r1.34 ca.c
--- usr.sbin/relayd/ca.c        19 Sep 2018 11:28:02 -0000      1.34
+++ usr.sbin/relayd/ca.c        9 May 2019 12:35:40 -0000
@@ -108,56 +108,60 @@ hash_x509(X509 *cert, char *hash, size_t
 void
 ca_launch(void)
 {
-       char             hash[TLS_CERT_HASH_SIZE];
-       char            *buf;
-       BIO             *in = NULL;
-       EVP_PKEY        *pkey = NULL;
-       struct relay    *rlay;
-       X509            *cert = NULL;
-       off_t            len;
+       char                     hash[TLS_CERT_HASH_SIZE];
+       char                    *buf;
+       BIO                     *in = NULL;
+       EVP_PKEY                *pkey = NULL;
+       struct relay            *rlay;
+       struct relay_cert       *cert;
+       X509                    *x509 = NULL;
+       off_t                    len;
 
-       TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
-               if ((rlay->rl_conf.flags & (F_TLS|F_TLSCLIENT)) == 0)
+       TAILQ_FOREACH(cert, env->sc_certs, cert_entry) {
+               if (cert->cert_fd == -1 || cert->cert_key_fd == -1)
                        continue;
 
-               if (rlay->rl_tls_cert_fd != -1) {
-                       if ((buf = relay_load_fd(rlay->rl_tls_cert_fd,
-                           &len)) == NULL)
-                               fatal("ca_launch: cert relay_load_fd");
+               if ((buf = relay_load_fd(cert->cert_fd, &len)) == NULL)
+                       fatal("ca_launch: cert relay_load_fd");
 
-                       if ((in = BIO_new_mem_buf(buf, len)) == NULL)
-                               fatalx("ca_launch: cert BIO_new_mem_buf");
+               if ((in = BIO_new_mem_buf(buf, len)) == NULL)
+                       fatalx("ca_launch: cert BIO_new_mem_buf");
 
-                       if ((cert = PEM_read_bio_X509(in, NULL,
-                           NULL, NULL)) == NULL)
-                               fatalx("ca_launch: cert PEM_read_bio_X509");
+               if ((x509 = PEM_read_bio_X509(in, NULL,
+                   NULL, NULL)) == NULL)
+                       fatalx("ca_launch: cert PEM_read_bio_X509");
 
-                       hash_x509(cert, hash, sizeof(hash));
+               hash_x509(x509, hash, sizeof(hash));
 
-                       BIO_free(in);
-                       X509_free(cert);
-                       purge_key(&buf, len);
-               }
-               if (rlay->rl_conf.tls_key_len) {
-                       if ((in = BIO_new_mem_buf(rlay->rl_tls_key,
-                           rlay->rl_conf.tls_key_len)) == NULL)
-                               fatalx("%s: key", __func__);
-
-                       if ((pkey = PEM_read_bio_PrivateKey(in,
-                           NULL, NULL, NULL)) == NULL)
-                               fatalx("%s: PEM", __func__);
-                       BIO_free(in);
+               BIO_free(in);
+               X509_free(x509);
+               purge_key(&buf, len);
 
-                       rlay->rl_tls_pkey = pkey;
+               if ((buf = relay_load_fd(cert->cert_key_fd, &len)) == NULL)
+                       fatal("ca_launch: key relay_load_fd");
 
-                       if (pkey_add(env, pkey, hash) == NULL)
-                               fatalx("tls pkey");
+               if ((in = BIO_new_mem_buf(buf, len)) == NULL)
+                       fatalx("%s: key", __func__);
 
-                       purge_key(&rlay->rl_tls_key,
-                           rlay->rl_conf.tls_key_len);
-               }
+               if ((pkey = PEM_read_bio_PrivateKey(in,
+                   NULL, NULL, NULL)) == NULL)
+                       fatalx("%s: PEM", __func__);
 
-               if (rlay->rl_tls_cacert_fd != -1) {
+               cert->cert_pkey = pkey;
+
+               if (pkey_add(env, pkey, hash) == NULL)
+                       fatalx("tls pkey");
+
+               BIO_free(in);
+               purge_key(&buf, len);
+       }
+
+       TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
+               if ((rlay->rl_conf.flags & (F_TLS|F_TLSCLIENT)) == 0)
+                       continue;
+
+               if (rlay->rl_tls_cacert_fd != -1 &&
+                   rlay->rl_conf.tls_cakey_len) {
                        if ((buf = relay_load_fd(rlay->rl_tls_cacert_fd,
                            &len)) == NULL)
                                fatal("ca_launch: cacert relay_load_fd");
@@ -165,17 +169,16 @@ ca_launch(void)
                        if ((in = BIO_new_mem_buf(buf, len)) == NULL)
                                fatalx("ca_launch: cacert BIO_new_mem_buf");
 
-                       if ((cert = PEM_read_bio_X509(in, NULL,
+                       if ((x509 = PEM_read_bio_X509(in, NULL,
                            NULL, NULL)) == NULL)
                                fatalx("ca_launch: cacert PEM_read_bio_X509");
 
-                       hash_x509(cert, hash, sizeof(hash));
+                       hash_x509(x509, hash, sizeof(hash));
 
                        BIO_free(in);
-                       X509_free(cert);
+                       X509_free(x509);
                        purge_key(&buf, len);
-               }
-               if (rlay->rl_conf.tls_cakey_len) {
+
                        if ((in = BIO_new_mem_buf(rlay->rl_tls_cakey,
                            rlay->rl_conf.tls_cakey_len)) == NULL)
                                fatalx("%s: key", __func__);
Index: usr.sbin/relayd/config.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/config.c,v
retrieving revision 1.36
diff -u -p -u -p -r1.36 config.c
--- usr.sbin/relayd/config.c    29 Nov 2017 15:24:50 -0000      1.36
+++ usr.sbin/relayd/config.c    9 May 2019 12:35:40 -0000
@@ -81,6 +81,12 @@ config_init(struct relayd *env)
                    calloc(1, sizeof(*env->sc_relays))) == NULL)
                        return (-1);
                TAILQ_INIT(env->sc_relays);
+
+               if ((env->sc_certs =
+                   calloc(1, sizeof(*env->sc_certs))) == NULL)
+                       return (-1);
+               TAILQ_INIT(env->sc_certs);
+
                if ((env->sc_pkeys =
                    calloc(1, sizeof(*env->sc_pkeys))) == NULL)
                        return (-1);
@@ -98,6 +104,7 @@ config_init(struct relayd *env)
                env->sc_proto_default.tcpflags = TCPFLAG_DEFAULT;
                env->sc_proto_default.tcpbacklog = RELAY_BACKLOG;
                env->sc_proto_default.tlsflags = TLSFLAG_DEFAULT;
+               TAILQ_INIT(&env->sc_proto_default.tlscerts);
                (void)strlcpy(env->sc_proto_default.tlsciphers,
                    TLSCIPHERS_DEFAULT,
                    sizeof(env->sc_proto_default.tlsciphers));
@@ -140,6 +147,7 @@ config_purge(struct relayd *env, u_int r
        struct netroute         *nr;
        struct router           *rt;
        struct ca_pkey          *pkey;
+       struct keyname          *keyname;
        u_int                    what;
 
        what = ps->ps_what[privsep_process] & reset;
@@ -185,6 +193,12 @@ config_purge(struct relayd *env, u_int r
                        free(proto->style);
                        free(proto->tlscapass);
                        free(proto);
+                       while ((keyname =
+                           TAILQ_FIRST(&proto->tlscerts)) != NULL) {
+                               TAILQ_REMOVE(&proto->tlscerts, keyname, entry);
+                               free(keyname->name);
+                               free(keyname);
+                       }
                }
                env->sc_protocount = 0;
        }
@@ -690,6 +704,7 @@ config_getproto(struct relayd *env, stru
        }
 
        TAILQ_INIT(&proto->rules);
+       TAILQ_INIT(&proto->tlscerts);
        proto->tlscapass = NULL;
 
        TAILQ_INSERT_TAIL(env->sc_protos, proto, entry);
@@ -773,12 +788,13 @@ config_getrule(struct relayd *env, struc
 }
 
 static int
-config_setrelayfd(struct privsep *ps, int id, int n, int rlay_id, int type,
-    int ofd)
+config_setrelayfd(struct privsep *ps, int id, int n,
+    objid_t obj_id, objid_t rlay_id, enum fd_type type, int ofd)
 {
        struct ctl_relayfd      rfd;
        int                     fd;
 
+       rfd.id = obj_id;
        rfd.relayid = rlay_id;
        rfd.type = type;
 
@@ -798,6 +814,7 @@ config_setrelay(struct relayd *env, stru
        struct ctl_relaytable    crt;
        struct relay_table      *rlt;
        struct relay_config      rl;
+       struct relay_cert       *cert;
        int                      id;
        int                      fd, n, m;
        struct iovec             iov[6];
@@ -824,12 +841,6 @@ config_setrelay(struct relayd *env, stru
                iov[c++].iov_len = sizeof(rl);
 
                if ((what & CONFIG_CA_ENGINE) == 0 &&
-                   rl.tls_key_len) {
-                       iov[c].iov_base = rlay->rl_tls_key;
-                       iov[c++].iov_len = rl.tls_key_len;
-               } else
-                       rl.tls_key_len = 0;
-               if ((what & CONFIG_CA_ENGINE) == 0 &&
                    rl.tls_cakey_len) {
                        iov[c].iov_base = rlay->rl_tls_cakey;
                        iov[c++].iov_len = rl.tls_cakey_len;
@@ -868,22 +879,42 @@ config_setrelay(struct relayd *env, stru
                        }
                }
 
-
-               if (what & CONFIG_CERTS) {
+               /* cert keypairs */
+               TAILQ_FOREACH(cert, env->sc_certs, cert_entry) {
+                       if (cert->cert_relayid != rlay->rl_conf.id)
+                               continue;
                        n = -1;
                        proc_range(ps, id, &n, &m);
-                       for (n = 0; n < m; n++) {
-                               if (rlay->rl_tls_cert_fd != -1 &&
+                       for (n = 0; (what & CONFIG_CERTS) && n < m; n++) {
+                               if (cert->cert_fd != -1 &&
                                    config_setrelayfd(ps, id, n,
-                                   rlay->rl_conf.id, RELAY_FD_CERT,
-                                   rlay->rl_tls_cert_fd) == -1) {
+                                   cert->cert_id, cert->cert_relayid,
+                                   RELAY_FD_CERT, cert->cert_fd) == -1) {
                                        log_warn("%s: fd passing failed for "
                                            "`%s'", __func__,
                                            rlay->rl_conf.name);
                                        return (-1);
                                }
-                               if (rlay->rl_tls_ca_fd != -1 &&
+                               if (id == PROC_CA &&
+                                   cert->cert_key_fd != -1 &&
                                    config_setrelayfd(ps, id, n,
+                                   cert->cert_id, cert->cert_relayid,
+                                   RELAY_FD_KEY, cert->cert_key_fd) == -1) {
+                                       log_warn("%s: fd passing failed for "
+                                           "`%s'", __func__,
+                                           rlay->rl_conf.name);
+                                       return (-1);
+                               }
+                       }
+               }
+
+               /* CA certs */
+               if (what & CONFIG_CERTS) {
+                       n = -1;
+                       proc_range(ps, id, &n, &m);
+                       for (n = 0; n < m; n++) {
+                               if (rlay->rl_tls_ca_fd != -1 &&
+                                   config_setrelayfd(ps, id, n, 0,
                                    rlay->rl_conf.id, RELAY_FD_CACERT,
                                    rlay->rl_tls_ca_fd) == -1) {
                                        log_warn("%s: fd passing failed for "
@@ -892,7 +923,7 @@ config_setrelay(struct relayd *env, stru
                                        return (-1);
                                }
                                if (rlay->rl_tls_cacert_fd != -1 &&
-                                   config_setrelayfd(ps, id, n,
+                                   config_setrelayfd(ps, id, n, 0,
                                    rlay->rl_conf.id, RELAY_FD_CAFILE,
                                    rlay->rl_tls_cacert_fd) == -1) {
                                        log_warn("%s: fd passing failed for "
@@ -933,10 +964,6 @@ config_setrelay(struct relayd *env, stru
                close(rlay->rl_s);
                rlay->rl_s = -1;
        }
-       if (rlay->rl_tls_cert_fd != -1) {
-               close(rlay->rl_tls_cert_fd);
-               rlay->rl_tls_cert_fd = -1;
-       }
        if (rlay->rl_tls_cacert_fd != -1) {
                close(rlay->rl_tls_cacert_fd);
                rlay->rl_tls_cacert_fd = -1;
@@ -945,6 +972,19 @@ config_setrelay(struct relayd *env, stru
                close(rlay->rl_tls_ca_fd);
                rlay->rl_tls_ca_fd = -1;
        }
+       TAILQ_FOREACH(cert, env->sc_certs, cert_entry) {
+               if (cert->cert_relayid != rlay->rl_conf.id)
+                       continue;
+
+               if (cert->cert_fd != -1) {
+                       close(cert->cert_fd);
+                       cert->cert_fd = -1;
+               }
+               if (cert->cert_key_fd != -1) {
+                       close(cert->cert_key_fd);
+                       cert->cert_key_fd = -1;
+               }
+       }
 
        return (0);
 }
@@ -965,7 +1005,6 @@ config_getrelay(struct relayd *env, stru
        s = sizeof(rlay->rl_conf);
 
        rlay->rl_s = imsg->fd;
-       rlay->rl_tls_cert_fd = -1;
        rlay->rl_tls_ca_fd = -1;
        rlay->rl_tls_cacert_fd = -1;
 
@@ -980,17 +1019,11 @@ config_getrelay(struct relayd *env, stru
        }
 
        if ((off_t)(IMSG_DATA_SIZE(imsg) - s) <
-           (rlay->rl_conf.tls_key_len + rlay->rl_conf.tls_cakey_len)) {
+           (rlay->rl_conf.tls_cakey_len)) {
                log_debug("%s: invalid message length", __func__);
                goto fail;
        }
 
-       if (rlay->rl_conf.tls_key_len) {
-               if ((rlay->rl_tls_key = get_data(p + s,
-                   rlay->rl_conf.tls_key_len)) == NULL)
-                       goto fail;
-               s += rlay->rl_conf.tls_key_len;
-       }
        if (rlay->rl_conf.tls_cakey_len) {
                if ((rlay->rl_tls_cakey = get_data(p + s,
                    rlay->rl_conf.tls_cakey_len)) == NULL)
@@ -1010,7 +1043,6 @@ config_getrelay(struct relayd *env, stru
        return (0);
 
  fail:
-       free(rlay->rl_tls_key);
        free(rlay->rl_tls_cakey);
        close(rlay->rl_s);
        free(rlay);
@@ -1062,22 +1094,37 @@ config_getrelaytable(struct relayd *env,
 int
 config_getrelayfd(struct relayd *env, struct imsg *imsg)
 {
-       struct relay_table      *rlt = NULL;
        struct ctl_relayfd       crfd;
-       struct relay            *rlay;
+       struct relay            *rlay = NULL;
+       struct relay_cert       *cert;
        u_int8_t                *p = imsg->data;
 
        IMSG_SIZE_CHECK(imsg, &crfd);
        memcpy(&crfd, p, sizeof(crfd));
 
-       if ((rlay = relay_find(env, crfd.relayid)) == NULL) {
-               log_debug("%s: unknown relay", __func__);
-               goto fail;
+       switch (crfd.type) {
+       case RELAY_FD_CERT:
+       case RELAY_FD_KEY:
+               if ((cert = cert_find(env, crfd.id)) == NULL) {
+                       if ((cert = cert_add(env, crfd.id)) == NULL)
+                               return (-1);
+                       cert->cert_relayid = crfd.relayid;
+               }
+               /* FALLTHROUGH */
+       default:
+               if ((rlay = relay_find(env, crfd.relayid)) == NULL) {
+                       log_debug("%s: unknown relay", __func__);
+                       return (-1);
+               }
+               break;
        }
 
        switch (crfd.type) {
        case RELAY_FD_CERT:
-               rlay->rl_tls_cert_fd = imsg->fd;
+               cert->cert_fd = imsg->fd;
+               break;
+       case RELAY_FD_KEY:
+               cert->cert_key_fd = imsg->fd;
                break;
        case RELAY_FD_CACERT:
                rlay->rl_tls_ca_fd = imsg->fd;
@@ -1092,8 +1139,4 @@ config_getrelayfd(struct relayd *env, st
            imsg->fd, crfd.type, rlay->rl_conf.name);
 
        return (0);
-
- fail:
-       free(rlt);
-       return (-1);
 }
Index: usr.sbin/relayd/parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/parse.y,v
retrieving revision 1.233
diff -u -p -u -p -r1.233 parse.y
--- usr.sbin/relayd/parse.y     13 Mar 2019 23:29:32 -0000      1.233
+++ usr.sbin/relayd/parse.y     9 May 2019 12:35:41 -0000
@@ -168,8 +168,8 @@ typedef struct {
 %token ALL APPEND BACKLOG BACKUP BUFFER CA CACHE SET CHECK CIPHERS CODE
 %token COOKIE DEMOTE DIGEST DISABLE ERROR EXPECT PASS BLOCK EXTERNAL FILENAME
 %token FORWARD FROM HASH HEADER HEADERLEN HOST HTTP ICMP INCLUDE INET INET6
-%token INTERFACE INTERVAL IP LABEL LISTEN VALUE LOADBALANCE LOG LOOKUP METHOD
-%token MODE NAT NO DESTINATION NODELAY NOTHING ON PARENT PATH PFTAG PORT
+%token INTERFACE INTERVAL IP KEYPAIR LABEL LISTEN VALUE LOADBALANCE LOG LOOKUP
+%token METHOD MODE NAT NO DESTINATION NODELAY NOTHING ON PARENT PATH PFTAG PORT
 %token PREFORK PRIORITY PROTO QUERYSTR REAL REDIRECT RELAY REMOVE REQUEST
 %token RESPONSE RETRY QUICK RETURN ROUNDROBIN ROUTE SACK SCRIPT SEND SESSION
 %token SNMP SOCKET SPLICE SSL STICKYADDR STYLE TABLE TAG TAGGED TCP TIMEOUT TLS
@@ -991,7 +991,7 @@ optdigest   : digest                        {
                ;
 
 proto          : relay_proto PROTO STRING      {
-                       struct protocol *p;
+                       struct protocol *p;
 
                        if (!loadcfg) {
                                free($3);
@@ -1028,6 +1028,7 @@ proto             : relay_proto PROTO STRING      {
                        p->tcpbacklog = RELAY_BACKLOG;
                        p->httpheaderlen = RELAY_DEFHEADERLENGTH;
                        TAILQ_INIT(&p->rules);
+                       TAILQ_INIT(&p->tlscerts);
                        (void)strlcpy(p->tlsciphers, TLSCIPHERS_DEFAULT,
                            sizeof(p->tlsciphers));
                        (void)strlcpy(p->tlsecdhecurves, TLSECDHECURVES_DEFAULT,
@@ -1256,6 +1257,17 @@ tlsflags : SESSION TICKETS { proto->tick
                        }
                        free($3);
                }
+               | KEYPAIR STRING                {
+                       struct keyname  *name;
+
+                       if ((name = calloc(1, sizeof(*name))) == NULL) {
+                               yyerror("calloc");
+                               free($2);
+                               YYERROR;
+                       }
+                       name->name = $2;
+                       TAILQ_INSERT_TAIL(&proto->tlscerts, name, entry);
+               }
                | NO flag                       { proto->tlsflags &= ~($2); }
                | flag                          { proto->tlsflags |= $1; }
                ;
@@ -1663,18 +1675,9 @@ relay            : RELAY STRING  {
                                YYACCEPT;
                        }
 
-                       TAILQ_FOREACH(r, conf->sc_relays, rl_entry)
-                               if (!strcmp(r->rl_conf.name, $2))
-                                       break;
-                       if (r != NULL) {
-                               yyerror("relay %s defined twice", $2);
-                               free($2);
-                               YYERROR;
-                       }
-                       TAILQ_INIT(&relays);
-
                        if ((r = calloc(1, sizeof (*r))) == NULL)
                                fatal("out of memory");
+                       TAILQ_INIT(&relays);
 
                        if (strlcpy(r->rl_conf.name, $2,
                            sizeof(r->rl_conf.name)) >=
@@ -1694,7 +1697,6 @@ relay             : RELAY STRING  {
                        r->rl_proto = NULL;
                        r->rl_conf.proto = EMPTY_ID;
                        r->rl_conf.dstretry = 0;
-                       r->rl_tls_cert_fd = -1;
                        r->rl_tls_ca_fd = -1;
                        r->rl_tls_cacert_fd = -1;
                        TAILQ_INIT(&r->rl_tables);
@@ -1706,7 +1708,16 @@ relay            : RELAY STRING  {
                        dstmode = RELAY_DSTMODE_DEFAULT;
                        rlay = r;
                } '{' optnl relayopts_l '}'     {
-                       struct relay    *r;
+                       struct relay            *r;
+                       struct relay_config     *rlconf = &rlay->rl_conf;
+                       struct keyname          *name;
+
+                       if (relay_findbyname(conf, rlconf->name) != NULL ||
+                           relay_findbyaddr(conf, rlconf) != NULL) {
+                               yyerror("relay %s or listener defined twice",
+                                   rlconf->name);
+                               YYERROR;
+                       }
 
                        if (rlay->rl_conf.ss.ss_family == AF_UNSPEC) {
                                yyerror("relay %s has no listener",
@@ -1730,11 +1741,23 @@ relay           : RELAY STRING  {
                                rlay->rl_proto = &conf->sc_proto_default;
                                rlay->rl_conf.proto = conf->sc_proto_default.id;
                        }
-                       if (relay_load_certfiles(rlay) == -1) {
+
+                       if (TAILQ_EMPTY(&rlay->rl_proto->tlscerts) &&
+                           relay_load_certfiles(conf, rlay, NULL) == -1) {
                                yyerror("cannot load certificates for relay %s",
                                    rlay->rl_conf.name);
                                YYERROR;
                        }
+                       TAILQ_FOREACH(name, &rlay->rl_proto->tlscerts, entry) {
+                               if (relay_load_certfiles(conf,
+                                   rlay, name->name) == -1) {
+                                       yyerror("cannot load keypair %s"
+                                           " for relay %s", name->name,
+                                           rlay->rl_conf.name);
+                                       YYERROR;
+                               }
+                       }
+
                        conf->sc_relaycount++;
                        SPLAY_INIT(&rlay->rl_sessions);
                        TAILQ_INSERT_TAIL(conf->sc_relays, rlay, rl_entry);
@@ -2280,6 +2303,7 @@ lookup(char *s)
                { "interval",           INTERVAL },
                { "ip",                 IP },
                { "key",                KEY },
+               { "keypair",            KEYPAIR },
                { "label",              LABEL },
                { "least-states",       LEASTSTATES },
                { "listen",             LISTEN },
@@ -3231,11 +3255,8 @@ relay_inherit(struct relay *ra, struct r
        rb->rl_conf.flags =
            (ra->rl_conf.flags & ~F_TLS) | (rc.flags & F_TLS);
        if (!(rb->rl_conf.flags & F_TLS)) {
-               rb->rl_tls_cert_fd = -1;
                rb->rl_tls_cacert_fd = -1;
                rb->rl_tls_ca_fd = -1;
-               rb->rl_tls_key = NULL;
-               rb->rl_conf.tls_key_len = 0;
        }
        TAILQ_INIT(&rb->rl_tables);
 
@@ -3253,10 +3274,12 @@ relay_inherit(struct relay *ra, struct r
 
        if (relay_findbyname(conf, rb->rl_conf.name) != NULL ||
            relay_findbyaddr(conf, &rb->rl_conf) != NULL) {
-               yyerror("relay %s defined twice", rb->rl_conf.name);
+               yyerror("relay %s or listener defined twice",
+                   rb->rl_conf.name);
                goto err;
        }
-       if (relay_load_certfiles(rb) == -1) {
+
+       if (relay_load_certfiles(conf, rb, NULL) == -1) {
                yyerror("cannot load certificates for relay %s",
                    rb->rl_conf.name);
                goto err;
Index: usr.sbin/relayd/relay.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relay.c,v
retrieving revision 1.243
diff -u -p -u -p -r1.243 relay.c
--- usr.sbin/relayd/relay.c     8 May 2019 23:22:19 -0000       1.243
+++ usr.sbin/relayd/relay.c     9 May 2019 12:35:41 -0000
@@ -2080,8 +2080,9 @@ relay_tls_ctx_create(struct relay *rlay)
 {
        struct tls_config       *tls_cfg, *tls_client_cfg;
        struct tls              *tls = NULL;
+       struct relay_cert       *cert;
        const char              *fake_key;
-       int                      fake_keylen;
+       int                      fake_keylen, keyfound = 0;
        char                    *buf = NULL, *cabuf = NULL;
        off_t                    len = 0, calen = 0;
 
@@ -2113,6 +2114,7 @@ relay_tls_ctx_create(struct relay *rlay)
                                log_warn("failed to read root certificates");
                                goto err;
                        }
+                       rlay->rl_tls_ca_fd = -1;
 
                        if (tls_config_set_ca_mem(tls_client_cfg, buf, len) !=
                            0) {
@@ -2141,24 +2143,49 @@ relay_tls_ctx_create(struct relay *rlay)
                 */
                tls_config_skip_private_key_check(tls_cfg);
 
-               if ((buf = relay_load_fd(rlay->rl_tls_cert_fd, &len)) == NULL) {
-                       log_warn("failed to load tls certificate");
-                       goto err;
-               }
+               TAILQ_FOREACH(cert, env->sc_certs, cert_entry) {
+                       if (cert->cert_relayid != rlay->rl_conf.id ||
+                           cert->cert_fd == -1)
+                               continue;
+                       keyfound++;
+
+                       if ((buf = relay_load_fd(cert->cert_fd,
+                           &len)) == NULL) {
+                               log_warn("failed to load tls certificate");
+                               goto err;
+                       }
+                       cert->cert_fd = -1;
 
-               if ((fake_keylen = ssl_ctx_fake_private_key(buf, len,
-                   &fake_key)) == -1) {
-                       /* error already printed */
-                       goto err;
-               }
+                       if ((fake_keylen = ssl_ctx_fake_private_key(buf, len,
+                           &fake_key)) == -1) {
+                               /* error already printed */
+                               goto err;
+                       }
 
-               if (tls_config_set_keypair_ocsp_mem(tls_cfg, buf, len,
-                   fake_key, fake_keylen, NULL, 0) != 0) {
-                       log_warnx("failed to set tls certificate: %s",
-                           tls_config_error(tls_cfg));
-                       goto err;
-               }
+                       log_debug("%s: keyfound %d", __func__, keyfound);
 
+                       if (keyfound == 1 &&
+                           tls_config_set_keypair_ocsp_mem(tls_cfg, buf, len,
+                           fake_key, fake_keylen, NULL, 0) != 0) {
+                               log_warnx("failed to set tls certificate: %s",
+                                   tls_config_error(tls_cfg));
+                               goto err;
+                       }
+
+                       /* loading certificate public key */
+                       if (keyfound == 1 &&
+                           !ssl_load_pkey(buf, len, NULL, &rlay->rl_tls_pkey))
+                               goto err;
+
+                       if (tls_config_add_keypair_ocsp_mem(tls_cfg, buf, len,
+                           fake_key, fake_keylen, NULL, 0) != 0) {
+                               log_warnx("failed to add tls certificate: %s",
+                                   tls_config_error(tls_cfg));
+                               goto err;
+                       }
+
+                       purge_key(&buf, len);
+               }
 
                if (rlay->rl_tls_cacert_fd != -1) {
                        if ((cabuf = relay_load_fd(rlay->rl_tls_cacert_fd,
@@ -2170,11 +2197,8 @@ relay_tls_ctx_create(struct relay *rlay)
                        if (!ssl_load_pkey(cabuf, calen,
                            &rlay->rl_tls_cacertx509, &rlay->rl_tls_capkey))
                                goto err;
-                       /* loading certificate public key */
-                       log_debug("%s: loading certificate", __func__);
-                       if (!ssl_load_pkey(buf, len, NULL, &rlay->rl_tls_pkey))
-                               goto err;
                }
+               rlay->rl_tls_cacert_fd = -1;
 
                tls = tls_server();
                if (tls == NULL) {
@@ -2191,14 +2215,8 @@ relay_tls_ctx_create(struct relay *rlay)
                rlay->rl_tls_ctx = tls;
 
                purge_key(&cabuf, calen);
-               purge_key(&buf, len);
        }
 
-       /* The fd for the keys/certs are not needed anymore */
-       close(rlay->rl_tls_cert_fd);
-       close(rlay->rl_tls_cacert_fd);
-       close(rlay->rl_tls_ca_fd);
-
        if (rlay->rl_tls_client_cfg == NULL)
                tls_config_free(tls_client_cfg);
        if (rlay->rl_tls_cfg == NULL)
@@ -2645,75 +2663,6 @@ relay_load_fd(int fd, off_t *len)
        close(fd);
        errno = err;
        return (NULL);
-}
-
-int
-relay_load_certfiles(struct relay *rlay)
-{
-       char     certfile[PATH_MAX];
-       char     hbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
-       struct protocol *proto = rlay->rl_proto;
-       int      useport = htons(rlay->rl_conf.port);
-
-       if (rlay->rl_conf.flags & F_TLSCLIENT) {
-               if (strlen(proto->tlsca)) {
-                       if ((rlay->rl_tls_ca_fd =
-                           open(proto->tlsca, O_RDONLY)) == -1)
-                               return (-1);
-                       log_debug("%s: using ca %s", __func__, proto->tlsca);
-               }
-               if (strlen(proto->tlscacert)) {
-                       if ((rlay->rl_tls_cacert_fd =
-                           open(proto->tlscacert, O_RDONLY)) == -1)
-                               return (-1);
-                       log_debug("%s: using ca certificate %s", __func__,
-                           proto->tlscacert);
-               }
-               if (strlen(proto->tlscakey) && proto->tlscapass != NULL) {
-                       if ((rlay->rl_tls_cakey =
-                           ssl_load_key(env, proto->tlscakey,
-                           &rlay->rl_conf.tls_cakey_len,
-                           proto->tlscapass)) == NULL)
-                               return (-1);
-                       log_debug("%s: using ca key %s", __func__,
-                           proto->tlscakey);
-               }
-       }
-
-       if ((rlay->rl_conf.flags & F_TLS) == 0)
-               return (0);
-
-       if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL)
-               return (-1);
-
-       if (snprintf(certfile, sizeof(certfile),
-           "/etc/ssl/%s:%u.crt", hbuf, useport) == -1)
-               return (-1);
-       if ((rlay->rl_tls_cert_fd = open(certfile, O_RDONLY)) == -1) {
-               if (snprintf(certfile, sizeof(certfile),
-                   "/etc/ssl/%s.crt", hbuf) == -1)
-                       return (-1);
-               if ((rlay->rl_tls_cert_fd = open(certfile, O_RDONLY)) == -1)
-                       return (-1);
-               useport = 0;
-       }
-       log_debug("%s: using certificate %s", __func__, certfile);
-
-       if (useport) {
-               if (snprintf(certfile, sizeof(certfile),
-                   "/etc/ssl/private/%s:%u.key", hbuf, useport) == -1)
-                       return -1;
-       } else {
-               if (snprintf(certfile, sizeof(certfile),
-                   "/etc/ssl/private/%s.key", hbuf) == -1)
-                       return -1;
-       }
-       if ((rlay->rl_tls_key = ssl_load_key(env, certfile,
-           &rlay->rl_conf.tls_key_len, NULL)) == NULL)
-               return (-1);
-       log_debug("%s: using private key %s", __func__, certfile);
-
-       return (0);
 }
 
 int
Index: usr.sbin/relayd/relayd.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.c,v
retrieving revision 1.176
diff -u -p -u -p -r1.176 relayd.c
--- usr.sbin/relayd/relayd.c    8 May 2019 23:22:19 -0000       1.176
+++ usr.sbin/relayd/relayd.c    9 May 2019 12:35:41 -0000
@@ -554,6 +554,7 @@ purge_relay(struct relayd *env, struct r
 {
        struct rsession         *con;
        struct relay_table      *rlt;
+       struct relay_cert       *cert, *tmpcert;
 
        /* shutdown and remove relay */
        if (event_initialized(&rlay->rl_ev))
@@ -572,7 +573,6 @@ purge_relay(struct relayd *env, struct r
        if (rlay->rl_dstbev != NULL)
                bufferevent_free(rlay->rl_dstbev);
 
-       purge_key(&rlay->rl_tls_key, rlay->rl_conf.tls_key_len);
        purge_key(&rlay->rl_tls_cakey, rlay->rl_conf.tls_cakey_len);
 
        if (rlay->rl_tls_pkey != NULL) {
@@ -597,6 +597,19 @@ purge_relay(struct relayd *env, struct r
                free(rlt);
        }
 
+       TAILQ_FOREACH_SAFE(cert, env->sc_certs, cert_entry, tmpcert) {
+               if (rlay->rl_conf.id != cert->cert_relayid)
+                       continue;
+               if (cert->cert_fd != -1)
+                       close(cert->cert_fd);
+               if (cert->cert_key_fd != -1)
+                       close(cert->cert_key_fd);
+               if (cert->cert_pkey != NULL)
+                       EVP_PKEY_free(cert->cert_pkey);
+               TAILQ_REMOVE(env->sc_certs, cert, cert_entry);
+               free(cert);
+       }
+
        free(rlay);
 }
 
@@ -1234,6 +1247,127 @@ pkey_add(struct relayd *env, EVP_PKEY *p
        TAILQ_INSERT_TAIL(env->sc_pkeys, ca_pkey, pkey_entry);
 
        return (ca_pkey);
+}
+
+struct relay_cert *
+cert_add(struct relayd *env, objid_t id)
+{
+       static objid_t           last_cert_id = 0;
+       struct relay_cert       *cert;
+
+       if ((cert = calloc(1, sizeof(*cert))) == NULL)
+               return (NULL);
+
+       if (id == 0)
+               id = ++last_cert_id;
+       cert->cert_id = id;
+       cert->cert_fd = -1;
+       cert->cert_key_fd = -1;
+
+       TAILQ_INSERT_TAIL(env->sc_certs, cert, cert_entry);
+
+       return (cert);
+}
+
+struct relay_cert *
+cert_find(struct relayd *env, objid_t id)
+{
+       struct relay_cert       *cert;
+
+       TAILQ_FOREACH(cert, env->sc_certs, cert_entry)
+               if (cert->cert_id == id)
+                       return (cert);
+       return (NULL);
+}
+
+int
+relay_load_certfiles(struct relayd *env, struct relay *rlay, const char *name)
+{
+       char     certfile[PATH_MAX];
+       char     hbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+       struct protocol *proto = rlay->rl_proto;
+       struct relay_cert *cert;
+       int      useport = htons(rlay->rl_conf.port);
+       int      cert_fd = -1, key_fd = -1;
+
+       if (rlay->rl_conf.flags & F_TLSCLIENT) {
+               if (strlen(proto->tlsca) && rlay->rl_tls_ca_fd == -1) {
+                       if ((rlay->rl_tls_ca_fd =
+                           open(proto->tlsca, O_RDONLY)) == -1)
+                               return (-1);
+                       log_debug("%s: using ca %s", __func__, proto->tlsca);
+               }
+               if (strlen(proto->tlscacert) && rlay->rl_tls_cacert_fd == -1) {
+                       if ((rlay->rl_tls_cacert_fd =
+                           open(proto->tlscacert, O_RDONLY)) == -1)
+                               return (-1);
+                       log_debug("%s: using ca certificate %s", __func__,
+                           proto->tlscacert);
+               }
+               if (strlen(proto->tlscakey) && !rlay->rl_conf.tls_cakey_len &&
+                   proto->tlscapass != NULL) {
+                       if ((rlay->rl_tls_cakey =
+                           ssl_load_key(env, proto->tlscakey,
+                           &rlay->rl_conf.tls_cakey_len,
+                           proto->tlscapass)) == NULL)
+                               return (-1);
+                       log_debug("%s: using ca key %s", __func__,
+                           proto->tlscakey);
+               }
+       }
+
+       if ((rlay->rl_conf.flags & F_TLS) == 0)
+               return (0);
+
+       if (name == NULL &&
+           print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL)
+               goto fail;
+       else if (name != NULL &&
+           strlcpy(hbuf, name, sizeof(hbuf)) >= sizeof(hbuf))
+               goto fail;
+
+       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)
+                       goto fail;
+               if ((cert_fd = open(certfile, O_RDONLY)) == -1)
+                       goto fail;
+               useport = 0;
+       }
+       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 ((key_fd = open(certfile, O_RDONLY)) == -1)
+               goto fail;
+       log_debug("%s: using private key %s", __func__, certfile);
+
+       if ((cert = cert_add(env, 0)) == NULL)
+               goto fail;
+
+       cert->cert_relayid = rlay->rl_conf.id;
+       cert->cert_fd = cert_fd;
+       cert->cert_key_fd = key_fd;
+
+       return (0);
+
+ fail:
+       if (cert_fd != -1)
+               close(cert_fd);
+       if (key_fd != -1)
+               close(key_fd);
+
+       return (-1);
 }
 
 void
Index: usr.sbin/relayd/relayd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.conf.5,v
retrieving revision 1.188
diff -u -p -u -p -r1.188 relayd.conf.5
--- usr.sbin/relayd/relayd.conf.5       4 Mar 2019 21:25:03 -0000       1.188
+++ usr.sbin/relayd/relayd.conf.5       9 May 2019 12:35:41 -0000
@@ -682,27 +682,10 @@ Like the previous directive, but for red
 .Xc
 Specify the address and port for the relay to listen on.
 The relay will accept incoming connections to the specified address.
-.Pp
 If the
 .Ic tls
 keyword is present, the relay will accept connections using the
 encrypted TLS protocol.
-The relay will attempt to look up a private key in
-.Pa /etc/ssl/private/address:port.key
-and a public certificate in
-.Pa /etc/ssl/address:port.crt ,
-where
-.Ar address
-is the specified IP address and
-.Ar port
-is the specified port that the relay listens on.
-If these files are not present, the relay will continue to look in
-.Pa /etc/ssl/private/address.key
-and
-.Pa /etc/ssl/address.crt .
-See
-.Xr ssl 8
-for details about SSL/TLS server certificates.
 .It Ic protocol Ar name
 Use the specified protocol definition for the relay.
 The generic TCP protocol options will be used by default;
@@ -963,6 +946,25 @@ Values higher than 1024 bits can cause i
 TLS clients.
 The default is
 .Ic no edh .
+.It Ic keypair Ar name
+The relay will attempt to look up a private key in
+.Pa /etc/ssl/private/name:port.key
+and a public certificate in
+.Pa /etc/ssl/name:port.crt ,
+where
+.Ar port
+is the specified port that the relay listens on.
+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 .
+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
+.Ar name .
+See
+.Xr ssl 8
+for details about SSL/TLS server certificates.
 .It Ic no cipher-server-preference
 Prefer the client's cipher list over the server's preferences when
 choosing a cipher for the connection.
Index: usr.sbin/relayd/relayd.h
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.h,v
retrieving revision 1.253
diff -u -p -u -p -r1.253 relayd.h
--- usr.sbin/relayd/relayd.h    8 May 2019 23:22:19 -0000       1.253
+++ usr.sbin/relayd/relayd.h    9 May 2019 12:35:41 -0000
@@ -137,13 +137,18 @@ struct ctl_relaytable {
        u_int32_t        flags;
 };
 
+enum fd_type {
+       RELAY_FD_CERT   = 1,
+       RELAY_FD_CACERT = 2,
+       RELAY_FD_CAFILE = 3,
+       RELAY_FD_KEY    = 4
+};
+
 struct ctl_relayfd {
+       objid_t          id;
        objid_t          relayid;
-       int              type;
+       enum fd_type     type;
 };
-#define RELAY_FD_CERT  1
-#define RELAY_FD_CACERT        2
-#define RELAY_FD_CAFILE        3
 
 struct ctl_script {
        objid_t          host;
@@ -704,6 +709,12 @@ struct relay_ticket_key {
 
 #define HTTPFLAG_WEBSOCKETS    0x01
 
+struct keyname {
+       TAILQ_ENTRY(keyname)     entry;
+       char                    *name;
+};
+TAILQ_HEAD(keynamelist, keyname);
+
 struct protocol {
        objid_t                  id;
        u_int32_t                flags;
@@ -722,6 +733,7 @@ struct protocol {
        char                     tlscacert[PATH_MAX];
        char                     tlscakey[PATH_MAX];
        char                    *tlscapass;
+       struct keynamelist       tlscerts;
        char                     name[MAX_NAME_SIZE];
        int                      tickets;
        enum prototype           type;
@@ -759,6 +771,16 @@ struct ca_pkey {
 };
 TAILQ_HEAD(ca_pkeylist, ca_pkey);
 
+struct relay_cert {
+       objid_t                  cert_id;
+       objid_t                  cert_relayid;
+       int                      cert_fd;
+       int                      cert_key_fd;
+       EVP_PKEY                *cert_pkey;
+       TAILQ_ENTRY(relay_cert)  cert_entry;
+};
+TAILQ_HEAD(relaycertlist, relay_cert);
+
 struct relay_config {
        objid_t                  id;
        u_int32_t                flags;
@@ -773,7 +795,6 @@ struct relay_config {
        struct timeval           timeout;
        enum forwardmode         fwdmode;
        union hashkey            hashkey;
-       off_t                    tls_key_len;
        off_t                    tls_cakey_len;
 };
 
@@ -798,10 +819,8 @@ struct relay {
        struct tls_config       *rl_tls_client_cfg;
        struct tls              *rl_tls_ctx;
 
-       int                     rl_tls_cert_fd;
-       int                     rl_tls_ca_fd;
-       int                     rl_tls_cacert_fd;
-       char                    *rl_tls_key;
+       int                      rl_tls_ca_fd;
+       int                      rl_tls_cacert_fd;
        EVP_PKEY                *rl_tls_pkey;
        X509                    *rl_tls_cacertx509;
        char                    *rl_tls_cakey;
@@ -1083,6 +1102,7 @@ struct relayd {
        struct routerlist       *sc_rts;
        struct netroutelist     *sc_routes;
        struct ca_pkeylist      *sc_pkeys;
+       struct relaycertlist    *sc_certs;
        struct sessionlist       sc_sessions;
        char                     sc_demote_group[IFNAMSIZ];
        u_int16_t                sc_id;
@@ -1176,7 +1196,6 @@ int        relay_privinit(struct relay *);
 void    relay_notify_done(struct host *, const char *);
 int     relay_session_cmp(struct rsession *, struct rsession *);
 char   *relay_load_fd(int, off_t *);
-int     relay_load_certfiles(struct relay *);
 void    relay_close(struct rsession *, const char *, int);
 int     relay_reset_event(struct ctl_relay_event *);
 void    relay_natlook(int, short, void *);
@@ -1290,6 +1309,10 @@ struct relay     *relay_findbyname(struct re
 struct relay   *relay_findbyaddr(struct relayd *, struct relay_config *);
 EVP_PKEY       *pkey_find(struct relayd *, char *hash);
 struct ca_pkey *pkey_add(struct relayd *, EVP_PKEY *, char *hash);
+struct relay_cert *cert_add(struct relayd *, objid_t);
+struct relay_cert *cert_find(struct relayd *, objid_t);
+int             relay_load_certfiles(struct relayd *, struct relay *,
+                   const char *);
 int             expand_string(char *, size_t, const char *, const char *);
 void            translate_string(char *);
 void            purge_key(char **, off_t);

Reply via email to