Re: [PATCH] MINOR: sample: add ssl_sni_check converter

2019-05-18 Thread Willy Tarreau
Hi guys,

On Fri, May 17, 2019 at 09:58:17PM +0200, bjun...@gmail.com wrote:
> Am Fr., 17. Mai 2019 um 21:15 Uhr schrieb Tim Düsterhus :
> >
> > Willy,
> >
> > Am 23.12.18 um 21:20 schrieb Moemen MHEDHBI:
> > > Hi,
> > >
> > > The attached patch adds the ssl_sni_check converter which returns true
> > > if the sample input string matches a loaded certificate's CN/SAN.
> > >
> > > This can be useful to check for example if a host header matches a
> > > loaded certificate CN/SAN before doing a redirect:
> > >
> > > frontent fe_main
> > >   bind 127.0.0.1:80
> > >   bind 127.0.0.1:443 ssl crt /etc/haproxy/ssl/
> > >   http-request redirect scheme https if !{ ssl_fc } { 
> > > hdr(host),ssl_sni_check() }
> > > This converter may be even more useful when certificates will be
> > > added/removed at runtime.

(...)
> Definitely thumbs up for this converter. I've implemented on-the-fly
> certificate generation for HAProxy with the help of Lua. The converter
> would help me to reduce or simplify parts of the code and could
> possible improve performance.

I vaguely remember having had a quickly look at it by then, and switched
to something else and forgot to respond as usual :-/

So I had another look at it, and there is something slightly wrong in
the concept but actually, fixing it will simplify the patch. What is
wrong is the lookup on a specific listener or any of them. We should
*only* check the listener that was used to accept the connection,
otherwise it opens a security hole. Example :

frontend web
bind 192.0.2.1:443 ssl crt /etc/haproxy/ssl/
bind 127.0.0.1:443 ssl crt /etc/haproxy/insecure-wildcard

You just use a set of dummy wildcard certs on 127.0.0.1 for use with
local tools but you use the real certs on the public access. This
converter will happily match incoming public connections against the
certs available on the other one which is unrelated. The right way to
do it is to compare the certs of the listener which accepted the
connection. Fortunately we do have it :-)

