标签 NginX 下的文章

前言

无意翻到 Let's Encrypt (下称 LE )已支持 OCSP Must-Staple 扩展的消息
再加上之前也想升级双证书
于是又有了重新折腾一次的动力。
记录如下——

特别提示:
如果仅是希望使现有证书支持 OCSP Must-Staple ,
用原私钥重新生成 CSR 申请证书即可。
详见 CSR 一节。

准备

服务器环境

推荐使用 nginx-autoinstall 脚本进行安装,
原 Repo 仅适用于 Debian 8
其它发行版需要稍作修改。

编译并安装完可以使用如下工具测试相关特性:

工具选择

CA 当然选择免费且开源的 LE
工具方面则依旧选用 ACME-Tiny
官方列表中亦罗列不少,
包括最近很流行的国人作品 ACME.sh
若选用其它工具则签发流程会有所变化(相应环节会稍作说明)。

密钥强度及算法选择

RFC5480 ,对 ECDSA 证书的推荐组合如下:













- 阅读剩余部分 -

前言

 QuChao.com 的 Stapled OCSP Response

去年末“赶时髦”申请了 Let's Encrypt(下称 LE )的免费证书,
遗憾的是还不支持泛域名 ECC 证书(后者已被支持,中间证书则尚未提供),
不过一切都在进行之中。
有兴趣者请密切关注其论坛。

其中间证书虽已让被广泛支持的 IdenTrust 根证书 交叉签发,
然而它仍旧只是个刚“出道”的 CA ,
不少服务器和本地程序还未信任其证书,
一些朋友可能与我一样在配置 ssl_stapling_file 时遇到一些困难,
于是我将这部分配置过程从笔记里摘出来与大家分享、探讨。

关于 OCSP Stapling 的资料很多,
自己也还在学习之中,
这里仅给出一些链接:
简单地说它是一种的优化手段——
将原本需要客户端实时发起的 OCSP 请求转嫁给服务端;
利用 NginX 的 ssl_stapling_file 指令更可将业已缓存的查询结果直接返回,
使得缓存的更新时间更可加控。

环境

我使用的环境如下:

其中 NginX 版本最为关键,
它从 1.3.7+ 开始支持该特性。

  • CentOS/7.2.x
  • NginX/1.9.x
  • OpenSSL/1.0.1e

启用 OCSP Stapling

我们假定你已从 LE 申请到了证书,
并能正常提供服务。<!--more-->

推荐在服务器上使用 ACME-Tiny 来申请、续期证书。

我的配置参考了 H5BPNginX Boilerplate Configs
在其 /directive-only/ssl-stapling.conf 中推荐定义如下:

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/certs/quchao.com/chained.pem;
resolver 8.8.8.8 8.8.4.4 216.146.35.35 216.146.36.36 valid=60s;
resolver_timeout 2s;

ssl_certificate 指令指定了完整的证书链,
ssl_trusted_certificate 可省略。

上述配置即方便地开启了 OCSP Stapling 特性。
当客户端访问时 NginX 将去指定的证书中查找 OCSP 服务的地址,
获得响应内容后通过证书链下发给客户端。

可通过此工具来查看证书的有效期及撤销状态,
配置成功可看到 (STAPLED) 状态关键词。

启用 ssl_stapling_file

2016-12-27 特别提示
目前由于 LE 的 OCSP 不支持多证书合并响应,
此指令也无法多次使用,
因此多证书情形下无法工作!

接下来我们更进一步——
直接将 OCSP 响应存成文件,
NginX 将其随证书下发而不实时查询。

首先我们需要找到 OCSP 服务地址:

chained.pem 即网站完整的证书链。

openssl x509 -in /path/to/certs/quchao.com/chained.pem -text | grep "OCSP - URI:" | cut -d: -f2,3

得到的地址是 http://ocsp.int-x1.letsencrypt.org/
自 26th Mar, 2016 已启用 http://ocsp.int-x3.letsencrypt.org/已兼容 WinXP )。

接着获取 OCSP 响应并写入 ocsp.resp 文件:

openssl ocsp -no_nonce \
             -respout /path/to/certs/quchao.com/ocsp.resp \
             -issuer /path/to/certs/lets-encrypt-x3-cross-signed.pem \
             -cert /path/to/certs/quchao.com/quchao.com_chained.pem \
             -url http://ocsp.int-x3.letsencrypt.org/ \
             -header "HOST" "ocsp.int-x3.letsencrypt.org"

