0%

一. HTTPS

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

SSL/TLS:

为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。SSL目前的版本是3.0,被IETF(Internet Engineering Task Force)定义在RFC 6101中,之后IETF对SSL 3.0进行了升级,于是出现了TLS(Transport Layer Security) 1.0,定义在RFC 2246。实际上我们现在的HTTPS都是用的TLS协议,但是由于SSL出现的时间比较早,并且依旧被现在浏览器所支持,因此SSL依然是HTTPS的代名词,但无论是TLS还是SSL都是上个世纪的事情,SSL最后一个版本是3.0,今后TLS将会继承SSL优良血统继续为我们进行加密服务

SSL/TLS协议运行机制的概述

简单的来说,SSL/TSL通过四次握手。SSL协议的工作流程:

  • 服务器认证阶段:
  • 客户端向服务器发送一个开始信息“Hello”以便开始一个新的会话连接;
  • 服务器根据客户的信息确定是否需要生成新的主密钥,如需要则服务器在响应客户的“Hello”信息时将包含生成主密钥所需的信息;
  • 客户根据收到的服务器响应信息,产生一个主密钥,并用服务器的公开密钥加密后传给服务器;
  • 服务器恢复该主密钥,并返回给客户一个用主密钥认证的信息,以此让客户认证服务器。
  • 用户认证阶段:

在此之前,服务器已经通过了客户认证,这一阶段主要完成对客户的认证。

经认证的服务器发送一个提问给客户,客户则返回(数字)签名后的提问和其公开密钥,从而向服务器提供认证

二. App Transport Security

iOS9中新增App Transport Security(简称ATS)特性, 主要使到原来请求的时候用到的HTTP,都转向TLS1.2协议进行传输。这也意味着所有的HTTP协议都强制使用了HTTPS协议进行传输。在 iOS 9 和 OS X 10.11 中,默认情况下非 HTTPS 的网络访问是被禁止的。当然,因为这样的推进影响面非常广,作为缓冲,我们可以在 Info.plist 中添加 NSAppTransportSecurity
字典并且将 NSAllowsArbitraryLoads设置为 YES来禁用 ATS。

不过,WWDC 16 中,Apple 表示将继续在 iOS 10 和 macOS 10.12 里收紧对普通 HTTP 的访问限制。从 2017 年 1 月 1 日起,所有的新提交 app 默认是不允许使用 NSAllowsArbitraryLoads 来绕过 ATS 限制的,也就是说,我们最好保证 app 的所有网络请求都是 HTTPS 加密的,否则可能会在应用审核时遇到麻烦

三. iOS10 NSAppTransportSecurity

通过在info.plist中配置这个键,开发者可以自定义网络安全策略。例如:

  • 允许针对个别服务器的不安全访问。
  • 允许不安全的 web 或媒体内容访问,但不影响整个app的ATS策略。
  • 启用新的安全特性,例如Certificate Transparency。
  • 对NSAppTransportSecurity的支持自 iOS9.0,OS X v10.11 开始,适用于 app 和 app extension。

自 iOS10.0,macOS 10.12 开始,增加了对下列子键的支持:

1
2
3
4
NSAllowsArbitraryLoadsInMedia
NSAllowsArbitraryLoadsInWebContent
NSRequiresCertificateTransparency
NSAllowsLocalNetworking

ATS Configuration Basics / ATS 配置基础知识

对于使用 iOS9.0, OS X v10.11 SDK 及以上的 app 来说,ATS(App Transport Security)默认开启,NSAllowsArbitraryLoads是字典NSAppTransportSecurity的根键,默认值NO。

在启用 ATS 的情况下,所有的 HTTP 请求必须为 HTTPS(RFC 2818) 连接。任何不安全的 HTTP 请求都将失败。ATS 使用 TLS(Transport Layer Security)v1.2(RFC 5246)。

下面是字典NSAppTransportSecurity的总体结构,所有键都是非必填:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NSAppTransportSecurity : Dictionary {
NSAllowsArbitraryLoads : Boolean
NSAllowsArbitraryLoadsInMedia : Boolean
NSAllowsArbitraryLoadsInWebContent : Boolean
NSAllowsLocalNetworking : Boolean
NSExceptionDomains : Dictionary {
: Dictionary {
NSIncludesSubdomains : Boolean
NSExceptionAllowsInsecureHTTPLoads : Boolean
NSExceptionMinimumTLSVersion : String
NSExceptionRequiresForwardSecrecy : Boolean // Default value is YES
NSRequiresCertificateTransparency : Boolean
}
}
}

可以看出,所有键可以分为两类:主键,这些键用来定义 app 的总体 ATS 策略;子键,即NSExceptionDomains下面的键,使用这些键针对某个域名单独配置.

主键包括:

NSAllowsArbitraryLoads

设置为 YES,解除整个 app 的 ATS 限制;但是,通过-NSExceptionDomains进 行的配置依然有效。默认值为 NO。

注意:设置为 YES,会引发 App Stroe 的审查,开发者必须说明原因。

NSAllowsArbitraryLoadsInMedia

设置为 YES,解除通过 AV Foundation 框架访问媒体内容时的 ATS 限制;启用这个 键,务必确保载入的媒体内容已经被加密,例如受FairPlay保护的文件,或者是安全的 HLS流媒,其中不包含敏感的个人信息。默认为 NO。

