0%

很不错的Objective-C编码规范中文版,转载于https://github.com/QianKaiLu/Objective-C-Coding-Guidelines-In-Chinese,感谢QianKaiLu的分享

Objective-C-Coding-Guidelines-In-Chinese

Objective-C编码规范,内容来自苹果、谷歌的文档翻译,自己的编码经验和对其它资料的总结。

转载请注明出处。

概要

Objective-C是一门面向对象的动态编程语言,主要用于编写iOS和Mac应用程序。关于Objective-C的编码规范,苹果和谷歌都已经有很好的

总结:

Apple Coding Guidelines for Cocoa

Google Objective-C Style Guide

本文主要整合了对上述文档的翻译、作者自己的编程经验和其他的相关资料,为公司总结出一份通用的编码规范。

Read more »

HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容 请看SSL。

它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同 于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。这个系统的最初研发由网景公司进行,提供了身份验证与加密通讯方法,现在它被广 泛用于万维网上安全敏感的通讯,例如交易支付方面。

Nginx 配置 HTTPS 并不复杂,主要有两个步骤:签署第三方可信任的 SSL 证书 和 配置 HTTPS

签署第三方可信任的 SSL 证书

关于 SSL 证书

有关 SSL 的介绍可以参阅维基百科的传输层安全协议和阮一峰先生的 《SSL/TLS协议运行机制的概述》。
SSL 证书主要有两个功能:加密和身份证明,通常需要购买,也有免费的,通过第三方 SSL 证书机构颁发,常见可靠的第三方 SSL 证书颁发机构有下面几个:

StartCom 机构上的 SSL 证书有以下几种:

企业级别:EV(Extended Validation)、OV(Organization Validation)

个人级别:IV(Identity Validation)、DV(Domain Validation)

其中 EV、OV、IV 需要付费

免费的证书安全认证级别一般比较低,不显示单位名称,不能证明网站的真实身份,仅起到加密传输信息的作用,适合个人网站或非电商网站。由于此类只验证域名所有权的低端 SSL 证书已经被国外各种欺诈网站滥用,因此强烈推荐部署验证单位信息并显示单位名称的 OV SSL 证书或申请最高信任级别的、显示绿色地址栏、直接在地址栏显示单位名称的 EV SSL 证书,就好像 StarCom 的地址栏一样:

使用 OpenSSL 生成 SSL Key 和 CSR 文件

配置 HTTPS 要用到私钥 example.key 文件和 example.crt 证书文件,申请证书文件的时候要用到 example.csr 文件,OpenSSL 命令可以生成 example.key 文件和 example.csr 证书文件。

CSR:Cerificate Signing Request,证书签署请求文件,里面包含申请者的 DN(Distinguished Name,标识名)和公钥信息,在第三方证书颁发机构签署证书的时候需要提供。证书颁发机构拿到 CSR 后使用其根证书私钥对证书进行加密并生成 CRT 证书文件,里面包含证书加密信息以及申请者的 DN 及公钥信息

Key:证书申请者私钥文件,和证书里面的公钥配对使用,在 HTTPS 『握手』通讯过程需要使用私钥去解密客戶端发來的经过证书公钥加密的随机数信息,是 HTTPS 加密通讯过程非常重要的文件,在配置 HTTPS 的時候要用到

使用 OpenSSl命令可以在系统当前目录生成 example.key 和 example.csr 文件:

openssl req -new -newkey rsa:2048 -sha256 -nodes -out example_com.csr -keyout example_com.key -subj “/C=CN/ST=ShenZhen/L=ShenZhen/O=Example Inc./OU=Web Security/CN=example.com”

下面是上述命令相关字段含义:

1
2
3
4
5
6
7
C:Country ,单位所在国家,为两位数的国家缩写,如: CN 就是中国
ST 字段: State/Province ,单位所在州或省
L 字段: Locality ,单位所在城市 / 或县区
O 字段: Organization ,此网站的单位名称;
OU 字段: Organization Unit,下属部门名称;也常常用于显示其他证书相关信息,如证书类型,证书产品名称或身份验证类型或验证内容等;
CN 字段: Common Name ,网站的域名;
生成 csr 文件后,提供给 CA 机构,签署成功后,就会得到一個 example.crt 证书文件,SSL 证书文件获得后,就可以在 Nginx 配置文件里配置 HTTPS 了。

配置 HTTPS

基础配置

