Let's HTTPS

更新说明

本文中的Let’s Encrypt配置部分内容已过时,更新内容见《HTTPS配置全记录》。

安全第一

在兲朝这样的网络环境中,相信大家都或多或少碰到过各种网络被劫持的情况吧:

  • 比如运营商在你访问的网站里插入广告脚本……
  • 比如路由器把你访问的网站404页面换成广告页面
  • 曾经有过的某些生财手段是把你访问的购物网站换成返利链接
  • 更不用过某些所谓的免费WIFI在默默在记录你的用户密码
  • 还有移动应用开发者应该都碰到过设计的API响应错误代码在中途被改得面目全非
  • 当然还有一些不可说的情况……

这些都是因为HTTP是一个明文传输的协议,不具有基本的安全性,所以我们需要HTTPS!

有什么障碍

早年,制约HTTPS普及的障碍主要有两个:

  • 证书成本
  • IP成本

至于HTTPS中加入的SSL所增加的服务器资源消耗其实也并不是不可接受的。

证书成本在于,虽然你可以使用免费的自签名证书,但是这种证书得不到浏览器的认可,会弹出警告信息,反而增加了用户的不安全感,而购买商业证书则成本较高,尤其是泛域名证书等高级一些证书。

IP成本则是因为SSL在HTTP下一层,所以传统的虚拟主机配置方式不可用,一个证书只能对应一个IP,如果在一个IP上配置了多个域名的虚拟主机就没办法了。

解决方案

还好现在这两个问题都不再是问题。

  • 证书:我们现在有免费的Let’s Encrypt,可以提供被各大浏览器认可的免费证书。
  • HTTPS虚拟主机:现在也有了被普遍实现的 TLS Server Name Indication extension(SNI, RFC 6066) 。

所以你为什么还不HTTPS?

nginx配置

以nginx为例,要支持SNI,必须使用包含了SNI扩展的OPENSSL,当然现在的新版本默认都已经加上了,只要用以下命令看一下,有 TLS SNI support enabled 一行即可。

nginx -V

之后就用像HTTP的虚拟主机类似的方法配置即可:

server  {
    listen 443 ssl http2;
    server_name   www.yoursite1.com;
    index index.html index.php;
    root  /var/www/www.yoursite1.com/;
    ssl_certificate "/etc/nginx/ssl/yoursite1.crt";
    ssl_certificate_key "/etc/nginx/ssl/yoursite1.key";
    ...
}

server  {
    listen 443 ssl http2;
    server_name   www.yoursite2.com;
    index index.html index.php;
    root  /var/www/www.yoursite2.com/;
    ssl_certificate "/etc/nginx/ssl/yoursite2.crt";
    ssl_certificate_key "/etc/nginx/ssl/yoursite2.key";
    ...
}

不过因为现在证书还没有配置好,所以这个配置暂时不要启用,否则会因为找不到证书文件而启动失败。

Let’s Encrypt配置

当然,用官方推荐的方式配置是可以的,但是在墙内试过失败了,大概是因为某些不可告人原因吧。

后来试了这篇文章《Let’s Encrypt,免费好用的 HTTPS 证书》介绍的acme-tiny,发现更简单好用。

具体做法分为以下几个步骤:

  • 生成用户私钥
  • 生成网站的CSR
  • 用Let’s Encrypt的服务对CSR签名生成证书
  • 部署证书并重启WEB服务

生成用户私钥

这是必做的第一步,用于向Let’s Encrypt标识你的用户身份。

# 生成private key
openssl genrsa 4096 > private.key

生成网站的CSR

需要两个步骤:第一步是生成网站的私钥(不同于前面的用户私钥),第二步是输入相应的网站信息生成CSR。

这里写成一个脚本以便使用:certinit.sh

#!/bin/bash

cd /etc/nginx/ssl/
openssl genrsa 4096 > $1.key
openssl req -new -sha256 -key $1.key -out $1.csr

使用时运行:

certinit.sh yoursite

即可在/etc/nginx/ssl下生成yoursite.key和yoursite.csr两个文件,别是网站私钥和网站CSR。

在交互操作中输入网站的相关信息,其中最重要的就是域名,不得有误,其它根据提示输入即可。

对CSR签名生成证书

这步最麻烦也最重要。

因为Let’s encrypt需要验证这个域名确实属于你,这跟商业证书是一样的,只是商业证书有很多方法可以验证(比如通过域名管理员邮箱之类),但Let’s encrypt只有这一个方法。

就是在此域名的服务器上创建一个特定文件,然后通过WEB方式访问,如果可以访问成功,则验证通过。

所以需要在nginx上加一个HTTP的服务:

server {
    ...
    server_name www.yoursite.com yoursite.com;
    location ^~ /.well-known/acme-challenge/ {
        alias /var/www/challenges/;
        try_files $uri =404;
    }

    location / {
        rewrite ^/(.*)$ https://yoursite.com/$1 permanent;
    }
}

注意:除了指定的验证路径,其它对HTTP的访问都被重定向到了HTTPS(虽然现在HTTPS还不能用)。

现在可以开始生成证书了。为简单起见,这里也写成一个脚本(后面还有用处):certrenew.sh

#!/bin/bash

cd /etc/nginx/ssl/
python acme_tiny.py --account-key private.key --csr $1.csr --acme-dir /var/www/challenges > signed.crt || exit
# wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > letsencrypt.pem
cat signed.crt letsencrypt.pem > $1.crt
rm signed.crt
nginx -t || exit
kill -HUP `cat /var/run/nginx.pid`

其中需要注意的是:

letsencrypt.pem这个中间证书可以提前下载好保存,不必每次生成证书都下载一遍,所以其中那句wget被注释掉了。

部署证书并重启WEB服务

当然,现在这样重启服务还是无效的。因为前面说了,证书没生成之前暂时不启用HTTPS配置,所以现在需要去把那个HTTPS配置给启用了,再重启一次即可。

注意:SNI配置下的每个域名都需要单独的证书配置。同样,也需要如上面所说,一个个域名单独生成证书再单独启用。

最后一个问题是:Let’s encrypt的证书只有三个月有效期,所以需要配置一个自动更新任务,在cron里加上(这里就用上了前面那个证书更新脚本):

0 0 1 */2 * /etc/nginx/ssl/certrenew.sh yoursite1 > /dev/null 2>&1

这样就可以每隔两个月在1号的凌晨0点(时间自己定)更新一次证书。

现在,你用https://www.yoursite1.com/访问你的网站,就可以看到浏览器地址前面有个绿色的小锁,表示你的网站已经是被认可的HTTPS网站。

还没有完

虽然启用了HTTPS的确已经比较HTTP安全很多,但是……还不够。

HTTPS中的S是个很复杂的概念,实现起来也很复杂,经过了多年的发展,也有了一些遗留的问题,特别是为了兼容旧版的浏览器或系统而保留的一些东西是存在安全隐患的。

所以,建议通过这个网站检查一下你的配置是不是有安全隐患:SSL labs

常见的安全隐患通常与加密算法和安全协议版本有关,比如最好使用TLS1.0以后的版本,而不要使用SSL2和3。

可供参考的配置如下:

ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers  ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:!DHE-RSA-AES128-GCM-SHA256:!DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:!DHE-RSA-AES128-SHA256:!DHE-RSA-AES128-SHA:!DHE-RSA-AES256-SHA256:!DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS;

如此配置已经兼容了目前绝大部分较新的系统和浏览器,包括手机等移动设备。但也的确有少量旧系统不兼容,那些多半是有一些安全风险的,自己考虑如何取舍了。

推送到[go4pro.org]