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;