bookmark_borderHTTPS中证书链不完整的解决方案

由于我们的部分业务场景是在微信(WebView)中打开https的web页面,在测试过程中却发现了一个问题,在Chrome中测试完全正常的https页面,在iOS的微信(WebView)中表现正常,但在Android中,不论是哪个版本的安卓系统,都不能正常打开页面,要么就是一片白,要么就是直接无法打开,解决这个问题,需要在服务器上配置完整的SSL证书链。
之后在Android自带的浏览器中测试,几乎所有的手机都出现下面这样的情况


证书链
看来Andorid的WebView不能打开页面应该是与这有关,造成这个问题的主要原因是我们服务器配置证书的证书链不全造成的。

证书链其实就是描述证书的签名环节,就比如是 A 颁发证书给 B ,B颁发证书给C,然后我们手里的就是证书C。当证书链不完整的情况下,也就是没有描述我们手中的证书C是由谁办法的,所以导致的浏览器不认为你这个证书是可信的授权证书。

每个设备中都会存有一些默认的可信的根证书,但很多CA是不使用根证书进行签名的,而是使用中间层证书进行签名,因为这样做能更快的进行替换(这句可能不对,原文是 because these can be rotated more frequently)。

如果你的服务器上没有中间件证书,这样的结果就是你的服务器上只有你的网站的证书,客户端的浏览器里只有CA的根证书,这样就会导致证书信任链不全,才导致了上面那两个截图中的问题。这种中间层证书不全的问题多出现在移动端的浏览器上(就我目前看,iOS8 iOS9 都没有出现问题,Andorid各个版本都有这个问题)。

当你服务器上的证书中的信任链不全的情况下,浏览器会认为当前的链接是一个不安全的,会阻止页面的打开。

解决方案
说清楚了原因,解决问题就很简单了,只要把我们的证书链补全就可以了。
从SSL证书服务商那里,你获得的crt证书文件大概是这个样子的:

-----BEGIN CERTIFICATE-----
    # 证书内容
-----END CERTIFICATE-----

在你补全中间层证书和根证书后,新的crt证书文件看起来是这样的:

-----BEGIN CERTIFICATE-----
    # 证书内容 1
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
    # 证书内容 2
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
    # 证书内容 3
-----END CERTIFICATE-----

这里包含了你的证书,以及从你的证书向上递归直至根证书的全部证书,这样就可以向浏览器证明你的链接是安全的。

补全证书链
比较方便的是使用这个在线的工具:
https://certificatechain.io
进入这个网站,粘贴进你的证书(只包含你的用户证书),或者上传你的证书,他就会给出补全后的证书文件,你只需要粘贴回你的文件或者下载覆盖就可以了,然后在服务器上重新部署就可以解决问题。
由于这里只需要上传证书,所以是安全的,不需要担心不安全的问题。
如果不喜欢用在线的工具,可以使用下面这个本地的工具,PHP写的,在命令行中运行:
Github ssl-certificate-chain-resolver

PS:
一般情况下
cert.pem 是证书
chain.pem 是证书链编码
fullchain.pem 是cert.pem和chain.pem的证书集合
privkey.pem 是私钥文件

bookmark_bordernginx配置ssl及优化

https是一种超文本传输安全协议,主要是用SSL/TLS来加密数据包,以达到数据加密传输的作用,同时也能一定程度达到防劫持的效果。

隐藏不必要的信息
细心的朋友会发现,请求响应头,有这么一行 server: nginx,说明我用的是 Nginx 服务器,但并没有具体的版本号。由于某些 Nginx 漏洞只存在于特定的版本,隐藏版本号可以提高安全性。这只需要在配置里加上这个就可以了:

server_tokens  off;

另外,一些 WEB 语言或框架默认输出的 x-powered-by 也会泄露网站信息,他们一般都提供了修改或移除的方法,可以自行查看手册。如果部署上用到了 Nginx 的反向代理,也可以通过 proxy_hide_header 指令隐藏它:

proxy_hide_header  X-Powered-By;

