Opened 12 years ago
Last modified 5 years ago
#195 accepted enhancement
Close connection if SSL not enabled for vhost
Reported by: | svario.it/gioele | Owned by: | somebody |
---|---|---|---|
Priority: | minor | Milestone: | |
Component: | nginx-module | Version: | 1.3.x |
Keywords: | Cc: | ||
uname -a: | Linux camelia.svario.it 3.2.0-29-virtual #46-Ubuntu SMP Fri Jul 27 17:23:50 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux | ||
nginx -V: |
nginx version: nginx/1.1.19
TLS SNI support enabled configure arguments: --prefix=/etc/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-log-path=/var/log/nginx/access.log --http-proxy-temp-path=/var/lib/nginx/proxy --lock-path=/var/lock/nginx.lock --pid-path=/var/run/nginx.pid --with-http_gzip_static_module --with-http_ssl_module --with-ipv6 --without-http_browser_module --without-http_geo_module --without-http_limit_req_module --without-http_limit_zone_module --without-http_memcached_module --without-http_referer_module --without-http_scgi_module --without-http_split_clients_module --with-http_stub_status_module --without-http_ssi_module --without-http_userid_module --without-http_uwsgi_module --add-module=/build/buildd/nginx-1.1.19/debian/modules/nginx-echo |
Description
Instead of using the default SSL certificate, nginx should (by default or when configured) close the SSL connection as soon as it realizes that the requested domain has not been configured to be served over HTTPS.
For example,
server { listen 80 default_server; listen 443 ssl; server_name aaa.example.net; ssl_certificate /etc/ssl/certs/aaa.example.net.pem; ssl_certificate_key /etc/ssl/private/aaa.example.net.key; } server { listen 80; server_name bbb.example.net; }
If a client starts an HTTPS request for bbb.example.net, it will be greeted with a (correct) error/warning: "This certificate is untrusted, wrong domain". This is correct because nginx is serving the aaa.example.net certificate.
What nginx should do is to close the connection as soon as it discovers the domain that is being requested (after reading the SNI data, I suppose). This will communicate to the client and the user that there is HTTPS connectivity on bbb.example.net. Also, this solution will not disclose information about the fact that aaa.example.net is served by the same nginx server.
Change History (15)
comment:1 by , 12 years ago
Resolution: | → worksforme |
---|---|
Status: | new → closed |
comment:2 by , 12 years ago
Resolution: | worksforme |
---|---|
Status: | closed → reopened |
The examples you posted do not address at all the problem I raised.
Before nginx send an error page, the browser has to pass though the SSL handshake. If domain in the certificate does not match the requested domain it will first show the user a big fat warning about insecure connections. Only after the user has accepted the mismatching certificate it will finally show the error page.
The linked page show examples that work for plain HTTP traffic, not HTTPS traffic.
It would be OK if I could do
server { listen 80; server_name bbb.example.net; } server { listen 443 ssl; server_name bbb.example.net; refuse_connections; }
This possibility does not currently exist, this is what I was requesting.
comment:3 by , 12 years ago
Status: | reopened → accepted |
---|
Ah, sorry, I see now what you are trying to achieve. This might indeed make sense as it might result in less scary messages in some cases.
comment:6 by , 9 years ago
sensitive: | → 0 |
---|
Readily available workaround is to use unsatisfiable cipher specification. E.g., something like this will work:
server { listen 443 ssl; server_name bbb.example.com; ssl_ciphers aNULL; ssl_certificate /path/to/dummy.crt; ssl_certificate_key /path/to/dummy.key; return 444; }
Given the workaround available, it's not a surprise that there are no developers willing to work on this. At least no one tried to submit a patch. Probably this ticket should be closed.
comment:7 by , 9 years ago
I want to work on this, but I'm not familiar with the nginx code base or OpenSSL in general. My first attempt on finding the correct place to insert the new check lead me to ngx_http_ssl_servername
; more specifically, I thought to insert the check here:
if (ngx_http_find_virtual_server(c, hc->addr_conf->virtual_names, &host, NULL, &cscf) != NGX_OK) { return SSL_TLSEXT_ERR_NOACK; } /* HERE */
I decided to go for this place because we need SNI information to safely reject such 'wrong' SSL connection. (Is that assumption even correct?)
Turns out that in this place the routine always returns with SSL_TLSEXT_ERR_NOACK
, but "the HTTPS connection still works" as I'm seeing the document I'm accessing in the browser. Doesn't SSL_TLSEXT_ERR_NOACK
mean that we want to close the SSL connection? Why does it continue?
Could someone point me to the right place(s) where the new checks have to be inserted, and maybe even provide me with a very rough sketch of how that would look like?
follow-up: 9 comment:8 by , 8 years ago
Is this reasonable to use the snakeoil certificate generated by openssl as a workaround?
Something like this (paths found on Ubuntu 16.04):
server { listen 443 ssl default_server; listen [::]:443 ssl default_server; ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; return 444; }
comment:9 by , 8 years ago
Replying to BertrandBordage@…:
Is this reasonable to use the snakeoil certificate generated by openssl as a workaround?
Use ssl_ciphers aNULL;
as suggested in comment:6, it works fine. Just an incorrect certificate will result in a warning in the browser due to incorrect certificate.
comment:11 by , 6 years ago
I came here to mention that the described workaround
server { listen 443 ssl; server_name bbb.example.com; ssl_ciphers aNULL; ssl_certificate /path/to/dummy.crt; ssl_certificate_key /path/to/dummy.key; return 444; }
can be problematic for HTTPS clients that don't support server name indication (SNI).
Notably, nginx's own HTTP client used for proxy_pass
does not support SNI by default unless you enable proxy_ssl_server_name on;
(docs).
So if you point a default proxy_pass
at an HTTPS server that uses the above workaround, then that proxying will fail immediately with ssl3_get_client_hello:no shared cipher
.
This is because if the client doesn't support SNI, the two sides have to establish the SSL connection before the Host
field is sent to nginx which matches it against server_name
.
A workaround is to enable the mentioned (for nginx's own HTTP client) is as mentioned
proxy_pass https://myrealdomain.../ proxy_ssl_server_name on;
but other TLS clients without SNI support will still have this problem.
And as per the the the issue description
after reading the SNI data, I suppose
it's not only the workaround that's problematic, but the "real" solution would suffer from the same problem.
This is not to discourage anybody from working on this issue; I think giving nginx a clean way to shut down SNI with something refuse_connections;
can make for better error messages in case SNI is supported (as most clients do support it).
I just wanted to leave this info here for others that, like me, mistakenly thought that this (or the workaround) might work for all (including non-SNI) clients; in retrospect it's quite obvious that it cannot.
(I also wonder why proxy_ssl_server_name
isn't on
by default.)
comment:12 by , 6 years ago
can be problematic for HTTPS clients that don't support server name indication (SNI).
This depends on how you've configured things. If you've used ssl_ciphers aNULL;
in the default server block, this will prevent non-SNI clients from connecting. If you've used it in a name-based virtual server, it will only block SNI-enabled connections to the server in question.
comment:14 by , 6 years ago
Try this patch. You needn't set HTTPS default_server in the config file.
+diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -882,7 +882,7 @@ servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); if (servername == NULL) { - return SSL_TLSEXT_ERR_NOACK; + return SSL_TLSEXT_ERR_ALERT_FATAL; } c = ngx_ssl_get_connection(ssl_conn); @@ -897,7 +897,7 @@ host.len = ngx_strlen(servername); if (host.len == 0) { - return SSL_TLSEXT_ERR_NOACK; + return SSL_TLSEXT_ERR_ALERT_FATAL; } host.data = (u_char *) servername; @@ -912,7 +912,7 @@ NULL, &cscf) != NGX_OK) { - return SSL_TLSEXT_ERR_NOACK; + return SSL_TLSEXT_ERR_ALERT_FATAL; } hc->ssl_servername = ngx_palloc(c->pool, sizeof(ngx_str_t));
comment:15 by , 5 years ago
The hack with aNULL or eNULL no longer work as soon as TLS1.3 is enabled, because TLS1.3 does not support that CIPHER. Clients successfully connect and exchange cert bringing browser warning.
So strict SNI should really be implemented. It's just a few if statements, is this too much for a long-standing issue?
Nobody stops you from configuring nginx in such a way which won't return anything for hosts not explicitly configured (on a listen socket in question) or will return some preconfigure error page. See here for an example.