
我们的服务器用 Docker 建立 nginx 环境,所以 ssl 直接丢在了 root 下,也没有太大关系。
服务端软件为: nginx-1.11.8 , openssl-1.1.0c (已排除软件版本因素)
以下是我能提供的所有信息,症状如标题,请大神赐教,谢谢!
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` #!/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` 1 feiyunfirst 2017-02-05 23:04:57 +08:00 via iPhone 应该是 Nginx 没有开启 TLS 吧。参考 https://www.i7i8.com/2016/11/nginx-ip-https/ |
2 SourceMan 2017-02-05 23:09:39 +08:00 via iPhone $ nginx -V TLS SNI support disabled 参考楼上开启它即可 |
3 fourstring OP @feiyunfirst #1 SNI 已开启 |
4 fourstring OP @SourceMan #2 SNI 已开启…… |
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 ,自己也碰到过。 |
6 lhbc 2017-02-06 03:41:59 +08:00 |
7 Systemd 2017-02-06 04:26:12 +08:00 via Android 试试注释掉 ssl_session_cache |
10 fourstring OP |
11 fourstring OP @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; |
12 lhbc 2017-02-06 10:26:29 +08:00 via iPhone nginx -V 贴上来 |
13 fourstring OP @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 |
14 lhbc 2017-02-06 10:30:58 +08:00 via iPhone 去掉 ct 所有配置,或者去掉 nginx-ct 模块编译试下。 |
15 fourstring OP @lhbc #14 好的 |
16 fourstring OP @lhbc #14 已关闭所有 ct 配置,仍然无效…… |
17 fourstring OP @lhbc #14 而且现在又有另一个诡异的问题,就是我的 session_cache 配置失效了,我怀疑是返回了其他域名的证书所致 |
18 wql 2017-02-06 10:57:33 +08:00 @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+3ES:!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 扩展不是主要矛盾,套件和证书才是。 |
19 fourstring OP @wql #18 谢谢,这是我的理解:浏览器取交集最后剩下 ECDSA 的 cipher ,但是为什么会一次性返回两个 ECC 证书?而不是返回本域名的一个 ECC 证书呢? 另外 ququ 的 csr 生成命令中似乎就默认开启了 SAN 扩展,您这里的 SAN 证书还有什么需要额外注意的吗? |
20 wql 2017-02-06 11:06:27 +08:00 |
21 fourstring OP @wql #20 那就是说不能双站点共存? |
22 wql 2017-02-06 12:08:45 +08:00 |
23 fourstring OP @wql #22 统一使用 SAN ?具体怎么操作呢? 我对 SAN 的了解仅限于指定备用名称??? |
24 wql 2017-02-06 13:42:03 +08:00 via Android @fourstring 我可能表达不清楚,就是分别签发 ECDSA 和 RSA 这两张证书,这两张证书要把四个域名都填上。就是备用名称。 |
25 fourstring OP @wql #24 啊, SAN 还能填非本域名的域名? |
26 lhbc 2017-02-06 13:49:18 +08:00 @fourstring SAN 可以包含多个顶级域名 你看下淘宝和天猫的证书 |
27 fourstring OP @lhbc #26 涨姿势了,谢谢! |
28 fourstring OP |
29 wql 2017-02-06 18:38:19 +08:00 via Android @fourstring 你是否知道,每签发一次证书,必须重新获取一次 SCT? |
30 fourstring OP @wql #29 我的脚本里都是这么做的,签发一次就获取一次 |
31 lightening 2017-02-06 18:43:22 +08:00 你还有一个选择,试试我的 https://github.com/SteveLTN/https-portal |
32 wql 2017-02-06 18:46:57 +08:00 via Android @fourstring 证书已经同一,那么 sct 必然是同一组啊。 |
33 fourstring OP @wql #32 是的,然并卵…… |
34 chromee 2017-02-06 19:05:39 +08:00 via Android @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 |
35 fourstring OP @chromee #34 这样啊,谢谢! |
36 bobylive 2017-02-06 19:45:59 +08:00 via Android 之前情,用 default_server 解,也研究出什好方法,因以前就有一 IP 一的法。 |
37 fourstring OP @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 |
38 evlos 2017-02-06 20:21:23 +08:00 via iPhone 另外我会单独加一个 default ,这样就算写错配置也不会很尴尬地进到其他 host 里 |