要开启 HTTPS 服务,在配置文件信息块(server block),必须使用监听命令 listen 的 ssl 参数和定义服务器证书文件和私钥文件,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
server {
#ssl参数
listen 443 ssl;
server_name example.com;
#证书文件
ssl_certificate example.com.crt;
#私钥文件
ssl_certificate_key example.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#...
}

证书文件会作为公用实体發送到每台连接到服务器的客戶端,私钥文件作为安全实体,应该被存放在具有一定权限限制的目录文件,并保证 Nginx 主进程有存取权限。

私钥文件也有可能会和证书文件同放在一個文件中,如下面情況:

1
2
ssl_certificate     www.example.com.cert;
ssl_certificate_key www.example.com.cert;

这种情況下,证书文件的的读取权限也应该加以限制,仅管证书和私钥存放在同一个文件里,但是只有证书会被发送到客戶端
命令 ssl_protocols 和 ssl_ciphers 可以用来限制连接只包含 SSL/TLS 的加強版本和算法,默认值如下:

1
2
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;

由于这两个命令的默认值已经好几次发生了改变,因此不建议显性定义,除非有需要额外定义的值,如定义 D-H 算法:

使用DH文件

1
2
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

##定义算法

1
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";

HTTPS服务器优化

减少 CPU 运算量

SSL 的运行计算需要消耗额外的 CPU 资源,一般多核处理器系统会运行多个工作进程(worker processes ),进程的数量不会少于可用的 CPU 核数。SSL 通讯过程中『握手』阶段的运算最占用 CPU 资源,有两个方法可以减少每台客户端的运算量:

激活 keepalive 长连接,一个连接发送更多个请求

复用 SSL 会话参数,在并行并发的连接数中避免进行多次 SSL『握手』

这些会话会存储在一个 SSL 会话缓存里面,通过命令 ssl_session_cache 配置,可以使缓存在机器间共享,然后利用客戶端在『握手』阶段使用的 seesion id 去查询服务端的 session cathe(如果服务端设置有的话),简化『握手』阶段。

