SSTP 客户端只有在 TLS 验证成功后才会接受服务器,而该验证会比较名称、证书链和用途。名称来自连接配置文件,而不是你碰巧能 ping 通的地址。三者中有一项对不上,客户端就会报告证书问题,永远到不了登录提示。

配置文件里的名称决定检查什么

SSTP 在 TCP 443 上运行 TLS,因此客户端的行为与其他 TLS 客户端一致:从配置文件取主机名,请求该名称,并用它与证书比对。若在配置文件中填入 IP 地址,比对就变成 IP 地址比对,而大多数服务器证书并不能满足。

所以浏览器测试可能成功,VPN 却失败,因为浏览器用的名称与配置文件不同。当 split-horizon DNS 把 VPN 主机名解析为内部地址、而浏览器访问到的是公网地址时,也会出现同样的分裂。先把配置文件对齐到有文档记录的主机名,再比对证书。在这之前改动的任何东西都只是猜测。

服务器证书必须包含什么

现代客户端与主体备用名称扩展比对,因此主机名必须以 DNS 条目出现在其中。仅有通用名称对当前验证栈不够用,通配符只覆盖一个标签,而不是你随意想出的任何子域。三张证书说明了边界:名称正确但用途错误,会先验证身份再在用途检查中失败;名称与用途都正确但缺少中间证书,会在证书链上失败;只出现在通用名称里的名称,在另外两者起作用之前就已失败。

证书还需要服务器身份验证用途,即 Microsoft 的 SSTP 规范 中所述的扩展密钥用法值,也就是带对象标识符 1.3.6.1.5.5.7.3.1 的 serverAuth。密钥用法与有效证书链共同补齐这一组。服务器上确实存在、为正确名称签发、但只用于客户端身份验证的证书,仍然会失败。

先检查证书链和时钟,再谈账号密码

时间与信任故障 产生的可见结果与名称不匹配相同,而且两者都比密码问题更容易诊断。检查服务器时钟与客户端时钟,然后按客户端看到的样子读取证书链:

# from the client, or any host that runs openssl
openssl s_client -connect vpn.example.com:443 -servername vpn.example.com -showcerts </dev/null 2>/dev/null | openssl x509 -noout -subject -dates -ext subjectAltName
openssl s_client -connect vpn.example.com:443 -servername vpn.example.com -verify_return_error </dev/null
# on the Windows server that holds the SSTP listener
w32tm /query /status

第一条命令会输出名称、有效期窗口以及客户端要比对的所有备用名称。第二条会连同所出示的证书链报告验证错误,缺少中间证书正是在那里暴露。在 Windows 上,时间服务查询会告诉你时钟是在被校正,还是已经漂移。

内部 CA 还会加一个条件:客户端必须信任该 CA。请通过其获批的分发渠道安装组织根证书,而不要从提供替换证书的页面获取,因为这个决定正是整个信任模型的缩影。

服务器验证与用户身份验证是先后两个阶段,失败原因不同。证书错误意味着客户端从未信任服务器,而密码被拒意味着它信任了服务器,只是凭据不对。为到达密码提示而关闭证书验证,是在移除保障而不是修复它。要证明其余路径可用,请从已信任该证书链的客户端测试同一主机名和端口,并让已部署的配置文件保持原样。

修正名称或证书,然后重新测试

三个条件决定如何修复。配置文件名称不对,就修正配置文件或把缺少的名称加入证书。证书链不完整,就在服务器上安装中间证书。用途不对,就重新签发,而不是绕着打补丁。

更改之后,用受支持的客户端重新连接,确认登录提示出现,然后确认隧道建立。在工单中记录所用的主机名、签发者和到期日期。正是这份记录让下一次续期成为计划任务而不是又一次故障,也向后来的人显示三个条件中究竟哪一个被破坏。

相关文章