设置Alamofire https validateHost 并输入服务器接口主域名有什么用?
- 首先在封装 Alamofire 的网络请求时,定义针对域名的服务器证书验证策略:
let policies = ["ml.test.cn": ServerTrustPolicy.performDefaultEvaluation(validateHost: true),
"threeup.test.cn": ServerTrustPolicy.performDefaultEvaluation(validateHost: true)]
ServerTrustPolicyManager.init(policies: policies)
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 15 // seconds
let manager = Alamofire.SessionManager.init(configuration: config, serverTrustPolicyManager: policyManager)
这个 ServerTrustPolicy 是 Alamofire 提供的策略枚举,我们这里选择的是 performDefaultEvaluation(validateHost: Bool) 并且 validateHost 为 true
- Alamofire 会在下面的自定义方法中实现系统网络库收到服务器发来 https 建立连接时的证书的代理方法:
@objc(URLSession:task:didReceiveChallenge:completionHandler:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
(disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if
let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
let serverTrust = challenge.protectionSpace.serverTrust
{
if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
disposition = .useCredential
credential = URLCredential(trust: serverTrust)
} else {
disposition = .cancelAuthenticationChallenge
}
}
} else {
if challenge.previousFailureCount > 0 {
disposition = .rejectProtectionSpace
} else {
credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
completionHandler(disposition, credential)
}
-
从服务器的证书信息(challenge)中取出域名(host),然后用这个证书 host 作为 key 去 1. 中的 policies 数组取对应的 ServerTrustPolicy :
输入服务器主域名的作用:是为了根据服务器证书域名设置相应的证书验证策略。
- 使用我们设置的 performDefaultEvaluation(validateHost: true) 策略对服务器证书中的 serverTrust 和 host 进行验证:
Alamofire 的 evaluate 方法中会调用系统的 SecPolicyCreateSSL(true,validateHost ? host as CFString : nil) 方法创建系统网络库的policy:
validateHost:true 的作用:对比传入的这个 host 去和服务器子节点证书的域名 host,但是感觉Alamofire这里有个bug,传入的host是从challenge取的而非 1. 中自己输入的host
然后调用系统方法 SecTrustSetPolicies(SecTrustRef trust, CFTypeRef policies) 对应特定的服务器传来的证书信息(serverTrust)设置验证策略 policy :
最后在 Alamofire 方法 trustIsValid(_ trust: SecTrust) 中调用系统方法 SecTrustEvaluate(SecTrustRef trust, SecTrustResultType result) 对证书信息按照策略进行验证*:
-
验证完服务器端证书信息后,在 2. 的代理中会生成相应的 AuthChallengeDisposition 和 URLCredential 并回传给服务器(信任服务器证书的话,返回的内容就包含https建立连接的第三次随机数并用证书公钥加密)
为什么设置了 validateHost 仍然会被 Charles 抓包?
- 用代码验证的时候一个是要注意在 iOS 10.3 以后不止要在【通用-描述文件】里信任证书,还要在【通用-关于本机-证书信任设置】里再次信任才能使用 Charles 等抓包工具拦截 https。
-
代码验证的时候发现即使 validateHost:true 也是防不住 Charles 这种需要用户在电脑端和移动端手动信任其根证书的,因为 Charles 根证书可以给自己签名任何域名的子节点证书,移动端验证这个自己发的证书时也会从系统的白名单的 Charles 根证书验证成功。
参考链接:
图解 HTTPS:Charles 捕获 HTTPS 的原理 · youngwind/blog · GitHub
打造安全的App!iOS安全系列之 HTTPS
iOS中HTTP/HTTPS授权访问(一) - 简书