AFNetworking 3.0 源码阅读笔记(七)

原文:http://itangqi.me/2016/05/20/the-notes-of-learning-afnetworking-seven/

前言

自 iOS 9 发布之后,由于新特性 App Transport Security 的引入,在默认行为下是不能发送 HTTP 请求的。很多网站都在转用 HTTPS,而 AFNetworking 中的 AFSecurityPolicy 就是为了阻止中间人攻击,以及其它漏洞的工具。

AFSecurityPolicy 主要作用就是验证 HTTPS 请求的证书是否有效,如果应用中有一些敏感信息或者涉及交易信息,一定要使用 HTTPS 来保证交易或者用户信息的安全。


AFSSLPinningMode

使用 AFSecurityPolicy 时,总共有三种验证服务器是否被信任的方式:

1
2
3
4
5
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,
    AFSSLPinningModePublicKey,
    AFSSLPinningModeCertificate,
};

AFSSLPinningModeNone

  • 这个模式不做本地证书验证(不做 SSL Pinning 操作)
  • 直接从客户端系统中的受信任颁发机构 CA 列表中去验证

AFSSLPinningModePublicKey

  • 客户端需要一份证书文件的拷贝
  • 验证时只验证证书里的公钥,不验证证书的有效期等信息
  • 即使伪造证书的公钥,也不能解密传输的数据,必须要私钥

AFSSLPinningModeCertificate

  • 客户端需要一份证书文件的拷贝
  • 第一步验证、先验证证书的域名/有效期等信息
  • 第二步验证、对比服务端返回的证书跟客户端存储的证书是否一致

初始化以及设置

在使用 AFSecurityPolicy 验证服务端是否受到信任之前,要对其进行初始化,使用初始化方法时,主要目的是设置验证服务器是否受信任的方式

1
2
3
4
5
6
7
8
9
10
11
12
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
    return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]];
}

+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = pinningMode;

    [securityPolicy setPinnedCertificates:pinnedCertificates];

    return securityPolicy;
}

这里没有什么地方值得解释的。不过在调用 pinnedCertificate 的 setter 方法时,会从全部的证书中取出公钥保存到 pinnedPublicKeys 属性中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
    _pinnedCertificates = pinnedCertificates;

    if (self.pinnedCertificates) {
        NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
        for (NSData *certificate in self.pinnedCertificates) {
            id publicKey = AFPublicKeyForCertificate(certificate);
            if (!publicKey) {
                continue;
            }
            [mutablePinnedPublicKeys addObject:publicKey];
        }
        self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
    } else {
        self.pinnedPublicKeys = nil;
    }
}

在这里调用了 AFPublicKeyForCertificate 对证书进行操作,返回一个公钥。


操作 SecTrustRef

serverTrust 的操作的函数基本上都是 C 的 API,都定义在 Security 模块中,先来分析一下在上一节中 AFPublicKeyForCertificate 的实现:

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
static id AFPublicKeyForCertificate(NSData *certificate) {
    // 1. 初始化临时变量
    id allowedPublicKey = nil;
    SecCertificateRef allowedCertificate;
    SecCertificateRef allowedCertificates[1];
    CFArrayRef tempCertificates = nil;
    SecPolicyRef policy = nil;
    SecTrustRef allowedTrust = nil;
    SecTrustResultType result;
    
    // 2. 因为此处传入的 certificate 参数是 NSData 类型的,所以使用 `SecCertificateCreateWithData` 通过 DER 表示的数据生成一个 `SecCertificateRef` 对象,然后判断返回值是否为 `NULL`
    allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
    __Require_Quiet(allowedCertificate != NULL, _out);

    // 3. 通过上面的 `allowedCertificate` 创建一个 `CFArray`
    allowedCertificates[0] = allowedCertificate;
    tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);

    // 4. 创建一个默认的符合 X509 标准的 `SecPolicyRef`,通过默认的 `SecPolicyRef` 和证书创建一个 `SecTrustRef` 用于信任评估,对该对象进行信任评估,确认生成的 `SecTrustRef` 是值得信任的,这里使用的 __Require_noErr_Quiet 和上面的宏差不多,只是会根据返回值判断是否存在错误
    policy = SecPolicyCreateBasicX509();
    __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
    __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
    
    // 5. 获取公钥,这里的 __bridge_transfer 会将结果桥接成 NSObject 对象,然后将 SecTrustCopyPublicKey 返回的指针释放
    allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);

_out:
    // 6. 释放各种 C 语言指针
    if (allowedTrust) {
        CFRelease(allowedTrust);
    }

    if (policy) {
        CFRelease(policy);
    }

    if (tempCertificates) {
        CFRelease(tempCertificates);
    }

    if (allowedCertificate) {
        CFRelease(allowedCertificate);
    }

    return allowedPublicKey;
}

每一个 SecTrustRef 的对象都是包含多个 SecCertificateRef SecPolicyRef。其中 SecCertificateRef 可以使用 DER 进行表示,并且其中存储着公钥信息。

