iOS安全系列之一:HTTPS

如何打造一个安全的App?这是每个移动开发者必须面对的问题。在移动App开发领域,开发工程师对于安全方面的考虑广泛比较欠缺,而因为iOS平台的封闭性,遭遇到的安全问题相比于Android来讲要少得多,这就致使了许多iOS开发人员对于安全性方面没有太多的深刻,但对于一个合格的软件开发者来讲,安全知识是必备知识之一。html

对于未越狱的iOS设备来讲,因为强大的沙箱和受权机制,以及Apple本身掌控的App Store, 基本上杜绝了恶意软件的入侵(非越狱)。但除系统安全以外,咱们仍是面临不少的安全问题:网络安全、数据安全等,每一项涉及也很是广,安全是很是大的课题,本人并不是专业的安全专家,只是从开发者的角度,分析咱们常遇到的各项安全问题,并提出一般的解决方法,与各位同窗交流学习。ios

每个软件工程师都有义务保护用户数据的隐私和安全。git



首先是网络安全,OSI模型各层都会面临相应的网络安全问题,涉及宽广,而网络安全也是安全领域发展最为繁荣的领域。本文咱们只是从移动应用开发角度,以尽可能简单的方式,讲解HTTPS核心概念知识,以及在iOS平台上的实现。建议如今还在使用HTTP的应用都升级到HTTPS。github

引读:互联网全站HTTPS的时代已经到来objective-c

 

1. HTTPS

其实HTTPS从最终的数据解析的角度,与HTTP没有任何的区别,HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密以后,将数据传给HTTP协议层,就是普通的HTTP数据。HTTP和SSL/TSL都处于OSI模型的应用层。从HTTP切换到HTTPS是一个很是简单的过程,在作具体的切换操做以前,咱们须要了解几个概念:api



SSL/TSL

关于SSL/TSL,阮一峰的两篇博客文章作了很好的介绍:数组

简单的来讲,SSL/TSL经过四次握手,主要交换三个信息:浏览器

  1. 数字证书:该证书包含了公钥等信息,通常是由服务器发给客户端,接收方经过验证这个证书是否是由信赖的CA签发,或者与本地的证书相对比,来判断证书是否可信;假如须要双向验证,则服务器和客户端都须要发送数字证书给对方验证;
  2. 三个随机数:这三个随机数构成了后续通讯过程当中用来对数据进行对称加密解密的“对话密钥”安全

    首先客户端先发第一个随机数N1,而后服务器回了第二个随机数N2(这个过程同时把以前提到的证书发给客户端),这两个随机数都是明文的;而第三个随机数N3(这个随机数被称为Premaster secret),客户端用数字证书的公钥进行非对称加密,发给服务器;而服务器用只有本身知道的私钥来解密,获取第三个随机数。这样,服务端和客户端都有了三个随机数N1+N2+N3,而后两端就使用这三个随机数来生成“对话密钥”,在此以后的通讯都是使用这个“对话密钥”来进行对称加密解密。由于这个过程当中,服务端的私钥只用来解密第三个随机数,历来没有在网络中传输过,这样的话,只要私钥没有被泄露,那么数据就是安全的。服务器

  3. 加密通讯协议:就是双方商量使用哪种加密方式,假如二者支持的加密方式不匹配,则没法进行通讯;

有个常见的问题,关于随机数为何要三个?只最后一个随机数N3不能够么?

这是因为SSL/TLS设计,就假设服务器不相信全部的客户端都可以提供彻底随机数,假如某个客户端提供的随机数不随机的话,就大大增长了“对话密钥”被破解的风险,因此由三组随机数组成最后的随机数,保证了随机数的随机性,以此来保证每次生成的“对话密钥”安全性。



数字证书

