Hi, not to interrupt development …

Can you make this completely optional from the servers perspective?  I don’t 
want my endpoints validating anonymous client certificates when I run a public 
endpoint.

I’ll just hack it out otherwise, but I think this opens a vector that should be 
completely optional from a relay service.


> On Dec 16, 2021, at 4:25 PM, rivo nurges <[email protected]> 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 *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 "
> @@ -987,6 +996,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;
> @@ -1012,6 +1025,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);
> }
> @@ -1034,6 +1051,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)
> @@ -1163,6 +1181,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 - 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*/    { $$ = 0; }
>        | WITH ssltls    { $$ = 1; }
>        ;
> +clientcaopt    : /*empty*/     { $$ = 0; }
> +        | OPTIONAL      { $$ = 1; }
> +        ;
> +
> http_type    : HTTP        { $$ = 0; }
>        | STRING    {
>            if (strcmp("https", $1) == 0) {
> @@ -1353,6 +1358,19 @@ tlsflags    : SESSION TICKETS { proto->tickets = 1; }
>            name->name = $2;
>            TAILQ_INSERT_TAIL(&proto->tlscerts, name, entry);
>        }
> +        | CLIENT CA STRING clientcaopt        {
> +            if (strlcpy(proto->tlsclientca, $3,
> +                sizeof(proto->tlsclientca)) >=
> +                sizeof(proto->tlsclientca)) {
> +                yyerror("tlsclientca truncated");
> +                free($3);
> +                YYERROR;
> +                }
> +                        if ($4) {
> +                proto->tlsflags  |= TLSFLAG_CLIENT_OPTIONAL;
> +                        }
> +            free($3);
> +        }
>        | NO flag            { proto->tlsflags &= ~($2); }
>        | flag                { proto->tlsflags |= $1; }
>        ;
> @@ -1824,6 +1842,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 +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 = -1;
>        rb->rl_tls_ca_fd = -1;
> +        rb->rl_tls_client_ca_fd = -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 = -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);
> +        }
> +        rlay->rl_tls_client_ca_fd = -1;
> +
>        tls = tls_server();
>        if (tls == 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 = NULL;
> @@ -1279,7 +1280,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);
> }
> @@ -2045,3 +2071,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 - 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) == 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
> 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 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 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    = 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,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;
> 

Reply via email to