美文网首页iOS-学习笔记iOS 账号证书那些事tom
iOS自签名HTTPS证书单向校验方案

iOS自签名HTTPS证书单向校验方案

作者: 仁伯 | 来源:发表于2017-01-13 18:03 被阅读2634次

前言

这两天公司选所谓的先进个人,结果也是我最满意的,没选上但能得到自己身边的伙伴对自己的认可,很开心,感冒也好了很多!虽然知道大家选我,只是对我工作层面的认可,与人品无关,但我宁愿伙伴能对我的人品层面也有同等认可。

毕业已近两年,出来工作时间总算起来,也有快三年了。从开始跟着师傅,到慢慢独立,再到带新伙伴一起做项目,这一路走来,有过无助、有过怀疑,有过为伙伴“甘愿独自离去”的冲动,有过低级趣味的诱惑,有过感动,有过坚定,说我傻B也好,说我怎样也罢,我都全盘接受。

说实话,有时也会矛盾。我是一个“虚心听取别人意见”的人,别人的建议我反思后,除了性格使然部分,其他大部分我都会调整,当然你也可以理解为没有主见,设计、测试怎么说就怎么改,领导安排工作也尽量去做。慢慢的对自己的工作方式也有过疑问,这样做是否合适?也有贵人给我提过一些东西,说让我学会表达自己,为此,我也做出些自己的改变,在技术方面,我开始表达自己的想法,不过有时会不注意表达的方法和方式,也会给领导给自己造成些许困惑。

不管怎样,猴年即将过去,这恐怕是年前最后一次项目上线了,给大家分享下前几天HTTPS证书校验的一些东西。
说好的写篇《iOS自签名HTTPS证书单向校验方案》呢,又扯了这么多闲篇,对大家不住。

HTTPS简析

虽说在去年圣诞节前夕苹果发出公告要推迟ATS适配截止时间,但既然它在iOS9.0始推出App Transport Security,适配HTTPS是早晚的事,总要做好技术储备。以鄙人浅显的技术而言,我觉得iOS APP适配HTTPS主要涉及三方面适配:

  • 普通网络请求;
  • H5页面加载;
  • SDWebImage加载HTTPS图片(一般公司测试库会用到自签名证书)。

所谓HTTPS,即HTTP+SSL/TSL,底部也就是在HTTP协议层和TCP/IP协议层之间添加安全传输层协议SSL,从而达到对HTTP数据包加密传输的目的,

sequenceDiagram
    participant Client
    participant Server
    Client->>Server: 以明文传输数据,主要有客户端支持的SSL版本等客户端支持的加密信息
    Server-->>Server: 服务端选择加密方式
    
    Server-->>Client: 服务端给客户端返回SSL版本、随机数等信息
    Client->>Client: 校验服务端证书是否合法、产生随机数
    
    Client->>Server: 使用服务端随机数加密数据发给服务端
    Server-->>Server: 服务端使用私钥解密客户端数据
    
    Server-->>Client: 使用收到的随机数,加密数据,返回给客户端
    Client->>Client: 客户端使用公钥解密数据

简书不支持MarkDown高级语法,上面的MarkDown文本对应下图:

HTTPS sequenceDiagram.png

iOS适配HTTPS注意点

  • 网络请求NSURLConnection/NSURLSession
  • H5页面适配WKWebView/UIWebView
  • SDWebImage图片加载适配

网络请求部分

NSURLConnection适配

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) 
    {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    } 
    else 
    {
        if ([challenge previousFailureCount] == 0) 
        {
            [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
        }
        else 
        {
            [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
        }
    }
}

NSURLSession适配

/** disposition:如何处理证书
    NSURLSessionAuthChallengeUseCredential 使用证书
    NSURLSessionAuthChallengePerformDefaultHandling  忽略证书 默认的做法
    NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消请求,忽略证书
    NSURLSessionAuthChallengeRejectProtectionSpace 拒绝,忽略证书
*/
#pragma mark - NSURLSessionDelegate代理方法 HTTPS ---开始---
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    // 判断服务器返回的证书是否是服务器信任的
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        if (credential)
        {
            disposition = NSURLSessionAuthChallengeUseCredential; // 使用证书
        }
        else
        {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling; // 忽略证书 默认的做法
        }
    }
    else
    {
        disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; // 取消请求,忽略证书
    }
    if (completionHandler)// 安装证书
    {
        completionHandler(disposition, credential);
    }
}

H5页面适配

基于WKWebView的H5页面HTTPS适配
#pragma mark: WKWebView的https配置
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust];

        completionHandler(NSURLSessionAuthChallengeUseCredential,card);
    }
}
基于UIWebView的H5页面HTTPS适配

详情见:Stretch's stackoverflow答案

#pragma mark - Webview delegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
{
    NSLog(@"Did start loading: %@ auth:%d", [[request URL] absoluteString], _authenticated);

    if (!_authenticated) 
    {
        _authenticated = NO;
        _urlConnection = [[NSURLConnection alloc] initWithRequest:_request delegate:self];
        [_urlConnection start];
        return NO;
    }
    return YES;
}