数字证书是一个电子文档,其中包含了持有者的信息、公钥以及证实该证书有效的数字签名。而数字证书以及相关的公钥管理和验证等技术组成了PKI(公钥基础设施)规范体系。通常来讲,数字证书是由数字证书认证机构(Certificate authority,即CA)来负责签发和管理,并承担PKI体系中公钥合法性的检验责任;数字证书的类型有不少,而HTTPS使用的是SSL证书。

怎么来验证数字证书是由CA签发的,而不是第三方伪造的呢? 在回答这个问题前,咱们须要先了解CA的组织结构。首先,CA组织结构中,最顶层的就是根CA,根CA下能够受权给多个二级CA,而二级CA又能够受权多个三级CA,因此CA的组织结构是一个树结构。对于SSL证书市场来讲,主要被Symantec(旗下有VeriSign和GeoTrust)、Comodo SSL、Go Daddy 和 GlobalSign 瓜分。 了解了CA的组织结构后,来看看数字证书的签发流程:


数字证书的签发流程

数字证书的签发机构CA,在接收到申请者的资料后进行核对并肯定信息的真实有效,而后就会制做一份符合X.509标准的文件。证书中的证书内容包含的持有者信息和公钥等都是由申请者提供的,而数字签名则是CA机构对证书内容进行hash加密后获得的,而这个数字签名就是咱们验证证书是不是有可信CA签发的数据。


数字证书的验证流程

假设上图证书是由证书签发机构CA1签发的。

1)接收端接到一份数字证书Cer1后,对证书的内容作Hash获得H1;

2)从签发该证书的机构CA1的数字证书中找到公钥,对证书上数字签名进行解密,获得证书Cer1签名的Hash摘要H2;

3)对比H1和H2,如相等,则表示证书没有被篡改。

4)但这个时候仍是不知道CA是不是合法的,咱们看到上图中有CA机构的数字证书,这个证书是公开的,全部人均可以获取到。而这个证书中的数字签名是上一级生成的,因此能够这样一直递归验证下去,直到根CA。根CA是自验证的,即他的数字签名是由本身的私钥来生成的。合法的根CA会被浏览器和操做系统加入到权威信任CA列表中,这样就完成了最终的验证。因此,必定要保护好本身环境(浏览器/操做系统)中根CA信任列表,信任了根CA就表示信任全部根CA下全部子级CA所签发的证书,不要随便添加根CA证书。

通常操做系统和浏览器只包含根CA机构的证书,而在配置Web服务器的HTTPS时,也会将配置整个证书链,因此整个校验流程是从最后的叶子节点证书开始,用父节点校验子节点,一层层校验整个证书链的可信性。

打个比喻:父(根CA数字证书)-子(CA数字证书)-孙(数字证书)三代人,假设父没有其余兄弟(至关于根CA机构是惟一的),假如子与父进行DNA亲子鉴定,检测DNA位点(即证书签名)相同,那就基本肯定子是由父所生;孙与子同样。这样就可以肯定孙确定是源于父一脉,是父(根CA数字证书)的合法继承人。数字证书的验证就是基于一样的原理。



Basic Constraint校验漏洞

那是否无论多少层均可以这样一直信任下去呢?理论上是可行的,但会遇到一个问题。假设我从可信CA机构购买了一张证书,使用这张证书签发的证书是否也会被操做系统和浏览器信任呢?明显是不该该相信的,由于我并非CA机构,假如我签发的证书也被信任的话,那我彻底能够本身签发任何域名的证书来进行伪造攻击。这就是著名的Basic Constraint校验漏洞,X.509证书中的Basic Constraint包含了这是否是一个CA机构,以及有效证书路径的最大深度(即,这个CA还可否继续签发CA机构证书及其签发子CA证书的路径深度)。但在几年前,包括微软和Apple都爆出了没有正确校验这些信息的漏洞。

Basic Constraint信息请看下图:


Google Internet Authority G2

上图是Google Internet Authority G2的证书,该证书是个CA机构证书;路径深度为0,表示该证书没法再签发CA证书,只能签发客户证书(client certificate)。


