OpenSSL报permitted subtree violation:证书SAN与CA名称约束排查

HTTPS叶证书没过期,SAN也包含访问域名,OpenSSL却报 permitted subtree violation。别急着重装根证书:这可能是CA证书的名称约束拒绝了下级名称。下面区分"访问名能否匹配"和"这条链能否为这些名称背书",再用error 47、48和离线命令定位。

一、SAN匹配,不代表名称约束通过

主机名验证检查访问名是否匹配叶证书;nameConstraints限制CA下级证书的名称范围。前者通过,后者仍可失败。好比姓名对得上,签发机构的授权范围却不覆盖这张证件。

中间CA只允许example.test,叶证书却含api.example.test与api.other.test。只访问前者,多余DNS SAN仍可让路径被拒绝。

二、从error 47和48定位,不把depth当约束来源

本次OpenSSL 1.1.1k FIPS离线实验中,47对应permitted subtree violation,48对应excluded subtree violation。前者是名称未落在允许范围,后者是命中排除范围。错误日志还会给出subject与depth,需要完整保留。

depth 0是待验证叶证书,1是签发它的CA,以此类推。但名称约束失败时,depth可以指向被检查名称所在的证书,并不保证指向写着nameConstraints的CA。本例约束放在中间CA,两类失败都报depth 0;别因此只修改叶证书配置。

hostname mismatch走主机名分支;unable to get local issuer certificate先处理构链。错误可不止一条,不用最后一行代替诊断。

三、逐张检查最终证书,不只看CSR

bash 复制代码
openssl version
# 每个文件只放一张证书;先确认文件角色
for f in root.pem issuing.pem leaf.pem; do
  printf "\nfile=%s\n" "$f"
  openssl x509 -in "$f" -noout -subject -issuer \
    -serial -fingerprint -sha256
  openssl x509 -in "$f" -noout \
    -ext subjectAltName,nameConstraints,basicConstraints
done

重点读取每张CA的Name Constraints和叶证书全部SAN。x509只读首张证书,fullchain.pem也不例外;多级链先拆分并编号。

RFC 5280规定名称约束扩展只用于CA证书,并应标为critical。permittedSubtrees表示允许范围,excludedSubtrees表示排除范围,命中排除范围仍应拒绝。存在多个受约束CA时,要满足路径上适用的各级限制,不是让下级写一个更宽范围就覆盖上级。

四、固定信任材料,分开验证名称与路径

bash 复制代码
openssl verify -no-CApath -CAfile root.pem \
  -untrusted issuing.pem -purpose sslserver \
  -verify_hostname api.example.test -show_chain leaf.pem
rc=$?
printf "verify_exit=%s\n" "$rc"

命令只验本地文件,不连接网站。CAfile固定根,untrusted提供中间链,leaf.pem是目标。名称、用途、路径一起检查,OK且退出0才通过;show_chain成功时显示路径。

为定位可另做不带verify_hostname的对照,但它只是不执行这项访问名匹配,并不会解除CA名称约束。本次多SAN越界样本去掉该参数仍报47。不能把这种诊断对照写进生产验收脚本,宣称域名也已验证。

不要提升中间CA为信任锚或关闭验证来消除错误;这改变了信任边界,并非修复原路径。

五、配置片段表达什么,修复就改什么

ini 复制代码
[issuing_ca]
basicConstraints = critical,CA:TRUE,pathlen:0
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
nameConstraints = critical,permitted;DNS:example.test,excluded;DNS:admin.example.test

这是CA扩展片段,不是完整生产CA配置:允许example.test及其DNS子域,排除admin.example.test及其子域。它不是DNS解析规则,也不是网站访问控制。pathlen:0描述中间CA层级,不能替代名称范围检查;两种约束要分别满足。