NSAllowsArbitraryLoadsInWebContent

设置为 YES,解除通过 web view 发出的网络请求的 ATS 限制。启用这个键,可以使 app 访问任意网页内容,但不影响 app 的总体 ATS 策略。此键值默认为 NO。

NSAllowsLocalNetworking

设置为 YES,使得 app 可以载入任意本地资源,但不影响 app 的总体 ATS 策略。默 认为 NO。

NSExceptionDomains

为一个或多个域名单独配置 ATS。

被单独配置的域名,默认受到完全的 ATS 限制,不管NSAllowsArbitraryLoads的值 如何;需要通过子键,进一步配置
所有的子键都属于NSExceptionDomain。向Info.plist中添加这一主键:创建字典,针对一个或多个域名,以便进行 ATS 配置。

这意味着之前使用主键所做的设置,对于这个域名来说,已经无效。

例如,及时之前设置NSAllowsArbitraryLoadsInMedia为 YES,然而NSExceptionDomain所代表的域名依然不能访问不安全的媒体内容。
基于这样的设定,可以针对域名进行 ATS 配置,增加或减少安全措施。例如:将NSExceptionAllowsInsecureHTTPLoads设置为 YES,就 ;这样做会引发 App Store 的审查,详情见App Store Review for ATS。通过配置NSExceptionRequiresForwardSecrecy为 NO,取消正向保密。

通过配置NSExceptionMinimumTLSVersion,更改 TLS 最低版本

NSExceptionDomains字典构成:

<域名字符串>代表想要配置的特定域名。可以添加多个域名(即添加多个这样的键),为它们统一配置 ATS 策略。这个键对应一个字典,包含以下子键:

  • NSIncludesSubdomains 设置为 YES,当前域名的 ATS 策略适用于其所有子域名。默认为 NO。
  • NSExceptionAllowsInsecureHTTPLoads 设置为 YES,可以同时通过 HTTP 和 HTTPS 访问当前域名。默认为 NO。注意,配置这个键值,将引发 App Store 的审查,开发者必须说明原因。
  • NSExceptionMinimumTLSVersion* 指定 TLS 的最低版本,因此可以使用版本较低,有安全漏洞的 TLS 协议。注意,配置这个键值,将引发 App Store 的审查,开发者必须说明原因。
  • NSExceptionRequiresForwardSecrecy* 设置为 NO,允许针对当前域名使用不支持正向保密的 TLS 加密算法。默认为 YES。
  • NSRequiresCertificateTransparency* 设置为 YES,将验证域名服务器证书的Certificate Transparency时间戳 。默认为 NO。

Requirements for Connecting Using ATS / 使用 ATS 的前提条件

在 ATS 完全开启的情况下,系统要求 app 的 HTTPS 连接必须满足以下要求:

X.509 数字证书必须满足下列标准中的一项:

由操作系统内嵌的根证书颁发机构签发
由通过操作系统管理员或用户主动安装的根证书颁发机构签发TLS 版本必须为1.2,任何不使用或使用较低版本 TLS / SSL 的连接,都将失败。

连接必须使用 AES-128 或 AES-256 对称加密算法。 TLS 算法套装必须以 ECDSA 密钥交换的形式支持正向保密,加密算法必须为下面之一:

1
2
3
4
5
6
7
8
9
10
11
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

服务端的叶证书签名密钥必须为下面之一:

至少2048位的 RSA 密钥

至少256位的 ECC 密钥

此外,服务器证书的哈希算法必须为 SHA-2,其摘要长度至少位256位(即 SHA-256 及以上)。上面的标准,未来可能会发生变化。但不会影响到 app 二进制包的兼容性。

App Store Review for ATS / App Store 对于 ATS 相关项的审核

某些对 ATS 的配置会引发 App Store 的审核,开发者必须说明原因。这些键有:

1
2
3
NSAllowsArbitraryLoads
NSExceptionAllowsInsecureHTTPLoads
NSExceptionMinimumTLSVersion

以下是一些原因说明例子,供参考:

  • 必须连接由其他机构控制的服务器,其还不支持安全连接。
  • 必须支持那些还未升级至可使用安全连接,不得不通过公共域名访问网络的设备。
  • 必须通过 web 展示来源不一的各种网络内容,但又不能完全使用NSAllowsArbitraryLoadsInWebContent所管理的类。
  • 向 App Store 提交审核时,开发者应主动提供足够的信息,以便解释 app 无法使用安全连接的原因

四. 实现支持安全ATS策略

ATS相关设置对iOS8及以下系统无效

需要解决的问题(iOS 9、iOS10及以上)

1、app内服务器网络请求访问支持https(即一般的请求)

2、webview内支持任意http访问

3、第三方sdk接入与支持http访问

主要是围绕info.pilst配置文件作相关的安全ATS策略

Info.plist文件是向操作系统描述应用程序的XML属性列表,是iPhone应用程序文件夹包含所有重要的Info.plist文件
你可能注意到一些关键字像是使用了一些其他关键字中的词但是在前面加上了”ThirdParty”字样,在功能上,这些关键字与不含有”ThirdParty”的关键字有同样的效果。而且实际运行中所调用的代码将会完全忽略是否使用”ThirdParty”关键字。

