Opened 9 years ago

Closed 9 years ago

Last modified 8 years ago

#848 closed defect (fixed)

HTTP2 domain redirect error with ssl_verify_client

Reported by: Jan Trejbal Owned by: Valentin V. Bartenev
Priority: major Milestone:
Component: nginx-module Version: 1.9.x
Keywords: http2 client-certificate Cc:
uname -a: Linux bbb6fdf82301 3.10.0-229.20.1.el7.x86_64 #1 SMP Tue Nov 3 19:10:07 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
nginx -V: nginx version: nginx/1.9.7
built by gcc 4.8.3 20140911 (Red Hat 4.8.3-9) (GCC)
built with OpenSSL 1.0.1e-fips 11 Feb 2013
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-threads --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-http_v2_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic'

Description

server {
        server_name www.domain.tld;
        listen 443 ssl;
        listen 80;
        ssl_certificate         /etc/nginx/certs/...;
        ssl_certificate_key     /etc/nginx/certs/...;
        include ssl.conf;
        ssl_trusted_certificate /etc/nginx/certs/...;

        rewrite ^ https://domain.tld$request_uri? permanent;
}
server {
        server_name domain.tld;
        listen 443 ssl http2;
        listen 80;
        ssl_certificate         /etc/nginx/certs/...; #trusted
        ssl_certificate_key     /etc/nginx/certs/...;
        include ssl.conf;
        ssl_trusted_certificate /etc/nginx/certs/...;

        ssl_client_certificate  /etc/nginx/certs/.../ca.crt; #not trusted
        ssl_verify_client       optional; #use this produce error

        location /log {
                if ($ssl_client_verify != SUCCESS) {
                        return 403;
                }
                ...
        }

        location / {
                ...
        }
}

ssl.conf:

ssl_dhparam dhparam.pem;
    
ssl_prefer_server_ciphers on;
    
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4';

ssl_session_cache shared:SSL:50m;
ssl_session_timeout 5m;

ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

Redirect in Chrome from https://www.domain.tld to https://domain.tld produce 400 Bad Request (on https://domain.tld) and browser do not ask for certificate.

In log I see:

2015/12/04 00:38:38 [notice] 48#48: *448 "^" matches "/", client: MY_IP, server: www.domain.tld, request: "GET / HTTP/2.0", host: "www.domain.tld"
2015/12/04 00:38:38 [notice] 48#48: *448 rewritten redirect: "https://domain.tld/", client: MY_IP, server: www.domain.tld, request: "GET / HTTP/2.0", host: "www.domain.tld"
2015/12/04 00:38:38 [info] 48#48: *448 client attempted to request the server name different from that one was negotiated while processing HTTP/2 connection, client: MY_IP, server: www.domain.tld, host: "domain.tld"

This occurs when ssl_verify_client is set to optional|optional_no_ca|on
With not used http2 all is well. (Browser redirect and ask for client certificate)

Change History (14)

comment:1 by Maxim Dounin, 9 years ago

Owner: set to Valentin V. Bartenev
Status: newassigned

No connection reuse between different SNI-selected server{} blocks is allowed by nginx as long as SSL client certificates are used. It looks like HTTP/2 authors didn't learn anything from early versions of SPDY and again try to reuse connections to different servers.

Valentin, please take a look, see https://tools.ietf.org/html/rfc7540#section-9.1.1 for details.

comment:2 by Valentin V. Bartenev, 9 years ago

Please try the following patch:

diff -r b1858fc47e3b -r 4b24c76a65ef src/http/ngx_http_header_filter_module.c
--- a/src/http/ngx_http_header_filter_module.c  Fri Nov 06 15:22:43 2015 +0300
+++ b/src/http/ngx_http_header_filter_module.c  Mon Dec 07 16:02:22 2015 +0300
@@ -100,7 +100,7 @@ static ngx_str_t ngx_http_status_lines[]
     /* ngx_null_string, */  /* "418 unused" */
     /* ngx_null_string, */  /* "419 unused" */
     /* ngx_null_string, */  /* "420 unused" */
-    /* ngx_null_string, */  /* "421 unused" */
+    ngx_string("421 Misdirected Request"),
     /* ngx_null_string, */  /* "422 Unprocessable Entity" */
     /* ngx_null_string, */  /* "423 Locked" */
     /* ngx_null_string, */  /* "424 Failed Dependency" */
diff -r b1858fc47e3b -r 4b24c76a65ef src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c       Fri Nov 06 15:22:43 2015 +0300
+++ b/src/http/ngx_http_request.c       Mon Dec 07 16:02:22 2015 +0300
@@ -2062,7 +2062,7 @@ ngx_http_set_virtual_server(ngx_http_req
             ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                           "client attempted to request the server name "
                           "different from that one was negotiated");
-            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+            ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST);
             return NGX_ERROR;
         }
     }
diff -r b1858fc47e3b -r 4b24c76a65ef src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h       Fri Nov 06 15:22:43 2015 +0300
+++ b/src/http/ngx_http_request.h       Mon Dec 07 16:02:22 2015 +0300
@@ -95,6 +95,7 @@
 #define NGX_HTTP_REQUEST_URI_TOO_LARGE     414
 #define NGX_HTTP_UNSUPPORTED_MEDIA_TYPE    415
 #define NGX_HTTP_RANGE_NOT_SATISFIABLE     416
+#define NGX_HTTP_MISDIRECTED_REQUEST       421
 
 
 /* Our own HTTP codes */

comment:3 by maxim, 9 years ago

Milestone: 1.9.8

Ticket retargeted after milestone closed