DNS、IP、URI、邮件地址是不同名称类型,不能把permitted;DNS当作"只允许所有类型身份属于这个域"的总开关。本文只测试DNS SAN,不把结果推广到IP范围、通配符或其他客户端实现;涉及前导点、国际化域名等边界时应另建准确样本验证。

如果多余SAN是申请错误,由授权CA按正确名称集合重新签发叶证书。若业务确需范围外名称,应选择政策允许的签发路径,或由CA管理员审批后调整并重签适用CA证书。只改CSR、配置文件或服务端server_name,不会改变已经签名的扩展。

六、用正反例确认,不只测一个能通的名字

本次用临时根、中间CA和serverAuth叶证书离线验证,未改系统信任、时钟或生产服务,材料已清理。不冒充浏览器或线上握手实测。

叶证书或条件 退出码与错误 结论
仅api.example.test 0;OK 允许范围内
仅api.other.test 2;47,depth 0 超出允许范围
api.example.test加api.other.test 2;47,depth 0 访问名匹配仍不足
仅admin.example.test 2;48,depth 0 允许范围内但被排除
合规证书,验证wrong.example.test 2;62,depth 0 主机名不匹配
bash 复制代码
# 在隔离夹具中比较四份证书的路径约束,不检查访问名
for f in good.pem outside.pem mixed.pem excluded.pem; do
  openssl verify -no-CApath -CAfile root.pem \
    -untrusted issuing.pem -purpose sslserver "$f"
  rc=$?
  printf "%s exit=%s\n" "$f" "$rc"
done

四份文件对应表中前四行,并非内置样本。循环不验访问名,需另用verify_hostname。总体退出码可能来自末尾printf;自动化应逐项判断rc。

七、上线前还差一次真实客户端验证

离线通过不说明服务已加载新证书。部署后从实际客户端读取线上指纹、SAN与链,以正确主机名完成握手和业务请求。不同TLS库支持可能不同,本机OK不能替所有客户端背书。

八、验收清单与官方依据

  • 记录错误原文、退出码、depth与证书指纹,定位名称及约束来源。
  • 检查全部DNS SAN和路径上适用的名称限制,而非只找一个匹配名。
  • 合规正例通过,越界及排除负例仍失败;主机名另作明确校验。
  • 签发修改经过授权,最终证书重新读取,线上版本另行验收。

依据:OpenSSL扩展配置、verify手册、RFC 5280名称约束。先分清"名称匹配"和"名称授权范围",再决定重签哪一层。

相关推荐
Z21-X14 小时前
云原生运维学习笔记:ECShop全链路容器化部署与Docker底层资源隔离深度剖析
运维·笔记·学习·云原生
进击的荆棘15 小时前
Linux系统——线程同步与互斥之线程池和安全
linux·运维·服务器·安全·单例模式·线程
瑞雪兆丰年兮16 小时前
JavaWeb-day11-SpringBootWeb案例(文件上传)
java·运维·服务器·javaweb·oss
那明眸似水17 小时前
云原生项目三:Docker秒级启动NextCloud个人网盘存储办公系统
linux·运维·docker·云原生·容器
小南家的青蛙18 小时前
微服务体系下DevOps运维岗位面试方案设计
运维·面试·职场和发展·devops
知已难寻1 天前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
Alluxio1 天前
Alluxio + OCI:为 AI 工作负载实现亚毫秒访问延迟与可扩展吞吐
运维·数据库·人工智能·分布式·缓存·ai·语言模型
Sayai2 天前
Neo4j 图数据质量检查实战:环路检测、孤立节点与连通分量,两条 Cypher 引出的数据体检清单(含变长路径性能陷阱)
运维·neo4j·图数据库·数据质量·cypher
Elastic 中国社区官方博客2 天前
更少的位数,相同的召回率:DiskBBQ 更保守的自动校准扫描
大数据·运维·开发语言·人工智能·elasticsearch·搜索引擎
方便面不加香菜2 天前
Docker Volume -- 管理卷Volume详解
运维·docker·容器