注意相关文件的权限;
lets-encrypt-x3-cross-signed.pemLE中间证书
有些 openssl 版本不支持指定 -header 参数,请选择合适的版本。

若运行时报错如下:

Response Verify Failure
140060623058848:error:27069076:OCSP routines:OCSP_basic_verify:signer certificate not found:ocsp_vfy.c:85:

则服务器可能并未信任 LE 的根证书或中间证书;
有多种途径解决:

  • 直接将这些证书加入系统的 ca-bundle.crt 里;
  • 生成一个专用的 bundle 。

下载相关根证书和中间证书

它们是:

  • 根证书:DST Root CA X3
  • 根证书:ISRG Root X1
  • 中间证书:Let’s Encrypt Authority X1 (分别被上述两个根证书签发)
  • X1 的候补中间证书:Let’s Encrypt Authority X2
  • 中间证书:Let’s Encrypt Authority X3 (兼容 WinXP)
  • X3 的候补中间证书:Let’s Encrypt Authority X4

不管采用哪种方式,
我们先将证书全部保存到本地。

mkdir -p /path/to/tmp/
cd /path/to/tmp/
curl -o ./DST_Root_CA_X3.pem -L https://ssl-tools.net/certificates/dac9024f54d8f6df94935fb1732638ca6ad77c13.pem

curl -O -L https://letsencrypt.org/certs/isrgrootx1.pem

curl -O -L https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
curl -O -L https://letsencrypt.org/certs/letsencryptauthorityx3.pem
curl -O -L https://letsencrypt.org/certs/lets-encrypt-x4-cross-signed.pem
curl -O -L https://letsencrypt.org/certs/letsencryptauthorityx4.pem

curl -O -L https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem
curl -O -L https://letsencrypt.org/certs/letsencryptauthorityx1.pem
curl -O -L https://letsencrypt.org/certs/lets-encrypt-x2-cross-signed.pem
curl -O -L https://letsencrypt.org/certs/letsencryptauthorityx2.pem

方法一:将它们加入系统的 ca-bundle.crt

以 CentOS/7 为例。

