2019独角兽企业重金招聘Python工程师标准>>>

深入理解HTTPS及在iOS系统中适配HTTPS类型网络请求

一、引言

本篇博客主要讨论如何在客户端与服务端之间进行HTTPS网络传输,为了深入理解网络传输的基础原理,更加灵活的校验证书,博客的前半部分也将介绍一些HTTPS网络传输原理。当然,文章中有不正和疏漏之处,还望朋友不吝指正,感谢!

二、HTTP与HTTPS

我们都知道,HTTP是一种常用的网络传输协议,它是基于TCP的一种应用层协议,应用层是什么样的一个概念,通过下面这张示意图可以很好的理解:

HTTP协议的网络传输十分常见,例如网易的主页http://www.163.com/。HTTP类型的网络传输使用十分方便,但是其在安全性上却有很大问题,列举如下:

1.HTTP协议在传输数据时是明文的,任何人通过一个简单的抓包工具,就可以截获到所有传输数据。

2.HTTP协议在传输数据时无法保证数据的完整,在截获到明文数据后,很容易就可以将其篡改,这也是一些网页总是被植入恶意广告的原因。

3.HTTP协议在传输数据时无法保证真实性,这也是最恐怖的一点。误入了域名欺骗的钓鱼网站,极容易对用户带来财产损失。

基于上面3点安全性的考虑,一种更加安全的网络传输协议势必要推行,那就是HTTPS。

要理解HTTPS协议,首先需要明白什么是SSL/TLS。SSL全称“Secure Sockets Layer”,意思为安全套接层。其实由网景公司为了解决HTTP传输协议在安全方面的缺陷而设计的。后来被标准化,更名为TLS,全称“Transport Layer Security”,意思为传输层安全协议。

那么现在就好理解了,其实HTTPS就是将HTTP协议与TLS协议组合起来,在不改变HTTP协议原设计的基础上,为其添加安全性校验并对传输的数据进行加密。那么TLS究竟在网络传输的那一层进行了处理了,下图可以很好的表示:

三、证书

通过前面所介绍,我们知道HTTPS主要是为了解决3个问题:数据加密、数据完整、数据真实。那么下一步就是如何解决这些问题,数据加密在发送数据前依赖SSL层对数据进行加密,数据完整与真实性则要靠另一种关键技术:数字证书。

通过一个小例子可以很容易的理解证书的作用,这个例子的来源是<编程随想>的作者,我这里暂且借用一下:A公司的a到B公司办事,为了证明a确实是A公司的职员而不是商业间谍,A公司会为a提供一个带有公章的证明,当B公司看到这个证明时,就可以信任办事员a。对比网络传输,这个证明就是证书,证书可以保证这个网站的真实性。我们继续往后分析,当B公司与越来越多的公司进行商业合作时,就又有新的问题出现了,比如C公司的c来B公司办事,就需要拿C公司带公章的证明,D公司的d来B公司办事就需要拿D公司带公章的证明...这样一来,B公司要存放好多公司的公章和证明的模板,才能够完成校验。这样未免也太麻烦了,对应到网络传输中,客户端就是B公司,各个网站都有自己的证书文件,这样客户端需要安装信任大量的证书,为了解决这样的问题,就有了第三方CA机构。第三方CA机构是由大家公认信任的机构,例如R公司为第三方信任机构,其业务是为其他公司提供公章证明,这样一来,B公司只要保有这个R公司的公章证明副本,其他A,C,D公司的办事员也只需要从R公司申请到一个公章证明就可以到B公司来交流业务了。

CA的全称是“Certificate Authority”,意为证书授权中心。大部分CA机构颁发的证书都是需要付费的,CA机构颁发的证书一般都是根证书,根证书也比较容易理解,首先证书是有链式信任关系的,例如Y证书是由CA机构颁发的根证书,由这个Y证书还可以创建出许多子证书,子证书可以继续创建子证书,只要根证书是受信任的,其下所有的子证书都是受信任的,如下图:

我们可以打开开源中国博客的主页:https://www.oschina.net/blog。在Chrome浏览器地址栏左边可以查看证书信息,如下:

点击证书信息,可以看到完整的证书链,如下图:

从图中可以看到,根证书是由CA机构VerSign公司颁发的。此处还可以看到当前证书是否有效以及过期时间,如果证书无效则说明此网页信息有可能被篡改过,用户在访问时就要小心了。

除了CA机构可以签发证书外,个人其实也是可以创建证书的,当然个人创建的证书也是不被信任的,我们姑且把这类证书叫做自签名证书,如果用自签名证书搭建了HTTPS的服务,则客户端需要安装对应的证书信任,才可以进行此服务的访问。后面我们会进一步讨论自签名证书的使用。

四、搭建一个本地的HTTPS服务

使用Node.js可以快速的搭建前端服务,我们这里使借助Express框架来搭建本地的HTTPS服务,用于测试我们后边将要进行HTTPS通讯。Express搭建搭建项目模板的过程在以前的一篇博客中有详细的介绍,这里就不再重复了,地址如下:

使用Express搭建前端项目:https://my.oschina.net/u/2340880/blog/794928。

根据前面所述,搭建HTTPS服务需要有证书凭证,两种证书我们可以选择,一种是CA机构签发的证书,还有一种是我们自己制作的自签名证书,在Mac电脑上打开钥匙串访问应用,打开其中的证书助理,如下图所示:

选择其中的为您自己创建证书选项,如下图:

在之后的界面中,输入证书的名称,选择证书类型,如下图所示:

上面,我把证书的名字创建成了珲少,身份类型选择的是自签名的根证书,证书类型选择SSL服务器,之后点击创建即可完成证书的创建。

创建完成后,在钥匙串访问的登录证书中,可以看到已经有了珲少这个自签名的证书,如下图:

在证书上点击右键,选择导出选项,名字我将其取名为huishao,文件类型要选择.p12,如下图所示:

点击存储后,需要设置一个访问密码,这个密码将来将用于从.p12文件中获取证书和密钥,如下图所示:

之后,系统有可能会让你再次输入一个密码,将入下图所示,注意,这里需要输入的是系统的登录密码:

完成上面操作后,我们已经将一个.p12文件导出到了桌面。那么这个.p12文件到底是个什么东西呢,它和证书之间又有什么关系呢,其实.p12文件一个复合文件,其中包装了私钥与证书信息,使用OpenSSL工具可以将其中的信息进行提取,搭建一个HTTPS的服务器需要两个文件,分别问证书文件和私钥文件,下面我们来从.p12文件中提取这些需要的文件。

打开终端,cd到huishao.p12文件所在的目录下,使用如下命令可以将.p12文件中的私钥分解出来:

openssl pkcs12 -in huishao.p12 -nocerts -out privateKey.pem -nodes

之间会要求输入导出.p12文件时所设置的密码。

使用如下命令将.p12文件中的证书分解出来:

openssl pkcs12 -in huishao.p12 -nokeys -out cert.pem -nodes

之间也会要求输入导出.p12文件时所设置的密码。完成上面两部操作后,可以看到当前文件夹下多了两个文件,分别为cert.pem与privateKey.pem,他们分别是证书文件与密钥文件,将他们拷贝到Express项目的bin文件夹下,使得Express项目的结构看起来如下图所示:

下面我们来配置Express项目。

在生成好的Express项目中的www文件的末尾添加如下代码:

/*
HTTPS
*/
var fs = require('fs');
var https = require('https');
/*
密钥文件
*/
var privatekey = fs.readFileSync('./privateKey.pem', 'utf8');
/*
证书文件
*/
var certificate = fs.readFileSync('./cert.pem', 'utf8');
var options={key:privatekey, cert:certificate};
var serverHttps = https.createServer(options, app);
/*
绑定端口
*/
serverHttps.listen(8080,function () {console.log('Https server listening on port ' + 8080);
}); 

用终端在bin文件夹下运行 node www,效果如下:

在浏览器打开:https://localhost:8080/users,如果服务器搭建成功,Chrome中会出现如下效果:

点击高级,点击其中的继续访问,可以正常获取到服务器返回的数据。到此,我们的HTTPS服务就搭建成功了。

五、iOS开发中通过配置info.plist文件来允许HTTP协议类型的通讯