#pragma mark - NURLConnection delegate
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
    NSLog(@"WebController Got auth challange via NSURLConnection");
    if ([challenge previousFailureCount] == 0)
    {
        _authenticated = YES;
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
    } else
    {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
{
    NSLog(@"WebController received response via NSURLConnection");

    // remake a webview call now that authentication has passed ok.
    _authenticated = YES;
    [_web loadRequest:_request];

    // Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!)
    [_urlConnection cancel];
}

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

SDWebImage加载HTTPS图片适配

低版本的SDWebImage是对NSURLConnection做的封装,新版本是对NSURLSession做的封装,适配HTTPS图片,记得确定自己使用的SDWebImage版本的SDWebImageDownloaderOperation类是否做过校验。
如果SDWebImage使用的是NSURLConnection看看文件是否有:

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

否则查看SDWebImageDownloaderOperation文件中是否有

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

确定好之后,如果所有图片均是HTTPS的,那么我们可以直接修改UIImageView+WebCache文件

- (void)sd_setImageWithURL:(nullable NSURL *)url 
{
//    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
    [self sd_setImageWithURL:url placeholderImage:nil options:SDWebImageAllowInvalidSSLCertificates progress:nil completed:nil]; // HTTPS配置问题
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder 
{
//    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
    [self sd_setImageWithURL:url placeholderImage:placeholder options:SDWebImageAllowInvalidSSLCertificates progress:nil completed:nil]; // HTTPS配置问题
}

其实质就是将option设置为SDWebImageAllowInvalidSSLCertificates,看SDWebImageDownloaderOperation文件你会发现,SDWebImage采用直接忽略证书验证的方式加载的,所以设置SDWebImageAllowInvalidSSLCertificates才有效。

SDWebImageDownloaderloaderOperation校验忽略.png
如有个别特殊情况,要支持某些HTTP的图片,按照上面方法适配后,还需在plist的Exception Domains添加响应配置,详情见苹果APP接入HTTPS延迟截止时间是妥协吗

相关文章

网友评论

  • Am汉阳:单向的 plist里面还需要配置什么么,我这报错NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9806)
    仁伯:@Am汉阳 <key>172.30.1.217</key> 试试,这个是常见问题,多试一下
    Am汉阳:@仁伯安 <key>NSAppTransportSecurity</key>
    <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSExceptionDomains</key>
    <dict>
    <key>https://172.30.1.217&lt;/key>
    <dict>
    <key>NSIncludesSubdomains</key>
    <true/>
    <key>NSExceptionMinimumTLSVersion</key>
    <string>TLSv1.0</string>
    </dict>
    </dict>
    </dict>
    还是有这个问题 CFNetwork SSLHandshake failed (-9806)
    仁伯:@Am汉阳 你问下后台用的TSL那个版本的协议,在plist文件,NSExceptionDomains设置部分统一一下。比如:<key>xxx.cn</key>
    <dict>
    <key>NSExceptionMinimumTLSVersion</key>
    <string>TLSv1.0</string>
    <key>NSIncludesSubdomains</key>
    <true/>
    <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
    <false/>
  • 糊涂0:wkwebview打开https ,我这边做的是自签名证书双向认证,用你的方法打不开https的web网页?
    仁伯:类似这样的:info.plist code:
    <key>NSExceptionDomains</key>
    <dict>
    <key>xxx.com</key>
    <dict>
    <key>NSIncludesSubdomains</key>
    <true/>
    <key> NSExceptionAllowsInsecureHTTPLoads </key>
    <true/>
    <key> NSExceptionRequiresForwardSecrecy </key>
    <false/>
    </dict>
    ......
    <key>xxx.cn</key>
    <dict>
    <key>NSExceptionMinimumTLSVersion</key>
    <string>TLSv1.0</string>
    <key>NSIncludesSubdomains</key>
    <true/>
    <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
    <false/>
    </dict>
    </dict>
    糊涂0:1 双向认证调用接口没有问题,我可以加载本地证书验证!现在的情况是wkwebview打开一个https的网页
    2 plist文件中,目前测试阶段没有域名,后台的证书是ip地址的,白名单我尝试加过 ,但是没有成功! 你可以把你的白名单贴出来看看嘛??
    仁伯:1、双向验证要本地加载证书;
    2、plist文件设置了木,自签名要设白名单;
  • 面向对象:查看了网上好多文章,很多都是粘贴复制,要么就是只说要这样做,而不说为什么这样做,完全没有思考的过程,只有你的文章还是比较系统的,希望以后能有更多优秀的文章!辛苦了,谢谢!(已关注)
  • 山林间迷雾能不能当障眼法的内容:我想问一下 前端不需要导入什么cer证书 在项目中吗? 我看到网上好多自签名导入cer 证书啥的,什么情况
    仁伯:@提督很忙啊 对的
    山林间迷雾能不能当障眼法的内容:@仁伯安 就是说单向 不用导入cer证书到xcode中了?
    仁伯:@提督很忙啊 双向验证要客户端把证书回传给服务端的,单项没有,我菜鸟一枚,不太懂
  • d5829f899636:你好,后台接口是https,证书是自签名证书,前端在后台没有提供任何文件的情况下,SDWebImage能访问到数据吗?我这边试了一直访问不到数据。
    面向对象:@仁伯安 这个问题好像也不是必现的,我们这边项目是自签名的证书,我用的SDWebImage是3.8.0版本的,就出现了SDWebImage加载不出来图片的问题,但是我朋友的项目就没有出现这样的问题,他的项目使用的是3.7.5版本的SDWebImage
    仁伯:1、你查下plist文件域名是否配置
    2、UIImageView+WebCache改了木,检查SDWebImage版本,看看SDWebImageDownloaderOperation这个类有没有didReceiveChallenge代理方法
  • 飘渺阿天:除了这些地方需要改动,其他的地方需要改动么?还需要注意点什么:smiley:
    飘渺阿天:@仁伯安 照您说的那样,看来我需要改的还得挺多
    飘渺阿天:@仁伯安 我只是把所有的http字符串,换成https,好像治标不治本:flushed:
    仁伯:@飘渺阿天 三方库,plist文件做Eexception Domains设置,其他没了吧
  • 庄烽:加油
    仁伯:@庄烽 峰哥哥:sweat_smile::sweat_smile:

本文标题:iOS自签名HTTPS证书单向校验方案

本文链接:https://www.haomeiwen.com/subject/enmdbttx.html