【Objective-c】 使用自签证书实现HTTPS

2016年苹果在发布会上信誓旦旦的宣布2017年1月1日全面实现https,所有已经上架或提交审核的都必须更换,否则审核不通过,下架。因此对https做了一些了解。

1、https原理

《【Objective-c】 使用自签证书实现HTTPS》 Paste_Image.png

HTTPS 是 http 的升级版,使信息的交互更加安全。
HTTPS 是由 HTTP + SSL / TLS组成的。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。
具体的加密和解密如下:

  1. 客户端发起HTTPS请求
    这个没什么好说的,就是用户在浏览器里输入一个https网址,然后连接到server的443端口。(http 连接的是server的80端口)
  2. 服务端的配置
    采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl就是个不错的选择,有1年的免费服务)。这套证书其实就是一对公钥和私钥。如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。
  3. 传送证书
    这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。
  4. 客户端解析证书
    这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值。然后用证书对该随机值进行加密。就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。
  5. 传送加密信息
    这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。
  6. 服务段解密信息
    服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密。所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。
  7. 传输加密后的信息
    这部分信息是服务段用私钥加密后的信息,可以在客户端被还原
  8. 客户端解密信息
    客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容。整个过程第三方即使监听到了数据,也束手无策。
2、获取证书

《【Objective-c】 使用自签证书实现HTTPS》 Paste_Image.png

上图是双向认证的示意图:客户端Client 和 服务端Server 都有一个证书信任库,因为是自签的,所以需要将证书导入,这样才能验证证书是否合法。比如:客户端要验证服务端的证书是否有效,就必须导入服务器的证书,做对比,获取服务器的证书方法如下:
使用IE浏览器,打开连接

《【Objective-c】 使用自签证书实现HTTPS》 D(0WAMWU`L8NUK9O)A7$B1.png
《【Objective-c】 使用自签证书实现HTTPS》 ORA6D_G@36UG44HCHU5{XZM.png
《【Objective-c】 使用自签证书实现HTTPS》 %$R3Z$})%$OQ3WAUPOY0}FC.png
《【Objective-c】 使用自签证书实现HTTPS》 80SJKL3(0S6~ZH`}HHX$161.png
《【Objective-c】 使用自签证书实现HTTPS》 UEEBFITOL0KZ2~W87%29UGP.png

![]SHQY$BWN{__8U@P((ZRQ.png](http://upload-images.jianshu.io/upload_images/1923392-00dbf4b2017e5dd0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

到此为止,证书获取成功

3、代码配置

本人用的是AFNetworking第三框架,配置如下。在请求方法中添加以下代码

    //https         
    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
    securityPolicy.allowInvalidCertificates = YES;  //是否允许使用自签证书
    securityPolicy.validatesDomainName = NO;        //是否需要验证域名,默认是YES
    
    NSString *requestString = [NSString stringWithFormat:@"%@%@",URLDoman,methodName];
    _session = [AFHTTPSessionManager manager];
    _session.responseSerializer = [AFHTTPResponseSerializer serializer];
    _session.securityPolicy = securityPolicy;    //设置证书校验模式
    
    //设置超时
    [_session.requestSerializer willChangeValueForKey:@"timeoutinterval"];
    _session.requestSerializer.timeoutInterval = TimeoutInterval;
    [_session.requestSerializer didChangeValueForKey:@"timeoutinterval"];
    _session.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringCacheData;
    
    //身份验证回调
    __weak typeof(self) weakSelf = self;
    [_session setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession * _Nonnull session, NSURLAuthenticationChallenge * _Nonnull challenge, NSURLCredential *__autoreleasing  _Nullable * _Nullable credential) {
        //获取服务器的trust object
        SecTrustRef serverTrust = [[challenge protectionSpace]serverTrust];
        //导入多张CA证书
        NSString *cerPath = [[NSBundle mainBundle]pathForResource:@"ca" ofType:@"cer"];//自签证书
        NSData *caCert = [NSData dataWithContentsOfFile:cerPath];
        NSArray *cerArray = @[caCert];
        weakSelf.session.securityPolicy.pinnedCertificates = cerArray;
        
        SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
        NSCAssert(caRef!=nil, @"caRef is nil");
        
        NSArray *caArray = @[(__bridge id)(caRef)];
        NSCAssert(caArray != nil, @"caArray is nil");
        
        OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
        SecTrustSetAnchorCertificatesOnly(serverTrust, NO);
        NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
        
        //选择质询认证的处理方式
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __autoreleasing NSURLCredential *credentiall = nil;
        
        //NSURLAuthenticationMethodServerTrus咨询认证方式
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            //基于客户端的安全策略来决定是否信任该服务器,不信任则响应质询
            if ([weakSelf.session.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                //创建质询证书
                credentiall = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                //确认质询方式
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                }else{
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            }else{
                //取消质询
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        }else{
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
        
        return disposition;
    }];

注意项:
苹果ATS对HTTPS证书也是有要求的
a、服务器所有的连接使用TLS1.2以上版本
b、HTTPS证书必须使用SHA256以上哈希算法签名
c、HTTPS证书必须使用RSA 2048位或ECC 256位以上公钥算法
d、使用前向加密技术

到此,启动App网络请求是没什么问题的了。

提醒:使用自签的证书,苹果审核不一定能通过。祝大家好运….

好文推荐:(码文的搬运工)
HTTPS原理
双向认证
单双向认证
iOS用自签名证书实现HTTPS请求的原理实例讲解

iOS开发HTTPS实现之信任SSL证书和自签名证书
iOS使用自签名证书实现HTTPS请求

20170216 补充
单向认证和双向认证的使用场景:
单向认证:一般都是使用在Web页面,用于提示用户当前访问页面没有得到验证,访问有风险。但是依然能够访问
双向认证:移动端建议是使用双向认证,安全性强,并且如果是只使用单向认证的话,跟http无区别,得不到安全保障。有如下两种情况出现:
第一:如果移动端使用https请求https服务端,移动端就会验证服务端的证书有效性,如果不是新人的证书,则禁止访问。
第二:如果移动端使用https请求http服务端,因为http是超文本,是无状态协议的,所以只要访问的资源存在就会返回数据给移动端。这样的话,就失去了验证服务器合法的作用

    原文作者:MR_詹
    原文地址: https://www.jianshu.com/p/238f790ba077
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