Different SSL protocols for different server blocks on the same port
aviram at adallom.com
Tue Apr 28 14:00:07 UTC 2015
The Nginx configuration allows you to define different server blocks that have different server names but listen on the same port in SSL. For an incoming connection, Nginx uses SNI in order to know under which server block the connection should be handled.
However, the 'ssl_protocols' directive doesn't work as expected in this scenario.
Let's say that we have two servers blocks - the first allows TLSv1, TLSv1.1 and TLSv1.2, and the second allows only TLSv1 connections ('ssl_protocols TLSv1;' directive). From what I've seen, in this case, the second server will still accept incoming TLSv1.1 and TLSv1.2 connections.
I've debugged it a bit, and wrote some independent code that uses OpenSSL, and it seems that OpenSSL doesn't allow you to change the supported TLS versions of a connection during the SNI callback.
The following may occur - an incoming connection to the second server starts, and it gets the default SSL CTX object (in Nginx, that would be the context of the first server block). When the SNI callback (ngx_http_ssl_servername) is called, the context is replaced for the connection (with the context of the second server block), along with the SSL options of the connections (SSL_set_options is called with the new CTX's options). These options contain the second server's allowed protocols. Even though the correct options are set, when the handshake continues, it would succeed even for TLSv1.1 and TLSv1.2 (which are enabled for the first server, but not for the second one).
A similar problem can occur as well; for example, if the first server allows only for TLSv1 connections and the second one allows for TLSv1.1, then TLSv1.1 connections will be rejected by OpenSSL, even if their SNI name is the name of the second server.
This seems like an OpenSSL issue (I've reproduced it on both v1.0.1 and v1.0.2), but I think that this should at least be documented in Nginx's SSL documentation, as the 'ssl_protocols' directive doesn't behave as expected for different server blocks with the same port - you'd expect that one server block would not affect the other, but it does.
Nginx development team (e.g. Maxim, Igor), I'd love to hear your thoughts about this.
Below is an example of this scenario.
The following is an example configuration:
listen 443 ssl;
ssl_protocols TLSv1 TLSv1.1;
listen 443 ssl;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
The following would occur when trying to connect (should succeed as server2 allows for TLSv1.2, but fails):
$ openssl s_client -connect localhost:443 -servername server2 -tls1_2
140495355434656:error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number:s3_pkt.c:339:
no peer certificate available
No client certificate CA names sent
SSL handshake has read 5 bytes and written 7 bytes
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Protocol : TLSv1.2
Cipher : 0000
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1430229239
Timeout : 7200 (sec)
Verify return code: 0 (ok)
And Nginx would show the error:
SSL_do_handshake() failed (SSL: error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:SSL alert number 70) while SSL handshaking
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the nginx-devel