mv /path/to/tmp/*.pem /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

执行完毕可看看 /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt 是否已包含上述证书。

方法二:生成专用的 ca-bundle.pem

我使用该方式。

cat /path/to/tmp/*.pem > /path/to/certs/ca-bundle.pem

特别注意
letsencryptauthorityx2.pem 文件为 Dos 格式,
会导致 concat 后 PEM 格式错误,
请尝试将其转换为 Unix 格式(末尾增加换行)后重新生成 Bundle 。

再次尝试缓存 ocsp.resp 文件

此时再执行:

openssl ocsp -no_nonce \
             -respout /path/to/certs/quchao.com/ocsp.resp \
             -issuer /path/to/certs/lets-encrypt-x3-cross-signed.pem \
             -cert /path/to/certs/quchao.com/quchao.com_chained.pem \
             -CAfile /path/to/certs/ca-bundle.pem \
             -VAfile /path/to/certs/ca-bundle.pem \
             -url http://ocsp.int-x3.letsencrypt.org/ \
             -header "HOST" "ocsp.int-x3.letsencrypt.org"

注意新增的 -CAfile-VAfile 参数。

若无其它意外,
你会看到如下结果:

Response verify OK
/path/to/certs/quchao.com/chained.pem: good
    This Update: Jan 17 06:00:00 2016 GMT
    Next Update: Jan 24 06:00:00 2016 GMT

证明 OCSP 请求缓存成功,
且告知了过期时间。

开启特性

接下来放心大胆地在 NginX 配置中增加一行:

ssl_stapling_file /path/to/certs/quchao.com/ocsp.resp;

重载配置后,
执行如下命令测试:

echo QUIT | openssl s_client -connect quchao.com:443 -status 2> /dev/null | grep -A 17 'OCSP response:' | grep -B 17 'Next Update'

若得到类似 OCSP Response Status: successful 的答复即算成功。(亦可通过之前的工具进行验证)

定期更新

我们注意到响应内容中有一行 Next Update: Jan 24 06:00:00 2016 GMT
表示我们所缓存的响应内容将于这个时间点过期。
为了避免过期的情形,
我们可以编写脚本并设定 CronJob 来一劳永逸地解决问题。

脚本

如下是我所使用的脚本(参考),
使用时请注意相关文件的路径权限问题。

#!/bin/bash
#############################
#
# OCSP Cache Updater v0.2.1
#   by Qu Chao
#
# ---------------------------
#
# Usage:
#   % update_ocsp_cache "example.com"
#
# ---------------------------
#
# v0.1.0 - Initialization.
# v0.2.0 - Use X3 intermediate certs by default.
# v0.2.1 - Add generation of intermediates as an option.
#
#############################

if [ -z $1 ]; then
    echo -e "No domain is specified!" >&2
    exit 1
fi

############################
# CONFIGS
############################
#LE_DIR="/usr/local/etc/nginx/ssl"

LE_DIR="/etc/nginx/ssl"
GEN_NUM="x3"

############################
# DEFAULTS
############################
DOMAIN=$1
CERT_DIR="${LE_DIR}/${DOMAIN}"
CHAINED_CERT="${CERT_DIR}/chained.pem"

ISSUER_CERT="${LE_DIR}/lets-encrypt-${GEN_NUM}-cross-signed.pem"
CA_FILE="${LE_DIR}/ca-bundle.pem"
OCSP_RESP_FILE="${CERT_DIR}/ocsp.resp"
OCSP_REPLY_FILE="${CERT_DIR}/ocsp.reply"

############################
# MAIN
############################
# Functions
existence_pattern_check(){
    [ -n "$1" ] && [ -e "$1" ] && ( [ "${2:-file}" = "file" ] && ( [ -f "$1" ] || [ -L "$1" ] ) || [ -d "$1" ] ) && [ -r "$1" ]
}

# Params Validation
if ! existence_pattern_check "${LE_DIR}" "dir"; then
    echo -e "Let's Encrypt folder is missing!" >&2
    exit 1
elif ! existence_pattern_check "${CERT_DIR}" "dir"; then
    echo -e "Domain cert folder is missing!" >&2
    exit 1
elif ! existence_pattern_check "${CHAINED_CERT}" || ! existence_pattern_check "${ISSUER_CERT}" || ! existence_pattern_check "${CA_FILE}"; then
    echo -e "Required certs file is missing!" >&2
    exit 1
fi

# Get OCSP URI & HOST
OCSP_URL=$(openssl x509 -in "${CHAINED_CERT}"  -text | grep "OCSP - URI:" | cut -d: -f2,3)
OCSP_HOST=$(echo "${OCSP_URL}" | awk -F/ '{print $3}')

# Output OCSP response
openssl ocsp -no_nonce \
             -respout "${OCSP_RESP_FILE}.new" \
             -issuer "${ISSUER_CERT}" \
             -cert "${CHAINED_CERT}" \
             -CAfile "${CA_FILE}" \
             -VAfile "${CA_FILE}" \
             -url "${OCSP_URL}" \
             -header HOST "${OCSP_HOST}" > "${OCSP_REPLY_FILE}" 2>&1

# Check if it's all okay?
if  grep -q "Response verify OK" "${OCSP_REPLY_FILE}" && grep -q "${CHAINED_CERT}: good" "${OCSP_REPLY_FILE}" ; then
    if  cmp -s "${OCSP_RESP_FILE}.new" "${OCSP_RESP_FILE}" ; then
        # No news is good news
        rm "${OCSP_RESP_FILE}.new"

        echo -e "OCSP cache is up-to-date!"
    else
        # Update the cache file
        mv "${OCSP_RESP_FILE}.new" "${OCSP_RESP_FILE}"

        # reload nginx's config
        /usr/sbin/nginx -s reload

        echo -e "OCSP cache is updated!"
    fi
else
    # Bad things happen all the time
    cat "${OCSP_REPLY_FILE}" >&2

    echo -e "Failed to update OCSP cache!" >&2
fi

# Make a backup
mv "${OCSP_REPLY_FILE}" "${OCSP_REPLY_FILE}.old"
echo -e "Detailed log located at ${OCSP_REPLY_FILE}.old"

将脚本保存为 /path/to/certs/scripts/update_ocsp_cache.sh 并赋予执行权限。

计划任务

接下来新增一个 Hourly CronJob :

1 * * * * /path/to/certs/scripts/update_ocsp_cache.sh quchao.com 2>&1 >/dev/null | (test -s /dev/stdin && /usr/bin/mail -s "$HOSTNAME - Hourly OCSP Cache Update" mail@to.me || echo 'OCSP Cache is Up-to-date!')

这样一来,
服务器每小时都会尝试缓存新的 OCSP 响应,
当响应内容有变化便会自动更新缓存重载配置
若过程中发生异常则会通过 mailx 邮件通知我们上线处理。

更新日志

20160327 - LE 中间证书已改用 X3 (兼容 WinXP ),OCSP 服务器亦有调整;本文已更新。












































- 阅读剩余部分 -