手机和操作系统
NSURLErrorDomain Code=-1202:iOS 提示“此服务器的证书无效”
NSURLErrorDomain Code=-1202NSURLErrorDomain Code=-1202 的意思是:苹果的网络库不信任服务器出示的证书。 对应常量是 NSURLErrorServerCertificateUntrusted(Swift 里是 URLError.Code.serverCertificateUntrusted)。苹果文档只提到“根证书不受信任”,但大多数信任失败都报这个码:内部 CA 或自签名证书、域名不匹配、证书链不完整、证书不符合苹果的规则。
iOS 信任一台服务器之前查什么
苹果文档列出了默认服务器信任校验的内容:证书签名完好、没有过期、名称和服务器域名一致、能通过有效的证书一级级链到受信任的锚证书;锚证书要么随系统自带,要么由用户或管理员安装到设备上。好几种不同的失败最后都表现为同一个 -1202,所以光看这个码分不出是哪一项没过,需要按下面的步骤缩小范围。
-1202 和其他 TLS 错误码的区别
-1202 专指证书信任校验失败;握手本身失败报 -1200,证书日期问题报 -1201。中文系统里,-1202 的日志通常以“此服务器的证书无效。您可能正在连接到一个伪装成……的服务器”开头。
| 错误码 | Swift 名称 | 含义 |
|---|---|---|
| -1200 | secureConnectionFailed |
握手失败,见 NSURLErrorDomain -1200 |
| -1201 | serverCertificateHasBadDate |
证书过期或尚未生效 |
| -1202 | serverCertificateUntrusted |
信任校验失败 |
| -1203 | serverCertificateHasUnknownRoot |
没有由任何根证书签发 |
| -1204 | serverCertificateNotYetValid |
证书尚未生效 |
| -1206 | clientCertificateRequired |
服务器要求客户端证书 |
Safari 里同样的情况显示为 此连接非私人连接。
看日志里的底层 SSL 错误码
日志里的 kCFStreamErrorDomainSSL 错误码比 -1202 具体,能直接指出是缺中间证书、根证书不受信任还是主机名不匹配。URLSession 的日志常常还有一行 NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)。后面这个数字来自苹果的 Security 框架,比 -1202 具体得多:
| 错误码 | 常量 | 含义 |
|---|---|---|
| -9807 | errSSLXCertChainInvalid |
证书链无效 |
| -9812 | errSSLUnknownRootCert |
链是完整的,但根证书不受信任 |
| -9813 | errSSLNoRootCert |
链没有被根证书验证,常见于缺中间证书或内部 CA |
| -9814 | errSSLCertExpired |
链里有证书过期 |
| -9843 | errSSLHostNameMismatch |
主机名和证书不匹配 |
常见原因(按出现频率排序)
最常见的原因是开发环境或内部服务器用了内部 CA,设备上没有它的根证书。
1. 开发环境或内部服务器用了内部 CA
测试环境 API、私有化部署的服务器、本机调试服务,经常用自签名证书或公司内部 CA,iOS 的信任库里都没有。看签发者:
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer
2. 域名或 IP 不在证书里
应用请求的是 https://203.0.113.10,或者某个没写进 SAN 的别名。从 iOS 13 起,写在 CN 里的域名完全不被信任,只写在 CN 里的证书同样会失败。国内很多应用接入了 HTTPDNS,把域名换成 IP 直连,日志里就会出现 -9843 和“伪装成 IP 地址的服务器”这样的提示;阿里云的文档给出的处理办法是在证书校验时改用原始域名。查看证书里的域名:
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
3. 证书链不完整
服务器只发了自己的证书。有些客户端能自己补上,不要指望。用 -showcerts 数证书数量,或者用 证书链工具 检查,详见 缺少中间证书。从阿里云、腾讯云下载证书时,Nginx 格式的文件里通常已经含有中间证书,部署时不要只保留第一段。
4. 证书不符合苹果的规则
苹果要求 iOS 13、macOS 10.15 及以上系统中的所有 TLS 服务器证书:RSA 密钥不少于 2048 位,签名使用 SHA-2,域名必须写在 SAN 里。2019 年 7 月 1 日之后签发的证书还必须带 id-kp-serverAuth 扩展用途,有效期不超过 825 天。2020 年 9 月 1 日及以后由系统预装根签发的公共证书,有效期不得超过 398 天。很多内部 CA 习惯签五年期、不带扩展用途的证书,就会在这里栽跟头。用 证书解析工具 逐项检查。
5. 抓包代理或网络拦截
Charles、Proxyman、mitmproxy、公司代理、需要网页认证的公共 Wi-Fi,都会出示自己的证书。它们的根证书没有装到设备上并打开完全信任,应用就会报 -1202。
在 Mac 上排查
nscurl --ats-diagnostics --verbose https://api.example.com/
nscurl 用的是苹果自己的网络库,它报信任失败,基本就复现了应用遇到的情况。再用 SSL 检测 从外部对比一下。
怎么修
公网服务器换成合规的公共证书并发送完整链;内部服务器按苹果规则签发证书,再把根证书下发到设备。应用代码保持默认的信任校验。
公网服务器: 用公共 CA 的证书,把应用会访问的每个域名都写进 SAN,并发送完整证书链(Nginx 用 fullchain.pem)。
内部或开发服务器(测试设备上): 1. 按苹果规则签发证书:SAN 写域名、带 serverAuth 扩展用途、SHA-256、有效期不超过 825 天。 2. 通过描述文件或 MDM 把根证书装到设备上。 3. 手动安装的描述文件,到“设置 > 通用 > 关于本机 > 证书信任设置”里给这个根证书打开完全信任。通过 MDM 或 Apple Configurator 部署的会自动信任。
应用代码: 保持默认的信任校验。苹果关于手动校验服务器信任的文档演示了如何在 urlSession(_:didReceive:completionHandler:) 里检查服务器信任,但关于不安全连接的文档也说明,ATS 启用时只能收紧校验(比如锁定公钥),不能放松。生产环境要用内部 CA,就把根证书下发到受管设备,而不是在代码里降低校验。无论如何不要在发布版本里“接受所有证书”,临时的也不行。
怎么确认已经修好
修好的标准是 openssl 验证返回 0、nscurl 诊断全部 PASS,并且真机和模拟器都能正常请求。
- 公共证书:
openssl s_client -connect api.example.com:443 -servername api.example.com -verify_return_error最后显示Verify return code: 0 (ok)。 nscurl --ats-diagnostics https://api.example.com/在默认配置下显示 PASS。- 真机和模拟器都跑一遍请求,它们的信任库不一样。
常见问题
在 Info.plist 里加 ATS 例外能解决吗?
不能当作修复手段。默认的服务器信任校验仍然要求证书链到一个系统自带、或者由用户/管理员安装的信任锚。苹果文档还特别说明:ATS 启用时,应用只能收紧信任校验(比如做证书锁定),不能放松。要么修好证书,要么把内部根证书装到设备上。
模拟器能用、真机不行(或者反过来),为什么?
模拟器和真机的证书信任库是分开的。装在 Mac 上或某个模拟器里的开发根证书,真机上没有;真机通过描述文件装的根证书,还要去“证书信任设置”里打开完全信任。
用 IP 直连(比如接了 HTTPDNS)为什么报 -1202?
证书里写的是域名,不是这个 IP,域名校验失败。阿里云 HTTPDNS 的文档就专门讲了这个报错:改为按原始域名做证书校验即可。公共 CA 只给公网 IP 签 IP 证书,通常还是应该用证书覆盖的域名来连接。
-1202 和 -1200 有什么区别?
-1200(NSURLErrorSecureConnectionFailed)是 TLS 握手失败,常见于 ATS 的协议或套件要求;-1202 是握手已经走到证书这一步,信任校验没有通过。