启用免费且自动更新的泛域名SSL证书

经过数次跳票之后,Let's Encrypt2018年3月13日开始提供支持泛域名的SSL证书了,每次颁发证书的有效时间是3个月,因此Let's Encrypt提供了一个自动颁发和更新SSL证书的工具acme.sh,使用下来感觉比收费的还要方便。

安装证书自动签发/续签工具

acme.sh 实现了 acme 协议, 可以从 letsencrypt 生成免费的证书。下面我们以CentOS为例进行说明:

安装系统依赖

acme.sh需要curl、cron和socat的依赖支持,使用下面的任务进行安装:

yum update -y &&  yum -y install curl cron socat

对于官方不支持的CentOS版本(如5.x),可以手动下载和编译上述包进行安装。
PS:CentOS 5.x可以使用socat v1.7.2.4,安装方法可以参考:socat的安装与使用

开始安装 acme.sh

可以使用下面的命令安装:

curl  https://get.acme.sh | sh

安装脚本将所有的文件安装到 ~/.acme.sh/目录下,并自动创建一个定时任务,每天0:00自动检测所有的证书,如果过期了就会自动更新证书。

生成证书

生成证书的方式就两种:http方式dns方式,相对来说我更喜欢dns方式,这种方式可以使用dns解析商的API自动进行域名的验证等操作,非常方便。目前支持 cloudflare, dnspod, cloudxns, godaddy 以及 ovh 等数十种解析商的自动集成,如果你的域名不是使用的这些解析商的话,智能使用http方式进行手动验证了。

这里以常用的dnspod来介绍如果使用dns方式来生成证书,首先在DNSPOD用户中心-安全设置中开启API Token,然后创建一个API Token,并记住ID和Token,执行下面的命令:

export DP_Id="<your_dnspod_id>"
export DP_Key="<your_dnspod_token>"
~/.acme.sh/acme.sh --issue --dns dns_dp -d javatang.com -d *.javatang.com

上面的DP_Id和DP_Key是dnspod.cn API定义的变量名,--dns参数后面的dns_dp也指定了服务商为dnspod.cn,其他服务商的API名称见https://github.com/Neilpang/acme.sh/wiki/dnsapi。相同的ID和Key只要指定一次就可以了,acme.sh会自动将其保存在account.conf文件中。

后面-d参数用于生成证书的域名,如果想要生成泛域名的SSL证书必须按照上面的例子那样设定两次-d参数,第一次必须是主域名,不可以直接写泛域名的格式,后面一次是*.javatang.com泛域名的格式。如果不需要泛域名的SSL证书的话,只要指定一次-d参数就可以了。

执行上面的命令之后,acme.sh会自动校验域名的有效性并像Let's Encrypt请求SSL证书,成功之后会将证书放在~/.acme.sh/目录下面,但一定要注意,不要在nginx/apache中直接使用此目录下面的证书文件,这是因为脚本升级之后此目录会发生变化,会造成引用错误。正确的做法是再执行复制并安装证书。

复制并安装证书

使用--installcert命令可以将证书复制到固定的位置,并保证在更新证书之后自动重启nginx/apache,这里一nginx为例,执行的脚本如下:

~/.acme.sh/acme.sh --installcert -d javatang.com --key-file /etc/nginx/conf/cert/javatang.com.key --fullchain-file /etc/nginx/conf/cert/javatang.cer --reloadcmd "service nginx force-reload"

-d参数表示需要复制的域名名称,如果是泛域名的话直接使用主域名。

--key-file--fullchain-file参数分别表示所要复制的key和fullchain文件的位置和文件名,nginx/apache配置文件中所引用的SSL文件级为这里所设置的路径。

最后一个参数--reloadcmd也非常重要,这里表示证书更新之后自动重启nginx/apache的命令,这样才能保证更新之后的证书有效。我一开始将这个参数设置错误了,导致证书到期更新之后没有应用于nginx。

配置nginx/apache

最后不要忘记了还要再nginx/apache中引用上面的SSL证书,这里以nginx为例,配置文件如下:

server {
    listen 443 ssl;
    server_name javatang.com *.javatang.com;

     ssl on; 
    ssl_certificate   cert/javatang.com.cer;
    ssl_certificate_key  cert/javatang.com.key;
    ssl_session_timeout 5m; 
    ssl_protocols SSLv2 SSLv3 TLSv1;
    ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
    ssl_prefer_server_ciphers on;
}

再强调一下:这里引用的SSL证书一定不要是acme.sh原始下载的证书,而是使用--installcert命令复制的证书。

使用acme.sh还有一个好处就是不需要担心证书过期的问题,因为脚本会自动更新证书,非常方便。

查看证书的情况

最后可以通过https://crt.sh查询指定域名的证书详情。

