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, optional validation(if client chooses to
provide certificate it will be validated) and no validation(cert is passed to
the backend but no validation will happen in relayd).

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"
  tls { client-insecure, client-optional }
}

This uses code from the patches submitted by Ashe Connor.

Rivo

diff 8c17c73d13a081367dafe608a20fbe1b0ed903ce /home/rix/src
blob - 3e60d63ef52437fed245ed2715e30a0b2bf7956b
file + usr.sbin/relayd/config.c
--- usr.sbin/relayd/config.c
+++ usr.sbin/relayd/config.c
@@ -956,6 +956,15 @@ config_setrelay(struct relayd *env, struct relay *rlay
                                            rlay->rl_conf.name);
                                        return (-1);
                                }
+                               if (rlay->rl_tls_client_ca_fd != -1 &&
+                                   config_setrelayfd(ps, id, n, 0,
+                                   rlay->rl_conf.id, RELAY_FD_CLIENTCACERT,
+                                   rlay->rl_tls_client_ca_fd) == -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) == -1) {
                                        log_warn("%s: failed to flush "
@@ -989,6 +998,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
                close(rlay->rl_s);
                rlay->rl_s = -1;
        }
+       if (rlay->rl_tls_client_ca_fd != -1) {
+               close(rlay->rl_tls_client_ca_fd);
+               rlay->rl_tls_client_ca_fd = -1;
+       }
        if (rlay->rl_tls_cacert_fd != -1) {
                close(rlay->rl_tls_cacert_fd);
                rlay->rl_tls_cacert_fd = -1;
@@ -1014,6 +1027,10 @@ config_setrelay(struct relayd *env, struct relay *rlay
                        cert->cert_ocsp_fd = -1;
                }
        }
+       if (rlay->rl_tls_client_ca_fd != -1) {
+               close(rlay->rl_tls_client_ca_fd);
+               rlay->rl_tls_client_ca_fd = -1;
+       }
return (0);
 }
@@ -1036,6 +1053,7 @@ config_getrelay(struct relayd *env, struct imsg *imsg)
        rlay->rl_s = imsg->fd;
        rlay->rl_tls_ca_fd = -1;
        rlay->rl_tls_cacert_fd = -1;
