mTLS配置里,CA放进ssl_trusted_certificate后,客户端看到的名称列表却没变;CA没出现在列表里,它签发的证书仍可能被接受。别急着认定配置没加载:“提示客户端选证”和“服务端信任谁”不是一回事。
用隔离Nginx验证,避免把提示列表当授权白名单。
一、先区分三个方向
普通HTTPS中客户端验证服务端;mTLS再增加反方向验证。本文使用独立服务端CA和客户端CA A、CA B,避免混淆信任方向。
ssl_certificate配置服务端证书;两种CA指令配置服务端验证客户端等用途的材料。curl的--cacert则用于客户端验证服务端。都有CA二字,方向不同。

图为独立检查点,不是完整流程。名称列表与信任CA可相同也可不同,不能只凭列表推断。加载材料不代表验证通过,通过也不等于业务授权。
二、两个指令到底差在哪里
Nginx官方文档说明,两者都加载PEM格式的可信CA,用于验证客户端证书,也可用于启用装订时验证OCSP响应。区别是:ssl_client_certificate设置的CA名称列表会发给客户端;ssl_trusted_certificate加入的CA不会因此进入该列表。
| 对象 | 核心职责 | 不能据此推断 |
|---|---|---|
| ssl_client_certificate | 加载信任材料并提供CA名称列表 | 列表就是全部授权对象 |
| ssl_trusted_certificate | 补充服务端信任材料 | 只供OCSP使用、不影响mTLS |
| ssl_verify_client on | 要求并验证客户端证书 | 业务接口已授权该身份 |
这里的列表是CertificateRequest里的可接受CA名称,不是把所有根证书原文发出去。RFC5246第7.4.4节把TLS1.2字段定义为DER编码的X.501名称。本文固定TLS1.2观测,不把同一输出格式套到所有TLS版本。
三、用两套客户端CA搭一个对照
准备由服务端CA签发、含api.example.test名称的server.pem及对应密钥;再准备客户端CA A、CA B与分别由它们签发、具有clientAuth用途的客户端证书。CA公钥文件和客户端私钥分开保存,示例路径仅供隔离实验。
下面是server片段,放入独立http上下文,不是可直接覆盖生产的完整配置。相对路径按独立Nginx配置目录解析;端口18443需换成自己的空闲回环端口。实际测试使用随机端口,私钥只存在于临时目录。
server {
listen 127.0.0.1:18443 ssl;
server_name api.example.test;
ssl_protocols TLSv1.2;
ssl_certificate server.pem;
ssl_certificate_key server.key;
ssl_client_certificate client-b-ca.pem;
ssl_trusted_certificate client-a-ca.pem;
ssl_verify_client on;
ssl_verify_depth 2;
location / { return 200 "$ssl_client_verify\n"; }
}
关键点:B进入可接受CA名称列表,A通过trusted配置加入信任。不要把B当作无效占位符:它同时是实际信任材料,B签发的合格客户端证书也可能通过。把旧CA从名称列表移走,却留在trusted文件里,不等于撤销信任。
四、先看握手提示,再看验证
在材料目录执行命令,显式使用A证书并验证服务端名称和链。保留verify_return_error,不能只看一行Verify return code: 0。
openssl s_client -connect 127.0.0.1:18443 \
-servername api.example.test -tls1_2 \
-CAfile server-ca.pem -no-CApath \
-verify_hostname api.example.test -verify_return_error \
-cert client-a.pem -key client-a.key </dev/null
检查Acceptable client certificate CA names段:本例有B而无A,与服务端Certificate chain是不同对象。显式传入证书不同于浏览器自动选证,不能保证浏览器自动挑到A。
五、HTTP结果必须单独读
s_client用于检查握手信息,随文命令没有发送HTTP请求。要验证Nginx是否真正接受该客户端,再执行curl;此处不使用-k,也不关闭客户端验证。
curl --noproxy '*' --tlsv1.2 --tls-max 1.2 \
--resolve api.example.test:18443:127.0.0.1 \
--cacert server-ca.pem \
--cert client-a.pem --key client-a.key \
--fail --silent --show-error --max-time 10 \
--output response.txt --write-out '%{http_code}\n' \
https://api.example.test:18443/
rc=$?
printf 'curl_exit=%s\n' "$rc"
exit "$rc"
成功判据是退出0、HTTP200、response.txt为SUCCESS。保留curl退出码,不能让printf覆盖失败;失败留下的文件不算新验收产物。
六、隔离实验得到什么
本次运行环境为Nginx1.26.3(构建链接OpenSSL1.1.1w)、命令行OpenSSL1.1.1k与curl7.61.1。三组都只监听127.0.0.1随机端口,使用临时CA和独立进程,没有reload生产实例。
基线仅配置client_certificate=A:名称列表有A,A客户端得到200/SUCCESS。改为client_certificate=B加trusted=A:列表只有B,A与B客户端均得到200/SUCCESS。再去掉trusted=A、只保留B:A请求被拒绝,curl使用--fail返回22、HTTP400;B仍成功。无客户端证书也被拒绝。
列表没有A不代表不信任A。400只是受检Nginx默认处理的观察值,不推广到所有后端、协议与错误映射。
七、迁移或收紧信任时怎么查
列出server及继承配置加载的CA文件,核对来源。搬到trusted不等于“隐藏但不信任”。收紧范围前先评审依赖,再用新旧CA做正反例。
SUCCESS不表示身份可以访问所有资源,业务授权另查。本文未测试OCSP、吊销或浏览器选证,不代表生产迁移完成。
八、最终验收清单与资料
- 服务端链与客户端CA方向分开,材料和私钥权限已核对。
- CA名称列表与完整信任配置分别记录,不把提示当授权清单。
- 正确证书、错误CA签发证书和无证书三类请求分别检查。
- 握手诊断、HTTP状态和业务授权各有证据,不由一个成功代替全部。
本文由AI辅助整理,命令在上述隔离环境抽取重放;临时密钥和监听已清理,不代表外部CA签发或生产部署验收。
384



被折叠的 条评论
为什么被折叠?