简单粗暴的方案:


NSExceptionDomains 的设置方法如下, 比如我们要将 weibo.com 这个域名排除在 ATS 验证之外,就可以这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
key>NSAppTransportSecurity</key>  
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>weibo.com</key>
<dict>
<key> NSIncludesSubdomains </key>
<true/>
<key> NSExceptionRequiresForwardSecrecy </key>
<false/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

注意:每个需添加的域都需要设置此三个属性。如果请求的网络图片是HTTP,也是需要设置的图片的域。

注意⚠️这个方案风险较大,有可能被拒绝。“需要访问的域名是第三方服务器,他们没有进行 HTTPS 对应”会是审核时的一个可选理由,但是这应该只需要针对特定域名,而非全面开放。如果访问的是自己的服务器的话,可能这个理由会无法通过.

实现方案

1、app内服务器网络请求访问支持https

解决方案:

搭建https服务器

搭建https服务器需要ssl证书

  • ssl自制证书:称自签名ssl证书,容易被假冒伪造,浏览器不信任。(审核不通过)
  • 免费CA证书:部分CA机构提供免费的SSL证书,如wosign,starts等(App Store pass掉不通过)
  • 付费CA证书:多指企业级及以上的数字证书。

HTTPS服务器满足ATS默认的条件,而且SSL证书是通过权威的CA机构认证过的,那么我们在使用Xcode开发的时候,对网络的适配什么都不用做,我们也能正常与服务器通信。但是,当我们对安全性有更高的要求时或者我们自建证书时,我们需要本地导入证书来进行验证。

使用AFNetworking来支持https

AFNetworking是iOS/OSX开发最流行的第三方开源库之一,现在iOS oc 代码90%以上都是用这个框架网络请求。AFNetworking已经将上面的逻辑代码封装好,甚至更完善,在AFSecurityPolicy文件中,有兴趣可以阅读这个模块的代码;以下就是在AFNetworking 2.6.0以前版本和3.0.0版本基于支持https的验证方式

步骤:

  • 新建一个manager
  • 在mainBundle中寻找我们刚才拖进项目中的https.cer, 并且将相关的数据读取出来
  • 新建一个AFSecurityPolicy,并进行相应的配置
  • 将这个AFSecurityPolicy 实例赋值给manager

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 3.0.0以上版本用的是AFHTTPSessionManager

AFHTTPSessionManager manager = [AFHTTPSessionManager manager];
NSString cerPath = [[NSBundle mainBundle] pathForResource:@”server” ofType:@”cer”];
NSData cerData = [NSData dataWithContentsOfFile:cerPath];
NSLog(@”%@”, cerData);
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:[[NSArray alloc] initWithObjects:cerData, nil nil]];
manager.securityPolicy.allowInvalidCertificates = YES;
[manager.securityPolicy setValidatesDomainName:NO];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
NSDictionary parameter = @{@”username”:self.username, @”password”:self.password};
[manager POST:@”https://192.168.1.4:9777" parameters:parameter success:^(NSURLSessionDataTask task, id responseObject) {
NSLog(@”success %@”, responseObject);
}
failure:^(NSURLSessionDataTask task, NSError * error) {
NSLog(@”failure %@”, error);
}]

NSAppTransportSecurity

1
2
3
4
5
6
7
NSAllowsArbitraryLoads //设置为 YES,解除整个app的ATS限制;但是通过NSExceptionDomains进行的配置依然有效

NSAllowsArbitraryLoadsInMedia //设置为 YES,解除通过AVFoundation框架访问媒体内容时的 ATS 限制

NSAllowsArbitraryLoadsInWebContent //设置为 YES,解除通过webview发出的网络请求的ATS限制

NSAllowsLocalNetworking //设置为 YES,使得app可以载入任意本地资源,但不影响app的总体 ATS 策略

2、webview内支持任意http访问

对于网页浏览和视频播放的行为,iOS 10 中新加入了 NSAllowsArbitraryLoadsInWebContent 键。通过将它设置为 YES,可以让 app 中的 WKWebView 和使用 AVFoundation 播放的在线视频不受 ATS 的限制。这也应该是绝大多数使用了相关特性的 app 的选择。但是坏消息是这个键在 iOS 9 中并不会起作用。

如果app只支持 iOS 10,并且有用户可以自由输入网址进行浏览的功能,或者是在线视频音频播放功能的话,简单地加入 NSAllowsArbitraryLoadsInWebContent,并且将组件换成 WKWebKit 或者 AVFoundation 就可以了。如果你还需要支持 iOS 9,并且需要访问网页和视频的话,可能只能去开启 NSAllowsArbitraryLoads 然后提交时进行说明,并且看 Apple 审核员决定让不让通过了。

