Hi, sorry for being a moron. I realize it’s already optional by not specifying client ca… sorry about the noise!
> On Dec 16, 2021, at 9:35 PM, Brian Brombacher <[email protected]> wrote: > > 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; >>
