本文参考链接:
Jamin
HTTPS信任证书的三种方法
在2016年底, Apple要求所有App适配App Transport Security,简单的说就是除了特殊情况外,App跟服务端通信必须使用HTTPS协议,否则将阻止明文的HTTP请求,从2017年1月1日起,所有的新提交app默认是不允许使用 Allow Arbitrary Loads来绕过ATS限制的。
但是在2017年1月11日,Apple发布声明宣布延长这个时限,提供开发者更多的时间来准备适应,目前Apple仍未发布最新的截止日期。
虽然这个消息让很多iOS开发者长出一口气,至少目前不必担心了,但是如果你不准备好HTTPS,那么它终究是你头顶上的达摩克利斯之剑,作为一名有追求的iOS开发者,有必要更深入的了解HTTPS。
一. HTTPS
其实HTTPS从最终的数据解析的角度,与HTTP没有任何的区别,HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,就是普通的HTTP数据。HTTP和SSL/TSL都处于OSI模型的应用层。从HTTP切换到HTTPS是一个非常简单的过程,在做具体的切换操作之前,我们需要了解几个概念:
SSL/TSL
关于SSL/TSL,阮一峰的两篇博客文章做了很好的介绍:
简单的来说,SSL/TSL通过四次握手,主要交换三个信息:
数字证书:该证书包含了公钥等信息,一般是由服务器发给客户端,接收方通过验证这个证书是不是由信赖的CA签发,或者与本地的证书相对比,来判断证书是否可信;假如需要双向验证,则服务器和客户端都需要发送数字证书给对方验证。
三个随机数:这三个随机数构成了后续通信过程中用来对数据进行对称加密解密的“对话密钥”。首先客户端先发第一个随机数N1,然后服务器回了第二个随机数N2(这个过程同时把之前提到的证书发给客户端),这两个随机数都是明文的;而第三个随机数N3(这个随机数被称为Premaster secret),客户端用数字证书的公钥进行非对称加密,发给服务器;而服务器用只有自己知道的私钥来解密,获取第三个随机数。只有服务端和客户端都有了三个随机数N1+N2+N3,然后两端就使用这三个随机数来生成“对话密钥”,在此之后的通信都是使用这个“对话密钥”来进行对称加密解密。因为这个过程中,服务端的私钥只用来解密第三个随机数,从来没有在网络中传输过,这样的话,只要私钥没有被泄露,那么数据就是安全的。
加密通信协议:就是双方商量使用哪一种加密方式,假如两者支持的加密方式不匹配,则无法进行通信。
有个常见的问题,关于随机数为什么要三个?只最后一个随机数N3不可以么?
这是由于SSL/TLS设计,就假设服务器不相信所有的客户端都能够提供完全随机数,假如某个客户端提供的随机数不随机的话,就大大增加了“对话密钥”被破解的风险,所以由三组随机数组成最后的随机数,保证了随机数的随机性,以此来保证每次生成的“对话密钥”安全性。
数字证书
数字证书是一个电子文档,其中包含了持有者的信息、公钥以及证明该证书有效的数字签名。而数字证书以及相关的公钥管理和验证等技术组成了PKI(公钥基础设施)规范体系。一般来说,数字证书是由数字证书认证机构(Certificate authority,即CA)来负责签发和管理,并承担PKI体系中公钥合法性的检验责任;数字证书的类型有很多,而HTTPS使用的是SSL证书。
怎么来验证数字证书是由CA签发的,而不是第三方伪造的呢? 在回答这个问题前,我们需要先了解CA的组织结构。首先,CA组织结构中,最顶层的就是根CA,根CA下可以授权给多个二级CA,而二级CA又可以授权多个三级CA,所以CA的组织结构是一个树结构。了解了CA的组织结构后,来看看数字证书的签发流程:
数字证书的签发机构CA,在接收到申请者的资料后进行核对并确定信息的真实有效,然后就会制作一份符合X.509标准的文件。证书中的证书内容包括了持有者信息和公钥等都是由申请者提供的,而数字签名则是CA机构对证书内容进行hash加密后等到的,而这个数字签名就是我们验证证书是否是有可信CA签发的数据。
接收端接到一份数字证书Cer1后,对证书的内容做Hash等到H1;然后在签发该证书的机构CA1的数字证书中找到公钥,对证书上数字签名进行解密,得到证书Cer1签名的Hash摘要H2;对比H1和H2,假如相等,则表示证书没有被篡改。但这个时候还是不知道CA是否是合法的,我们看到上图中有CA机构的数字证书,这个证书是公开的,所有人都可以获取到。而这个证书中的数字签名是上一级生成的,所以可以这样一直递归验证下去,直到根CA。根CA是自验证的,即他的数字签名是由自己的私钥来生成的。合法的根CA会被浏览器和操作系统加入到权威信任CA列表中,这样就完成了最终的验证。所以,一定要保护好自己环境(浏览器/操作系统)中根CA信任列表,信任了根CA就表示信任所有根CA下所有子级CA所签发的证书,不要随便添加根CA证书。
了解了上面两个概念之后,对HTTPS就有了个初步的了解,下面我们看如何在iOS上实现对HTTPS的支持。
二. 实现APP支持HTTPS
首先,需要明确你使用HTTP/HTTPS的用途,因为OSX和iOS平台提供了多种API,来支持不同的用途,官方文档《Making HTTP and HTTPS Requests》有详细的说明,而文档《HTTPS Server Trust Evaluation》则详细讲解了HTTPS验证相关知识,这里就不多说了。本文主要讲解我们最常用的NSURLSession支持HTTPS的实现,以及怎么样使用AFNetworking这个非常流行的第三方库来支持HTTPS。本文假设你对HTTP以及NSURLSession的接口有了足够的了解。
使用NSURLSession来支持HTTPS
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:@"https://域名"];
// 发起数据任务
[[self.session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@---%@",response,[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
// 在代理方法中实现证书的信任
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
/*
<NSURLProtectionSpace: 0x7fef2b686e20>:
Host:mail.itcast.cn,
Server:https,
Auth-Scheme:NSURLAuthenticationMethodServerTrust,
*/
// 判断是否是信任服务器证书
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
// 告诉服务器,客户端信任证书
// 创建凭据对象
NSURLCredential *credntial = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 通过completionHandler告诉服务器信任证书
completionHandler(NSURLSessionAuthChallengeUseCredential,credntial);
}
NSLog(@"protectionSpace = %@",challenge.protectionSpace);
}
上面是代码是通过系统默认验证流程来验证证书的。假如我们是自建证书的呢?这样的话服务器的证书因为不是可信任的CA签发的,所以直接使用进行验证是不会成功的。又或者,即使服务器返回的证书是信任CA签发的,又如何确定这证书就是我们想要的特定证书?这就需要先在本地导入证书,再验证。
使用AFNetworking来支持HTTPS
AFNetworking是iOS/OSX开发最流行的第三方开源库之一,其作者是非常著名的iOS/OSX开发者Mattt Thompson,其博客NSHipster也是iOS/OSX开发者学习和开阔技术视野的好地方。AFNetworking已经将上面的逻辑代码封装好,甚至更完善,在AFSecurityPolicy文件中,有兴趣可以阅读这个模块的代码。 下面是代码部分:
// 1.初始化单例类
AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
mgr.securityPolicy.SSLPinningMode = AFSSLPinningModeCertificate;
// 2.设置证书模式
NSString * cerPath = [[NSBundle mainBundle] pathForResource:@"xxx" ofType:@"此处填文件的拓展名"];
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
mgr.securityPolicy.pinnedCertificates = [[NSArray alloc] initWithObjects:cerData, nil];
// 客户端是否信任非法证书
mgr.securityPolicy.allowInvalidCertificates = YES;
// 是否在证书域字段中验证域名
[mgr.securityPolicy setValidatesDomainName:NO];
最后建议采用本地导入证书的方式验证证书,来保证足够的安全性。
更多的验证方法,请查看官方文档《HTTPS Server Trust Evaluation》