3

I want to run this url: https://192.168.1.254 and get a website with the correct content and certificate in the address bar. I am getting the website but I get an invalid certificate error in the address bar because the cert is taken from a different server block: the default server block 000-default.conf.

Can someone explain this behavior to me please?

My client browser is Google Chrome Version 87.0.4280.88 (Official Build) (64-bit)

My Nginx server is:

root@OpenWrt:/etc/nginx/conf.d# nginx -V
nginx version: nginx/1.19.4 (x86_64-pc-linux-gnu)
built with OpenSSL 1.1.1h  22 Sep 2020
TLS SNI support enabled

I think the issue is related to how SNI apparently does not allow a Literal IPv4 and IPv6 addresses as a "HostName". But is that really the case?

I have a default server block 000-default.conf like this:

server {
    server_name _;
    listen 80 default_server;
    listen 443 ssl default_server;

    ## To also support IPv6, uncomment this block
    # listen [::]:80 default_server;
    # listen [::]:443 ssl default_server;

    ssl_certificate '/etc/nginx/conf.d/_lan.crt';
    ssl_certificate_key '/etc/nginx/conf.d/_lan.key';
    return 404; # or whatever
}

And another server called luci-http.conf like this:

server {
        listen 80;
        listen [::]:80;
        server_name openwrt.lan 192.168.1.254;
        # access_log /proc/self/fd/1 openwrt; # use logd (init forwards stdout).
        include conf.d/*.locations;
}

When I put http://192.168.1.254 in the address bar it serves me up the correct webpage.

I also have this https server: luci-https.conf

server {
        listen 443 ssl;
        listen [::]:443 ssl;

        server_name openwrt.lan 192.168.1.254;
        #include '/var/lib/nginx/lan_ssl.listen.default';
        ssl_certificate '/etc/nginx/conf.d/_lan.crt';
        ssl_certificate_key '/etc/nginx/conf.d/_lan.key';
        ssl_session_cache 'shared:SSL:32k';
        ssl_session_timeout '64m';
        # access_log /proc/self/fd/1 openwrt; # use logd (init forwards stdout).
        include conf.d/*.locations;
}

When I put https://192.168.1.254 in the address bar it serves me up the correct webpage and the certificate in _lan.crt. As you can see I have the same Cert/Key pair in this and the default server block.

However when I remove that ip address as a server_name from luci-https.conf and add it as a server_name in: mysite.lan.conf I don't see the same behavior.

server {
        listen 443 ssl;
        listen [::]:443 ssl;
        #listen 192.168.1.254 ssl;
        #include '/var/lib/nginx/lan_ssl.listen';

        server_name mysite.lan www.mysite.lan fun.mysite.lan 192.168.1.254;

        root /www/mysite;
        index index.html index.htm index.nginx-debian.html;

        ssl_certificate '/etc/nginx/conf.d/mysite.lan.crt';
        ssl_certificate_key '/etc/nginx/conf.d/mysite.lan.key';
        ssl_session_cache 'shared:SSL:32k';
        ssl_session_timeout '64m';

        location / {
                try_files $uri $uri/ =404;
        }

        access_log /var/log/nginx/mysite.lan.access.log;
        error_log /var/log/nginx/mysite.lan.error.log;
}

Now when I put https://192.168.1.254 in the address bar it serves me up the correct webpage but again the certificate in _lan.crt not the certificate: mysite.lan.crt from mysite.lan.conf as expected.

When I put..

ssl_certificate '/etc/nginx/conf.d/mysite.lan.crt';
ssl_certificate_key '/etc/nginx/conf.d/mysite.lan.key';

in the default server block 000-default.conf then I get that certificate instead when I put https://192.168.1.254 in the browser address bar whether 192.168.1.254 is specified as a server_name in luci-https.conf or mysite.lan.conf.

So it seems that SNI will match on a "hostname" that is an ip address but it takes the certificate from the default server block. Why is that?

FlexMcMurphy
  • 203
  • 2
  • 9

1 Answers1

5

... SNI apparently does not allow a Literal IPv4 and IPv6 addresses as a "HostName". But is that really the case?

The idea behind SNI is to distinguish between multiple domains on the same IP address. Insofar using SNI with an IP address does not really make sense. Therefore it is also restricted to actual hostnames. To cite from RFC 6066:

"HostName" contains the fully qualified DNS hostname of the server, as understood by the client. ... Literal IPv4 and IPv6 addresses are not permitted in "HostName"

    server_name mysite.lan www.mysite.lan fun.mysite.lan 192.168.1.254;

...
So it seems that SNI will match on a "hostname" that is an ip address but it takes the certificate from the default server block.

Since SNI is only used for actual hostnames there will be no SNI inside the TLS handshake and thus the default HTTPS configuration gets used. Inside the HTTPS there is the HTTP protocol though which includes the Host header. Since the Host header specifies the IP address (because the URL does) it will match this specific virtual host. Therefore: wrong certificate, right content.

Steffen Ullrich
  • 13,227
  • 27
  • 39
  • Yes I agree it makes more sense to specify an actual hostname as a server_name rather than an ip address. But now I am only interested in understanding the behavior I described above. Can you explain it? Nginx seems to be matching the ip address as though it is a hostname and it is serving up the correct website. Why does the certificate come from the default server? – FlexMcMurphy Dec 23 '20 at 23:21
  • 1
    @FlexMcMurphy: Because (as I've tried to write in my answer) there is no SNI used and therefore certificate selection cannot be based on SNI. Content selection is based on the `Host` HTTP header though and not on SNI. – Steffen Ullrich Dec 23 '20 at 23:25
  • Yes I think you were updating your answer when I commented. Thank you so much. I spent hours on this and it's such a relief to understand what is going on! – FlexMcMurphy Dec 23 '20 at 23:27