Hi again, Checking in again to see if there's any appetite for this.
Best, Ashe On Thu, Oct 15, 2020, at 5:52 PM, Ashe Connor wrote: > Hi there, > > A year or two ago I submitted a patch for adding TLS client certificate > validation to relayd. At the time it didn't make it in, and I stopped > pursuing it further. > (https://marc.info/?l=openbsd-tech&m=154509330608643&w=2) > > I'd still like to see this landed, if at all possible. I'm continuing to use > this feature on my own personal websites, and it works well. > > The latest diff is attached, or can be viewed online here: > https://github.com/openbsd/src/compare/master...kivikakk:relayd-client-verification.patch > > I've added a test that confirms client failure to connect without a > certificate at regress/usr.sbin/relayd/args-ssl-client-verify-fail.pl -- it's > a bit awkward. Let me know if I can redo it better. > > Best, > > Ashe > > --- > > From c63bca7ba7889b43e0a9317e807499eb8ca0db55 Mon Sep 17 00:00:00 2001 > From: Asherah Connor <a...@kivikakk.ee> > Date: Thu, 15 Oct 2020 17:23:15 +1100 > Subject: [PATCH] TLS client certificate validation > > --- > regress/usr.sbin/relayd/Client.pm | 13 ++++++++++ > regress/usr.sbin/relayd/Makefile | 18 ++++++++++++- > regress/usr.sbin/relayd/Relayd.pm | 3 +++ > .../relayd/args-ssl-client-verify-fail.pl | 25 +++++++++++++++++++ > .../usr.sbin/relayd/args-ssl-client-verify.pl | 19 ++++++++++++++ > usr.sbin/relayd/config.c | 21 ++++++++++++++++ > usr.sbin/relayd/parse.y | 15 ++++++++++- > usr.sbin/relayd/relay.c | 21 ++++++++++++++++ > usr.sbin/relayd/relayd.c | 9 +++++++ > usr.sbin/relayd/relayd.conf.5 | 4 +++ > usr.sbin/relayd/relayd.h | 14 +++++++---- > 11 files changed, 155 insertions(+), 7 deletions(-) > create mode 100644 regress/usr.sbin/relayd/args-ssl-client-verify-fail.pl > create mode 100644 regress/usr.sbin/relayd/args-ssl-client-verify.pl > > diff --git a/regress/usr.sbin/relayd/Client.pm > b/regress/usr.sbin/relayd/Client.pm > index b3e011de13d..ec6fa64274e 100644 > --- a/regress/usr.sbin/relayd/Client.pm > +++ b/regress/usr.sbin/relayd/Client.pm > @@ -57,6 +57,11 @@ sub child { > PeerAddr => $self->{connectaddr}, > PeerPort => $self->{connectport}, > SSL_verify_mode => SSL_VERIFY_NONE, > + SSL_use_cert => $self->{offertlscert} ? 1 : 0, > + SSL_cert_file => $self->{offertlscert} ? > + "client.crt" : "", > + SSL_key_file => $self->{offertlscert} ? > + "client.key" : "", > ) or die ref($self), " $iosocket socket connect failed: $!,$SSL_ERROR"; > if ($self->{sndbuf}) { > setsockopt($cs, SOL_SOCKET, SO_SNDBUF, > @@ -86,6 +91,14 @@ sub child { > print STDERR "ssl cipher: ",$cs->get_cipher(),"\n"; > print STDERR "ssl peer certificate:\n", > $cs->dump_peer_certificate(); > + > + if ($self->{offertlscert}) { > + print STDERR "ssl client certificate:\n"; > + print STDERR "Subject Name: ", > + "${\$cs->sock_certificate('subject')}\n"; > + print STDERR "Issuer Name: ", > + "${\$cs->sock_certificate('issuer')}\n"; > + } > } > > *STDIN = *STDOUT = $self->{cs} = $cs; > diff --git a/regress/usr.sbin/relayd/Makefile > b/regress/usr.sbin/relayd/Makefile > index cd01aa3fb63..f2198f43cc9 100644 > --- a/regress/usr.sbin/relayd/Makefile > +++ b/regress/usr.sbin/relayd/Makefile > @@ -96,7 +96,23 @@ server.req: > server.crt: ca.crt server.req > openssl x509 -CAcreateserial -CAkey ca.key -CA ca.crt -req -in server.req > -out server.crt > > -${REGRESS_TARGETS:M*ssl*} ${REGRESS_TARGETS:M*https*}: server.crt > +client-ca.crt: > + openssl req -batch -new \ > + -subj /L=OpenBSD/O=relayd-regress/OU=client-ca/CN=root/ \ > + -nodes -newkey rsa -keyout client-ca.key -x509 \ > + -out client-ca.crt > + > +client.req: > + openssl req -batch -new \ > + -subj /L=OpenBSD/O=relayd-regress/OU=client/CN=localhost/ \ > + -nodes -newkey rsa -keyout client.key \ > + -out client.req > + > +client.crt: client-ca.crt client.req > + openssl x509 -CAcreateserial -CAkey client-ca.key -CA client-ca.crt \ > + -req -in client.req -out client.crt > + > +${REGRESS_TARGETS:M*ssl*} ${REGRESS_TARGETS:M*https*}: server.crt client.crt > .if empty (REMOTE_SSH) > ${REGRESS_TARGETS:M*ssl*} ${REGRESS_TARGETS:M*https*}: 127.0.0.1.crt > .else > diff --git a/regress/usr.sbin/relayd/Relayd.pm > b/regress/usr.sbin/relayd/Relayd.pm > index 98f2ada5db9..896c0b401be 100644 > --- a/regress/usr.sbin/relayd/Relayd.pm > +++ b/regress/usr.sbin/relayd/Relayd.pm > @@ -84,6 +84,9 @@ sub new { > print $fh "\n\ttls ca cert ca.crt"; > print $fh "\n\ttls ca key ca.key password ''"; > } > + if ($self->{verifyclient}) { > + print $fh "\n\ttls client ca client-ca.crt"; > + } > # substitute variables in config file > foreach (@protocol) { > s/(\$[a-z]+)/$1/eeg; > diff --git a/regress/usr.sbin/relayd/args-ssl-client-verify-fail.pl > b/regress/usr.sbin/relayd/args-ssl-client-verify-fail.pl > new file mode 100644 > index 00000000000..93c9b22a63d > --- /dev/null > +++ b/regress/usr.sbin/relayd/args-ssl-client-verify-fail.pl > @@ -0,0 +1,25 @@ > +# test client ssl certificate verification > + > +use strict; > +use warnings; > + > +our %args = ( > + client => { > + ssl => 1, > + offertlscert => 0, > + up => 'failed', > + down => 'connect attempt failed', > + dryrun => 1, > + nocheck => 1, > + }, > + relayd => { > + listenssl => 1, > + verifyclient => 1, > + }, > + server => { > + noserver => 1, > + nocheck => 1, > + }, > +); > + > +1; > diff --git a/regress/usr.sbin/relayd/args-ssl-client-verify.pl > b/regress/usr.sbin/relayd/args-ssl-client-verify.pl > new file mode 100644 > index 00000000000..c5cc3110724 > --- /dev/null > +++ b/regress/usr.sbin/relayd/args-ssl-client-verify.pl > @@ -0,0 +1,19 @@ > +# test client ssl certificate verification > + > +use strict; > +use warnings; > + > +our %args = ( > + client => { > + ssl => 1, > + offertlscert => 1, > + }, > + relayd => { > + listenssl => 1, > + verifyclient => 1, > + }, > + len => 251, > + md5 => "bc3a3f39af35fe5b1687903da2b00c7f", > +); > + > +1; > diff --git a/usr.sbin/relayd/config.c b/usr.sbin/relayd/config.c > index 3e60d63ef52..5b409aa1134 100644 > --- a/usr.sbin/relayd/config.c > +++ b/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 *imsg) > 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__, > diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y > index ce219245731..a524447aa72 100644 > --- a/usr.sbin/relayd/parse.y > +++ b/usr.sbin/relayd/parse.y > @@ -179,7 +179,7 @@ typedef struct { > %token TIMEOUT TLS TO ROUTER RTLABEL TRANSPARENT TRAP 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 optstring path > @@ -1373,6 +1373,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; } > ; > @@ -1830,6 +1840,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"); > @@ -2423,6 +2434,7 @@ lookup(char *s) > { "check", CHECK }, > { "checks", CHECKS }, > { "ciphers", CIPHERS }, > + { "client", CLIENT }, > { "code", CODE }, > { "connection", CONNECTION }, > { "context", CONTEXT }, > @@ -3408,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); > > diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c > index 43b5c377fa5..2f9f293368c 100644 > --- a/usr.sbin/relayd/relay.c > +++ b/usr.sbin/relayd/relay.c > @@ -2259,6 +2259,27 @@ 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); > + > + 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"); > diff --git a/usr.sbin/relayd/relayd.c b/usr.sbin/relayd/relayd.c > index 9390b2e4606..52093f6cdc9 100644 > --- a/usr.sbin/relayd/relayd.c > +++ b/usr.sbin/relayd/relayd.c > @@ -1359,6 +1359,15 @@ relay_load_certfiles(struct relayd *env, struct relay > *rlay, const char *name) > 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; > diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/relayd.conf.5 > index 491fc216b71..fe6dff92174 100644 > --- a/usr.sbin/relayd/relayd.conf.5 > +++ b/usr.sbin/relayd/relayd.conf.5 > @@ -946,6 +946,10 @@ will be used (strong crypto cipher suites without > anonymous DH). > 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-renegotiation > Allow client-initiated renegotiation. > To mitigate a potential DoS risk, > diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h > index 354c4e7d01f..5d7c71dcbff 100644 > --- a/usr.sbin/relayd/relayd.h > +++ b/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 { > @@ -401,6 +402,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" \ > @@ -744,6 +746,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; > @@ -833,6 +836,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; > >