动态加载证书和 OCSP stapling
Nginx 启动时会读取配置的证书内容,并经过一系列解析后,最终通过调用 OpenSSL 的 来设置证书。
对于匹配的私钥,Nginx 调用的是 SSL_use_PrivateKey
。
于是有了个新的想法:既然 OpenSSL 允许我们动态地设置证书和私钥,也许我们可以在建立连接前才设置证书和私钥呢? 这样一来,我们可以结合 SNI,针对不同的请求域名动态设置不同的证书和私钥,而无需事先把可能用到的证书和私钥都准备好。
借助 OpenResty,我们可以轻易地把这个想法变成现实。
所需的,是 指令和来自 lua-resty-core
的 模块。另外,编译 OpenResty 时指定的 OpenSSL 需要 1.0.2e 或以上的版本。
证书/私钥的格式分两种,一种是文本格式的 PEM,另一种是二进制格式的 DER。我们看到的证书一般是 PEM 格式的。 这两种不同的格式,处理代码有所不同。
— 解析出 cdata 类型的证书值,你可以用 lua-resty-lrucache 缓存解析结果 local cert, err = ssl.parse_pem_cert(cert_data) if not cert then ngx.log(ngx.ERR, “failed to parse PEM cert: “, err) return end
local ok, err = ssl.set_cert(cert) if not ok then ngx.log(ngx.ERR, “failed to set cert: “, err) return end
local pkey, err = ssl.parse_pem_priv_key(pkey_data) if not pkey then ngx.log(ngx.ERR, “failed to parse pem key: “, err) return end
local ok, err = ssl.set_priv_key(pkey) if not ok then ngx.log(ngx.ERR, “failed to set private key: “, err) return end
OCSP stapling
基于 CA 的 Public key infrastructure(PKI)需要有及时更新证书吊销情况的机制。 目前的主流方式是 Online Certificate Status Protocol (OCSP)。 即在获取到证书信息时,由浏览器负责向对应的 CA 发起证书吊销状态的查询。除了 Chrome ,其他浏览器都支持这一协议。
该方式有两个问题:
作为开发者,我们并不关心第一点。但第二点却不能不解决。 还好 OCSP 有一个“补丁”,叫 OCSP stapling。 Web 应用可以定期通过 OCSP stapling 从 CA 处获取自己证书的吊销状态,然后在 SSL 握手时把结果返回给浏览器。
既然我们的证书已经是动态加载的,我们也需要实现动态的 OCSP stapling。
— 上接动态获取 DER 格式的证书 — 当前 OCSP 接口只支持 DER 格式的证书 local ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(cert_der_data) if not ocsp_url then ngx.log(ngx.ERR, “failed to get OCSP responder: “, err) return ngx.exit(ngx.ERROR) end
local httpc = http.new() httpc:set_timeout(10000) local res, req_err = httpc:request_uri(ocsp_url, { method = “POST”, body = ocsp_req, headers = { [“Content-Type”] = “application/ocsp-request”, } })
— 校验 CA 的返回结果 if not res then ngx.log(ngx.ERR, “OCSP responder query failed: “, err) return ngx.exit(ngx.ERROR) end
local http_status = res.status
if http_status ~= 200 then ngx.log(ngx.ERR, “OCSP responder returns bad HTTP status code “, http_status) return ngx.exit(ngx.ERROR) end
local ocsp_resp = res.body
if ocsp_resp and #ocsp_resp > 0 then local ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert_chain) if not ok then ngx.log(ngx.ERR, “failed to validate OCSP response: “, err) return ngx.exit(ngx.ERROR) end
end ```
CA 返回的 OCSP stapling 结果需要缓存起来,直到需要刷新为止。目前 OpenResty 还缺乏提取 OCSP stapling 有效时间 (nextUpdate - thisUpdate)
的接口。
具体缓存多久会比较好,你也可以咨询下签发证书的 CA。