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; >
