OpenResty 如何配置 HTTPs/SSL 证书介绍。
SSL
- SSL/TLS 原理详解
- OpenResty 使用 SSL 证书配置,
ssl_certificate/ssl_certificate_key
加载静态证书如下:
server {
// 监听端口
listen 443 ssl http2;
// 证书,必须是 PEM 格式
ssl_certificate ssl/demo.crt;
// 私钥,必须是 PEM 格式
ssl_certificate_key ssl/demo.key;
// 回话超时时间为 10 分钟
ssl_session_timeout 10m;
// 优先使用服务器加密算法
ssl_prefer_server_ciphers on;
}
ngx.ssl
ngx.ssl
库提供了专门处理 HTTPs 功能的接口。SSL操作比较耗时,建议使用 lrucache 共共享内存缓存起来,SSL 相关的内置变量
-- 显示加载
local ssl = require "ngx.ssl"
-- 获取版本号,数字格式
local ver, err = ssl.get_tls1_version()
ngx.say(string.format("0x%x", ver))
-- 低于 TLS1.0 则拒绝连接
if ver < ssl.TLS1_VERSION then
ngx.exit(400)
end
-- 获取字符格式版本号
local ver, err = ssl.get_tls1_version_str()
ngx.say(string.format("ver: ", ver))
-- 获取 SNI 主机名,即访问服务的域名
local name, err = ssl.server_name()
ngx.say("sni: ", name)
-- 服务器地址
-- addr_type inet: IPv4;inet6: IPv6;unix: Unix Domain Socket
addr_data, addr_type, err = ssl.raw_server_addr()
-- IPv4/IPv6 地址是二进制数字,解析如下
local byte = string.byte
string.format("%d.%d.%d.%d", byte(addr_data, 1), byte(addr_data, 2), byte(addr_data, 3), byte(addr_data, 4))
-- 客户端地址
addr_data, addr_type, err = ssl.raw_client_addr()
动态加载证书示例
- 加载动态证书,也需要配置
ssl_certificate/ssl_certificate_key
(Nginx 内部机制决定)
server {
// 监听端口
listen 443 ssl http2;
// 占位用,无实际意义
ssl_certificate ssl/demo.crt;
// 占位用,无实际意义
ssl_certificate_key ssl/demo.key;
// 回话超时时间为 10 分钟
ssl_session_timeout 10m;
// 优先使用服务器加密算法
ssl_prefer_server_ciphers on;
// 动态加载证书
ssl_certificate_by_lua_file cert.lua;
...
}
- 通过
ssl_certificate_by_lua
加载证书,在 server 中和 ssl_certificate/ssl_certificate_key
是同一个级别的,cert.lua
示例:
-- 显示加载
local ssl = require "ngx.ssl"
-- 解析 PEM 格式证书并设置
cert, err = ssl.parse_pem_cert(pem_cert)
ok, err = ssl.set_cert(cert)
-- 证书缓存
certs = {}
local name, err = ssl.server_name()
if not name then
ngx.exit(ngx.ERROR)
end
if not certs[name]; then
ngx.exit(ngx.ERROR)
end
-- 解析 cert
local cert, err = ssl.parse_pem_cert(certs[name].cert)
if not cert then
ngx.say("failed: ", err)
ngx.exit(ngx.ERROR)
end
-- 设置证书
local ok, err = ssl.set_cert(cert)
if not ok then
ngx.say("failed: ", err)
ngx.exit(ngx.ERROR)
end
-- 设置私钥
local priv_key, err = ssl.parse_pem_priv_key(pem_priv_key)
if not key then
ngx.say("failed: ", err)
ngx.exit(ngx.ERROR)
end
local ok, err = ssl.set_priv_key(key)
if not ok then
ngx.say("failed: ", err)
ngx.exit(ngx.ERROR)
end
ocsp
- server 中使用
ssl_stapling on
开启 OCSP Stapling 功能,ngx.ocsp
库提供相关操作 - 查询证书状态步骤
- 将证书转化为 DER 格式
ssl.cert_pem_to_der
- 获取 OCSP 服务器的 URL
ocsp.get_ocsp_responder_from_der_chain
- 生成 OCSP 请求体
ocsp.create_ocsp_request
- 通过 HTTP 协议发送请求,获取响应
- 检查响应
oscp.validate_ocsp_response
local ocsp = require "ngx.ocsp"
local ssl = require "ngx.ssl"
local http = require "resty.http"
local cert, err = ssl.parse_pem_cert(pem_cert)
-- 证书格式转化为 DER 格式
local der_cert, err = ssl.cert_pem_to_der(cert)
-- 获取 OCSP 服务器的 URL
local ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert)
-- 生成 OCSP 请求体
local ocsp_req, err = ocsp.create_ocsp_request(der_cert)
- 通过 HTTP 协议发送请求,获取响应
local httpc = http.new()
local res, err = httpc:request_uri(ocsp_url, {
method = "POST",
body = ocsp_req,
headers = {
["Content-Type"] = "application/ocsp-request",
}
})
local ocsp_resp = res.body
- 检查响应
if ocsp_resp and #ocsp_resp > 0 then
local ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert)
if not ok then
ngx.say("fail to validate: ", err)
ngx.exit(ngx.ERROR)
end
end
-- 可以将 ocsp_resp 缓存起来,提高效率
-- 通知客户端,必须在 ssl_certificate_by_lua 阶段
local ok, err = ocsp.set_ocsp_status_resp(ocsp_resp)
if not ok then
ngx.say("fail to set: ", err)
ngx.exit(ngx.ERROR)
end
SSL 会话复用
会话复用(Session Resumption)
SSL 完成握手后,将通信使用的密钥存起来,下次直接使用。有两种实现方式:
Session ID
Nginx 的 ssl_session_cache
支持,仅支持本机复用- 通过
ssl_session_fetch_by_lua
和 ssl_session_store_by_lua
可以将会话存储到 Redis 等外部服务器,实现多实例复用 ssl_session_fetch_by_lua
发生在 ssl_certificate_by_lua
之前,获取历史会话信息ssl_session_store_by_lua
发生在 ssl_certificate_by_lua
之后,Preread 之前,用来存储回话信息- 通过 ngx.ssl.session 库实现,由于
Session ID
存在缺陷,TLS1.2 中被 Session Tickets
取代
Session Tickets
会将服务器加密后的会话信息发送给客户端,有客户端负责保存(Ticket),再次 TLS 通信时,客户端只需要将 Ticket 发送给服务器验证即可
server {
listen 443 ssl;
ssl_session_tickets on; // 启用 session Tickets
ssl_session_ticket_key ssl/ticket.key; // 加密 ticket 使用的密钥文件,ngx.ssl.session.ticket.key_rotation 可以利用 memcached 使用 ticket 的定期轮换 rotation
}