Index | Thread | Search

From:
Theo Buehler <tb@theobuehler.org>
Subject:
Re: [PATCH] relayd client certificate validation again
To:
Sören Tempel <soeren@soeren-tempel.net>
Cc:
tech@openbsd.org, markus.l2ll@gmail.com, rivo@elnit.ee, brian@planetunix.net
Date:
Tue, 1 Oct 2024 11:44:23 +0200

Download raw body.

Thread
On Mon, Sep 30, 2024 at 06:57:26PM +0200, Sören Tempel wrote:
> Hello!
> 
> Is there a reason why this patch never got a review? The feature (TLS
> client certificate validation within relayd) seems to be requested
> frequently [1]. I just came across this while looking into working on
> such a patch myself.
> 
> Is the feature not deemed useful, or is the patch not in a good shape?
> If the latter is the case, I would be willing to revise the patch.

There is currently no real relayd maintainer. I don't think anyone
objected to this feature. Quite the opposite: it makes sense.

The quoted (and mangled) patch has no regress coverage. The last patch
in the linked thread had some regress but broke some other things.
There were also some minor requests for changes. These didn't happen
since the author lost interest or no longer had need for it, so it
didn't land.

I think it was close to being ready, but someone (not me) has to add the
finishing touches. Once we have a patch that applies to current and
passes regress, I'll be happy to review or commit.

