证书明明写着192.0.2.10,用IP访问却提示不匹配?先别加信任根,也别把验证关掉。数字相同,不代表身份类型相同。本文定位OpenSSL的IP address mismatch:对照DNS类型SAN与IP类型SAN,复现error 64,再用失败分支检查修复是否真正生效。
一、先确认你验证的是IP,不是域名
域名服务的节点定点测试,与真正的IP身份验证是两件事。前者仍验证域名,后者验证IP地址。本文只讨论后者,不把改Host头或指定连接地址当成证书修复。192.0.2.10是文档示例地址,以下实验完全离线,不连接该地址。
OpenSSL的-verify_ip检查叶证书SAN里的IP地址;X509_check_ip文档明确:放在DNS名称或Common Name中的IP文本会被忽略。因此CN写对、甚至SAN里出现同一串数字,也可能失败。要看字段类型,不是肉眼找字符串。
二、在临时目录准备同一份请求
下面四段按顺序在同一个Bash子进程运行;不要逐段换终端。本轮实测OpenSSL 1.1.1k FIPS,命令使用1.1.1支持的参数。保留默认信任库和系统时钟不动,临时CA只通过CAfile授权给本次命令。未加密私钥、一天有效期和固定序列号仅供实验,退出该Shell自动清理。
umask 077 work=$(mktemp -d) || exit 1 cd "$work" || exit 1 trap 'cd /; rm -rf -- "$work"' EXIT printf '[req]\ndistinguished_name=dn\n[dn]\n' > openssl.cnf export OPENSSL_CONF="$PWD/openssl.cnf" openssl version openssl req -new -x509 -newkey rsa:2048 -nodes -days 1 \ -subj /CN=LabRoot -keyout root.key -out root.pem \ -addext basicConstraints=critical,CA:TRUE \ -addext keyUsage=critical,keyCertSign,cRLSign openssl req -new -newkey rsa:2048 -nodes \ -subj /CN=192.0.2.10 -keyout leaf.key -out leaf.csr三、只改变SAN的类型,签两张证书
同一CSR、同一叶子密钥、同一CA,把其他条件尽量固定。DNS.pem写DNS:192.0.2.10,IP.pem写IP:192.0.2.10;配置由签发端显式提供,不依赖CSR扩展自动复制。
n=1 for kind in DNS IP; do printf '%s\n' 'basicConstraints=critical,CA:FALSE' \ 'keyUsage=critical,digitalSignature,keyEncipherment' \ 'extendedKeyUsage=serverAuth' \ "subjectAltName=$kind:192.0.2.10" > leaf.ext openssl x509 -req -in leaf.csr -CA root.pem -CAkey root.key \ -set_serial "$n" -days 1 -sha256 -extfile leaf.ext -out "$kind.pem" n=$((n+1)) done openssl x509 -in DNS.pem -noout -ext subjectAltName openssl x509 -in IP.pem -noout -ext subjectAltName输出中一张是DNS:192.0.2.10,另一张是IP Address:192.0.2.10。签发成功只是文件生成,不代表身份适合应用。两张证书的CN都一样,正好排除“把CN再改一遍”的干扰。
四、复现error 64,再看一个容易误判的对照
for cert in DNS.pem IP.pem; do openssl verify -no-CApath -CAfile root.pem \ -purpose sslserver -verify_ip 192.0.2.10 "$cert" printf 'exit=%s\n' "$?" done openssl verify -no-CApath -CAfile root.pem -purpose sslserver DNS.pem本轮DNS.pem报error 64 at 0 depth lookup: IP address mismatch,进程退出2;IP.pem返回OK、退出0。64是证书验证错误号,不是Shell退出码;depth 0指被检查的叶证书。
最后一条去掉IP身份条件,两张材料中的DNS.pem也能通过链和服务端用途检查。这恰恰说明:信任路径没问题,不等于IP身份正确。排查脚本漏掉-verify_ip,就像验了门禁卡,却没看持卡人。
五、把现象与修复动作分开
| 现象 | 先检查 | 处理方向 |
|---|---|---|
| 数字相同却error 64 | SAN是DNS还是IP | 经授权签发正确IP SAN |
| 只改CN仍失败 | 最终叶证书的SAN | 不要依赖CN兜底 |
| 另一个IP失败 | 实际请求的身份 | 核对批准地址范围 |
| 不加verify_ip就OK | 测试条件是否缩水 | 恢复身份条件再验 |
本轮还测了错误IP、只有CN的证书和缺失文件,均不能放行。IPv6由IP类型SAN承载,等价文本写法应按地址匹配,不必强求显示字符串一致;本轮用2001:db8::10及其展开写法做了离线正对照,另一个地址为负对照。
六、脚本必须保留失败,不只搜索OK
下面先把完整输出写入日志,成功条件直接绑定verify进程。错误地址、错误类型、缺文件或日志无法创建都不能走成功分支;不要在verify后插入其他命令再读取退出码,也别用管道最后一段的成功覆盖前段失败。
check_ip() { openssl verify -no-CApath -CAfile root.pem \ -purpose sslserver -verify_ip "$1" "$2" } if check_ip 192.0.2.10 IP.pem >check.log 2>&1; then printf 'IP identity and chain passed\n' else rc=$? cat check.log >&2 exit "$rc" fi七、修复后仍要回到真实HTTPS
正式修复是核对业务真正需要的IP身份、经CA授权签发并部署新证书,再读取服务实际发送的叶证书。不能把DNS数字字段改个显示标签:SAN属于签名保护内容,修改后必须重新签发。
本文离线通过只证明给定材料、时间、信任锚、用途和IP身份下验证通过;没有测试浏览器、负载均衡或生产握手。上线还要核对实际终点、发链、客户端信任配置和HTTP业务结果。证书文件正确,不代表流量已经用上它。
八、交付前的检查清单与资料
- 确认访问身份到底是IP还是域名,测试条件保持一致。
- 读取最终证书而非只看CSR;检查IP SAN、有效期与用途。
- 正确IP通过,错误IP和DNS数字类型负例失败。
- 保留退出码和完整错误;检查日志创建失败也会阻断。
- 离线验证与真实服务验收分别记录,不关闭验证换取绿色结果。
参考资料与实现:
- OpenSSL verify
- OpenSSL X509v3配置
- OpenSSL身份检查API
- CertbotX