前面扯了太多,终于提到重点部分了。Apple在iOS9中就已经漏出一些强制HTTPS通讯的端倪,只是给了开发者一些过渡,在iOS10及以后的审核机制中,Apple对于强制HTTPS的推动将会越来越强,如何让自己的应用程序尽快的适配HTTPS相关的标准,是iOS开发者必须面对的任务。

通过前面的分析我们了解,CA机构签发的证书是被默认信任的,这就是说,如果你的公司比较有钱,愿意花钱从CA机构申请一个付费的证书,那么很幸运,你的iOS工程是不需要做任何修改的,这些CA机构签发的证书是默认受信任的,因此你可以直接在程序中进行HTTPS类型的请求,所需要修改的只是将请求url改成https开头。但是另一种情况,无论出于什么原因,你的后台服务用的是自签名的证书,就想我们上面搭建的HTTPS服务一样,如果在不做任何处理的情况下在项目中访问这样的服务,就会出现问题了,原因是我们自己创建的自签名证书是不受信任的,系统默认拒绝了请求,示例如下:

-(void)normalHttps{NSURLRequest * req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://localhost:8080/users"]];NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];NSURLSession * session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];[[session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {NSLog(@"%@,%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],error);}] resume];
}

运行工程后可以看到,并没有获取到相关数据,Xcode提示为:

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)

好了,那么我们先不管HTTPS的问题,如果我们直接对HTTP协议的服务进行请求,会不会有问题呢,将代码修改如下:

-(void)normalHttps{NSURLRequest * req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:3000/users"]];NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];NSURLSession * session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];[[session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {NSLog(@"%@,%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],error);}] resume];
}

需要注意:Express在进行项目模板的创建时,会默认帮我们绑定一个3000端口的HTTP服务。

运行工程后,可以发现HTTP协议的请求也无法访问,报错如下:

 App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

其意思大致是说应用程序传输安全要求强制使用HTTPS类型的服务,但是开发者可以通过配置info.plsit文件来回避这一政策。这就是我们这节的重点,通过文件配置的方式来跳过应用安全传输协议。

在iOS9之后,开发者可以在Info.plist文件中添加如下键:NSAppTransportSecurity。这个键用来配置APP传输安全的相关策略,是字典类型,其中可以设置的键有五个,如下:

NSAllowsArbitraryLoads:布尔值,默认为NO,设置为YES则代表除了NSExceptionDomains中设置的域名外,其他所有请求的协议类型都不受限制,也就是说可以支持HTTP类型的请求,这个键的作用域是全局的,App内所有的请求都受影响,但是如果开发者设置为了YES,在提交审核时需要说明原因。

NSAllowsArbitraryLoadsForMedia:布尔值,默认为NO,设置为YES的话,则应用程序内所有的媒体数据的加载将不受协议类型的限制,同样如果开发者设置为了YES,则在提交审核时需要说明原因。

NSAllowsArbitraryLoadsInWebContent:布尔值,默认为NO。如果设置为YES,则应用程序内所有WebView的请求加载不受协议类型的限制,开发者设置为了YES,则在提交审核时需要说明原因。

NSAllowsLocalNetworking:布尔值,默认为NO,如果设置为YES,则在加载本地资源时不受安全传输协议的限制。

NSExceptionDomains:字典,其主要对某些特殊域名做限制。其中结构可以表示如下:

NSAppTransportSecurity : Dictionary {NSAllowsArbitraryLoads : BooleanNSAllowsArbitraryLoadsForMedia : BooleanNSAllowsArbitraryLoadsInWebContent : BooleanNSAllowsLocalNetworking : Boolean//对某些域名做特殊限制NSExceptionDomains : Dictionary {<domain-name-string> : Dictionary {NSIncludesSubdomains : BooleanNSExceptionAllowsInsecureHTTPLoads : BooleanNSExceptionMinimumTLSVersion : StringNSExceptionRequiresForwardSecrecy : Boolean   // Default value is YESNSRequiresCertificateTransparency : Boolean}}
}

NSIncludesSubdomains:布尔值,这个键的作用是设置此域名下的所有子域名是否采用和父域名相同的配置。