常见问题

安装的时候出现 error code: 35

在有的服务器中遇到了下面的错误提示:

Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: 35

升级本地的openssl版本,可以使用下面的命令进行升级:

wget -c https://www.openssl.org/source/openssl-1.1.0-latest.tar.gz
tar xzvf openssl-1.1.0-latest.tar.gz
cd openssl-1.1.0*
./config --prefix=/usr/local/openssl
make && make install
mv /usr/bin/openssl /usr/bin/openssl.old -f
mv /usr/lib64/openssl /usr/lib64/openssl.old -f
mv /usr/lib64/libssl.so /usr/lib64/libssl.so.old -f
mv /usr/include/openssl /usr/include/openssl.old -f
ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl
ln -s /usr/local/openssl/include/openssl /usr/include/openssl
ln -s /usr/local/openssl/lib/libssl.so /usr/lib64/libssl.so
echo "/usr/local/openssl/lib" >> /etc/ld.so.conf
ldconfig -v

Let's Encrypt证书申请频率的限制

  • 同一个主域名一周之内只能申请50个证书
  • 每个账号下每个域名每小时申请验证失败的次数为5次
  • 每周只能创建5个重复的证书,即使是通过不同的账号进行创建
  • 每个账号同一个IP地址每3小时最多可以创建10个证书
  • 每个多域名(SAN) SSL证书(不是通配符域名证书)最多只能包含100个子域
  • 更新证书没有次数的限制,但是更新证书会受到上述重复证书的限制

访问SSL证书之后的系统偶尔会出现卡顿的问题

对服务器资源和网络状况进行排除之后,在nginx的error.log文件中发现有很多类似下面的错误信息:

2019/03/22 17:28:42 [crit] 20807#0: *249015212 SSL_shutdown() failed (SSL: error:140E0197:SSL routines:SSL_shutdown:shutdown while in init) while closing request, client: 100.200.70.100, server: 0.0.0.0:443

查阅了相关的资料发现,是nginx和openssl的版本太低,需要保证nginx的版本在1.9.12以上,openssl的版本在1.1.0以上。

出现tls_process_client_hello:version too low错误

使用低版本的IE浏览器会无法访问https,在nginx的error.log文件中出现下面的错误信息:

SSL_do_handshake() failed (SSL: error:1417D18C:SSL routines:tls_process_client_hello:version too low) while SSL handshaking

通过命令wget --secure-protocol=SSLv3 -O - https://www.javatang.com/ 进行测试,结果如下:

OpenSSL: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
Unable to establish SSL connection.

这是因为低版本的IE浏览器采用了SSLv3进行访问,而OpenSSL从1.1.0开始默认取消了SSLv3,即使在nginx的ssl_protocols配置项中增加SSLv3也是无效的,可以通过下面几种方法进行解决:
(1)编译OpenSSL v1.1.x的时候config的时候增加enable-ssl3 enable-ssl3-method参数,编译Nginx的时候configure的时候增加--with-openssl-opt="enable-ssl3 enable-ssl3-method",然后重新编译OpenSSL和Nginx。不过不建议这样操作,因为SSLv3有安全漏洞,可以采用下面的方法。
(2)在Nginx中通常会采用80端口做301跳转到433端口,可以取消跳转同时保留80和433两个端口的访问,或者判断浏览器是IE或采用了SSLv3的时候进行跳转,设置如下:

server {
    listen 80;

    set $oldclient 0;
    if ($http_user_agent ~* "MSIE") {
        set $oldclient 1;
    }
    if ($ssl_protocol = SSLv3) {
        set $oldclient 1;
    }
    if ($oldclient = 0) {
        rewrite ^(.*) https://$host$1 permanent;
        break;
    }
}

但我试了一下无法达到使用SSLv3访问SSL的时候自动跳转到80端口,直接就报错了。

参考资料:

acme.sh官方说明
获取免费的 Let’s Encrypt 泛域名 SSL 证书
Let’s Encrypt免费泛域名SSL证书申请教程
Let'sEncrypt 免费通配符/泛域名SSL证书添加使用教程
Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: 35
OpenSSL: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failur
Can't wget from github, sslv3 handshake error
Using acme.sh with nginx
SSL_shutdown() failed (SSL: error:140E0197:SSL routines:SSL_shutdown:shutdown while in init) while SSL handshaking
SSL error in nginx log
Nginx SSL_do_handshake() failed SSL: error:1417D18C:SSL
SSL negotiating too low a protocol?
How can I know the protocol versions supported at OpenSSL1.1.0g
Compiling OpenSSL 1.1.0f with SSLv3
Redirect users connecting with SSLv3 within nginx
SSL证书的自动化管理

发表评论

电子邮件地址不会被公开。 必填项已用*标注