AFNetworking设置SSL链接

引言

之前公司为网站端配置了SSL证书,上午刚好有空就做了下iOS端的支持,随便记录一下。

准备

获取Certification file

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

www.google.com:443换成你自己的地址。当然网管有的话直接向他索取就好了。如果格式是.crt可以通过下面的代码转成.cer

1
openssl x509 -in https.crt -out https.cer -outform der

这里顺便提供一些其他格式转化的例子。具体可以看这里

Parse a PKCS#12 file and output it to a file:

1
openssl pkcs12 -in file.p12 -out file.pem

Output only client certificates to a file:

1
openssl pkcs12 -in file.p12 -clcerts -out file.pem

Don’t encrypt the private key:

1
openssl pkcs12 -in file.p12 -out file.pem -nodes

Print some info about a PKCS#12 file:

1
openssl pkcs12 -in file.p12 -info -noout

Create a PKCS#12 file:

1
openssl pkcs12 -export -in file.pem -out file.p12 -name "My Certificate"

Include some extra certificates:

1
2
openssl pkcs12 -export -in file.pem -out file.p12 -name "My Certificate" \
-certfile othercerts.pem

AFNetworking(3.0)配置

首先将cer证书导入工程。

设置securityPolicy

1
2
3
4
5
6
7
8
9
10
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"https" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
#if DEBUG//为了方便开发时抓包
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
#endif
[securityPolicy setAllowInvalidCertificates:NO];
[securityPolicy setPinnedCertificates:[NSSet setWithObject:certData]];
[securityPolicy setValidatesDomainName:YES];
sessionManager.securityPolicy = securityPolicy;

说明

SSLPinningMode
The criteria by which server trust should be evaluated against the pinned SSL certificates. Defaults to AFSSLPinningModeNone.

1
2
3
4
5
6
7
8
9
10
11
12
  /*
`AFSSLPinningModeNone` 不验证证书信息.

`AFSSLPinningModePublicKey` 只验证证书包含的公钥信息.

`AFSSLPinningModeCertificate` 验证证书信息包括有效期、公钥等.
*/
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};

选择哪种模式?
AFSSLPinningModeCertificate优点是安全,但是是证书是有有效期的,如果打包的证书过期了或者服务器证书替换了就会链接失败;当然你也可以下载新证书进行更新,但这时候下载的地址也就不具安全性了。
AFSSLPinningModePublicKey 没前面的安全,但是这种模式只会验证public key这样就不用考虑证书过期的问题了。
所以综合比较确保不能及时更新所有过期app的情况下还是建议用AFSSLPinningModePublicKey。
AFSSLPinningModeNone的话用于开发抓包调试最好了。
具体的差别建议看下源码

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

switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}

// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);

for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}

return NO;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}

链接复用优化

类似手淘可以使用是twitter的CocoaSPDY,或者Voxer/iSPDY来支持SPDY协议,以建立连接的互用,当然前提是服务器支持SPDY协议。另外,google已决定移除chrome对SPDY的支持转而支持http/2(也是基于SPDY),目前iOS9.2也已经支持http/2协议。所以后续可以考虑用http/2优化。