Charles
是一款出色的HTTP
抓包工具,相信大家都有过相关使用经验。(没有的话,它值得你去试一试。)它的基本原理是使用中间人攻击
。该篇并不会深入讨论其原理,而是以防守方的角色看看如何使用Alamofire
避免被抓包。
没有绝对的安全,这里只是提供一个防守策略。
准备工作
为了后续的测试,我们先准备一些代码(Alamofire
中的事件观察者),将每次HTTPS
握手的证书信息打印出来。如下:
1 |
|
下面我们将这个观察者添加到Session
中:
1 |
|
这里准备了2个Session
一个普通没有加入证书校验的,另一个加上了,方便做对照。
然后在MasterViewController
中使用:
⚠️ 这里的sb为啥都不显示,不知为啥。有知道的烦请留言告知下,谢谢!
最后,我们还缺少一个证书
。打开https://httpbin.org/get
,按照下面的步骤可以获取:
准备完毕。如果感觉到麻烦,可以在这里获取完整代码。
启动实验
配置好Charles
,启动APP
,先点击第一个:
控制台会将证书链打印出来:
start dump
httpbin.org
Charles Proxy CA (30 Mar 2020, bogon)
----end---
可以看到这里的证书链被修改了,正确的应该是:
start dump
httpbin.org
Amazon
Amazon Root CA 1
----end---
虽然这个证书链被修改了,但是请求依然能成功。接着,我们点击第二个,这次请求不会成功,因为我们加入了证书校验。
知其然,知其所以然
可以看到,我们只需一张证书加上一个简单的配置,就可以避免被抓包
。那么如此强大的功能到底是如何实现的呢?一起来瞅瞅。
ServerTrustEvaluating
ServerTrustEvaluating
是一个协议,代表评审团的角色,可以对一次握手进行评估。想成为评审团也很容易,只需遵循该协议。
PinnedCertificatesTrustEvaluator
就是Alamofire
提供的一个使用固定证书作为评审标准的实现。除此之外,还有:
- DefaultTrustEvaluator 执行默认的评审标准
- RevocationTrustEvaluator 在默认标准之上加入了
是否吊销
校验。 - PublicKeysTrustEvaluator 在默认的标准之上加入了
公钥
校验。 - DisabledTrustEvaluator 不执行任何校验,不要在生产环境使用。
- CompositeTrustEvaluator 复合校验,混合使用上面的一个或多个。
接下来我们分析下具体实现过程。
Alamofire
中大量使用了类似type.af.feature
格式的写法,这种技巧在之前的文章中有提到,感到陌生的同学请移步这里。
DefaultTrustEvaluator
DefaultTrustEvaluator
的实现不是很繁琐,只有短短的几行代码:
1 |
|
其评估过程具体包含了2步:
- 按需执行
performValidation
校验host
,默认是校验的。 - 执行
performDefaultValidation
进行默认校验。
这里使用到的两个api
都是SecTrust
的扩展中的,具体的有:
这里使用的数据类型多数来自于系统框架
Security
- 对
SecTrust
应用校验策略1
2
3
4
5
6
7
8
9
10
11public func apply(policy: SecPolicy) throws -> SecTrust { let status = SecTrustSetPolicies(type, policy) guard status.af.isSuccess else { throw AFError.serverTrustEvaluationFailed(reason: .policyApplicationFailed(trust: type, policy: policy, status: status)) } return type }
- 执行评估:
1
2
3
4
5
6
7
8public func evaluate() throws { var error: CFError? let evaluationSucceeded = SecTrustEvaluateWithError(type, &error) if !evaluationSucceeded { throw AFError.serverTrustEvaluationFailed(reason: .trustEvaluationFailed(error: error)) } }
- 应用策略并执行评估,只是对上面两个
api
的组合调用1
2
3public func evaluate(afterApplying policy: SecPolicy) throws { try apply(policy: policy).af.evaluate() }
- 校验
host
以及默认校验1
2
3
4
5
6
7
8
9public func performValidation(forHost host: String) throws { if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) { try evaluate(afterApplying: SecPolicy.af.hostname(host)) } else { try validate(policy: SecPolicy.af.hostname(host)) { status, result in AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, type, status, result))) } } }
1
2
3
4
5
6
7
8
9public func performDefaultValidation(forHost host: String) throws { if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) { try evaluate(afterApplying: SecPolicy.af.default) } else { try validate(policy: SecPolicy.af.default) { status, result in AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, type, status, result))) } } }
这两种校验只是在使用不同的策略对象使用上面提到的
evaluate(afterApplying:)
,其他并无区别。
RevocationTrustEvaluator
和DefaultTrustEvaluator
一样,这里使用了SecPolicy.af.revocation(options: options)
的策略进行校验。值得注意的是,校验证书是否被吊销,也是有多种策略可选的,这里使用Options
进行配置。
PinnedCertificatesTrustEvaluator
固定证书评审,使用指定证书数据作为依赖,也支持自签名证书
。所以它可以配置证书文件以及是否为自签名证书。具体的评审过程如下:
1 |
|
PublicKeysTrustEvaluator
公钥评审,使用指定公钥进行校验。
1 |
|
总结
该篇我们了解到了在使用Alamofire
时如何防止应用接口被抓包,并简单了解了其内部原理。这其中依赖接口而不是依赖实现的设计很值得我们学习。该模式使得我们后续添加其他评估逻辑变得很简单。希望大家有所收获,感谢您的阅读。再会!