另外,当 NSAllowsArbitraryLoads 和NSAllowsArbitraryLoadsInWebContent 同时存在时,根据系统不同,表现的行为也会不一样。简单说,iOS 9 只看NSAllowsArbitraryLoads,而 iOS 10 会先看NSAllowsArbitraryLoadsInWebContent。在 iOS 10 中,要是NSAllowsArbitraryLoadsInWebContent 存在的话,就忽略掉NSAllowsArbitraryLoads,如果它不存在,则遵循NSAllowsArbitraryLoads 的设定UIWebView 在 NSAllowsArbitraryLoadsInWebContent 为 YES 时访问 HTTP,无效。WKWebView 在 NSAllowsArbitraryLoadsInWebContent 为 YES 时在iOS 10 中访问 HTTP,有效,iOS 9无效。如果用WkWebView替换UIWebView,iOS 7 将无法使用WkWebView,可做适配加载,没有特殊的什么需求的话,尽早将 UIWebView 全部换为 WkWebView 会比较好。所以为了能让WebView在所有版本都能访问非https内容,只能把NSAllowsArbitraryLoads设置为YES。

解决方案一:

开启 NSAllowsArbitraryLoads 为 YES,然后提交时进行说明

解决方案二:

设置 NSExceptionDomains 属性来访问指定域名,然后提交时进行说明

3、第三方sdk接入与支持http访问

但是按照国内的现状,关闭这个限制也许是更实际的做法。至于原因就太多了,第三方SDK(几乎都是访问http),合作伙伴接入(不能要求它们一定要支持https)第三方sdk,同样需要遵守ATS规则,即第三方sdk也有被ATS过滤的风险,微信,qq,分享,登陆功能都能正常,微博登陆不能正常通过。另在网上找到了一些可能存在有问题的sdk,目前已知的有:

  • 友盟 (已经有最新的v1.4.0版本sdk,支持https,待验证)
  • 百度地图

解决方案一:

更新最新sdk,接入并测试

解决方案二:

可以设置 NSExceptionDomains属性来将需要排除强制验证的域名写进来:

五. 总结

开启 NSAllowsArbitraryLoads 为 YES

对第三方访问的服务器设置NSExceptionDomains方式添加白名单

提交审核说明:

必须连接由其他机构控制的服务器,其还不支持安全连接。必须通过 web 展示来源不一的各种网络内容,但又不能完全使用NSAllowsArbitraryLoadsInWebContent所管理的类。

  • 一、HTTPS
  • 二、App Transport Security
  • 三、iOS 中用HTTPS 注意的问题
  • 四、使用 AFNetworking HTTPS 遇到的问题
  • 五、问题的解决方法
  • 六、注意服务器端 两种证书的区别(以及如何验证HTTPS服务器是否符合ATS特性中的要求)

一、HTTPS

HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,就是普通的HTTP数据。HTTP和SSL/TSL都处于OSI模型的应用层。

二、App Transport Security

iOS9中新增App Transport Security(简称ATS)特性, 主要使到原来请求的时候用到的HTTP,都转向TLS1.2协议进行传输。这也意味着所有的HTTP协议都强制使用了HTTPS协议进行传输。

一般我们如果还是使用的http,不更新的话,可通过在 Info.plist 中声明,倒退回不安全的网络请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
```


## 三、iOS 中用HTTPS 注意的问题

先看文档中的描述:

These are the App Transport Security requirements:

* The protocol Transport Security Layer (TLS) must be at least version 1.2.
* Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)
* Certificates must use at least an SHA256 fingerprint with either a 2048 bit or greater RSA key, or a 256 bit or greater Elliptic-Curve (ECC) key.

根据原文描述,首先必须要基于TLS 1.2版本协议。再来就是连接的加密方式要提供Forward Secrecy,文档中罗列出支持的加密算法(如下表)。 最后就是证书至少要使用一个SHA256的指纹与任一个2048位或者更高位的RSA密钥,或者是256位或者更高位的ECC密钥。如果不符合其中一项,请求将被中断并返回nil。

* 第一条就是TLS版本所需要支持的协议


第一条满足

第一条就是连接的加密方式需要提供“Foward Secrecy”这个东东,下面是支持Forward Secrecy的加密方式

TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这是一个满足条件的证书信息中的情况

满足上面中的条件

但是也要注意证书的合法性,注意是否有效,iOS要求连接的HTTPS站点必须为CA签名过的合法证书。


证书不被信任

证书未经过验证

### 有效
注意以上不同的情况决定了AFSecurityPolicy--setAllowInvalidCertificates:是否要验证证书的有效性。

## 四、使用AFNetworking HTTPS 遇到的问题(3.0.0之前)

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;
//validatesDomainName 是否需要验证域名,默认为YES;
securityPolicy.validatesDomainName = YES;
manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager POST:urlString parameters:dic success:finishedBlock failure:failedBlock];

1
2
3
4
5
6

## 五、Code=-1012 错误解决方法

### 步骤1:获取到站点的证书

我们可以使用以下openssl命令来获取到服务器的公开二进制证书(以google为例)

openssl s_client -connect www.google.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > https.cer

1
2
3
4
5
6
7
8
9
10

冒号中的为命令主要部分。该条命令将会在当前路径下,形成google.com站点的公开二进制证书,命名为https.cer。您可以将www.google.com 替换成您自己的站点以此来获取您自己站点的https.cer。

### 步骤2:导入到Xcode中

直接将https.cer放到资源目录中就好了,让我们可以通过pathForResource:获取到证书,就OK了。

https

### 步骤3:代码导入

NSString *urlString = @”https://www.example.com/app/publicRequest";
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@”https” ofType:@”cer”];
NSData * certData =[NSData dataWithContentsOfFile:cerPath];
NSSet * certSet = [[NSSet alloc] initWithObjects:certData, nil];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// 是否允许,NO– 不允许无效的证书
[securityPolicy setAllowInvalidCertificates:YES];
// 设置证书
[securityPolicy setPinnedCertificates:certSet];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// request
[manager GET:urlString parameters:nil progress:^(NSProgress * progress){
} success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray * array = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableLeaves error:nil];
NSLog(@”OK === %@”,array);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@”error ==%@”,error.description);
}];

1
2
3
4
5
6
7
8
9
10
11

然后,就OK了。

## 六、注意下服务器端两种证书的区别

* 第一种是创建证书请求,然后到权威机构认证,随之配置到服务器;
* 第二种是自建证书,需要自己配置给服务器。

使用第一种还是好一点的,至少在我们 app 端不需要为ATS做过多的适配。但是如何才能知道一个HTTPS服务器是否符合ATS特性中的要求的呢?使用nscurl命令:

```nscurl --ats-diagnostics --verbose https://example.com