NSExceptionAllowsInsecureHTTPLoads:布尔值,设置是否允许此域名使用自签名的证书进行请求,默认为NO,如果设置为YES,则在提交时需要说明原因。

NSExceptionMinimumTLSVersion:设置所使用的TLS版本。

NSExceptionRequiresForwardSecret:设置为NO,则不允许向前加密方式。

NSRequiresCertificateTransparency:如果设置为YES,则服务端的证书要有有效的透明时间戳。

六、iOS中使用自签名的证书进行HTTPS请求校验

通过Info.plist文件我们是可以绕过安全传输协议的,但是不幸的是,从文档上看,无论开发者通过哪种方式来绕过安全传输协议,Apple都要求开发者在提审时提供合适的理由,这就是说:如果你使用了HTTP协议的请求,没有充足理由的话,你的App有很大的可能被审核拒绝。因此,更加保险的一种方式是将所有的服务都换成HTTPS协议的,如果有CA证书,当然完事大吉,如果没有,我们也可以通过验证自签名证书的方式来适配HTTPS协议。

在进行HTTPS请求时,服务端会先将证书文件返回给客户端,如果客户端的证书信任列表中包含这个证书,则此请求可以正常进行,如果没有,则请求会被拒绝。因此,在iOS中适配自签名证书的HTTPS请求实际上就是将这个自签名的证书安装进客户端的信任列表。iOS中需要使用的证书是der格式的,可以使用如下命令将pem格式的证书转换成der格式的证书:

openssl x509 -inform PEM -in cert.pem -outform DER -out cert.der

将生成的cert文件添加进工程中,修改请求如下:

-(void)normalHttps{NSURLRequest * req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://localhost:8080/users"]];NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];NSURLSession * session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];NSURLSessionTask * task = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {NSLog(@"%@,%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],error);}];[task resume];
}

除此之外,需要实现一个SURLSessionDelegate的协议方法如下:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challengecompletionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {NSLog(@"证书认证");//先判断证书是否有效if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {//证书验证请求SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];/***  导入多张CA证书(Certification Authority,支持SSL证书以及自签名的CA)*/NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cert" ofType:@"der"];//自签名证书NSData* caCert = [NSData dataWithContentsOfFile:cerPath];//可以添加多张证书NSArray *caArray = @[caCert];//验证规则NSMutableArray *policies = [NSMutableArray array];[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);NSMutableArray *pinnedCertificates = [NSMutableArray array];//进行自签名证书的添加for (NSData *certificateData in caArray) {[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];}SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);SecTrustResultType result = -1;//通过本地导入的证书来验证服务器的证书是否可信SecTrustEvaluate(serverTrust, &result);NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];completionHandler(NSURLSessionAuthChallengeUseCredential,credential);return [[challenge sender] useCredential: credentialforAuthenticationChallenge: challenge];}
}

如上修改后,再次运行工程,可以看到已经成功请求到了HTTPS自签名证书服务提供的数据:

介于篇幅过长,关于NSURLAuthenticationChallenge相关类的更多探讨和常用网络库AFNetworking中HTTPS的适配,下篇博客会继续介绍。

专注技术,热爱生活,交流技术,也做朋友。

——珲少 QQ群:203317592

转载于:https://my.oschina.net/u/2340880/blog/807358