禁用非必要的方法
一般情况下,只需要处理 GET、POST 两种请求方法,而 HTTP/1 协议还规定了 TRACE 这样的方法用于网络诊断,这也可能会暴露一些信息。所以我针对 GET、POST 以及 HEAD 之外的请求,直接返回了 444 状态码(444 是 Nginx 定义的响应状态码,会立即断开连接,没有响应正文)。具体配置是这样的:

if ($request_method !~ ^(GET|HEAD|POST)$ ) {
    return    444;
}

合理配置响应头

#减少点击劫持
add_header X-Frame-Options DENY;

#禁止服务器自动解析资源类型
add_header X-Content-Type-Options nosniff;

#防XSS攻击
add_header X-XSS-Protection "1; mode=block";

#HSTS策略
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

X-Frame-Options 用来指定此网页是否允许被 iframe 嵌套,deny 就是不允许任何嵌套发生。这部分内容更多信息
X-Content-Type-Options 用来指定浏览器对未指定或错误指定 Content-Type 资源真正类型的猜测行为,nosniff 表示不允许任何猜测。这部分内容更多信息
X-XSS-Protection 这个响应头是用来防范XSS的。最早我是在介绍IE8的文章里看到这个,现在主流浏览器都支持,并且默认都开启了XSS保护,用这个header可以关闭它。它有几种配置:
0:禁用XSS保护;
1:启用XSS保护;
1; mode=block:启用XSS保护,并在检查到XSS攻击时,停止渲染页面(例如IE8中,检查到攻击时,整个页面会被一个#替换);
浏览器提供的XSS保护机制并不完美,但是开启后仍然可以提升攻击难度,总之没有特别的理由,不要关闭它。
Strict-Transport-Security(简称为 HSTS)可以告诉浏览器,在指定的 max-age 内,始终通过 HTTPS 访问我的博客。即使用户自己输入 HTTP 的地址,或者点击了 HTTP 链接,浏览器也会在本地替换为 HTTPS 再发送请求。另外由于我的证书不支持多域名,我没有加上 includeSubDomains。关于 HSTS 更多信息。

HTTPS 安全配置
启用 HTTPS 并正确配置了证书,意味着数据传输过程中无法被第三者解密或修改。有了 HTTPS,也得合理配置好 Web Server,才能发挥最大价值。关于 HTTPS 这一块有以下配置:

worker_processes auto;

http {

    #配置会话超时时间
    ssl_session_timeout  10m;

    #配置共享会话缓存大小,视站点访问情况设定
    ssl_session_cache shared:SSL:10m;

    server {
        listen      443 ssl;
        server_name www.example.com;

        #设置长连接
        keepalive_timeout 70;

        #开启HTTPS协议(推荐使用listen指令的ssl参数来代替这个指令)
        #ssl on;

        #证书文件
        ssl_certificate www.example.com.crt;

        #私钥文件
        ssl_certificate_key www.example.com.key;

        #使用DH文件
        ssl_dhparam /etc/ssl/certs/dhparam.pem;

        #版本选择
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

        #定义算法
        ssl_ciphers 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;

        #减少点击劫持
        add_header X-Frame-Options DENY;

        #禁止服务器自动解析资源类型
        add_header X-Content-Type-Options nosniff;

        #防XSS攻击
        add_header X-XSS-Protection "1; mode=block";

        #HSTS策略
        add_header Strict-Transport-Security "max-age=31536000;" always;
    }
}

最终效果在 ssllabs 中测试结果如下。

如何配置 ssl_ciphers 可以参考这个网站。需要注意的是,这个网站默认提供的加密方式安全性较高,一些低版本客户端并不支持,例如 IE9-、Android2.2- 和 Java6-。如果需要支持这些老旧的客户端,需要点一下网站上的「Yes, give me a ciphersuite that works with legacy / old software」链接,同时也可以使用 Mozilla 基金会 来生成配置。

另外 ssl_ciphers 还支持 CHACHA20 加密套件,只要 Nginx 编译时增加了 CHACHA20_POLY1305 加密算法即可,这是由 Google 开发的新一代加密方式,它有两方面优势:更好的安全性和更好的性能(尤其是在移动和可穿戴设备上)。下面有一张移动平台上它与 AES-GCM 的加密速度对比图(via):

启用 CHACHA20_POLY1305 最简单的方法是在编译 Nginx 时,使用 LibreSSL 代替 OpenSSL。用 Chrome 访问网站时,点击地址栏小锁显示的信息,可以看到加密方式使用的是不是 CHACHA20_POLY1305。
关于 CHACHA20_POLY1305 安全性和性能的详细介绍可以查看本文

补充:
1.使用 CHACHA20_POLY1305 的最佳实践是「仅针对不支持 AES-NI 的终端使用 CHACHA20 算法,否则使用 AES-GCM」。

2.我们通过 openssl 工具看一下 ssl_ciphers 具体包含哪些 Cipher Suites 例如:
openssl ciphers -V 'EECDH+AES128' | column -t

0xC0,0x2F  -  ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(128)  Mac=AEAD
0xC0,0x2B  -  ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)  Mac=AEAD
0xC0,0x27  -  ECDHE-RSA-AES128-SHA256        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA256
0xC0,0x23  -  ECDHE-ECDSA-AES128-SHA256      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA256
0xC0,0x13  -  ECDHE-RSA-AES128-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA1
0xC0,0x09  -  ECDHE-ECDSA-AES128-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA1

