SNI扩展,使单IP主机支持多域名HTTPS配置


SNI 介绍

SNI (Server Name Indication) 是用来改善服务器与客户端 SSL (Secure Socket Layer) 和 TLS (Transport Layer Security) 的一个扩展。

主要解决一台服务器只能使用一个证书(一个域名)的缺点,随着服务器对虚拟主机的支持,一个服务器上可以为多个域名提供服务,因此SNI必须得到支持才能满足需求。

SNI 的工作原理

它的工作原理是:在客户端与服务器建立 SSL 连接之前,Client Hello 阶段会通过 SNI 扩展,将要访问站点的域名(Hostname)提前告诉服务器,这样服务器会根据这个域名匹配对应的证书返回给客户端,以完成校验过程。

目前,大多数操作系统和浏览器都已经很好地支持SNI扩展。

SNI兼容性

说明: SNI兼容TLS1.0及以上协议,但不被SSL支持,并且SNI扩展是双向的,必须客户端、服务端同时支持发送与接收,该特性才算生效。

  • SNI支持以下桌面版浏览器:

    • Chrome 5及以上版本
    • Chrome 6及以上版本(Windows XP)
    • Firefox 2及以上版本
    • IE 7及以上版本(运行在Windows Vista/Server 2008及以上版本系统中,在XP系统中任何版本的IE浏览器都不支持SNI)
    • Konqueror 4.7及以上版本
    • Opera 8及以上版本
    • Safari 3.0 on Windows Vista/Server 2008及以上版本,Mac OS X 10.5.6 及以上版本
  • SNI支持以下手机端浏览器:

    • Android Browser on 3.0 Honeycomb及以上版本
    • iOS Safari on iOS 4及以上版本
    • Windows Phone 7及以上版本
  • SNI支持以下服务器:

    • Apache 2.2.12及以上版本
    • Apache Traffic Server 3.2.0及以上版本
    • HAProxy 1.5及以上版本
    • IIS 8.0及以上版本
    • lighttpd 1.4.24及以上版本
    • LiteSpeed 4.1及以上版本
    • nginx 0.5.32及以上版本
  • SNI 支持以下命令行:

    • cURL 7.18.1及以上版本
    • wget 1.14及以上版本

Nginx 配置 SNI

检测支持性

nginx -V

...
TLS SNI support enabled
...

核心配置

全站SSL证书配置均需要使用变量形式匹配赋予。

  • 启用了 sni 的 nginx ,如下变量会被赋值
$ssl_server_name
  • 证书命令 ssl_certificate ,也可以通过变量进行设置,形如:
ssl_certificate     $ssl_server_name.crt;
ssl_certificate_key $ssl_server_name.key;

测试试验

多站点 SSL 配置

  • 传统配置,拆分 server ,单独写证书配置

    server {
        listen    80;
        listen    443 ssl;
        server_name  test1.nestealin.com;
        charset utf-8;
        access_log  logs/test1.access.main.log  main;
        error_log  logs/test1.error.log error;
    
        ssl_certificate     /data/keys/server1.cer;
        ssl_certificate_key /data/keys/server1.key;
    
        add_header content-type application/json;
        return 200 'ok';
    }
    
    server {
        listen    80;
        listen    443 ssl;
        server_name  test2.nestealin.com;
        charset utf-8;
        access_log  logs/test2.access.main.log  main;
        error_log  logs/test2.error.log error;
    
        ssl_certificate     /data/keys/server2.cer;
        ssl_certificate_key /data/keys/server2.key;
    
        add_header content-type application/json;
        return 200 'ok';
    }
  • MAP 方式正则匹配证书名称

    map $ssl_server_name $sni_test1_string {
            "~*^.*\.nestealin\.com.*" 'server';
            default '';
    }
    server {
        listen    80;
        listen    443 ssl;
        server_name  _;
        charset utf-8;
        access_log  logs/test1.access.main.log  main;
        error_log  logs/test1.error.log error;
    
        ssl_certificate     /data/keys/$sni_test1_string.cer;
        ssl_certificate_key /data/keys/$sni_test1_string.key;
    
        add_header content-type application/json;
        return 200 'ok';
    }
  • 本次我们采用 MAP 方式进行通配测试

访问测试

  • 写hosts

    192.168.7.55 test1.nestealin.com
    192.168.7.55 test2.nestealin.com
    192.168.7.55 test3.baidu.com
  • 通过 openssl s_client 方式请求验证

    openssl s_client -connect 192.168.7.55:443 -tls1_2 -servername test1.nestealin.com | grep subject
    • test1.nestealin.com 匹配到根域域名证书文件 server.cer、server.key,验证通过

      depth=3 O = Digital Signature Trust Co., CN = DST Root CA X3
      verify return:1
      depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
      verify return:1
      depth=1 C = US, O = Let's Encrypt, CN = R3
      verify return:1
      depth=0 CN = *.nestealin.com
      verify return:1
      subject=/CN=*.nestealin.com
    • 同样 test2.nestealin.com 属于同一根域,验证通过

      [root@localhost ~]# openssl s_client -connect 192.168.7.55:443 -tls1_2 -servername test2.nestealin.com | grep subject
      depth=3 O = Digital Signature Trust Co., CN = DST Root CA X3
      verify return:1
      depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
      verify return:1
      depth=1 C = US, O = Let's Encrypt, CN = R3
      verify return:1
      depth=0 CN = *.nestealin.com
      verify return:1
      subject=/CN=*.nestealin.com
    • test3.baidu.com 由于匹配不到证书,握手失败

      [root@localhost ~]# openssl s_client -connect 192.168.7.55:443 -tls1_2 -servername test3.baidu.com | grep subject
      140177640814480:error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:s3_pkt.c:1493:SSL alert number 80
      140177640814480:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:659:
      • 验证日志文件报错信息,匹配证书名为空,找不到证书导致握手失败

        2021/08/26 01:32:26 [error] 8056#0: *45 cannot load certificate "/data/keys/.cer": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/data/keys/.cer','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: 192.168.7.27, server: 0.0.0.0:443
    • 不带域名直接请求主机443端口,同样因为匹配不到证书,导致握手失败

      [root@localhost ~]# openssl s_client -connect 192.168.7.55:443 -tls1_2
      CONNECTED(00000003)
      139661997975440:error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:s3_pkt.c:1493:SSL alert number 80
      139661997975440:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:659:
      ---
      no peer certificate available
      ---
      No client certificate CA names sent

以上结果均符合预期,实现单机支持多域名HTTPS证书匹配。

  1. 如果主机符合正则匹配,则匹配已有证书,通过 SSL 握手;
  2. 如果无法匹配主机MAP,则无法找到证书配置,导致握手失败;

总结

SNI 不是 Nginx 的功能,无需单独配置,它的特性仅是为了让单IP主机支持多域名HTTPS配置、不同域名使用不同证书。



文章作者: NesTeaLin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 NesTeaLin !
  目录