iOS深入理解Https的单向认证

SRAM MWA.png

一直知道客户端这边第三步:需要校验服务端的证书.但是需要怎么校验.使用AFN怎么设置使用呢?

先看看AFN配置证书相关的代码:

    NSData *cerData = [[NSData alloc] initWithBase64EncodedString:[self getSSLCertificates] options:0];
    AFSecurityPolicy* policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    policy.allowInvalidCertificates = YES;
    policy.validatesDomainName = YES;
    policy.pinnedCertificates = [NSSet setWithArray:@[cerData]];
    return policy;

我们可以看到是有一个加载证书的过程.并且有一个mode类型,这里使用的是:AFSSLPinningModeCertificate.我们先了解一下AFN提供的三种类型:

除了去系统信任 CA 机构列表验证的 AFSSLPinningModeNone 模式,AFNetworking 还提供了 SSL Pinning 方式的验证。
SSL Pinning 方式(AFSSLPinningModeCertificate,AFSSLPinningModePublicKey)把服务端下发的证书预先打包到 APP 的 bundle 中,然后通过比较服务端下发的证书和本地证书是否相同来校验证书。
使用该方式的原因是CA机构颁发的证书比较昂贵,一些企业或者个人不申请CA颁发的证书,而是自己手动创建证书。用 SSL Pinning 的方式只要比较证书内容一样,无需验证证书的权威性。

  • AFSSLPinningModeNone这个模式表示不做 SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书(链)。 若证书是信任机构签发的就会通过;若是自己服务器生成的证书,这里是不会通过的。
    AFSSLPinningModeCertificate这个模式表示用证书绑定方式验证证书,需要客户端保存服务端的证书拷贝。 适用于非浏览器应用,因为浏览器跟很多未知服务端打交道,无法把每个服务端的证书都保存到本地。但CS架构的APP应用一般事先知道要进行通信的服务端(例如 QQ 文件后台服务器:.ftn.qq.com),可以直接在客户端保存这些固定服务端的证书用于校验。 验证分两步:第一步验证证书的域名/有效期等信息;第二步是对比服务端返回的证书跟客户端返回的是否一致。 从代码上看,和去系统信任机构列表里验证一样调用 SecTrustEvaluate,只是这里的列表换成了客户端预先保存的证书列表(链)。
  • AFSSLPinningModePublicKey 这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。 只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
AFNetworking单向认证.png

针对AFSecurityPolicyallowInvalidCertificates属性怎么理解呢?
意思是:是否允许非CA机构证书(也就是自建的证书),默认为NO.如果是自建非CA机构证书则需要设置为YES

针对AFSecurityPolicyvalidatesDomainName属性怎么理解呢?

说一下我们一般自建证书时都有一个域名需要注册:
[alt_names]
DNS.1 = flymote.com
DNS.2 = *.flymote.com
DNS.3 = www.flymot.com
DNS.4 = *.flymot.com
IP.1 = 192.168.0.198
实际上不填写也是可以获取证书的.但一般为了保障通信的安全性.防止中间人攻击

中间人攻击.png

因为我们可以从CA获取证明.别人也可以通过CA获取证书.
那么只做证书是否是CA证书机构发布显然不那么安全.所以还需要验证一下域名和颁发机构
validatesDomainName的意思是:是否需要验证域名.域名与证书的是否一致

到这里才对单向认证的第三步:客户端对服务器证书是否合法有所理解!

实践出真理.下面我们做一下验证,帮助理解:

单向认证:
CA证书:https://nemo.zhulongan.com/1.txt
自生成证书:https://nemo.zhulongan.com:8001/1.txt

代码:

AFSecurityPolicy* policy          = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
policy.allowInvalidCertificates   = NO;
policy.validatesDomainName        = YES;
return policy;

结果:

————————————CA的请求成功———————————
调用接口(requestKey=10000)成功,
请求Domain:https://nemo.zhulongan.com/1.txt,返回结果如下:{length = 32, bytes = 0x68656c6c 6f776f72 6c642073 656e6c64 ... 302d3132 2d30390a }

————————————自建的请求失败———————————
https://nemo.zhulongan.com:8001/1.txt,错误信息如下:Error Domain=NSURLErrorDomain Code=-999 "已取消" UserInfo={NSErrorFailingURLStringKey=https://nemo.zhulongan.com:8001/1.txt?_deviceId=4C76CC95-E4C0-49B3-98B9-E78F8E39B7D3&_channel_id=32&_client_version_no=1.0.6, NSErrorFailingURLKey=https://nemo.zhulongan.com:8001/1.txt?_deviceId=4C76CC95-E4C0-49B3-98B9-E78F8E39B7D3&_channel_id=32&_client_version_no=1.0.6, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <35BF7B1D-A6B5-4938-B2E6-9DD25E2BDD99>.<1>"), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <35BF7B1D-A6B5-4938-B2E6-9DD25E2BDD99>.<1>, NSLocalizedDescription=已取消}

自建证书保存到本地
代码:

    NSData *cerData = [[NSData alloc] initWithBase64EncodedString:[self getSSLCertificates] options:0];
    AFSecurityPolicy* policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    policy.allowInvalidCertificates = YES;
    policy.validatesDomainName = NO;
    policy.pinnedCertificates = [NSSet setWithArray:@[cerData]];
    return policy;

结果:

————————————CA的请求失败———————————
调用接口(requestKey=10000)失败,
请求Domain:https://nemo.zhulongan.com/1.txt,错误信息如下:Error Domain=NSURLErrorDomain Code=-999 "已取消" UserInfo={NSErrorFailingURLStringKey=https://nemo.zhulongan.com/1.txt?_deviceId=4C76CC95-E4C0-49B3-98B9-E78F8E39B7D3&_channel_id=32&_client_version_no=1.0.6, NSErrorFailingURLKey=https://nemo.zhulongan.com/1.txt?_deviceId=4C76CC95-E4C0-49B3-98B9-E78F8E39B7D3&_channel_id=32&_client_version_no=1.0.6, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <7C39B89E-6948-4210-A95C-1DEABF71241D>.<1>"), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <7C39B89E-6948-4210-A95C-1DEABF71241D>.<1>, NSLocalizedDescription=已取消}

————————————自建的请求成功———————————
调用接口(requestKey=10001)成功,
请求Domain:
https://nemo.zhulongan.com:8001/1.txt,
返回结果如下:{length = 32, bytes = 0x68656c6c 6f776f72 6c642073 656e6c64 ... 302d3132 2d30390a }

推荐阅读更多精彩内容