深入理解HTTPS及在iOS系统中适配HTTPS类型网络请求(上)相关推荐

  1. iOS系统中应用程序间通信的方法及装置

    申请号 :CN 201210548391 申请时间 : 2101.12.17 申请人 : 北京奇虎科技有限公司 [摘要] 本发明公开了iOS系统中应用程序间通信的方法及装置,用于在当前应用程序与目标应 ...

  2. iPhone iOS 系统中 设置-开发者 对应的翻译

    iOS 系统中 设置开发者对应的翻译 ------ 第一屏 第二屏 第三屏 针对网络差的调试, 模拟网络特别慢的环境 原文链接 https://www.jianshu.com/p/55ec0d466f ...

  3. iOS系统中导航栏的转场解决方案与最佳实践

    背景 目前,开源社区和业界内已经存在一些 iOS 导航栏转场的解决方案,但对于历史包袱沉重的美团 App 而言,这些解决方案并不完美.有的方案不能满足复杂的页面跳转场景,有的方案迁移成本较大,为此我们 ...

  4. IOS系统中H5页面实现摇一摇功能

    公司准备开年会了,年会活动用了一套别人的系统,根据测试,有些游戏的摇一摇功能在IOS上无法使用,为了修复该功能,踩了一些坑,特此记录如下: 1. 因为IOS系统的安全要求,项目必须是在https的域名 ...

  5. 在MacOS和iOS系统中使用OpenCV

    在MacOS和iOS系统中使用OpenCV 前言 OpenCV 是一个开源的跨平台计算机视觉库,实现了图像处理和计算机视觉方面的很多通用算法. 最近试着在 MacOS 和 iOS 上使用 OpenCV ...

  6. vue单应用在ios系统中实现微信分享功能

    表示是第一次使用vue做单应用显目,也是在逐渐的摸索中~更是各种踩坑,各种填坑,打算写博客么?是因为不想写笔记了,嗯嗯 就是这么简单 进入正题. 刚开始做微信分享的这个功能的时候,脑补了官方文档微信J ...

  7. 解决Android软键盘弹出覆盖h5页面输入框问题 // 在ios系统中输入框软键盘消失后,页面不回弹的问题 // 解决苹果不回弹页面 // 微信环境打开

    //解决Android软键盘弹出覆盖h5页面输入框问题 window.addEventListener('resize', () => {if (document.activeElement.t ...

  8. IOS系统中input标签获取焦点页面会放大的问题

    IOS系统中input标签获取焦点页面会放大的问题 在iOS系统中打开网页,input框输入时,输入框获取焦点的时候页面会放大,如果没有经过设置,页面放大之后就不会自动还原.使内容撑开浏览器,底部产生 ...

  9. Android系统中如何添加USB网络共享

    Android系统中如何添加USB网络共享 类别 需求 索引类别 USB网络共享 问题描述 平台是RT1296,在Android系统中已经有支持USB网络共享,但相应的产品系统中还未开启USB网络共享 ...

最新文章

  1. CodeForces - 993E Nikita and Order Statistics
  2. mysql 5.6 gtid mha_MySQL MHA--故障切换模式(GTID模式和非GTID模式)
  3. 服务器发消息给所有人,我需要服务器向所有客户端(Python、sockets)发送消息...
  4. golang的reflection(转)(一)
  5. mysql order by按照汉字拼音进行排序
  6. AbstractQueuedSynchronizer浅析——同步
  7. mysql 当天创建分区表_mysql8.0 定时创建分区表记录 每天定时创建下一天的分区表...
  8. 【Siddhi】Siddhi的语法关键字
  9. 算法应用三:【图像分割】+【边缘检测】canny边缘检测--冈萨雷斯--《数字图像处理》
  10. reactive streams的Mono及Flux
  11. angular2+typescript在asp.net MVC Web项目上的实现
  12. python字符串format格式化二
  13. Android学习小Demo(14)Android中关于PopupWindow的使用
  14. Linux上通过SUU更新Dell服务器固件
  15. 【Chrome小技巧】Chrome浏览器如何实现下载速度加快?
  16. 学习笔记--数据结构与算法基础(青岛大学-王卓)--第六章图
  17. linux C++ socket编程 实例
  18. mac os无法登录appstore的解决方法
  19. JVM面试知识点合集 — Android 春招 2022
  20. 传统会计和计算机会计的职能,论会计信息化对传统财务会计职能的影响

热门文章

  1. LeetCode 80. Remove Duplicates from Sorted Array II
  2. 【C++】使用setprecision控制输出流显示浮点数的有效数字个数
  3. python打乱数据集_在Keras中利用np.random.shuffle()打乱数据集实例
  4. Linux修改主机名称
  5. curl模拟http发送get或post接口测试
  6. mysql 多个字段拼接 concat
  7. Net::SSH::Perl 连接远程主机慢的原因
  8. Nginx 架构——【核心流程+模块介绍】
  9. [Android Pro] app_process command in Android
  10. CentOS 安装jdk1.7 32位