comment:4 by Jan Trejbal, 9 years ago

Now it produce HTTP code 421 instead of 400, but same problem (do not show content of website)

comment:5 by Maxim Dounin, 9 years ago

Well, if it doesn't work with 421 either, it's probably nothing that can be done on nginx side to prevent such inappropriate connection reuse. You should complain to browser vendors instead.

comment:6 by Jan Trejbal, 9 years ago

Solved adding

server {
        server_name www.domain.tld;
        listen 443 ssl;

        http2_idle_timeout 10ms;
}

comment:7 by hostmaster.myevo.net@…, 9 years ago

I have the same problem.
I'm using ssl_verify_client optional and different subdomains to identify different projects.
If someone has a valid certificate and then changes subdomain to access a different project, authentication is required again and everything works fine.
If someone hasn't any valid certificate, he can access the first subdomain without problems, but if he changes subdomain, he gets a 400 error (client attempted to request the server name different from that one was negotiated while reading client request headers).

I think that the server should simply authenticate again if subdomain changes in order to solve this.
Edit: I think that the server should simply accept connections for different subdomains if the server certificate is valid for both subdomains, since this is what it does when ssl_verify_client is turned off.

If ssl_verify_client is set to on or off, the problem disappears but I have to make changes to my web application to use only/avoid certificate authentication.
This happens with both Firefox and Chrome so I think I can't complain to browser vendors.
Meanwhile, I set http2_idle_timeout to 1s, but this reduces http2 advantages.

Last edited 9 years ago by hostmaster.myevo.net@… (previous) (diff)

comment:8 by hostmaster.myevo.net@…, 9 years ago

I upgraded now to 1.10.0. The problem is still there.

Last edited 9 years ago by hostmaster.myevo.net@… (previous) (diff)

comment:9 by Jan Trejbal, 9 years ago

Do you use same certificate to both subdomains?

comment:10 by hostmaster.myevo.net@…, 9 years ago

Yes, the certificate is the same.

Version 0, edited 9 years ago by hostmaster.myevo.net@… (next)

comment:11 by Valentin Bartenev <vbart@…>, 9 years ago

Resolution: fixed
Status: assignedclosed

In 6556:654d2dae97d3/nginx:

HTTP/2: the "421 Misdirected Request" response (closes #848).

Since 4fbef397c753 nginx rejects with the 400 error any attempts of
requesting different host over the same connection, if the relevant
virtual server requires verification of a client certificate.

While requesting hosts other than negotiated isn't something legal
in HTTP/1.x, the HTTP/2 specification explicitly permits such requests
for connection reuse and has introduced a special response code 421.

According to RFC 7540 Section 9.1.2 this code can be sent by a server
that is not configured to produce responses for the combination of
scheme and authority that are included in the request URI. And the
client may retry the request over a different connection.

Now this code is used for requests that aren't authorized in current
connection. After receiving the 421 response a client will be able
to open a new connection, provide the required certificate and retry
the request.

Unfortunately, not all clients currently are able to handle it well.
Notably Chrome just shows an error, while at least the latest version
of Firefox retries the request over a new connection.

comment:12 by hostmaster.myevo.net@…, 8 years ago

I am sure you're right, but I think the problem is a bit different from what you wrote.
I recap:
ssl_verify_client off, all subdomains can be browsed
ssl_verify_client on, all subdomains can be browsed
ssl_verify_client optional and client with certificate, all subdomains can be browsed
ssl_verify_client optional and client without certificate, error 400 (not 421) with the second subdomain (page appears without problems when I browse to the first subdomain).

This happens with latest versions of all browsers. Are you sure that this is on client's side?
Thanks again for your help.

Last edited 8 years ago by hostmaster.myevo.net@… (previous) (diff)

in reply to:  12 comment:13 by Valentin V. Bartenev, 8 years ago

ssl_verify_client off, all subdomains can be browsed

In this case client authorization isn't required and browser is allowed to request any domain in connection.

ssl_verify_client on, all subdomains can be browsed
ssl_verify_client optional and client with certificate, all subdomains can be browsed

It seems the fact that the client certificate is used prevents browser from connection reuse. In these cases browser uses separate connections as it should be.

ssl_verify_client optional and client without certificate

But in this case browser tries to reuse connection and gets 400. If you upgrade to 1.11.0, then the browser will get 421. This fixes Firefox since it handles 421 well.

Unfortunately, Chrome has a ticket about 421: https://bugs.chromium.org/p/chromium/issues/detail?id=546991

Browsers should either don't try to reuse connection, or handle the 421 response and resend request over a separate connection.

comment:14 by Valentin Bartenev <vbart@…>, 8 years ago

In 6749:f88a145b093e/nginx:

HTTP/2: the "421 Misdirected Request" response (closes #848).

Since 4fbef397c753 nginx rejects with the 400 error any attempts of
requesting different host over the same connection, if the relevant
virtual server requires verification of a client certificate.

While requesting hosts other than negotiated isn't something legal
in HTTP/1.x, the HTTP/2 specification explicitly permits such requests
for connection reuse and has introduced a special response code 421.

According to RFC 7540 Section 9.1.2 this code can be sent by a server
that is not configured to produce responses for the combination of
scheme and authority that are included in the request URI. And the
client may retry the request over a different connection.

Now this code is used for requests that aren't authorized in current
connection. After receiving the 421 response a client will be able
to open a new connection, provide the required certificate and retry
the request.

Unfortunately, not all clients currently are able to handle it well.
Notably Chrome just shows an error, while at least the latest version
of Firefox retries the request over a new connection.

Note: See TracTickets for help on using tickets.