iOS绕过HTTPS证书校验

  iOS开发中使用的通信协议,除了聊天和物联网相关的APP可能会用到XMPP和MQTT,剩下的应用通常都只会用到HTTP和HTTPS这两种协议,而由于ATS的限制,iOS默认都是实用HTTPS,除非修改项目的info.plist:

对于HTTPS,熟悉HTTPS协议的人都知道,HTTPS基本上就是在HTTP协议的基础上加了一层SSL/TLS加密协议,这层加密协议在建立通信时除了HTTP本身的3次握手外,还会再经历额外的身份校验、数据加解密过程:

这些过程中会混合使用非对称加密、对称加密、证书校验和数字签名等手段,其中证书校验的过程:

  1. 首先,服务器的运营人员向数字证书认证机构提出公开密钥的申请。数字证书认证机构在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公钥证书后绑定在一起。
  2. 服务器会将这份由数字证书认证机构颁发的公钥证书发送给客户端,以进行公开密钥加密方式通信。公钥证书也可叫做数字证书或直接称为证书。
  3. 接到证书的客户端可使用数字证书认证机构的公开密钥,对那张证书上的数字签名进行验证,一旦验证通过,客户端便可明确两件事:一,认证服务器的公开密钥的是真实有效的数字证书认证机构。二,服务器的公开密钥是值得信赖的。
  4. 此处认证机关的公开密钥必须安全地转交给客户端。使用通信方式时,如何安全转交是一件很困难的事。因此,多数浏览器开发商发布版本时,会事先在内部植入常用认证机关的公开密钥。

而通常在开发环境中服务端会使用自签名证书,或者即使在生产环境中可能也会使用免费的证书,这些证书的认证机构可信度通常低于那些收费的大型认证机构,这样,在步骤3的时候客户端对证书的认证可能就失败了。最近在开发公司的iOS SDK就碰到这个问题,直接调用HTTPS接口会报错:

HTTP load failed (kCFStreamErrorDomainSSL, -9843)

明白了原理后,解决方案就简单了,只需要客户端在校验时对使用的域名设置白名单就行了,以下是使用NSURLSession时需要在delegate上做的修改:

NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:Nil];


- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
    if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
        if([challenge.protectionSpace.host isEqualToString:@"recvapi.log.dtstack.com"]){
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        } else {
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
        }
    }
}

这样就可以解决开发时的证书校验失败的问题。

但这只限于开发阶段,治标不治本,不能在生产环境下使用,要想治本,最好还是更换服务器证书,找个可信度高的大型认证机构重新弄份证书。

服务端更换证书也简单,以Nginx为例,拿到证书后,修改Nginx的配置:

server { 
    listen 443 ssl;
    server_name example.com;

    ssl on;
    ssl_certificate /etc/ssl/private/example_com.crt;
    ssl_certificate_key /etc/ssl/private/example_com.key;
}

然后重启服务就行了:

nginx -t && nginx -s reload
坚持原创技术分享,您的支持将鼓励我继续创作!