+       rlay->rl_tls_client_ca_fd = -1;
if (ps->ps_what[privsep_process] & CONFIG_PROTOS) {
                if (rlay->rl_conf.proto == EMPTY_ID)
@@ -1165,6 +1183,9 @@ config_getrelayfd(struct relayd *env, struct imsg *ims
        case RELAY_FD_CAFILE:
                rlay->rl_tls_cacert_fd = imsg->fd;
                break;
+       case RELAY_FD_CLIENTCACERT:
+               rlay->rl_tls_client_ca_fd = imsg->fd;
+               break;
        }
DPRINTF("%s: %s %d received relay fd %d type %d for relay %s", __func__,
blob - 5f7513e788d7ba0615942bd1efee8d9cff6e9941
file + usr.sbin/relayd/parse.y
--- usr.sbin/relayd/parse.y
+++ usr.sbin/relayd/parse.y
@@ -179,7 +179,7 @@ typedef struct {
 %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
@@ -1353,6 +1353,16 @@ tlsflags : SESSION TICKETS { proto->tickets = 1; }
                        name->name = $2;
                        TAILQ_INSERT_TAIL(&proto->tlscerts, name, entry);
                }
+               | CLIENT CA STRING              {
+                       if (strlcpy(proto->tlsclientca, $3,
+                           sizeof(proto->tlsclientca)) >=
+                           sizeof(proto->tlsclientca)) {
+                               yyerror("tlsclientca truncated");
+                               free($3);
+                               YYERROR;
+                               }
+                       free($3);
+               }
                | NO flag                       { proto->tlsflags &= ~($2); }
                | flag                          { proto->tlsflags |= $1; }
                ;
@@ -1374,6 +1384,10 @@ flag             : STRING                        {
                                $$ = TLSFLAG_CIPHER_SERVER_PREF;
                        else if (strcmp("client-renegotiation", $1) == 0)
                                $$ = TLSFLAG_CLIENT_RENEG;
+                       else if (strcmp("client-insecure", $1) == 0)
+                               $$ = TLSFLAG_CLIENT_INSECURE;
+                       else if (strcmp("client-optional", $1) == 0)
+                               $$ = TLSFLAG_CLIENT_OPTIONAL;
                        else {
                                yyerror("invalid TLS flag: %s", $1);
                                free($1);
@@ -1824,6 +1838,7 @@ relay             : RELAY STRING  {
                        r->rl_conf.dstretry = 0;
                        r->rl_tls_ca_fd = -1;
                        r->rl_tls_cacert_fd = -1;
+                       r->rl_tls_client_ca_fd = -1;
                        TAILQ_INIT(&r->rl_tables);
                        if (last_relay_id == INT_MAX) {
                                yyerror("too many relays defined");
@@ -2413,6 +2428,7 @@ lookup(char *s)
                { "check",            CHECK },
                { "checks",           CHECKS },
                { "ciphers",          CIPHERS },
+               { "client",           CLIENT },
                { "code",             CODE },
                { "connection",               CONNECTION },
                { "context",          CONTEXT },
@@ -3399,6 +3415,7 @@ relay_inherit(struct relay *ra, struct relay *rb)
        if (!(rb->rl_conf.flags & F_TLS)) {
                rb->rl_tls_cacert_fd = -1;
                rb->rl_tls_ca_fd = -1;
+               rb->rl_tls_client_ca_fd = -1;
        }
        TAILQ_INIT(&rb->rl_tables);
blob - da4a1aa0cc1158b22506c6d81e4d36b8810c025c
file + usr.sbin/relayd/relay.c
--- usr.sbin/relayd/relay.c
+++ usr.sbin/relayd/relay.c
@@ -2255,6 +2255,33 @@ relay_tls_ctx_create(struct relay *rlay)
                }
                rlay->rl_tls_cacert_fd = -1;
+ if (rlay->rl_tls_client_ca_fd != -1) {
+                       if ((buf = relay_load_fd(rlay->rl_tls_client_ca_fd,
+                           &len)) ==
+                           NULL) {
+                               log_warn(
+                                   "failed to read tls client CA certificate");
+                               goto err;
+                       }
+
+                       if (tls_config_set_ca_mem(tls_cfg, buf, len) != 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);
+
+                       if (rlay->rl_proto->tlsflags & TLSFLAG_CLIENT_INSECURE)
+                               tls_config_insecure_noverifycert(tls_cfg);
+               }
+               rlay->rl_tls_client_ca_fd = -1;
+
                tls = tls_server();
                if (tls == NULL) {
                        log_warnx("unable to allocate TLS context");
blob - 29e401e0935bd33dc81523b0d6baeed62dabf6bb
file + usr.sbin/relayd/relay_http.c
--- 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 = NULL; @@ -1268,7 +1269,32 @@ relay_expand_http(struct ctl_relay_event *cre, char *v
                if (expand_string(buf, len, "$TIMEOUT", ibuf) != 0)
                        return (NULL);
        }
-
+       if (strstr(val, "$CLIENT_CERT_") != NULL && 
tls_peer_cert_provided(cre->tls)) {
+               if (strstr(val, "$CLIENT_CERT_SUBJECT") != NULL) {
+                       if (expand_string(buf, len,
+                           "$CLIENT_CERT_SUBJECT", 
tls_peer_cert_subject(cre->tls)) != 0)
+                               return (NULL);
+               }
+               if (strstr(val, "$CLIENT_CERT_ISSUER") != NULL) {
+                       if (expand_string(buf, len,
+                           "$CLIENT_CERT_ISSUER", 
tls_peer_cert_issuer(cre->tls)) != 0)
+                               return (NULL);
+               }
+               if (strstr(val, "$CLIENT_CERT_CHAIN") != NULL) {
+                       const char *pem;
+                       char *cbuf;
+                       size_t plen;
+                       pem = tls_peer_cert_chain_pem(cre->tls, &plen);
+                       cbuf = malloc(plen);
+                       sprintf(cbuf, "%.*s", (int)plen - 1, pem);
+                       if (expand_string(buf, len,
+                           "$CLIENT_CERT_CHAIN", url_encode(cbuf)) != 0) {
+                               free(cbuf);
+                               return (NULL);
+                       } else
+                               free(cbuf);
+               }
+       }
        return (buf);
 }
@@ -2034,3 +2060,27 @@ server_root_strip(char *path, int n)
        return (path);
 }
+char *
+url_encode(const char *src)
+{
+       static char      hex[] = "0123456789ABCDEF";
+       char            *dp, *dst;
+       unsigned char    c;
+
+       /* We need 3 times the memory if every letter is encoded. */
+       if ((dst = calloc(3, strlen(src) + 1)) == NULL)
+               return (NULL);
+
+       for (dp = dst; *src != 0; src++) {
+               c = (unsigned char) *src;
+               if (c == ' ' || c == '#' || c == '%' || c == '?' || c == '"' ||
+                   c == '&' || c == '<' || c <= 0x1f || c >= 0x7f) {
+                       *dp++ = '%';
+                       *dp++ = '%';
+                       *dp++ = hex[c >> 4];
+                       *dp++ = hex[c & 0x0f];
+               } else
+                       *dp++ = *src;
+       }
+       return (dst);
+}
blob - 46f31306eabe08b001b260fb1c3f66bd16689573
file + usr.sbin/relayd/relayd.c
--- 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) == 0)
                return (0);
+ if (strlen(proto->tlsclientca) &&
+           rlay->rl_tls_client_ca_fd == -1) {
+               if ((rlay->rl_tls_client_ca_fd =
+                   open(proto->tlsclientca, O_RDONLY)) == -1)
+                       return (-1);
+               log_debug("%s: using client ca %s", __func__,
+                   proto->tlsclientca);
+       }
+
        if (name == NULL &&
            print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL)
                goto fail;
blob - cecbae71f87e603b3e30d4c0114bf1c60a82b52a
file + usr.sbin/relayd/relayd.conf.5
--- usr.sbin/relayd/relayd.conf.5
+++ usr.sbin/relayd/relayd.conf.5
@@ -948,6 +948,15 @@ will be used (strong crypto cipher suites without anon
 See the CIPHERS section of
 .Xr openssl 1
 for information about SSL/TLS cipher suites and preference lists.
+.It Ic client ca Ar path
+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.
+.It Ic client-optional
+Request client certificate and verify if its present.
+.It Ic client-insecure
+Request client certificate but do not require it to be signed by
+a trusted CA.
 .It Ic client-renegotiation
 Allow client-initiated renegotiation.
 To mitigate a potential DoS risk,
@@ -1361,6 +1370,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 - d83ec825aa2ffd8271c87f80dec08e46b0f4845a
file + usr.sbin/relayd/relayd.h
--- usr.sbin/relayd/relayd.h
+++ usr.sbin/relayd/relayd.h
@@ -139,11 +139,12 @@ struct ctl_relaytable {
 };
enum fd_type {
-       RELAY_FD_CERT   = 1,
-       RELAY_FD_CACERT = 2,
-       RELAY_FD_CAFILE = 3,
-       RELAY_FD_KEY    = 4,
-       RELAY_FD_OCSP   = 5
+       RELAY_FD_CERT           = 1,
+       RELAY_FD_CACERT         = 2,
+       RELAY_FD_CAFILE         = 3,
+       RELAY_FD_KEY            = 4,
+       RELAY_FD_OCSP           = 5,
+       RELAY_FD_CLIENTCACERT   = 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,8 @@ TAILQ_HEAD(relay_rules, relay_rule);
 #define TLSFLAG_VERSION                                0x1f
 #define TLSFLAG_CIPHER_SERVER_PREF             0x20
 #define TLSFLAG_CLIENT_RENEG                   0x40
+#define        TLSFLAG_CLIENT_INSECURE                 0x41
+#define        TLSFLAG_CLIENT_OPTIONAL                 0x42
 #define TLSFLAG_DEFAULT                                \
        (TLSFLAG_TLSV1_2|TLSFLAG_TLSV1_3|TLSFLAG_CIPHER_SERVER_PREF)
@@ -746,6 +750,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 +840,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;

Reply via email to