加强 HTTPS 安全性
HTTPS 基础配置采取的默认加密算法是 SHA-1,这个算法非常脆弱,安全性在逐年降低,在 2014 年的时候, Google 官方博客就宣布在 Chrome 浏览器中逐渐降低 SHA-1 证书的安全指示,会从 2015 年起使用 SHA-2 签名的证书,可参阅 Rabbit_Run 在 2014 年发表的文章:《为什么Google急着杀死加密算法SHA-1》

为此,主流的 HTTPS 配置方案应该避免 SHA-1,可以使用 迪菲-赫尔曼密钥交换(D-H,Diffie–Hellman key exchange)方案。

首先在目录 /etc/ssl/certs 运行以下代码生成 dhparam.pem 文件:

openssl dhparam -out dhparam.pem 2048

关于 ssl_dhparam 的配置,可以参考这篇文章:Guide to Deploying Diffie-Hellman for TLS

SSLv3 已被证实不安全,所以在 ssl_protocols 指令中,我并没有包含它。
将 ssl_prefer_server_ciphers 配置为 on,可以确保在 TLSv1 握手时,使用服务端的配置项,以增强安全性。

bookmark_bordernginx(openresty)升级ssl模块 以支持 HTTP/2

我们并不需要升级系统自带的 openssl,因为随便升级 openssl 会带来安全风险。我们只下载 openssl 1.0.2 源码包,借助它来重新编译(相当于重新安装)服务器上的 NginX 即可。一旦成了 built with OpenSSL 1.0.2,就意味着能够支持 http/2 了。

1、下载并解压 OpenSSL

cd /usr/local/src
wget https://www.openssl.org/source/openssl-1.0.2-latest.tar.gz
tar -zxf openssl-1.0.2-latest.tar.gz

2、修改编译参数

#指定opensll包位置
--with-openssl=/usr/local/src/openssl-1.0.2m

#开启ssl模块
--with-http_ssl_module

#开启http/2模块
--with-http_v2_module

#编译报错是“collect2: ld returned 1 exit status”需要指定pcre8.36及以上版本
--with-pcre=/usr/local/src/pcre-8.40

3、开启ssl 修改配置

server {
    listen       443 ssl;
    server_name  localhost;
    ....
}

4、开启http/2 修改配置

server {
    listen       443 ssl http2;
    server_name  localhost;
    ....
}

附:
pcre下载:ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/

bookmark_borderCentos下 Nginx安装与配置

首先由于nginx的一些模块依赖一些lib库,所以在安装nginx之前,必须先安装这些lib库,这些依赖库主要有g++、gcc、openssl-devel、pcre-devel和zlib-devel 所以执行如下命令安装
1.安装依赖包

yum install gcc gcc-c++ pcre pcre-devel zlib zlib-devel openssl openssl-devel ncurses-devel perl

2.下载最新版的nginx

wget http://nginx.org/download/nginx-1.10.2.tar.gz
tar -zxvf nginx-1.10.2.tar.gz

3.编译与安装

./configure --prefix=/usr/local/nginx
make
make install

4.查看nginx的安装目录

whereis nginx

5.启动

/usr/local/nginx

安装完成