Instead of the loop doing this :

 list_for_each_entry(l, >conf.listeners, by_fe) {
  if ( args->type == ARGT_STR && l->name && 
(strcmp(args->data.str.area, l->name) != 0))
 continue;

You simply have to do this and remove the "name" argument :

 l = smp->sess->l;
 if (!l)
   return 0;

Another point is that I don't find the name "ssl_sni_check" very explicit
about the fact that it compares strings with listeners. In fact "ssl_sni_"
tends to make me think it does something with the SNI advertised on the
SSL connection. Shouldn't we use something like "crt-lookup" for example,
since it's really what it does ?

And to go a bit further, is it really valid to lookup a name into a bunch
of certificates which were not involved in the connection ? I don't think
so. If you use a crt-list with some names requiring client authentication
and others not, this will happily validate a host name sent on an
unauthenticated connection because another unrelated cert does support
this name.

I'm starting to think that instead the mechanism should involve the SSL
CTX used on the front connection, and figure from this CTX what names
are permitted. Then the converter should be named like the other ones to
indicate it is related to the front SSL connection. We could have for
example "ssl_fc_name_lookup" or "ssl_fc_sni_lookup". We seem to have
the SSL_CTX available in conn->xprt_ctx->xprt_ctx but I don't know how
we are supposed to retrieve the list of supported names from an SSL_CTX,
we'll probably need some help from Emeric or Manu who have worked much
more on this.

At least we need to be extremely careful here, because doing the wrong
test is much worse than not having the check as it will make users feel
their config is secure while it is not.

Willy



Re: [PATCH] MINOR: sample: add ssl_sni_check converter

2019-05-17 Thread bjun...@gmail.com
Am Fr., 17. Mai 2019 um 21:15 Uhr schrieb Tim Düsterhus :
>
> Willy,
>
> Am 23.12.18 um 21:20 schrieb Moemen MHEDHBI:
> > Hi,
> >
> > The attached patch adds the ssl_sni_check converter which returns true
> > if the sample input string matches a loaded certificate's CN/SAN.
> >
> > This can be useful to check for example if a host header matches a
> > loaded certificate CN/SAN before doing a redirect:
> >
> > frontent fe_main
> >   bind 127.0.0.1:80
> >   bind 127.0.0.1:443 ssl crt /etc/haproxy/ssl/
> >   http-request redirect scheme https if !{ ssl_fc } { 
> > hdr(host),ssl_sni_check() }
> >
> >
> > This converter may be even more useful when certificates will be
> > added/removed at runtime.
> >
>
> This email serves to bump the patch which appears to have slipped
> through the cracks. For the context see the "Re: Host header and sni
> extension differ" thread.
>
> Best regards
> Tim Düsterhus
>

Definitely thumbs up for this converter. I've implemented on-the-fly
certificate generation for HAProxy with the help of Lua. The converter
would help me to reduce or simplify parts of the code and could
possible improve performance.


Best regards / Mit freundlichen Grüßen

Bjoern



Re: [PATCH] MINOR: sample: add ssl_sni_check converter

2019-05-17 Thread Tim Düsterhus
Willy,

Am 23.12.18 um 21:20 schrieb Moemen MHEDHBI:
> Hi,
> 
> The attached patch adds the ssl_sni_check converter which returns true
> if the sample input string matches a loaded certificate's CN/SAN.
> 
> This can be useful to check for example if a host header matches a
> loaded certificate CN/SAN before doing a redirect:
> 
> frontent fe_main 
>   bind 127.0.0.1:80
>   bind 127.0.0.1:443 ssl crt /etc/haproxy/ssl/
>   http-request redirect scheme https if !{ ssl_fc } { 
> hdr(host),ssl_sni_check() }
> 
> 
> This converter may be even more useful when certificates will be
> added/removed at runtime.
> 

This email serves to bump the patch which appears to have slipped
through the cracks. For the context see the "Re: Host header and sni
extension differ" thread.

Best regards
Tim Düsterhus



[PATCH] MINOR: sample: add ssl_sni_check converter

2018-12-23 Thread Moemen MHEDHBI
Hi,

The attached patch adds the ssl_sni_check converter which returns true
if the sample input string matches a loaded certificate's CN/SAN.

This can be useful to check for example if a host header matches a
loaded certificate CN/SAN before doing a redirect:

frontent fe_main 
  bind 127.0.0.1:80
  bind 127.0.0.1:443 ssl crt /etc/haproxy/ssl/
  http-request redirect scheme https if !{ ssl_fc } { hdr(host),ssl_sni_check() 
}


This converter may be even more useful when certificates will be
added/removed at runtime.

++

-- 
Moemen MHEDHBI
>From 14ed628ab9badbb06c45bab324eb00f998de49af Mon Sep 17 00:00:00 2001
From: Moemen MHEDHBI 
Date: Sun, 23 Dec 2018 20:50:04 +0100
Subject: [PATCH] MINOR: sample: add ssl_sni_check converter

This adds the ssl_sni_check converter. The converter returns
true if the sample input string matches a loaded certificate's CN/SAN.
Lookup can be done through certificates of a specified bind line (by
) otherwise the search will include all bind lines of the current
proxy.
---
 doc/configuration.txt |  6 ++
 src/ssl_sock.c| 43 +++
 2 files changed, 49 insertions(+)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 6ca63d64a..0be043e73 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -13651,6 +13651,12 @@ sha1
   Converts a binary input sample to a SHA1 digest. The result is a binary
   sample with length of 20 bytes.
 
+ssl_sni_check()
+  Returns true if the sample input string matches a loaded certificate's CN/SAN.
+  Otherwise false is returned. When  is provided the lookup is done only
+  through the certificates of the  bind line named , if not all bind 
+  lines of the current frontend will be searched.
+
 strcmp()
   Compares the contents of  with the input value of type string. Returns
   the result as a signed integer compatible with strcmp(3): 0 if both strings
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 282b85ddd..b24d78978 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -7276,6 +7276,41 @@ smp_fetch_ssl_c_verify(const struct arg *args, struct sample *smp, const char *k
 	return 1;
 }
 
+/* boolean, returns true if input string matches a loaded certificate's CN/SAN. */
+/* The lookup is done only for the bind named  if the param is prvided. */
+static int smp_conv_ssl_sni_check(const struct arg *args, struct sample *smp, void *private)
+{
+	struct proxy *px = smp->px;
+	struct listener *l;
+	struct ebmb_node *node = NULL;
+	char *wildp = NULL;
+	int i;
+
+	for (i = 0; i < trash.size && i < smp->data.u.str.data; i++) {
+		trash.area[i] = tolower(smp->data.u.str.area[i]);
+		if (!wildp && (trash.area[i] == '.'))
+			wildp = [i];
+	}
+	trash.area[i] = 0;
+
+	list_for_each_entry(l, >conf.listeners, by_fe) {
+		if ( args->type == ARGT_STR && l->name && (strcmp(args->data.str.area, l->name) != 0))
+			continue;
+		/* lookup in full qualified names */
+		node = ebst_lookup(>bind_conf->sni_ctx, trash.area);
+		/* lookup in wildcards names */
+		if (!node && wildp)
+			node = ebst_lookup(>bind_conf->sni_w_ctx, wildp);
+		if (node != NULL)
+			break;
+	}
+
+	smp->data.type = SMP_T_BOOL;
+	smp->data.u.sint = !!node;
+	smp->flags = SMP_F_VOL_TEST;
+	return 1;
+}
+
 /* parse the "ca-file" bind keyword */
 static int ssl_bind_parse_ca_file(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, char **err)
 {
@@ -9047,6 +9082,14 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
 
 INITCALL1(STG_REGISTER, sample_register_fetches, _fetch_keywords);
 
+/* Note: must not be declared  as its list will be overwritten */
+static struct sample_conv_kw_list sample_conv_kws = {ILH, {
+	{ "ssl_sni_check", smp_conv_ssl_sni_check, ARG1(0,STR), NULL, SMP_T_STR, SMP_T_BOOL },
+	{ /* END */ },
+}};
+
+INITCALL1(STG_REGISTER, sample_register_convs, _conv_kws);
+
 /* Note: must not be declared  as its list will be overwritten.
  * Please take care of keeping this list alphabetically sorted.
  */
-- 
2.19.2