下面是测百度时,返回的某一段,结论是OK的,里面有很多测试的情况,我们可以逐一观察是否正确。

1
2
3
4
5
6
7
8
9
10
11
12
TLSv1.0 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
NSExceptionDomains = {
"www.baidu.com" = {
NSExceptionAllowsInsecureHTTPLoads = true;
NSExceptionMinimumTLSVersion = "TLSv1.0";
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS

SSL证书相关介绍

SSL

SSL - Secure Sockets Layer,现在应该叫”TLS”,但由于习惯问题,我们还是叫”SSL”比较多.HTTP协议默认情况下是不加密内容的,这样就很可能在内容传播的时候被别人监听到,对于安全性要求较高的场合,必须要加密,HTTPS就是带加密的HTTP协议,而HTTPS的加密是基于SSL的,它执行的是一个比较下层的加密,也就是说,在加密前,你的服务器程序在干嘛,加密后也一样在干嘛,不用动,这个加密对用户和开发者来说都是透明的.More:[Transport_Layer_Security]:http://en.wikipedia.org/wiki/Transport_Layer_Security

OpenSSL - 简单地说,OpenSSL是SSL的一个实现,SSL只是一种规范.理论上来说,SSL这种规范是安全的,目前的技术水平很难破解,但SSL的实现就可能有些漏洞,如著名的”心脏出血”.OpenSSL还提供了一大堆强大的工具软件,强大到90%我们都用不到.

Read more »

证书、私钥和CA

现在我们所说的证书一般是指服务器证书,也就是公钥加上域名、公司信息、序列号、签名信息等组成;

私钥和公钥相对应,私钥加密的东西只能公钥解密,公钥加密的东西只能私钥解密,并且不能由公钥推断出私钥,但是可以由私钥算出公钥来。所以这里有一件非常有意思的事情,你可以在公私钥不变的情况下换一张证书,因为证书其实包括其他信息,所以你换的是别的信息,这些信息的变化会导致证书签名也会变化,所以就变成了一张新的证书,但是公钥私钥是没有变化的。

CA也由证书和私钥组成,它的证书和普通的证书长得差不多,只是其中的Basic Constraint字段里面的CA值为True而已,普通的服务器证书这一块为False,但是因为有了这个True,所以CA可以签发别的证书。
服务器证书的分类

可以从两个维度去看证书的分类,一种是商业角度,为了区分不同的用户级别,服务端证书可以分成DV、OV和EV证书。

Read more »

导语

本文的目的,一是简要分析下对服务器身份验证的完整握手过程,二是证书链的验证,三是探索下iOS中原生库NSURLConnection或NSURLSession如何支持实现https。

Read more »

基本技巧

标题

1
2
3
4
5
6
7
8
9
10
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
标题1
======
标题2
------

粗斜体

1
2
3
*斜体文本*    _斜体文本_
**粗体文本** __粗体文本__
***粗斜体文本*** ___粗斜体文本___

链接

1
2
文字链接[链接名称](http://链接网址)
网址链接

高级链接技巧

1
2
3
4
5
6
链接 1 作为网址变量 [Google][1].
链接 yahoo 作为网址变量 [Yahoo!][yahoo].
然后在文档的结尾为变量赋值(网址)

[1]\: http://www.google.com/
[yahoo]\: http://www.yahoo.com/

列表 - 普通 - 无序 - 列表

1
2
3
\- 列表文本前使用 [减号+空格]
\+ 列表文本前使用 [加号+空格]
\* 列表文本前使用 [星号+空格]
  • 普通
  • 有序
  • 列表
  1. 列表前使用 [数字+空格]
  2. 我们会自动帮你添加数字
  3. 不用担心数字不对,显示的时候我们会自动把这行的 7 纠正为 3

列表嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1. 列出所有元素:
- 无序列表元素 A
1. 元素 A 的有序子列表
- 前面加四个空格
2. 列表里的多段换行:
前面必须加四个空格,
这样换行,整体的格式不会乱
3. 列表里引用:

> 前面空一行
> 仍然需要在 > 前面加四个空格

4. 列表里代码段:


1
前面四个空格,之后按代码语法 ``` 书写


或者直接空八个,引入代码块

引用 > 普通引用

1
2
3
4
5
6
7
8
9
10
> 引用文本前使用 [大于号+空格]
> 折行可以不加,新起一行都要加上哦
> 引用里 > > 嵌套引用
> 最外层引用
> > 多一个 > 嵌套一层引用
> > > 可以嵌套很多层
> - 引用里嵌套列表
> - 这是引用里嵌套的一个列表
> - 还可以有子列表
> * 子列表需要从 - 之后延后四个空格开始

图片

跟链接的方法区别在于前面加了个感叹号 !,这样是不是觉得好记多了呢?

1
![图片名称](http://图片网址)

当然,你也可以像网址那样对图片网址使用变量

链接 1 作为网址变量 [Google][1]. 然后在文档的结尾位变量赋值(网址)

也可以使用 HTML 的图片语法来自定义图片的宽高大小

1
2
3
<code>
`<image src="http://7xt737.com1.z0.clouddn.com/blog_image14384920.jpg" width="100">`
</code>

换行

如果另起一行,只需在当前行结尾加 2 个空格

1
2
3
在当前行的结尾加 2 个空格  
这行就会新起一行
如果是要起一个新段落,只需要空出一行即可。

分隔符

如果你有写分割线的习惯,可以新起一行输入三个减号-。当前后都有段落时,请空出一行:

1
2
3
4
5
6
7
<pre>
前面的段落

---

后面的段落
</pre>

高级技巧

行内 HTML 元素

目前只支持部分段内 HTML 元素效果,包括
<kdb> <b> <i> <em> <sup> <sub> <br>

键位显示

使用

<kbd>Ctrl<kbd>+<kbd>Alt<kbd>+<kbd>Del<kbd> shi重启电脑

代码块

使用 (`
`) 元素同样可以形成代码块

粗斜体

<b> Markdown 在此处同样适用,如 <b>加粗<b> == 加粗

符号转义

如果你的描述中需要用到 markdown 的符号,比如 _ # * 等,但又不想它被转义,这时候可以在这些符号前加反斜杠,如 \_ \# \* 进行避免。

_不想这里的文本变斜体_

**不想这里的文本被加粗**

扩展

支持 jsfiddle、gist、runjs、优酷视频,直接填写 url,在其之后会自动添加预览点击会展开相关内容。

1
2
3
4
http://{url_of_the_fiddle}/embedded/[{tabs}/[{style}]]/
https://gist.github.com/{gist_id}
http://runjs.cn/detail/{id}
http://v.youku.com/v_show/id_{video_id}.html

公式

当你需要在编辑器中插入数学公式时,可以使用两个美元符 $$ 包裹 TeX 或 LaTeX 格式的数学公式来实现。提交后,问答和文章页会根据需要加载 Mathjax 对数学公式进行渲染。如:

``
$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a}. $$

$$ x \href{why-equal.html}{=} y^2 + 1 $$
``

同时也支持 HTML 属性,如:

``
$$ (x+1)^2 = \class{hidden}{(x+1)(x+1)} $$

$$ `(x+1)^2 = \cssId{step1}{\style{visibility:hidden}{(x+1)(x+1)}} $$
``

表格

1
2
3
4
| 一个普通标题 | 一个普通标题 | 一个普通标题 |
| ------| ------ | ------ |
| 短文本 | 中等文本 | 稍微长一点的文本 |
| 稍微长一点的文本 | 短文本 | 中等文本 |
一个普通标题 一个普通标题 一个普通标题
短文本 中等文本 稍微长一点的文本
稍微长一点的文本 短文本 中等文本

update 2017.1.9

update 2017.7.24 markdowm 格式优化

导致卡顿的操作

CPU 消耗型任务

布局计算

布局计算是 iOS 中最为常见的消耗 CPU 资源的地方,如果视图层级关系比较复杂,计算出所有图层的布局信息就会消耗一部分时间。因此我们应该尽量提前计算好布局信息,然后在合适的时机调整对应的属性。还要避免不必要的更新,只在真正发生了布局改变时再更新。
对象创建

对象创建过程伴随着内存分配、属性设置、甚至还有读取文件等操作,比较消耗 CPU 资源。尽量用轻量的对象代替重量的对象,可以对性能有所优化。比如 CALayer 比 UIView 要轻量许多,如果视图元素不需要响应触摸事件,用 CALayer 会更加合适。

通过 Storyboard 创建视图对象还会涉及到文件反序列化操作,其资源消耗会比直接通过代码创建对象要大非常多,在性能敏感的界面里,Storyboard 并不是一个好的技术选择。

对于列表类型的页面,还可以参考 UITableView 的复用机制。每次要初始化 View 对象时先根据 identifier 从缓存池里取,能取到就复用这个 View 对象,取不到再真正执行初始化过程。滑动屏幕时,会将滑出屏幕外的 View 对象根据 identifier 放入缓存池,新进入屏幕可见范围内的 View 又根据前面的规则来决定是否要真正初始化。

Autolayout

Autolayout 是苹果在 iOS6 之后新引入的布局技术,在大多数情况下这一技术都能大大提升开发速度,特别是在需要处理多语言时。比如阿拉伯语下布局是从右往左,通过 Autolayout 设置 leading 和 trailing 即可。
但是 Autolayout 对于复杂视图来说常常会产生严重的性能问题,对于性能敏感的页面建议还是使用手动布局的方式,并控制好刷新频率,做到真正需要调整布局时再重新布局。

文本计算

如果一个界面中包含大量文本(比如微博、微信朋友圈等),文本的宽高计算会占用很大一部分资源,并且不可避免。
一个比较常见的场景是在 UITableView 中,heightForRowAtIndexPath这个方法会被频繁调用,即使不是耗时的计算在调用次数多了之后也会带来性能损耗。这里的优化就是尽量避免每次都重新进行文本的行高计算,可以在获取到 Model 数据后就根据文本内容计算好布局信息,然后将这份布局信息作为一个属性保存到对应的 Model 中,这样在 UITableView 的回调中就可以直接使用 Model 中的属性,减少了文本的计算。

文本渲染

屏幕上能看到的所有文本内容控件,包括 UIWebView,在底层都是通过 CoreText 排版、绘制为 Bitmap 显示的。常见的文本控件 (UILabel、UITextView 等),其排版和绘制都是在主线程进行的,当显示大量文本时,CPU 的压力会非常大。
这一部分的性能优化就需要我们放弃使用系统提供的上层控件转而直接使用 CoreText 进行排版控制。
Wherever possible, try to avoid making changes to the frame of a view that contains text, because it will cause the text to be redrawn. For example, if you need to display a static block of text in the corner of a layer that frequently changes size, put the text in a sublayer instead.
上面这段话引用自 iOS Core Animation: Advanced Techniques,翻译过来的意思就是说包含文本的视图在改变布局时会触发文本的重新渲染,对于静态文本我们应该尽量减少它所在视图的布局修改。

图像的绘制

图像的绘制通常是指用那些以 CG 开头的方法把图像绘制到画布中,然后从画布创建图片并显示的过程。调用 CG 开头的方法消耗的是 CPU 资源。我们可以将绘制过程放到后台线程,然后在主线程里将结果设置到 layer 的 contents 中。代码如下:

1
2
3
4
5
6
7
8
9
10
11
- (void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}

图片的解码

1
Once an image file has been loaded, it must then be decompressed. This decompression can be a computationally complex task and take considerable time. The decompressed image will also use substantially more memory than the original.

图片被加载后需要解码,图片的解码是一个复杂耗时的过程,并且需要占用比原始图片还多的内存资源。

为了节省内存,iOS 系统会延迟解码过程, 在图片被设置到 layer 的 contents 属性或者设置成 UIImageView 的 image 属性后才会执行解码过程,但是这两个操作都是在主线程进行,还是会带来性能问题。

如果想要提前解码,可以使用 ImageIO 或者提前将图片绘制到 CGContext 中,这部分实践可以参考 iOS Core Animation: Advanced Techniques

这里多提一点,常用的 UIImage 加载方法有 imageNamedimageWithContentsOfFile。其中 imageNamed 加载图片后会马上解码,并且系统会将解码后的图片缓存起来,但是这个缓存策略是不公开的,我们无法知道图片什么时候会被释放。因此在一些性能敏感的页面,我们还可以用 static 变量 hold 住 imageNamed 加载到的图片避免被释放掉,以空间换时间的方式来提高性能。

GPU消耗型任务

相对于 CPU 来说,GPU 能干的事情比较单一:接收提交的纹理(Texture)和顶点描述(三角形),应用变换(transform)、混合并渲染,然后输出到屏幕上。宽泛的说,大多数 CALayer 的属性都是用 GPU 来绘制。
以下一些操作会降低 GPU 绘制的性能,

大量几何结构

所有的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。不论是提交到显存的过程,还是 GPU 调整和渲染 Texture 的过程,都要消耗不少 GPU 资源。当在较短时间显示大量图片时(比如 TableView 存在非常多的图片并且快速滑动时),CPU 占用率很低,GPU 占用非常高,界面仍然会掉帧。避免这种情况的方法只能是尽量减少在短时间内大量图片的显示,尽可能将多张图片合成为一张进行显示。

另外当图片过大,超过 GPU 的最大纹理尺寸时,图片需要先由 CPU 进行预处理,这对 CPU 和 GPU 都会带来额外的资源消耗。
视图的混合

当多个视图(或者说 CALayer)重叠在一起显示时,GPU 会首先把他们混合到一起。如果视图结构过于复杂,混合的过程也会消耗很多 GPU 资源。为了减轻这种情况的 GPU 消耗,应用应当尽量减少视图数量和层次,并且减少不必要的透明视图。

离屏渲染

离屏渲染是指图层在被显示之前是在当前屏幕缓冲区以外开辟的一个缓冲区进行渲染操作。

离屏渲染需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕,而上下文环境的切换是一项高开销的动作。

会造成 offscreen rendering 的原因有:

  • 阴影(UIView.layer.shadowOffset/shadowRadius/…)
  • 圆角(当 UIView.layer.cornerRadius 和 UIView.layer.maskToBounds 一起使用时)
  • 图层蒙板
  • 开启光栅化(shouldRasterize = true)

使用阴影时同时设置 shadowPath 就能避免离屏渲染大大提升性能。

CALayer 有一个 shouldRasterize 属性,将这个属性设置成 true 后就开启了光栅化。开启光栅化后会将图层绘制到一个屏幕外的图像,然后这个图像将会被缓存起来并绘制到实际图层的 contents 和子图层,对于有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧来更加高效。但是光栅化原始图像需要时间,而且会消耗额外的内存。

光栅化也会带来一定的性能损耗,是否要开启就要根据实际的使用场景了,图层内容频繁变化时不建议使用。最好还是用 Instruments 比对开启前后的 FPS 来看是否起到了优化效果。

自定义 IOS TextView,高度自增自减

如何实现UITextView文本框高度随文字行数自动增减呢?

思路1、UITextView的Delegate方法

-textViewDidChange:

在Text变化时计算文字高度,刷新TextView高度

问题:

Text变化与实际有出入,此时的问题变化还没有存储到TextView的Text中,现在进行计算会得到上一个状态下的文字高度,导致TextView高度刷新延迟,换行第二个文字才增加高度,如果采用这个方法,就要进行文字拼接,再计算文字高度。

实现:不够优雅,不实现。

思路2、TextView本质是一个ScrollView

文字高度超出TextView高度会自动增加contentSize属性的height,而且增加的高度刚好就是TextView需要改变的高度,不错。

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//宽度、高度与坐标

\#define Main_Screen_Height [[UIScreen mainScreen] bounds].size.height
\#define Main_Screen_Width [[UIScreen mainScreen] bounds].size.width
static float TextViewDefaultHeight = 30.;
static float TextViewDefaultY = 5.;
static float TextViewContentSizeDefaultHeight = 22.;
//测试中使用[UIFont systemFontOfSize:14],每行高度变化为17
self.TextViewInput = [[UITextView alloc]initWithFrame:CGRectMake(45, UUInputTextViewDefaultY, Main_Screen_Width, UUInputTextViewDefaultHeight)];
[self.TextViewInput setContentSize:CGSizeMake(self.TextViewInput.width, UUInputTextViewContentSizeDefaultHeight)];
self.TextViewInput.delegate = self;
self.TextViewInput.font = [UIFont systemFontOfSize:14];
self.TextViewInput.textContainerInset = UIEdgeInsetsMake(6,0, 0, 0);
//KVO监听TextViewInput的contentSize变化
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.TextViewInput addObserver:self forKeyPath:@"contentSize" options:options context:nil];
[self addSubview:self.TextViewInput];
\#pragma mark -- KVO --
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"contentSize"]){
//最高行数设置,本demo中最高四行,对应paste场景
if (self.TextViewInput.height > UUInputTextViewDefaultHeight+17*2 && self.TextViewInput.contentSize.height > UUInputTextViewContentSizeDefaultHeight+17*2) {
return;
}
//文本为空时处理,对应文字发送、cut场景
if (self.TextViewInput.text.length == 0) {
[UIView animateWithDuration:0.3 animations:^{
self.TextViewInput.frame = CGRectMake(45, UUInputTextViewDefaultY, Main_Screen_Width-2*45, UUInputTextViewDefaultHeight);
} completion:nil];
return;
}
CGRect frame = self.TextViewInput.frame;
//高度变化值
float changeHeight = self.TextViewInput.contentSize.height - UUInputTextViewContentSizeDefaultHeight;
//超过限定高度的场景处理
if (changeHeight > 17*2) {
changeHeight = 17*2;
}
frame.origin.y = UUInputTextViewDefaultY - changeHeight;
frame.size.height = UUInputTextViewDefaultHeight + changeHeight;
//动画效果
[UIView animateWithDuration:0.3 animations:^{
self.TextViewInput.frame = frame;
} completion:nil];
NSLog(@"%lf-- TextViewInput",self.TextViewInput.contentSize.height);
}
}
//注意要注销KVO