1M 的会话缓存大概包含 4000 個会话,默认的缓存超时时间为 5 分钟,可以通过使用 ssl_session_timeout 命令设置缓存超时时间。下面是一個拥有 10M 共享会话缓存的多核系统优化配置例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
worker_processes auto;
http {
#配置共享会话缓存大小
ssl_session_cache shared:SSL:10m;
#配置会话超时时间
ssl_session_timeout 10m;
server {
listen 443 ssl;
server_name www.example.com;
#设置长连接
keepalive_timeout 70;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#...

使用 HSTS 策略强制浏览器使用 HTTPS 连接

HSTS – HTTP Strict Transport Security,HTTP严格传输安全。它允许一个 HTTPS 网站要求浏览器总是通过 HTTPS 来访问,这使得攻击者在用戶与服务器通讯过程中拦截、篡改信息以及冒充身份变得更为困难。

只要在 Nginx 配置文件加上以下头信息就可以了:

add_header Strict-Transport-Security “max-age=31536000; includeSubDomains;preload” always;

max-age:设置单位时间内強制使用 HTTPS 连接

includeSubDomains:可选,所有子域同时生效

preload:可选,非规范值,用于定义使用『HSTS 预加载列表』

always:可选,保证所有响应都发送此响应头,包括各种內置错误响应

当用户进行 HTTPS 连接的时候,服务器会发送一个 Strict-Transport-Security 响应头:

浏览器在获取该响应头后,在 max-age 的时间内,如果遇到 HTTP 连接,就会通过 307 跳转強制使用 HTTPS 进行连接,并忽略其它的跳转设置(如 301 重定向跳转):

307 跳转 Non-Authoritative-Reason 响应头

Google HSTS 预加载列表(HSTS Preload List)

由于 HSTS 需要用戶经过一次安全的 HTTPS 连接后才会在 max-age 的时间內生效,因此HSTS 策略并不能完美防止 HTTP 会话劫持(HTTP session hijacking),在下面这些情況下还是存在被劫持的可能:

  • 从未访问过的网站
  • 近期重裝过操作系統
  • 近期重裝过浏览器
  • 使用新的浏览器
  • 使用了新的设备(如手机)
  • 刪除了浏览器缓存
  • 近期沒有打开过网站且 max-age 过期

针对这种情況,Google 维护了一份『HSTS 预加载列表』,列表里包含了使用了 HSTS 的站点主域名和子域名,可以通过以下页面申请加入:
https://hstspreload.appspot.com/.

申请的时候会先验证站点是否符合资格,一般会检验待验证的站点主域和子域是否能通过 HTTPS 连接、HTTPS 和 HTTP 配置是否有 STS Header 等信息,通过验证后,会让你确认一些限制信息.

当确认提交后,就会显示处理状态:

申请通过后,列表内的站点名会被写进主流的浏览器,当浏览器更新版本后,只要打开列表内的站点,浏览器会拒绝所有 HTTP 连接而自动使用 HTTPS,即使关闭了 HSTS 设置。

可以在下面两个连接分別查找 Chrome 和 Firfox 的『HSTS 预加载列表』内容:

1
2
The Chromium Projects - HTTP Strict Transport Security
Firefox HSTS preload list - nsSTSPreloadList.inc

需要注意的是:

一旦把自己的站点名加入『HSTS 预加载列表』,将很难彻底从列表中移除,因为不能保证其它浏览器可以及时移除,即使 Chrome 提供有便捷的移除方法,也是要通过出邮件联系,注明移除原因,并等到最新的浏览器版本更新发布才有机会(用戶不一定会及时更新)
所有不具备有效证书的子域或內嵌子域的访问将会被阻止

因此,如果自己站点子域名变化比较多,又沒有泛域证书,又沒法确定全站是否能应用 HTTPS 的朋友,就要谨慎申请了。

更多关于 HSTS 配置可参考:

《HTTP Strict Transport Security (HSTS) and NGINX》

MDN的《HTTP Strict Transport Security》

浏览器兼容性

加强 HTTPS 安全性

HTTPS 基础配置采取的默认加密算法是 SHA-1,这个算法非常脆弱,安全性在逐年降低,在 2014 年的时候, Google 官方博客就宣布在 Chrome 浏览器中逐渐降低 SHA-1 证书的安全指示,会从 2015 年起使用 SHA-2 签名的证书,可参阅 Rabbit_Run 在 2014 年发表的文章:《为什么Google急着杀死加密算法SHA-1》

为此,主流的 HTTPS 配置方案应该避免 SHA-1,可以使用 迪菲-赫尔曼密钥交换(D-H,Diffie–Hellman key exchange)方案。
首先在目录 /etc/ssl/certs 运行以下代码生成 dhparam.pem 文件:

openssl dhparam -out dhparam.pem 2048

然后加入 Nginx 配置:

1
2
3
4
5
6
7
#优先采取服务器算法
ssl_prefer_server_ciphers on;
#使用DH文件
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
#定义算法
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";

如果服务器夠強大,可以使用更为复杂的 4096 位进行加密。
一般情況下还应该加上以下几个增强安全性的命令:

1
2
3
4
5
6
#减少点击劫持
add_header X-Frame-Options DENY;
#禁止服务器自动解析资源类型
add_header X-Content-Type-Options nosniff;
#防XSS攻击
add_header X-Xss-Protection 1;

这几个安全命令在 Jerry Qu 大神的文章《一些安全相关的HTTP响应头》有详细的介紹。

优化后的综合配置

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
worker_processes auto;
http {
#配置共享会话缓存大小,视站点访问情况设定
ssl_session_cache shared:SSL:10m;
#配置会话超时时间
ssl_session_timeout 10m;
server {
listen 443 ssl;
server_name www.example.com;
#设置长连接
keepalive_timeout 70;
#HSTS策略
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
#证书文件
ssl_certificate www.example.com.crt;
#私钥文件
ssl_certificate_key www.example.com.key;
#优先采取服务器算法
ssl_prefer_server_ciphers on;
#使用DH文件
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
#定义算法
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
#减少点击劫持
add_header X-Frame-Options DENY;
#禁止服务器自动解析资源类型
add_header X-Content-Type-Options nosniff;
#防XSS攻擊
add_header X-Xss-Protection 1;
#...

HTTP/HTTPS混合服务器配置

可以同时配置 HTTP 和 HTTPS 服务器:

1
2
3
4
5
6
7
8
server {
listen 80;
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
#...
}

在 0.7.14 版本之前,在独立的 server 端口中是不能选择性开启 SSL 的。如上面的例子,SSL 只能通过使用 ssl 命令为单个 server

端口开启

1
2
3
4
5
6
7
8
9
server {
listen 443;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
#ssl命令开启 https
ssl on;
#...
}

因此没有辦法设置 HTTP/HTTPS 混合服务器。于是 Nginx 新增了监听命令 listen参数 ssl 來解决这个问题,Nginx 現代版本的ssl命令并不推荐使用

基于服务器名称(name-based)的 HTTPS 服务器

一个常见的问题就是当使用同一个 IP 地址去配置两个或更多的 HTTPS 服务器的时候,出现证书不匹配的情況:

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
#...
}
server {
listen 443 ssl;
server_name www.example.org;
ssl_certificate www.example.org.crt;
#...
}

这种情况下浏览器会获取默认的服务器证书(如上面例子的 www.example.com.crt)而忽视请求的服务器名,如输入网址:www.example.org,服务器会发送 www.example.com.crt 的证书到客戶端,而不是 www.exaple.org.crt。

这是因为 SSL 协议行为所致,SSL 连接在浏览器发送 HTTP 请求之前就被建立,Nginx 并不知道被请求的服务器名字,因此 Nginx 只会提供默认的服务器证书。

解決这个问题最原始最有效的方法就是为每个 HTTPS 服务器分配独立的 IP 地址:

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 192.168.1.1:443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
#...
}
server {
listen 192.168.1.2:443 ssl;
server_name www.example.org;
ssl_certificate www.example.org.crt;
#...
}

更多解決方案

除此之外,官方还介绍了两个方法:泛域证书和域名指示(SNI)
其实 OpenSSL 在 0.9.8f版本就支持 SNI 了,只要在安裝的时候加上 –enable-tlsext 选项就可以。到了 0.9.8j版本,这个选项在安裝的时候会默认启用。如果创建 Nginx 的时候支持 SNI,可以在 Nginx 版本信息查到以下的字段:

TLS SNI support enabled

因此,如果较新版本的 Nginx 使用默认的 OpenSSL 库,是不存在使用 HTTPS 同时支持基于名字的虚拟主机的时候同 IP 不同域名证书不匹配的问题。

注意:即使新版本的 Nginx 在创建时支持了 SNI,如果 Nginx 动态加载不支持 SNI 的 OpenSSL 库的话,SNI 扩展将不可用
有兴趣的朋友可以看下:

An SSL certificate with several names && Server Name Indication

总结

OK,我们简单总结一下在 Nginx 下配置 HTTPS 的关键要点:

  • 获得 SSL 证书
  • 通过 OpenSSL 命令获得 example.key 和 example.csr 文件
  • 提供 example.csr 文件给第三方可靠证书颁发机构,选择适合的安全级别证书并签署,获得 example.crt 文件
  • 通过 listen 命令 SSL 参数以及引用 example.key 和 example.crt 文件完成 HTTPS 基础配置
  • HTTPS优化
  • 减少 CPU 运算量
  • 使用 keepalive 长连接
  • 复用 SSL 会话参数
  • 使用 HSTS 策略强制浏览器使用 HTTPS 连接
  • 添加 Strict-Transport-Security 头部信息
  • 使用 HSTS 预加载列表(HSTS Preload List)
  • 加强 HTTPS 安全性
  • 使用迪菲-赫尔曼密钥交换(D-H,Diffie–Hellman key exchange)方案
  • 添加 X-Frame-Options 头部信息,减少点击劫持
  • 添加 X-Content-Type-Options 头部信息,禁止服务器自动解析资源类型
  • 添加 X-Xss-Protection 头部信息,防XSS攻击
  • HTTP/HTTPS混合服务器配置
  • 基于服务器名称(name-based)的 HTTPS 服务器
  • 为每个 HTTPS 服务器分配独立的 IP 地址
  • 泛域证书
  • 域名标识(SNI)

全站ssl

全站做ssl是最常见的一个使用场景,默认端口443,而且一般是单向认证。

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 443;
server_name example.com;
root /apps/www;
index index.html index.htm;
ssl on;
ssl_certificate ../SSL/ittest.pem;
ssl_certificate_key ../SSL/ittest.key;
# ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
# ssl_prefer_server_ciphers on;
}

如果想把http的请求强制转到https的话:

1
2
3
4
5
6
7
server {
listen 80;
server_name example.me;
rewrite ^ https://$server_name$request_uri? permanent;
### 使用return的效率会更高
# return 301 https://$server_name$request_uri;
}

ssl_certificate证书其实是个公钥,它会被发送到连接服务器的每个客户端,ssl_certificate_key私钥是用来解密的,所以它的权限要得到保护但nginx的主进程能够读取。当然私钥和证书可以放在一个证书文件中,这种方式也只有公钥证书才发送到client。

ssl_protocols指令用于启动特定的加密协议,nginx在1.1.13和1.0.12版本后默认是ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2,TLSv1.1与TLSv1.2要确保OpenSSL >= 1.0.1 ,SSLv3 现在还有很多地方在用但有不少被攻击的漏洞。

ssl_ciphers选择加密套件,不同的浏览器所支持的套件(和顺序)可能会不同。这里指定的是OpenSSL库能够识别的写法,你可以通过 openssl -v cipher ‘RC4:HIGH:!aNULL:!MD5’(后面是你所指定的套件加密算法) 来看所支持算法。

ssl_prefer_server_ciphers on设置协商加密算法时,优先使用我们服务端的加密套件,而不是客户端浏览器的加密套件。

https优化参数

ssl_session_cache shared:SSL:10m; : 设置ssl/tls会话缓存的类型和大小。如果设置了这个参数一般是shared,buildin可能会参数内存碎片,默认是none,和off差不多,停用缓存。如shared:SSL:10m表示我所有的nginx工作进程共享ssl会话缓存,官网介绍说1M可以存放约4000个sessions。

ssl_session_cache。

ssl_session_timeout : 客户端可以重用会话缓存中ssl参数的过期时间,内网系统默认5分钟太短了,可以设成30m即30分钟甚至4h。
设置较长的keepalive_timeout也可以减少请求ssl会话协商的开销,但同时得考虑线程的并发数了。
提示:在生成证书请求csr文件时,如果输入了密码,nginx每次启动时都会提示输入这个密码,可以使用私钥来生成解密后的key来代替,效果是一样的,达到免密码重启的效果:

openssl rsa -in ittest.key -out ittest_unsecure.key

部分页面ssl

一个站点并不是所有信息都是非常机密的,如网上商城,一般的商品浏览可以不通过https,而用户登录以及支付的时候就强制经过https传输,这样用户访问速度和安全性都得到兼顾。

但是请注意不要理解错了,是对页面加密而不能针对某个请求加密,一个页面或地址栏的URL一般会发起许多请求的,包括css/png/js等静态文件和动态的java或php请求,所以要加密的内容包含页面内的其它资源文件,否则就会出现http与https内容混合的问题。在http页面混有https内容时,页面排版不会发生乱排现象;在https页面中包含以http方式引入的图片、js等资源时,浏览器为了安全起见会阻止加载。

下面是只对example.com/account/login登录页面进行加密的栗子:

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
root /apps/www;
index index.html index.htm;
server {
listen 80;
server_name example.com;
location ^~ /account/login {
rewrite ^ https://$server_name:443$request_uri? permanent;
}
location / {
proxy_pass http://localhost:8080;
### Set headers ####
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}
}
server {
listen 443 ssl;
server_name example.com;
ssl on;
ssl_certificate ../SSL/ittest.pem;
ssl_certificate_key ../SSL/ittest.key;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
location ^~ /account/login {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
### Most PHP, Python, Rails, Java App can use this header -> https ###
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
rewrite ^ http://$server_name$request_uri? permanent;
}
}

上面配置中使用了proxy_set_header X-Forwarded-Proto $scheme,在jsp页面使用request.getScheme()得到的是https 。如果不把请求的$scheme协议设置在header里,后端jsp页面会一直认为是http,将导致响应异常。

ssl配置块还有个与不加密的80端口类似的location /,它的作用是当用户直接通过https访问首页时,自动跳转到不加密端口,你可以去掉它允许用户这样做。

实现双向ssl认证

上面的两种配置都是去认证被访问的站点域名是否真实可信,并对传输过程加密,但服务器端并没有认证客户端是否可信。(实际上除非特别重要的场景,也没必要去认证访问者,除非像银行U盾这样的情况)

要实现双向认证HTTPS,nginx服务器上必须导入CA证书(根证书/中间级证书),因为现在是由服务器端通过CA去验证客户端的信息。还有必须在申请服务器证书的同时,用同样的方法生成客户证书。取得客户证书后,还要将它转换成浏览器识别的格式(大部分浏览器都认识PKCS12格式):

openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

然后把这个client.p12发给你相信的人,让它导入到浏览器中,访问站点建立连接的时候nginx会要求客户端把这个证书发给自己验证,如果没有这个证书就拒绝访问。

同时别忘了在 nginx.conf 里配置信任的CA:(如果是二级CA,请把根CA放在后面,形成CA证书链)

一. 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"];
}