V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
NGINX
NGINX Trac
3rd Party Modules
Security Advisories
CHANGES
OpenResty
ngx_lua
Tengine
在线学习资源
NGINX 开发从入门到精通
NGINX Modules
ngx_echo
fourstring
V2EX  ›  NGINX

诡异的问题:和朋友共用一台服务器,在访问我的网站时, nginx 同时发送了他的证书

  •  
  •   fourstring · 2017-02-05 22:54:54 +08:00 · 5780 次点击
    这是一个创建于 2637 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我们的服务器用 Docker 建立 nginx 环境,所以 ssl 直接丢在了 root 下,也没有太大关系。

    服务端软件为: nginx-1.11.8 , openssl-1.1.0c (已排除软件版本因素)

    我的域名为 a.com ,他的域名为 b.com

    以下是我能提供的所有信息,症状如标题,请大神赐教,谢谢!

    Dockerfile 如下:

    FROM ubuntu:latest
    COPY crontabs /root
    RUN apt update && \
    	apt-get install -y -q libpcre3 libpcre3-dev zlib1g zlib1g-dev git wget gcc make cmake cpp autoconf automake cron && \
    	cd /usr/local/src && \
    	wget http://nginx.org/download/nginx-1.11.8.tar.gz && \
    	tar xzf nginx-1.11.8.tar.gz && \
    	wget https://www.openssl.org/source/openssl-1.1.0d.tar.gz && \
    	tar xzf openssl-1.1.0d.tar.gz && \
    	groupadd www && \
    	useradd www -g www -s /sbin/nologin && \
    	git clone https://github.com/grahamedgecombe/nginx-ct && \
    	cd /usr/local/src/nginx-1.11.8 && \
    	./configure --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-http_v2_module --with-openssl=../openssl-1.1.0d --add-module=../nginx-ct && \
    	make -j2 && make install && \
    	ln -s /usr/local/nginx/sbin/nginx /usr/local/bin/nginx && \
    	rm -rf /usr/local/src/* && \
    	apt remove -y -q git wget gcc make cmake cpp autoconf automake && \
    	apt autoremove -y && \
    	apt purge -y -q git wget gcc make cmake cpp autoconf automake && \
    	mkdir -p /usr/local/nginx/conf/vhost && \
    	mkdir -p /home/wwwroot && \
    	crontab /root/crontabs
    COPY ["nginx.conf","fastcgi.conf","/usr/local/nginx/conf/"]
    COPY ["a.com.conf","b.com.conf","/usr/local/nginx/conf/vhost/"]
    ADD ssl.tar.gz /root
    EXPOSE 80
    EXPOSE 443
    CMD ["nginx","-g","daemon off;"]
    

    a.com.conf:

    server {
        server_name a.com www.a.com;
        listen 80;
        location ^~ /.well-known/acme-challenge/ {
            alias /home/wwwroot/challenges/;
            try_files $uri =404;
        }
    
        location / {
            rewrite ^/(.*)$ https://a.com/$1 permanent;
        }
    }
    server {
        server_name a.com www.a.com;
        listen               443 ssl http2 reuseport fastopen=3;
        root /home/wwwroot/hexo;
        server_tokens        off;
        ssl_ct on;
        ssl_certificate      /root/ssl/a/a.com.rsa.pem;
        ssl_certificate_key  /root/ssl/a/a.com.rsa.key;
        ssl_ct_static_scts   /root/ssl/a/scts/rsa;
    
        ssl_certificate      /root/ssl/a/a.ecc.pem;
        ssl_certificate_key  /root/ssl/a/a.ecc.key;
        ssl_ct_static_scts   /root/ssl/a/scts/ecc;
        ssl_dhparam          /root/ssl/a/dhparams.pem;
        ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
        ssl_prefer_server_ciphers  on;
        ssl_ecdh_curve secp384r1;
        ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
        ssl_session_cache          shared:SSL:50m;
        ssl_session_timeout        1d;
        ssl_session_tickets        on;
        ssl_stapling               on;
        ssl_stapling_verify        on;
        resolver                   8.8.8.8 8.8.4.4 valid=300s;
        resolver_timeout           10s;
        add_header    Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
        add_header    Public-Key-Pins 'pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=";pin-sha256="Fbs+o+IxVNTHBpjNQYfX/TBnxPC+OWLYxQLEtqkrAfM=";max-age=2592000; includeSubDomains';
        index index.html;
        location / {
        	expires 120s;
        }
        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|ico)$ {
        expires 30d;
        access_log off;
        }
    location ~ .*\.(js|css)?$ {
        expires 7d;
        access_log off;
        }
    }
    

    b.conf:

    server {
        server_name b.com,www.b.com;
        location ^~ /.well-known/acme-challenge/ {
            alias /home/wwwroot/challenges/;
            try_files $uri =404;
        }
    
        location / {
            rewrite ^/(.*)$ https://b.com/$1 permanent;
        }
    }
    server {
        server_name b.com,www.b.com;
        listen               443 ssl http2;
        index index.php;
        root  /home/wwwroot/b;
    
        if (!-e $request_filename) {
            rewrite ^(.*)$ /index.php$1 last;
        }
    
        location ~ .*\.php(\/.*)*$ {
            include fastcgi.conf;
            fastcgi_pass  cgi:9001;
        }
    
        server_tokens        off;
        ssl_ct on;
        ssl_certificate      /root/ssl/b/b.com.rsa.pem;
        ssl_certificate_key  /root/ssl/b/b.com.rsa.key;
        ssl_ct_static_scts   /root/ssl/b/scts/rsa;
    
        ssl_certificate      /root/ssl/b/b.com.ecc.pem;
        ssl_certificate_key  /root/ssl/b/b.com.ecc.key;
        ssl_ct_static_scts   /root/ssl/b/scts/ecc;
        ssl_dhparam          /root/ssl/b/dhparams.pem;
        ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
        ssl_prefer_server_ciphers  on;
        ssl_ecdh_curve secp384r1;
        ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
        ssl_session_cache          shared:SSL:50m;
        ssl_session_timeout        1d;
        ssl_session_tickets        on;
        ssl_stapling               on;
        ssl_stapling_verify        on;
        resolver                   8.8.8.8 8.8.4.4 valid=300s;
        resolver_timeout           10s;
        add_header    Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
        add_header    Public-Key-Pins 'pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=";pin-sha256="Fbs+o+IxVNTHBpjNQYfX/TBnxPC+OWLYxQLEtqkrAfM=";max-age=2592000; includeSubDomains';
    }
    
    error_log  /root/b_error.log  crit;
    

    /root/ssl 目录的结构图:

    /root/ssl
    |-- b.com.ecc.pem
    |-- a
    |   |-- account.key
    |   |-- acme_tiny.py
    |   |-- ct-submit-1.0.0
    |   |   |-- LICENSE
    |   |   |-- README.markdown
    |   |   |-- ct-submit
    |   |   `-- ct-submit.go
    |   |-- ct-submit.zip
    |   |-- dhparams.pem
    |   |-- intermediate.pem
    |   |-- renew_ecc.sh
    |   |-- renew_rsa.sh
    |   |-- root.pem
    |   |-- scts
    |   |   |-- ecc
    |   |   |   |-- b.com.ecc.sct
    |   |   |   `-- a.com.ecc.sct
    |   |   `-- rsa
    |   |       `-- a.com.rsa.sct
    |   |-- signed_ecc.crt
    |   |-- signed_rsa.crt
    |   |-- a.com.ecc.csr
    |   |-- a.com.ecc.key
    |   |-- a.com.ecc.pem
    |   |-- a.com.rsa.csr
    |   |-- a.com.rsa.key
    |   `-- a.com.rsa.pem
    |-- intermediate.pem
    |-- root.pem
    |-- signed_ecc.crt
    `-- b
        |-- account.key
        |-- acme_tiny.py
        |-- b.com.ecc.csr
        |-- b.com.ecc.key
        |-- b.com.ecc.pem
        |-- b.com.rsa.csr
        |-- b.com.rsa.key
        |-- b.com.rsa.pem
        |-- ct-submit-1.0.0
        |   |-- LICENSE
        |   |-- README.markdown
        |   |-- ct-submit
        |   `-- ct-submit.go
        |-- dhparams.pem
        |-- intermediate.pem
        |-- renew_ecc.sh
        |-- renew_rsa.sh
        |-- root.pem
        |-- scts
        |   |-- ecc
        |   |   `-- b.com.ecc.sct
        |   `-- rsa
        |       `-- b.com.rsa.sct
        |-- signed_ecc.crt
        `-- signed_rsa.crt
    

    *.ecc.pem表示该域名的 ECC 证书,*.rsa.pem表示该域名的 RSA 证书,signed_*.crt表示 Let ‘ s encrypt 签发但还未进行证书链合并的证书;*.ecc.key表示对应域名 ECC 证书私钥,*.rsa.key表示对应域名 RSA 证书私钥;*.ecc.sct表示该域名 ECC 证书的 SCT 文件,*.rsa.sct表示该域名 RSA 证书的 SCT 文件。account.key为用户密钥。

    renew_*.sh为对应证书更新脚本。 renew_ecc.sh:

    #!/bin/bash
    cd /root/ssl/a
    python acme_tiny.py --account-key ./account.key --csr ./a.com.ecc.csr --acme-dir /home/wwwroot/challenges/ > ./signed_ecc.crt
    wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
    wget -O - https://letsencrypt.org/certs/isrgrootx1.pem > root.pem
    cat signed_ecc.crt intermediate.pem > a.com.ecc.pem
    ./ct-submit-1.0.0/ct-submit ct.googleapis.com/pilot </root/ssl/a/a.com.ecc.pem >/root/ssl/a/scts/ecc/a.com.ecc.sct
    kill -HUP `cat /usr/local/nginx/logs/nginx.pid`
    

    renew_rsa.sh:

    #!/bin/bash
    cd /root/ssl/a
    python acme_tiny.py --account-key ./account.key --csr ./a.com.rsa.csr --acme-dir /home/wwwroot/challenges/ > ./signed_rsa.crt
    wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
    wget -O - https://letsencrypt.org/certs/isrgrootx1.pem > root.pem
    cat signed_rsa.crt intermediate.pem > a.com.rsa.pem
    ./ct-submit-1.0.0/ct-submit ct.googleapis.com/pilot </root/ssl/a/a.com.rsa.pem >/root/ssl/a/scts/rsa/a.com.rsa.sct
    kill -HUP `cat /usr/local/nginx/logs/nginx.pid`
    
    40 条回复    2017-02-07 07:11:59 +08:00
    feiyunfirst
        1
    feiyunfirst  
       2017-02-05 23:04:57 +08:00 via iPhone
    应该是 Nginx 没有开启 TLS 吧。参考 https://www.i7i8.com/2016/11/nginx-ip-https/
    SourceMan
        2
    SourceMan  
       2017-02-05 23:09:39 +08:00 via iPhone
    $ nginx -V
    TLS SNI support disabled
    参考楼上开启它即可
    fourstring
        3
    fourstring  
    OP
       2017-02-05 23:09:41 +08:00
    @feiyunfirst #1 SNI 已开启
    fourstring
        4
    fourstring  
    OP
       2017-02-05 23:09:56 +08:00
    @SourceMan #2 SNI 已开启……
    wql
        5
    wql  
       2017-02-06 01:50:31 +08:00 via Android
    同时配上两个单一域名的证书导致的。
    根据 cipher 中 ecdsa 在 rsa 前,如果 b.com 这一个因为一些原因匹配不上(我估计 sct 搞的鬼),那么优先返回默认 ecdsa 证书,正好有一个 a.com.ecc.crt 。
    顺带说一句,@qgy18 的配置可能对于这种情况有 bug ,自己也碰到过。
    lhbc
        6
    lhbc  
       2017-02-06 03:41:59 +08:00   ❤️ 2
    配置里两处写错了
    server_name b.com,www.b.com;
    应该为
    server_name b.com www.b.com;
    Systemd
        7
    Systemd  
       2017-02-06 04:26:12 +08:00 via Android
    试试注释掉 ssl_session_cache
    wql
        8
    wql  
       2017-02-06 08:53:07 +08:00 via Android   ❤️ 1
    六楼 @lhbc 正解。
    逗号那个 nginx 读不出来可能就变成 default 了。
    HLT
        9
    HLT  
       2017-02-06 09:30:18 +08:00   ❤️ 1
    server_name b.com,www.b.com;
    fourstring
        10
    fourstring  
    OP
       2017-02-06 10:23:30 +08:00
    @wql #8
    @wql #5
    @lhbc #6
    @HLT #9 谢谢,但是目前的情况是,访问我的( a.com )会返回两个域名的四个证书,访问他的( b.com )只会返回他的证书。而且我用 F12 看了一下,访问我的不会返回 sct ,但是他的可以返回 sct 。

    参照各位的解决方案,修改了配置文件,删除多余的 sct ,并且启用 Mozilla 的 cipher 配置之后,这个问题仍然存在……
    fourstring
        11
    fourstring  
    OP
       2017-02-06 10:25:29 +08:00
    @fourstring #10 补充一下,我选用 Mozilla 的 modern 配置,结果访问我的域名只返回了 3 个证书,两个域名的 ECC ,以及我自己的 RSA

    Cipher :
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
    lhbc
        12
    lhbc  
       2017-02-06 10:26:29 +08:00 via iPhone
    nginx -V
    贴上来
    fourstring
        13
    fourstring  
    OP
       2017-02-06 10:27:01 +08:00
    @lhbc #12 nginx version: nginx/1.11.9
    built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4)
    built with OpenSSL 1.1.0d 26 Jan 2017
    TLS SNI support enabled
    configure arguments: --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-http_v2_module --with-openssl=../openssl-1.1.0d --add-module=../nginx-ct
    lhbc
        14
    lhbc  
       2017-02-06 10:30:58 +08:00 via iPhone
    去掉 ct 所有配置,或者去掉 nginx-ct 模块编译试下。
    fourstring
        15
    fourstring  
    OP
       2017-02-06 10:34:04 +08:00
    @lhbc #14 好的
    fourstring
        16
    fourstring  
    OP
       2017-02-06 10:36:23 +08:00
    @lhbc #14 已关闭所有 ct 配置,仍然无效……
    fourstring
        17
    fourstring  
    OP
       2017-02-06 10:38:55 +08:00
    @lhbc #14 而且现在又有另一个诡异的问题,就是我的 session_cache 配置失效了,我怀疑是返回了其他域名的证书所致
    wql
        18
    wql  
       2017-02-06 10:57:33 +08:00   ❤️ 1
    @fourstring 这是双证书本身的缺点。
    你的 cipher 是这样的:
    EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5
    或者是 ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
    这都是强行把 ECDHE-ECDSA 这种套件提前的设置方式( EECDH 在我看来基本等价于 ECDHE )。
    所以,在大部分浏览器的套件选择默认以 ECDSA 为先的情况下,你又开了 ssl_prefer_server_ciphers on ,那无疑在取交集时,会优先选择 EECDH+ECDSA+CHACHA20 或 EECDH+ECDSA+AES128 ;因而这是必然:返回证书前两个一定都会是 ECDSA 证书了。
    解决方案其实只能是保守的:使用 SAN 证书。在这种双证书的特殊情况下, SNI 扩展不是主要矛盾,套件和证书才是。
    fourstring
        19
    fourstring  
    OP
       2017-02-06 11:04:16 +08:00
    @wql #18 谢谢,这是我的理解:浏览器取交集最后剩下 ECDSA 的 cipher ,但是为什么会一次性返回两个 ECC 证书?而不是返回本域名的一个 ECC 证书呢?

    另外 ququ 的 csr 生成命令中似乎就默认开启了 SAN 扩展,您这里的 SAN 证书还有什么需要额外注意的吗?
    wql
        20
    wql  
       2017-02-06 11:06:27 +08:00
    @fourstring 按照我的个人理解,这个问题还是在于 Nginx 在实现的时候把所有的 ECDSA 证书全返回去了。
    我的 SAN 证书的 CSR 就是按照 @qgy18 的方式生成的
    fourstring
        21
    fourstring  
    OP
       2017-02-06 11:10:09 +08:00
    @wql #20
    那就是说不能双站点共存?
    wql
        22
    wql  
       2017-02-06 12:08:45 +08:00
    @fourstring 我目前部署下来只发现两个解决方案:
    1.放弃给每个站点单独设双证书
    2.统一使用 SAN 来部署双证书
    fourstring
        23
    fourstring  
    OP
       2017-02-06 13:20:43 +08:00
    @wql #22 统一使用 SAN ?具体怎么操作呢?
    我对 SAN 的了解仅限于指定备用名称???
    wql
        24
    wql  
       2017-02-06 13:42:03 +08:00 via Android
    @fourstring 我可能表达不清楚,就是分别签发 ECDSA 和 RSA 这两张证书,这两张证书要把四个域名都填上。就是备用名称。
    fourstring
        25
    fourstring  
    OP
       2017-02-06 13:44:42 +08:00
    @wql #24 啊, SAN 还能填非本域名的域名?
    lhbc
        26
    lhbc  
       2017-02-06 13:49:18 +08:00
    @fourstring SAN 可以包含多个顶级域名
    你看下淘宝和天猫的证书
    fourstring
        27
    fourstring  
    OP
       2017-02-06 13:54:31 +08:00
    @lhbc #26 涨姿势了,谢谢!
    fourstring
        28
    fourstring  
    OP
       2017-02-06 18:35:36 +08:00
    @lhbc #26
    @wql #24 这个问题已经解决,但是 CT 策略失效了,是因为 SAN 有多个顶级域名的原因吗?
    wql
        29
    wql  
       2017-02-06 18:38:19 +08:00 via Android
    @fourstring 你是否知道,每签发一次证书,必须重新获取一次 SCT?
    fourstring
        30
    fourstring  
    OP
       2017-02-06 18:39:03 +08:00
    @wql #29 我的脚本里都是这么做的,签发一次就获取一次
    lightening
        31
    lightening  
       2017-02-06 18:43:22 +08:00
    你还有一个选择,试试我的 https://github.com/SteveLTN/https-portal
    wql
        32
    wql  
       2017-02-06 18:46:57 +08:00 via Android
    @fourstring 证书已经同一,那么 sct 必然是同一组啊。
    fourstring
        33
    fourstring  
    OP
       2017-02-06 18:47:32 +08:00
    @wql #32 是的,然并卵……
    chromee
        34
    chromee  
       2017-02-06 19:05:39 +08:00 via Android   ❤️ 2
    @fourstring 看这个 issue https://github.com/grahamedgecombe/nginx-ct/issues/13
    nginx-ct 用 OpenSSL 1.1.0 并用 SNI 的话不会对非默认的 vhost 返回 ct 信息
    我之前也遇到了这个问题 我觉得用 OpenSSL 1.0.2 的最新版就行 没必要上 1.1.0
    fourstring
        35
    fourstring  
    OP
       2017-02-06 19:17:14 +08:00
    @chromee #34 这样啊,谢谢!
    bobylive
        36
    bobylive  
       2017-02-06 19:45:59 +08:00 via Android
    之前試過這個情況,臨時用 default_server 解決,暫時也沒研究出什麼好方法,因為以前就有一個 IP 一個證書的說法。
    fourstring
        37
    fourstring  
    OP
       2017-02-06 19:48:29 +08:00   ❤️ 1
    @bobylive #36 已经解决。方案如下:用
    openssl req -new -sha256 -key double.ecc.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:a.com,DNS:www.a.com,DNS:b.com,DNS:www.b.com")) > double.ecc.csr
    生成带 SAN 的 CSR ,然后提交给 let ‘ s encrypt (如果是别家那得支持签发 SAN 证书)即可。 SAN 上限 100 个域名,这样一个证书可以给多个顶级域名使用。
    如果用 nginx-ct 开启 CT ,不要使用 openssl1.1.0
    evlos
        38
    evlos  
       2017-02-06 20:21:23 +08:00 via iPhone
    另外我会单独加一个 default ,这样就算写错配置也不会很尴尬地进到其他 host 里
    bobylive
        39
    bobylive  
       2017-02-06 22:47:17 +08:00 via Android
    @fourstring 感謝,等我試試
    Showfom
        40
    Showfom  
       2017-02-07 07:11:59 +08:00 via iPhone
    @bobylive 同意 楼主没有加 default_server 的锅
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2803 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 02:47 · PVG 10:47 · LAX 19:47 · JFK 22:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.