google.com

上图是google.com的证书,这是个客户证书(client certificate),不可再签发子证书,因此由该证书签发的子证书是不会被信任的。

了解了上面关于SSL/TSL通讯加密策略以及数字证书的概念以后,对HTTPS的安全机制就有了个初步的了解,下面咱们看如何在iOS上实现对HTTPS的支持。



2. 实现支持HTTPS

首先,须要明确你使用HTTP/HTTPS的用途,由于OSX和iOS平台提供了多种API,来支持不一样的用途,官方文档《Making HTTP and HTTPS Requests》有详细的说明,而文档《HTTPS Server Trust Evaluation》则详细讲解了HTTPS验证相关知识,这里就很少说了。本文主要讲解咱们最经常使用的NSURLConnection支持HTTPS的实现(NSURLSession的实现方法相似,只是要求受权证实的回调不同而已),以及怎么样使用AFNetworking这个很是流行的第三方库来支持HTTPS。本文假设你对HTTP以及NSURLConnection的接口有了足够的了解。

 

2.1 验证证书的API

相关的Api在Security Framework中,验证流程以下:

1). 第一步,先获取须要验证的信任对象(Trust Object)。这个Trust Object在不一样的应用场景下获取的方式都不同,对于NSURLConnection来讲,是从delegate方法-connection:willSendRequestForAuthenticationChallenge:回调回来的参数challenge中获取([challenge.protectionSpace serverTrust])。

2). 使用系统默认验证方式验证Trust Object。SecTrustEvaluate会根据Trust Object的验证策略,一级一级往上,验证证书链上每一级数字签名的有效性(上一部分有讲解),从而评估证书的有效性。

3). 如第二步验证经过了,通常的安全要求下,就能够直接验证经过,进入到下一步:使用Trust Object生成一份凭证([NSURLCredential credentialForTrust:serverTrust]),传入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge])处理,创建链接。

4). 假若有更强的安全要求,能够继续对Trust Object进行更严格的验证。经常使用的方式是在本地导入证书,验证Trust Object与导入的证书是否匹配。更多的方法能够查看Enforcing Stricter Server Trust Evaluation,这一部分在讲解AFNetworking源码中会讲解到。

5). 假如验证失败,取消这次Challenge-Response Authentication验证流程,拒绝链接请求。

ps: 假如是自建证书的,则不使用第二步系统默认的验证方式,由于自建证书的根CA的数字签名未在操做系统的信任列表中。

iOS受权验证的API和流程大概了解了,下面,咱们看看在NSURLConnection中的代码实现:

 

2.2 使用NSURLConnection支持HTTPS的实现

// Now start the connection
NSURL * httpsURL = [NSURL URLWithString:@"https://www.google.com"];
self.connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:httpsURL] delegate:self];

    
//回调
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
     //1)获取trust object
    SecTrustRef trust = challenge.protectionSpace.serverTrust;
    SecTrustResultType result;
    
    //2)SecTrustEvaluate对trust进行验证
    OSStatus status = SecTrustEvaluate(trust, &result);
    if (status == errSecSuccess &&
        (result == kSecTrustResultProceed ||
           result == kSecTrustResultUnspecified)) {
           
           //3)验证成功,生成NSURLCredential凭证cred,告知challenge的sender使用这个凭证来继续链接
        NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
        [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
        
    } else {
    
        //5)验证失败,取消此次验证流程
        [challenge.sender cancelAuthenticationChallenge:challenge];
        
  }
}

 

上面是代码是经过系统默认验证流程来验证证书的。假如咱们是自建证书的呢?这样Trust Object里面服务器的证书由于不是可信任的CA签发的,因此直接使用SecTrustEvaluate进行验证是不会成功。又或者,即便服务器返回的证书是信任CA签发的,又如何肯定这证书就是咱们想要的特定证书?这就须要先在本地导入证书,设置成须要参与验证的Anchor Certificate(锚点证书,经过SecTrustSetAnchorCertificates设置了参与校验锚点证书以后,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书自己,则信任该证书),再调用SecTrustEvaluate来验证。代码以下

 