对它的操作还有 AFCertificateTrustChainForServerTrustAFPublicKeyTrustChainForServerTrust 但是它们几乎调用了相同的 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
    // 1. 使用 SecTrustGetCertificateCount 函数获取到 serverTrust 中需要评估的证书链中的证书数目,并保存到 certificateCount 中
    CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
    NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
    // 2. 使用 SecTrustGetCertificateAtIndex 函数获取到证书链中的每个证书,并添加到 trustChain 中,最后返回 trustChain
    for (CFIndex i = 0; i < certificateCount; i++) {
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
        // 3. 使用 SecCertificateCopyData 从证书中或者 DER 表示的数据
        [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
    }

    return [NSArray arrayWithArray:trustChain];
}

验证服务端是否受信

验证服务端是否守信是通过 - [AFSecurityPolicy evaluateServerTrust:forDomain:] 方法进行的,也是 AFSecurityPolicy 类中的核心方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{

    #1:不能隐式地信任自己签发的证书

    #2:设置 policy
    
    #3:验证证书是否有效

    #4:根据 SSLPinningMode 对服务端进行验证
    
    return NO;
}

1. 不能隐式地信任自己签发的证书

1
2
3
4
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
    NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
    return NO;
}

Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
Instead, add your own (self-signed) CA certificate to the list of trusted anchors.

所以如果没有提供证书或者不验证证书,并且还设置 allowInvalidCertificates,满足上面的所有条件,说明这次的验证是不安全的,会直接返回 NO

2. 设置 policy

1
2
3
4
5
6
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}

如果要验证域名的话,就以域名为参数创建一个 SecPolicyRef,否则会创建一个符合 X509 标准的默认 SecPolicyRef 对象。

3. 验证证书的有效性

1
2
3
4
5
if (self.SSLPinningMode == AFSSLPinningModeNone) {
    return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
    return NO;
}
  • 如果只根据信任列表中的证书进行验证,即 self.SSLPinningMode == AFSSLPinningModeNone。如果允许无效的证书的就会直接返回 YES。不允许就会对服务端信任进行验证

  • 如果服务器信任无效,并且不允许无效证书,就会返回 NO

4. 根据 SSLPinningMode 对服务器信任进行验证

1
2
3
4
5
6
7
8
9
10
11
switch (self.SSLPinningMode) {
    case AFSSLPinningModeNone:
    default:
        return NO;
    case AFSSLPinningModeCertificate: {
        ...
    }
    case AFSSLPinningModePublicKey: {
        ...
    }
}
AFSSLPinningModeNone 直接返回 NO
AFSSLPinningModeCertificate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
  1. self.pinnedCertificates 中获取 DER 表示的数据
  2. 使用 SecTrustSetAnchorCertificates 为服务器信任设置证书
  3. 判断服务器信任的有效性
  4. 使用 AFCertificateTrustChainForServerTrust 获取服务器信任中的全部 DER 表示的证书
  5. 如果 pinnedCertificates 中有相同的证书,就会返回 YES
AFSSLPinningModePublicKey
1
2
3
4
5
6
7
8
9
10
11
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;

这部分的实现和上面的差不多,区别有两点:

  1. 会从服务器信任中获取公钥
  2. pinnedPublicKeys 中的公钥与服务器信任中的公钥相同的数量大于 0,就会返回真

与 AFURLSessionManager 协作

在代理协议 - URLSession:didReceiveChallenge:completionHandler: 或者 - URLSession:task:didReceiveChallenge:completionHandler: 代理方法被调用时会运行这段代码

1
2
3
4
5
6
7
8
9
10
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
    if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
        disposition = NSURLSessionAuthChallengeUseCredential;
        credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
    } else {
        disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
    }
} else {
    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}

NSURLAuthenticationChallenge 表示一个认证的挑战,提供了关于这次认证的全部信息。它有一个非常重要的属性 protectionSpace,这里保存了需要认证的保护空间, 每一个 NSURLProtectionSpace 对象都保存了主机地址,端口和认证方法等重要信息。

在上面的方法中,如果保护空间中的认证方法为 NSURLAuthenticationMethodServerTrust,那么就会使用在上一小节中提到的方法 - [AFSecurityPolicy evaluateServerTrust:forDomain:] 对保护空间中的 serverTrust 以及域名 host 进行认证

根据认证的结果,会在 completionHandler 中传入不同的 disposition credential 参数。


小结

  • AFSecurityPolicy 同样也作为一个即插即用的模块,在 AFNetworking 中作为验证 HTTPS 证书是否有效的模块存在,在 iOS 对 HTTPS 日渐重视的今天,在我看来,使用 HTTPS 会成为今后 API 开发的标配。

总结

到这里,AFNetworking 源码阅读之旅(UIKit+AFNetworking 除外)就要告一段落了,定有许多不足与遗憾。不过没关系,就让我们去实践中将理论升华,想必到时候一定会有一番新的体会。


参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值