【iOS】HTTPS的使用学习

使用HTTPS最主要的考虑就是数据的安全性。对于HTTPS如何确保数据安全,依靠的就是SSL/TSL层的加密机制。HTTPS也就是在原来的HTTP层的基础上增加了一个安全层。

我们需要了解的是HTTPS在建立安全连接时采取四次握手机制,会话密钥的产生。使用会话密钥主要是因为建立安全机制的密钥算法是非对称算法(一般是RSA),计算比较耗时,而会话密钥使用的是对称密钥,可以提高会话的效率。

  1. 客户端向服务器端发起连接请求,上送信息有:支持协议版本、随机数1、加密方法、支持压缩算法;
  2. 服务器回应,下发信息有:确认协议版本、随机数2、确认加密方法、服务器公钥证书;
  3. 客户端再次上送信息:随机数3(采用公钥加密)、编码改变通知、客户端握手结束通知;
  4. 服务器采用随机数1、随机数2和随机数3组成会话密钥,并回应:编码改变通知、服务器握手结束通知。

以上四次握手中,完成了几件事情,第一,证书下发;第二,会话密钥;第三,确认双方参数(双方协议版本、加密方法等)。其中随机数1和随机数2是明文,随机数3是密文,之所以采用三个随机数来确定会话密钥,主要是三组随机数组成的随机数,确保了随机数的随机性,从而保证每次生成的会话密钥的安全性。

在默认情况下,iOS要求连接的HTTPS站点必须为CA签名过得合法证书。AFNetworking基于iOS的HTTP网络通讯库,自然在证书方面要求和系统是一致,也需要合法的站点证书。所以探讨HTTPS在iOS开发中的使用,我们主要考虑两个问题,一个是合法证书,一个是自建证书。

先来了解一下系统API的验证流程,HTTPS的相关API在Security.Framework中,如下:

  1. 获取需要验证的信任对象(Trust Object),在NSURLConnection来说,是从delegate方法-connection:willSendRequestForAuthenticationChallenge:回调回来的参数challenge中获取([challenge.protectionSpace serverTrust]);
  2. 采用系统默认方式验证Trust Object,SecTrustEvaluate会根据Trust Object的验证策略,一级一级往上,验证证书上每一级数字签名的有效性,从而评估证书的有效性;
  3. 通过上一步,一般安全要求下,直接验证通过,下一步使用Trust Object生成一份凭证([NSURLCredential credentialForTrust:serverTrust]),传入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge])处理,建立连接;
  4. 如果有更强的安全要求,还可以继续对Trust Object进行更严格验证,常用方式为在本地导入证书,验证Trust Object与导入证书是否匹配;
  5. 如果验证失败,取消此次Challenge-Response Authentication验证流程,拒绝连接请求。

如果是自建证书,那么会跳过第二步,因为自建证书的根CA数字签名不在系统的信任列表中。

上代码

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
// Now start the connection
NSURL * httpsURL = [NSURL URLWithString:@"https://www.google.com"];
self.connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:httpsURL] delegate:self];

//回调
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    //1)获取trust object
    SecTrustRef trust = challenge.protectionSpace.serverTrust;
    SecTrustResultType result;

   //2)SecTrustEvaluate对trust进行验证
    OSStatus status = SecTrustEvaluate(trust, &result);
    if (status == errSecSuccess &&
        (result == kSecTrustResultProceed ||
        result == kSecTrustResultUnspecified)) {

   //3)验证成功,生成NSURLCredential凭证cred,告知challenge的sender使用这个凭证来继续连接
        NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
        [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
    } else {
        //5)验证失败,取消这次验证流程
        [challenge.sender cancelAuthenticationChallenge:challenge];

  }
}

对于自建证书我们需要先在本地导入证书,设置成需要验证的Anchor Certificate,再来验证。如下:

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
//先导入证书
NSString * cerPath = ...; //证书的路径
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
self.trustedCertificates = @[CFBridgingRelease(certificate)];

//回调
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    //1)获取trust object
    SecTrustRef trust = challenge.protectionSpace.serverTrust;
    SecTrustResultType result;

    //注意:这里将之前导入的证书设置成下面验证的Trust Object的anchor certificate
    SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);

    //2)SecTrustEvaluate会查找前面SecTrustSetAnchorCertificates设置的证书或者系统默认提供的证书,对trust进行验证
    OSStatus status = SecTrustEvaluate(trust, &result);
    if (status == errSecSuccess &&
        (result == kSecTrustResultProceed ||
        result == kSecTrustResultUnspecified)) {

        //3)验证成功,生成NSURLCredential凭证cred,告知challenge的sender使用这个凭证来继续连接
        NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
        [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];

    } else {

        //5)验证失败,取消这次验证流程
        [challenge.sender cancelAuthenticationChallenge:challenge];

  }
}

一般我们很少直接使用原生API,而是使用AFNetworking封装好的接口来完成以上繁琐的过程,目前AFNetworking也已经更新到2.x版本了。

一般我们只需要如下代码就可使接口支持HTTPS:

1
2
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
    securityPolicy.allowInvalidCertificates = YES;

对于自建证书,AFNetworking2也是支持的,需要将pem格式的证书转成cer格式的,可以在mac下使用如下命令行:openssl x509 -in <你的服务器证书>.pem -outform der -out server.cer将该证书引入到ap的bundle目录里,AFNetworking会自动扫描boundle中的.cer文件。

1
2
3
4
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy AFSSLPinningModeCertificate];
或者
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy AFSSLPinningModePublicKey];
securityPolicy.allowInvalidCertificates = YES; //还是必须设成YES

上面只列了一个参数,我想下面技术最好都要知道:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;

//validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO
//主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
securityPolicy.validatesDomainName = NO;

//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;

另外需要知道的是,验证站点证书时通过域名的,如果服务器站点没有绑定域名,仅靠IP地址,上面的方法是不行的,解决的办法就是修改AFNetworking2的代码,在AFSecurityPolicy.m文件中,修改:- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain方法,将里面的部分校验代码注释掉:

1
2
3
4
5
6
7
8
9
//            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
//
//            if (!AFServerTrustIsValid(serverTrust)) {
//                return NO;
//            }
//
//            if (!self.validatesCertificateChain) {
//                return YES;
//            }

有了上面的了解,我们就可以去实践一下HTTPS的开发了。Enjoy it!

学习来源:

  1. iOS实用技巧 - AFNetworking2安全的使用自签证书访问HTTPS
  2. iOS安全系列之一:HTTPS
  3. SSL/TLS协议运行机制的概述
  4. 图解SSL/TLS协议
作者: Peter
出处: http://codefunny.github.io/
本文基于
署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 Peter(包含链接)。