//先导入证书
NSString * cerPath = ...; //证书的路径
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
self.trustedCertificates = @[CFBridgingRelease(certificate)];

//回调
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
     //1)获取trust object
    SecTrustRef trust = challenge.protectionSpace.serverTrust;
    SecTrustResultType result;

    //注意:这里将以前导入的证书设置成下面验证的Trust Object的anchor certificate
      SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);

    //2)SecTrustEvaluate会查找前面SecTrustSetAnchorCertificates设置的证书或者系统默认提供的证书,对trust进行验证
    OSStatus status = SecTrustEvaluate(trust, &result);
    if (status == errSecSuccess &&
        (result == kSecTrustResultProceed ||
           result == kSecTrustResultUnspecified)) {
           
           //3)验证成功,生成NSURLCredential凭证cred,告知challenge的sender使用这个凭证来继续链接
        NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
        [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
        
    } else {
    
        //5)验证失败,取消此次验证流程
        [challenge.sender cancelAuthenticationChallenge:challenge];
        
  }
}
 

建议采用本地导入证书的方式验证证书,来保证足够的安全性。更多的验证方法,请查看官方文档《HTTPS Server Trust Evaluation》

 

2.3 使用AFNetworking来支持HTTPS

AFNetworking是iOS/OSX开发最流行的第三方开源库之一,其做者是很是著名的iOS/OSX开发者Mattt Thompson,其博客NSHipster也是iOS/OSX开发者学习和开阔技术视野的好地方。AFNetworking已经将上面的逻辑代码封装好,甚至更完善,在AFSecurityPolicy文件中,有兴趣能够阅读这个模块的代码;

AFNetworking上配置对HTTPS的支持很是简单:

 

NSURL * url = [NSURL URLWithString:@"https://www.google.com"];
AFHTTPRequestOperationManager * requestOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:url];
dispatch_queue_t requestQueue = dispatch_create_serial_queue_for_name("kRequestCompletionQueue");
requestOperationManager.completionQueue = requestQueue;

AFSecurityPolicy * securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];

//allowInvalidCertificates 是否容许无效证书(也就是自建的证书),默认为NO
//若是是须要验证自建证书,须要设置为YES
securityPolicy.allowInvalidCertificates = YES;

//validatesDomainName 是否须要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其余可信任机构颁发的证书,也能够创建链接,这个很是危险,建议打开。
//置为NO,主要用于这种状况:客户端请求的是子域名,而证书上的是另一个域名。由于SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是没法验证经过的;固然,有钱能够注册通配符的域名*.google.com,但这个仍是比较贵的。
//如置为NO,建议本身添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = YES;

//validatesCertificateChain 是否验证整个证书链,默认为YES
//设置为YES,会将服务器返回的Trust Object上的证书链与本地导入的证书进行对比,这就意味着,假如你的证书链是这样的:
//GeoTrust Global CA 
//    Google Internet Authority G2
//        *.google.com
//那么,除了导入*.google.com以外,还须要导入证书链上全部的CA证书(GeoTrust Global CA, Google Internet Authority G2);
//如是自建证书的时候,能够设置为YES,加强安全性;假如是信任的CA所签发的证书,则建议关闭该验证,由于整个证书链一一比对是彻底没有必要(请查看源代码);
securityPolicy.validatesCertificateChain = NO;

requestOperationManager.securityPolicy = securityPolicy;
 

这就是AFNetworking的支持HTTPS的主要配置说明,AFHTTPSessionManager与之基本一致,就不重复了。

参考连接: http://oncenote.com/2014/10/21/Security-1-HTTPS/

     http://www.cnblogs.com/oc-bowen/p/5896041.html

相关文章
相关标签/搜索