> I would also be fine with just implementing the validation part without
> adding the capability to forward the validated certificate, which should
> shrink the patchset a bit.
> 
> Cheers
> Sören
> 
> [1]: https://marc.info/?t=154295268000001&r=1&w=2
> 
> Markus Läll <markus.l2ll@gmail.com> wrote:
> > Hi, we've got the patch ready for client certificate validation, cc'ing
> > related people.
> > 
> > The patch adds two features:
> > 1. client certificate validation itself
> > 2. passing on certificate and select fields in HTTP headers
> > 
> > 
> > ## Brief description of client certificates (for whoever else is reading)
> > 
> > Client certificates, also known as mutual authentication, are the reverse
> > to TLS server certificates (like letsencrypt) where the server
> > authenticates the user instead of the other way around.
> > 
> > In principle client certificates work as follows:
> > - the server has keypair and a CA certificate created from this keypair
> > - the user has a keypair and submits their public key for certification (to
> > being signed by the CA)
> > - the server (relayd) has the CA certificate configured as 'client ca
> > "/path/to/ca.pem"'
> > - the user provides their certificate when connecting, the provided
> > certificate is validated against the CA certificate.
> > 
> > How this is set up in practice is up to whoever implements the
> > infrastructure. Client certificates can be installed to operating systems'
> > certificate stores (Windows, macOS) where browsers can use them, or into
> > browsers own certificate stores (Firefox has its own), or specified on the
> > command line (curl, wget) etc.
> > 
> > 
> > ## Configuration
> > 
> > To turn on client certificate validation add
> > 
> > tls { client ca "/path/to/ca-cert.pem" }
> > 
> > to relayd.conf.
> > 
> > Add "optional" flag to make the certificate not required:
> > 
> > tls { client ca "/path/to/cert.pem" optional }
> > 
> > With "optional" relayd will succeed in the TLS handshake when no client
> > certificate is provided. But if a certificate *is* provided then it *must*
> > validate with the configured CA, otherwise the TLS handshake fails.
> > 
> > 
> > ## Pass certificate on in HTTP header
> > 
> > To pass on the certificate in an url-encoded PEM:
> > 
> > match header set "ANY_HEADER_NAME" value "$CLIENT_CERT_CHAIN"
> > 
> > With this configuration the downstream can inspect the known-to-be-valid
> > certificate further (e.g extract identity or other info from x509
> > extensions).
> > 
> > There was discussion privately on is there any standard for putting
> > certificates in HTTP headers, repeating the reply here as well:
> > 
> > There appears to be no standard, but this is how other HTTP servers do it:
> > - nginx urlencodes the PEM file with $ssl_client_escaped_cert[1] (this is
> > what is done in this patch too). There is also the $ssl_client_cert
> > variable which adds a tab to each next new line, but this way of doing
> > multiline HTTP headers is deprecated[2]. There is also
> > $ssl_client_raw_cert, but the raw multiline PEM is invalid HTTP header;
> > - envoy also urlencodes the PEM[3];
> > - haproxy has only the binary DER, but base64 encoding it like
> > %[ssl_c_der,base64]) should result in PEM with no newlines and no headers;
> > - apache has the %{SSL_CLIENT_CERT} with raw (multiline) PEM[4], which is
> > invalid in HTTP headers, but this can be processed with the escape
> > function[5], something like "expr=3D %{escape:SSL_CLIENT_CERT}"
> > 
> > [1]
> > https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_esca=
> > ped_cert
> > [2] https://tools.ietf.org/html/rfc7230#section-3.2.4
> > [3]
> > https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_ma=
> > n/headers#x-forwarded-client-cert
> > [4] https://httpd.apache.org/docs/current/mod/mod_ssl.html#envvars
> > [5] https://httpd.apache.org/docs/current/expr.html#functions
> > 
> > In addition to extracting the entire certificate, subject and issuer can be
> > put to HTTP headers too for convenience:
> > 
> > match header set "CS_SUBJECT" value "$CLIENT_CERT_SUBJECT"
> > match header set "CS_ISSUER" value "$CLIENT_CERT_ISSUER"
> > 
> > 
> > 
> > --=20
> > Markus L=C3=A4ll
> > 
> > On Thu, Dec 16, 2021 at 11:23 PM rivo nurges <rivo@elnit.ee> wrote:
> > 
> > > Hi!
> > >
> > > Here comes the support for relayd client certificate validation.
> > > Full certificate chain, subject and issuer can be passed over in http
> > > headers.
> > > It supports mandatory validation and optional validation(if client chooses
> > > to
> > > provide certificate it will be validated).
> > >
> > > Part of my sample config.
> > >
> > > http protocol test {
> > >    match header set "CS_SUBJECT" value "$CLIENT_CERT_SUBJECT"
> > >    match header set "CS_ISSUER" value "$CLIENT_CERT_ISSUER"
> > >    match header set "CS_CERT" value "$CLIENT_CERT_CHAIN"
> > >    pass
> > >    tls {client ca "/tmp/easyrsa3/pki/ca.crt" optional }
> > > }
> > >
> > > This uses code from the patches submitted by Ashe Connor.
> > >
> > > Rivo
> > >
> > > diff refs/heads/master refs/heads/relay-clc3
> > > blob - a2f1c130d6b45e3082048218c11537dca485998a
> > > blob + 5070a7d48f58403f53d818231e1676db749aa9d7
> > > --- usr.sbin/relayd/config.c
> > > +++ usr.sbin/relayd/config.c
> > > @@ -954,6 +954,15 @@ config_setrelay(struct relayd *env, struct relay *rl=
> > ay
> > >                                             rlay->rl_conf.name);
> > >                                         return (-1);
> > >                                 }
> > > +                               if (rlay->rl_tls_client_ca_fd !=3D -1 &&
> > > +                                   config_setrelayfd(ps, id, n, 0,
> > > +                                   rlay->rl_conf.id,
> > > RELAY_FD_CLIENTCACERT,
> > > +                                   rlay->rl_tls_client_ca_fd) =3D=3D -1)=
> >  {
> > > +                                       log_warn("%s: fd passing failed
> > > for "
> > > +                                           "`%s'", __func__,
> > > +                                           rlay->rl_conf.name);
> > > +                                       return (-1);
> > > +                               }
> > >                                 /* Prevent fd exhaustion in the parent. */
> > >                                 if (proc_flush_imsg(ps, id, n) =3D=3D -1)=
> >  {
> > >                                         log_warn("%s: failed to flush "
> > > @@ -987,6 +996,10 @@ config_setrelay(struct relayd *env, struct relay *rl=
> > ay
> > >                 close(rlay->rl_s);
> > >                 rlay->rl_s =3D -1;
> > >         }
> > > +       if (rlay->rl_tls_client_ca_fd !=3D -1) {
> > > +               close(rlay->rl_tls_client_ca_fd);
> > > +               rlay->rl_tls_client_ca_fd =3D -1;
> > > +       }
> > >         if (rlay->rl_tls_cacert_fd !=3D -1) {
> > >                 close(rlay->rl_tls_cacert_fd);
> > >                 rlay->rl_tls_cacert_fd =3D -1;
> > > @@ -1012,6 +1025,10 @@ config_setrelay(struct relayd *env, struct relay
> > > *rlay
> > >                         cert->cert_ocsp_fd =3D -1;
> > >                 }
> > >         }
> > > +       if (rlay->rl_tls_client_ca_fd !=3D -1) {
> > > +               close(rlay->rl_tls_client_ca_fd);
> > > +               rlay->rl_tls_client_ca_fd =3D -1;
> > > +       }
> > >
> > >         return (0);
> > >   }
> > > @@ -1034,6 +1051,7 @@ config_getrelay(struct relayd *env, struct imsg
> > > *imsg)
> > >         rlay->rl_s =3D imsg->fd;
> > >         rlay->rl_tls_ca_fd =3D -1;
> > >         rlay->rl_tls_cacert_fd =3D -1;
> > > +       rlay->rl_tls_client_ca_fd =3D -1;
> > >
> > >         if (ps->ps_what[privsep_process] & CONFIG_PROTOS) {
> > >                 if (rlay->rl_conf.proto =3D=3D EMPTY_ID)
> > > @@ -1163,6 +1181,9 @@ config_getrelayfd(struct relayd *env, struct imsg
> > > *ims
> > >         case RELAY_FD_CAFILE:
> > >                 rlay->rl_tls_cacert_fd =3D imsg->fd;
> > >                 break;
> > > +       case RELAY_FD_CLIENTCACERT:
> > > +               rlay->rl_tls_client_ca_fd =3D imsg->fd;
> > > +               break;
> > >         }
> > >
> > >         DPRINTF("%s: %s %d received relay fd %d type %d for relay %s",
> > > __func__,
> > > blob - 22beb857229a16e5b2c17a25a2944231d41e7e08
> > > blob + fe5e8ff4dfed10e8f09e3226bdfe33f8bc031c8e
> > > --- usr.sbin/relayd/parse.y
> > > +++ usr.sbin/relayd/parse.y
> > > @@ -172,14 +172,14 @@ typedef struct {
> > >   %token        CODE COOKIE DEMOTE DIGEST DISABLE ERROR EXPECT PASS BLOCK
> > > EXTERNAL
> > >   %token        FILENAME FORWARD FROM HASH HEADER HEADERLEN HOST HTTP ICMP
> > > INCLUDE INET
> > >   %token        INET6 INTERFACE INTERVAL IP KEYPAIR LABEL LISTEN VALUE
> > > LOADBALANCE LOG
> > > -%token LOOKUP METHOD MODE NAT NO DESTINATION NODELAY NOTHING ON PARENT
> > > PATH
> > > +%token LOOKUP METHOD MODE NAT NO DESTINATION NODELAY NOTHING ON OPTIONAL
> > > PARENT PATH
> > >   %token        PFTAG PORT PREFORK PRIORITY PROTO QUERYSTR REAL REDIRECT
> > > RELAY REMOVE
> > >   %token        REQUEST RESPONSE RETRY QUICK RETURN ROUNDROBIN ROUTE SACK
> > > SCRIPT SEND
> > >   %token        SESSION SOCKET SPLICE SSL STICKYADDR STRIP STYLE TABLE TAG
> > > TAGGED TCP
> > >   %token        TIMEOUT TLS TO ROUTER RTLABEL TRANSPARENT URL WITH TTL
> > > RTABLE
> > >   %token        MATCH PARAMS RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE
> > > PASSWORD ECDHE
> > >   %token        EDH TICKETS CONNECTION CONNECTIONS CONTEXT ERRORS STATE
> > > CHANGES CHECKS
> > > -%token WEBSOCKETS
> > > +%token WEBSOCKETS CLIENT
> > >   %token        <v.string>      STRING
> > >   %token  <v.number>    NUMBER
> > >   %type <v.string>      context hostname interface table value path
> > > @@ -188,6 +188,7 @@ typedef struct {
> > >   %type <v.number>      opttls opttlsclient
> > >   %type <v.number>      redirect_proto relay_proto match
> > >   %type <v.number>      action ruleaf key_option
> > > +%type  <v.number>      clientcaopt
> > >   %type <v.port>        port
> > >   %type <v.host>        host
> > >   %type <v.addr>        address rulesrc ruledst addrprefix
> > > @@ -244,6 +245,10 @@ opttlsclient       : /*empty*/     { $$ =3D 0; }
> > >                 | WITH ssltls   { $$ =3D 1; }
> > >                 ;
> > >
> > > +clientcaopt    : /*empty*/     { $$ =3D 0; }
> > > +               | OPTIONAL      { $$ =3D 1; }
> > > +               ;
> > > +
> > >   http_type     : HTTP          { $$ =3D 0; }
> > >                 | STRING        {
> > >                         if (strcmp("https", $1) =3D=3D 0) {
> > > @@ -1353,6 +1358,19 @@ tlsflags : SESSION TICKETS { proto->tickets =3D 1;=
> >  }
> > >                         name->name =3D $2;
> > >                         TAILQ_INSERT_TAIL(&proto->tlscerts, name, entry);
> > >                 }
> > > +               | CLIENT CA STRING clientcaopt          {
> > > +                       if (strlcpy(proto->tlsclientca, $3,
> > > +                           sizeof(proto->tlsclientca)) >=3D
> > > +                           sizeof(proto->tlsclientca)) {
> > > +                               yyerror("tlsclientca truncated");
> > > +                               free($3);
> > > +                               YYERROR;
> > > +                               }
> > > +                        if ($4) {
> > > +                               proto->tlsflags  |=3D
> > > TLSFLAG_CLIENT_OPTIONAL;
> > > +                        }
> > > +                       free($3);
> > > +               }
> > >                 | NO flag                       { proto->tlsflags &=3D
> > > ~($2); }
> > >                 | flag                          { proto->tlsflags |=3D $1=
> > ; }
> > >                 ;
> > > @@ -1824,6 +1842,7 @@ relay             : RELAY STRING  {
> > >                         r->rl_conf.dstretry =3D 0;
> > >                         r->rl_tls_ca_fd =3D -1;
> > >                         r->rl_tls_cacert_fd =3D -1;
> > > +                       r->rl_tls_client_ca_fd =3D -1;
> > >                         TAILQ_INIT(&r->rl_tables);
> > >                         if (last_relay_id =3D=3D INT_MAX) {
> > >                                 yyerror("too many relays defined");
> > > @@ -2413,6 +2432,7 @@ lookup(char *s)
> > >                 { "check",              CHECK },
> > >                 { "checks",             CHECKS },
> > >                 { "ciphers",            CIPHERS },
> > > +               { "client",             CLIENT },
> > >                 { "code",               CODE },
> > >                 { "connection",         CONNECTION },
> > >                 { "context",            CONTEXT },
> > > @@ -2458,6 +2478,7 @@ lookup(char *s)
> > >                 { "nodelay",            NODELAY },
> > >                 { "nothing",            NOTHING },
> > >                 { "on",                 ON },
> > > +               { "optional",           OPTIONAL },
> > >                 { "params",             PARAMS },
> > >                 { "parent",             PARENT },
> > >                 { "pass",               PASS },
> > > @@ -3399,6 +3420,7 @@ relay_inherit(struct relay *ra, struct relay *rb)
> > >         if (!(rb->rl_conf.flags & F_TLS)) {
> > >                 rb->rl_tls_cacert_fd =3D -1;
> > >                 rb->rl_tls_ca_fd =3D -1;
> > > +               rb->rl_tls_client_ca_fd =3D -1;
> > >         }
> > >         TAILQ_INIT(&rb->rl_tables);
> > >
> > > blob - da4a1aa0cc1158b22506c6d81e4d36b8810c025c
> > > blob + 2d16b9d91e594a06d4b1b2bfc791c7f0c861fc57
> > > --- usr.sbin/relayd/relay.c
> > > +++ usr.sbin/relayd/relay.c
> > > @@ -2255,6 +2255,30 @@ relay_tls_ctx_create(struct relay *rlay)
> > >                 }
> > >                 rlay->rl_tls_cacert_fd =3D -1;
> > >
> > > +               if (rlay->rl_tls_client_ca_fd !=3D -1) {
> > > +                       if ((buf =3D relay_load_fd(rlay->rl_tls_client_ca=
> > _fd,
> > > +                           &len)) =3D=3D
> > > +                           NULL) {
> > > +                               log_warn(
> > > +                                   "failed to read tls client CA
> > > certificate");
> > > +                               goto err;
> > > +                       }
> > > +
> > > +                       if (tls_config_set_ca_mem(tls_cfg, buf, len) !=3D=
> >  0)
> > > {
> > > +                               log_warnx(
> > > +                                   "failed to set tls client CA cert: %s=
> > ",
> > > +                                   tls_config_error(tls_cfg));
> > > +                               goto err;
> > > +                       }
> > > +                       purge_key(&buf, len);
> > > +
> > > +                       if (rlay->rl_proto->tlsflags &
> > > TLSFLAG_CLIENT_OPTIONAL)
> > > +                               tls_config_verify_client_optional(tls_cfg=
> > );
> > > +                       else
> > > +                               tls_config_verify_client(tls_cfg);
> > > +               }
> > > +               rlay->rl_tls_client_ca_fd =3D -1;
> > > +
> > >                 tls =3D tls_server();
> > >                 if (tls =3D=3D NULL) {
> > >                         log_warnx("unable to allocate TLS context");
> > > blob - d493c238813cfc692d83f65a88d4556b2fa35b0f
> > > blob + 58ba35c16ea8d80b36796d977ad7920d3bed3a9c
> > > --- usr.sbin/relayd/relay_http.c
> > > +++ usr.sbin/relayd/relay_http.c
> > > @@ -78,6 +78,7 @@ int            relay_match_actions(struct
> > > ctl_relay_event *,
> > >                     struct relay_table **);
> > >   void           relay_httpdesc_free(struct http_descriptor *);
> > >   char *                 server_root_strip(char *, int);
> > > +char           *url_encode(const char *);
> > >
> > >   static struct relayd  *env =3D NULL;
> > >
> > > @@ -1279,7 +1280,32 @@ relay_expand_http(struct ctl_relay_event *cre, char
> > > *v
> > >                 if (expand_string(buf, len, "$TIMEOUT", ibuf) !=3D 0)
> > >                         return (NULL);
> > >         }
> > > -
> > > +       if (strstr(val, "$CLIENT_CERT_") !=3D NULL &&
> > > tls_peer_cert_provided(cre->tls)) {
> > > +               if (strstr(val, "$CLIENT_CERT_SUBJECT") !=3D NULL) {
> > > +                       if (expand_string(buf, len,
> > > +                           "$CLIENT_CERT_SUBJECT",
> > > tls_peer_cert_subject(cre->tls)) !=3D 0)
> > > +                               return (NULL);
> > > +               }
> > > +               if (strstr(val, "$CLIENT_CERT_ISSUER") !=3D NULL) {
> > > +                       if (expand_string(buf, len,
> > > +                           "$CLIENT_CERT_ISSUER",
> > > tls_peer_cert_issuer(cre->tls)) !=3D 0)
> > > +                               return (NULL);
> > > +               }
> > > +               if (strstr(val, "$CLIENT_CERT_CHAIN") !=3D NULL) {
> > > +                       const char *pem;
> > > +                       char *cbuf;
> > > +                       size_t plen;
> > > +                       pem =3D tls_peer_cert_chain_pem(cre->tls, &plen);
> > > +                       cbuf =3D malloc(plen);
> > > +                       sprintf(cbuf, "%.*s", (int)plen - 1, pem);
> > > +                       if (expand_string(buf, len,
> > > +                           "$CLIENT_CERT_CHAIN", url_encode(cbuf)) !=3D =
> > 0) {
> > > +                               free(cbuf);
> > > +                               return (NULL);
> > > +                       } else
> > > +                               free(cbuf);
> > > +               }
> > > +       }
> > >         return (buf);
> > >   }
> > >
> > > @@ -2045,3 +2071,27 @@ server_root_strip(char *path, int n)
> > >         return (path);
> > >   }
> > >
> > > +char *
> > > +url_encode(const char *src)
> > > +{
> > > +       static char      hex[] =3D "0123456789ABCDEF";
> > > +       char            *dp, *dst;
> > > +       unsigned char    c;
> > > +
> > > +       /* We need 3 times the memory if every letter is encoded. */
> > > +       if ((dst =3D calloc(3, strlen(src) + 1)) =3D=3D NULL)
> > > +               return (NULL);
> > > +
> > > +       for (dp =3D dst; *src !=3D 0; src++) {
> > > +               c =3D (unsigned char) *src;
> > > +               if (c =3D=3D ' ' || c =3D=3D '#' || c =3D=3D '%' || c =3D=
> > =3D '?' || c =3D=3D
> > > '"' ||
> > > +                   c =3D=3D '&' || c =3D=3D '<' || c <=3D 0x1f || c >=3D=
> >  0x7f) {
> > > +                       *dp++ =3D '%';
> > > +                       *dp++ =3D '%';
> > > +                       *dp++ =3D hex[c >> 4];
> > > +                       *dp++ =3D hex[c & 0x0f];
> > > +               } else
> > > +                       *dp++ =3D *src;
> > > +       }
> > > +       return (dst);
> > > +}
> > > blob - 54e26e646fae5804e66d2d3cfeba68e06914ab2b
> > > blob + cd99c21d7cdaf9fc5fdc33e5a0ad886afaa9b889
> > > --- usr.sbin/relayd/relayd.c
> > > +++ usr.sbin/relayd/relayd.c
> > > @@ -1360,6 +1360,15 @@ relay_load_certfiles(struct relayd *env, struct
> > > relay
> > >         if ((rlay->rl_conf.flags & F_TLS) =3D=3D 0)
> > >                 return (0);
> > >
> > > +       if (strlen(proto->tlsclientca) &&
> > > +           rlay->rl_tls_client_ca_fd =3D=3D -1) {
> > > +               if ((rlay->rl_tls_client_ca_fd =3D
> > > +                   open(proto->tlsclientca, O_RDONLY)) =3D=3D -1)
> > > +                       return (-1);
> > > +               log_debug("%s: using client ca %s", __func__,
> > > +                   proto->tlsclientca);
> > > +       }
> > > +
> > >         if (name =3D=3D NULL &&
> > >             print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) =3D=3D NULL)
> > >                 goto fail;
> > > blob - cecbae71f87e603b3e30d4c0114bf1c60a82b52a
> > > blob + cfb7a314811730723449a5109d500014711db3ae
> > > --- usr.sbin/relayd/relayd.conf.5
> > > +++ usr.sbin/relayd/relayd.conf.5
> > > @@ -948,6 +948,13 @@ will be used (strong crypto cipher suites without an=
> > on
> > >   See the CIPHERS section of
> > >   .Xr openssl 1
> > >   for information about SSL/TLS cipher suites and preference lists.
> > > +.It Ic client ca Ar path Op optional
> > > +Require TLS client certificates whose authenticity can be verified
> > > +against the CA certificate(s) in the specified file in order to
> > > +proceed beyond the TLS handshake.
> > > +If the
> > > +.Ic optional
> > > +keyword is present, the certificate is verified only if presented.
> > >   .It Ic client-renegotiation
> > >   Allow client-initiated renegotiation.
> > >   To mitigate a potential DoS risk,
> > > @@ -1361,6 +1368,12 @@ The value string may contain predefined macros that
> > > wi
> > >   at runtime:
> > >   .Pp
> > >   .Bl -tag -width $SERVER_ADDR -offset indent -compact
> > > +.It Ic $CLIENT_CERT_CHAIN
> > > +The certificate chain of the client certificate.
> > > +.It Ic $CLIENT_CERT_ISSUER
> > > +The issuer of the client certificate.
> > > +.It Ic $CLIENT_CERT_SUBJECT
> > > +The subject of the client certificate.
> > >   .It Ic $HOST
> > >   The Host header's value of the relay.
> > >   .It Ic $REMOTE_ADDR
> > > blob - 2236d140f7e6b9477bac401cbcdd559db171680b
> > > blob + 2a1166599bfd57b0682c4d4bacd15d340ff9b5ad
> > > --- usr.sbin/relayd/relayd.h
> > > +++ usr.sbin/relayd/relayd.h
> > > @@ -139,11 +139,12 @@ struct ctl_relaytable {
> > >   };
> > >
> > >   enum fd_type {
> > > -       RELAY_FD_CERT   =3D 1,
> > > -       RELAY_FD_CACERT =3D 2,
> > > -       RELAY_FD_CAFILE =3D 3,
> > > -       RELAY_FD_KEY    =3D 4,
> > > -       RELAY_FD_OCSP   =3D 5
> > > +       RELAY_FD_CERT           =3D 1,
> > > +       RELAY_FD_CACERT         =3D 2,
> > > +       RELAY_FD_CAFILE         =3D 3,
> > > +       RELAY_FD_KEY            =3D 4,
> > > +       RELAY_FD_OCSP           =3D 5,
> > > +       RELAY_FD_CLIENTCACERT   =3D 6
> > >   };
> > >
> > >   struct ctl_relayfd {
> > > @@ -403,6 +404,7 @@ union hashkey {
> > >   #define F_TLSINSPECT          0x04000000
> > >   #define F_HASHKEY             0x08000000
> > >   #define F_AGENTX_TRAPONLY     0x10000000
> > > +#define F_TLSVERIFY            0x20000000
> > >
> > >   #define F_BITS
> > >       \
> > >         "\10\01DISABLE\02BACKUP\03USED\04DOWN\05ADD\06DEL\07CHANGED"    \
> > > @@ -703,6 +705,7 @@ TAILQ_HEAD(relay_rules, relay_rule);
> > >   #define TLSFLAG_VERSION                               0x1f
> > >   #define TLSFLAG_CIPHER_SERVER_PREF            0x20
> > >   #define TLSFLAG_CLIENT_RENEG                  0x40
> > > +#define        TLSFLAG_CLIENT_OPTIONAL                 0x80
> > >   #define TLSFLAG_DEFAULT                               \
> > >         (TLSFLAG_TLSV1_2|TLSFLAG_TLSV1_3|TLSFLAG_CIPHER_SERVER_PREF)
> > >
> > > @@ -746,6 +749,7 @@ struct protocol {
> > >         char                     tlscacert[PATH_MAX];
> > >         char                     tlscakey[PATH_MAX];
> > >         char                    *tlscapass;
> > > +       char                     tlsclientca[PATH_MAX];
> > >         struct keynamelist       tlscerts;
> > >         char                     name[MAX_NAME_SIZE];
> > >         int                      tickets;
> > > @@ -835,6 +839,7 @@ struct relay {
> > >
> > >         int                      rl_tls_ca_fd;
> > >         int                      rl_tls_cacert_fd;
> > > +       int                      rl_tls_client_ca_fd;
> > >         EVP_PKEY                *rl_tls_pkey;
> > >         X509                    *rl_tls_cacertx509;
> > >         char                    *rl_tls_cakey;
>