iOS 中对 HTTPS 证书链的验证

path_length_constraint.png iOS 上对证书链的验证

在 Overriding TLS Chain Validation Correctly 中提到:

When a TLS certificate is verified, the operating system verifies its chain of trust. If that chain of trust contains only valid certificates and ends at a known (trusted) anchor certificate, then the certificate is considered valid. 所以在 iOS 中,证书是否有效的标准是:

信任链中如果只含有有效证书并且以可信锚点(trusted anchor)结尾,那么这个证书就被认为是有效的。

其中可信锚点指的是系统隐式信任的证书,通常是包括在系统中的 CA 根证书。不过你也可以在验证证书链时,设置自定义的证书作为可信的锚点。

NSURLSession 实现 HTTPS

具体到使用 NSURLSession 走 HTTPS 访问网站,-URLSession:didReceiveChallenge:completionHandler: 回调中会收到一个 challenge,也就是质询,需要你提供认证信息才能完成连接。这时候可以通过 challenge.protectionSpace.authenticationMethod 取得保护空间要求我们认证的方式,如果这个值是 NSURLAuthenticationMethodServerTrust 的话,我们就可以插手 TLS 握手中“验证数字证书有效性”这一步。

默认的实现

系统的默认实现(也即代理不实现这个方法)是验证这个信任链,结果是有效的话则根据 serverTrust 创建 credential 用于同服务端确立 SSL 连接。否则会得到 “The certificate for this server is invalid...” 这样的错误而无法访问。

比如在访问 https://www-google-com 的时候咧,我们不实现这个方法也能访问成功的。系统对 Google 服务器返回来的证书链,从叶节点证书往根证书层层验证(有效期、签名等等),遇到根证书时,发现作为可信锚点的它存在与可信证书列表中,那么验证就通过,允许与服务端建立连接。

google.png 而当我们访问 https://www-12306-cn 时,就会出现 \this server is invalid. You might be connecting to a server that is pretending to be “www-12306-cn” which could put your confidential

information at risk.\的错误。原因就是系统在验证到根证书时,发现它是自签名、不可信的。

12306.png 自定义实现

如果我们要实现这个代理方法的话,需要提供 NSURLSessionAuthChallengeDisposition(处置方式)和 NSURLCredential(资格认证)这两个参数给 completionHandler 这个 block: -(void)URLSession:(NSURLSession *)session

didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition,

NSURLCredential * _Nullable))completionHandler {

// 如果使用默认的处置方式,那么 credential 就会被忽略

NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;

NSURLCredential *credential = nil;

if ([challenge.protectionSpace.authenticationMethod isEqualToString:

NSURLAuthenticationMethodServerTrust]) { /* 调用自定义的验证过程 */

if ([self myCustomValidation:challenge]) {

credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; if (credential) {

disposition = NSURLSessionAuthChallengeUseCredential; } } else { /* 无效的话,取消 */

disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge } }

if (completionHandler) {

completionHandler(disposition, credential); } }

在 [self myCustomValidation:challenge] 调用自定义验证过程,结果是有效的话才创建 credential 确立连接。

自定义的验证过程,需要先拿出一个 SecTrustRef 对象,它是一种执行信任链验证的抽象实体,包含着验证策略(SecPolicyRef)以及一系列受信任的锚点证书,而我们能做的也是修改这两样东西而已。

SecTrustRef trust = challenge.protectionSpace.serverTrust; 拿到 trust 对象之后,可以用下面这个函数对它进行验证。 staticBOOL serverTrustIsVaild(SecTrustRef trust) { BOOL allowConnection = NO; // 假设验证结果是无效的

SecTrustResultType trustResult = kSecTrustResultInvalid; // 函数的内部递归地从叶节点证书到根证书的验证

OSStatus statue = SecTrustEvaluate(trust, &trustResult); if (statue == noErr) {

// kSecTrustResultUnspecified: 系统隐式地信任这个证书

// kSecTrustResultProceed: 用户加入自己的信任锚点,显式地告诉系统这个证书是值得信任的

allowConnection = (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified); }

return allowConnection; }

这个函数什么时候调用完全取决于你的需求,如果你不想对验证策略做修改而直接调用的话,那你居然还看到这里!?(╯‵□′)╯︵┻━┻ 域名验证

可以通过以下的代码获得当前的验证策略: CFArrayRef policiesRef;

SecTrustCopyPolicies(trust, &policiesRef);

联系客服:779662525#qq.com(#替换为@) 苏ICP备20003344号-4