-(void)dealloc{
[self.TextViewInput removeObserver:self forKeyPath:@"contentSize"];
}

如何实现UITextView文本框高度随文字行数自动增减呢?

思路1、UITextView的Delegate方法

-textViewDidChange:

在Text变化时计算文字高度,刷新TextView高度

问题:Text变化与实际有出入,此时的问题变化还没有存储到TextView的Text中,现在进行计算会得到上一个状态下的文字高度,导致TextView高度刷新延迟,换行第二个文字才增加高度,如果采用这个方法,就要进行文字拼接,再计算文字高度。

思路2、TextView本质是一个ScrollView

文字高度超出TextView高度会自动增加contentSize属性的height,而且增加的高度刚好就是TextView需要改变的高度

Read more »

事到用时方恨少,想来还是记录下来靠谱;

开始写博客大概是2013年,那时候是在CSDN上写写博客,不过那时候还在上大学,更新文章基本看心情,而且大多文章也不过是转载看到的一些技术博客,原创记录基本很少;现在回头看来那时就是浮躁,肚子里没点墨水却硬要去写,矫情啦!

现在,大学毕业,会想过去四年的确发生了很多事情,有些事情很有趣、有些事情很悲伤,但还有很多事情已经没什么印象;

现在建立个人博客,想记录下生活感想,想分享下技术心得,一点点的记录,用时间和文字来见证自己吧。