Alamofire源码分析(12)——网络挑战证书
- HTTP
特点:1. 支持客户/服务器模式;2. 简单快速;3. 灵活;4. 无连接;5. 无状态。
问题:1. 通信使用明文;2. 不验证通信方身份,无论是客户端还是服务器;3. 无法证明报文的完整性,可能被篡改。
- HTTPS
http
不安全,所以也就有了https
。之前在网络学习(一)中提过https
的加密方式,比http安全很多。
加密过程
- 服务器把自己的公开密钥登录至数字证书认证机构。
- 数字证书认证机构用自己的私有密钥向服务器的公开密码署数字签名并颁发公钥证书。
- 客户端拿到服务器的公钥证书后,使用数字签名认证机构的公开密钥,向数字证书认证机构验证公钥证书上的数字签名,以确认服务器的公开密钥的真实性。
- 使用服务器的公开密钥对报文加密后发送。
- 服务器用私有密钥对报文解密。
工作流程基本分为三个阶段:1. 认证服务器;2. 协商会话密钥;3. 加密通讯。
今天就来简单了解一下使用证书的例子:
let serverTrustPlolicies:[String: ServerTrustPolicy] = [
hostUrl: .pinCertificates(
certificates: ServerTrustPolicy.certificates(), //遍历获取所有证书
validateCertificateChain: true, //一般为true,验证证书链
validateHost: true) //一般为true
]
let serverTrustPolicyManager = ServerTrustPolicyManager(policies: serverTrustPlolicies)
let sessionManger = SessionManager(serverTrustPolicyManager: serverTrustPolicyManager)
sessionManager.request(url).response { (response) in
print(response)
}
- 首先遍历工程获取证书:
public enum ServerTrustPolicy {
...
public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
var certificates: [SecCertificate] = []
let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
}.joined())
for path in paths {
if
let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
let certificate = SecCertificateCreateWithData(nil, certificateData)
{
certificates.append(certificate)
}
}
return certificates
}
...
}
- 验证证书会在
TaskDelegate
代理中回调:
open class TaskDelegate: NSObject {
...
//验证证书回调
@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 {
//Challenge 就是为了验证用户身份,向访问者发送一个质询,然后访问者需要提供一个正确的回答以示身份
//URLProtectionSpace 这个表示服务器上的一块受保护的区域,访问这一块需要进行质询
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 { ... }
}
} else { ... }
completionHandler(disposition, credential)
}
...
}
质询验证方式有如下几种是常用的
NSURLAuthenticationMethodHTTPBasic
: HTTP基本验证,服务器向客户端询问用户名,密码
NSURLAuthenticationMethodClientCertificate
: 客户端证书验证,服务器向客户端询客户端身份证书
NSURLAuthenticationMethodServerTrust
: 服务器端证书验证,客户端对服务器端的证书进行验证。HTTPS中的服务器端证书验证属于这一种。
UrlCredential
是客户端对服务器端质询的响应。根据验证方式不一样,有如下几种:
基于用户名密码的;
基于客户端证书的;
基于服务器端证书的;(就是这里验证服务器端的证书要用到的)
它们分别对应于UrlCredential
的三种构造方式。
- 其重点在于
evaluate
:
public enum ServerTrustPolicy {
...
public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
var serverTrustIsValid = false
//根据情况验证
switch self {
case let .performDefaultEvaluation(validateHost): ...
case let .performRevokedEvaluation(validateHost, revocationFlags): ...
case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
if validateCertificateChain {//验证证书链
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, policy)
SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
SecTrustSetAnchorCertificatesOnly(serverTrust, true)
serverTrustIsValid = trustIsValid(serverTrust)
} else {//验证证书数据
let serverCertificatesDataArray = certificateData(for: serverTrust)
let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)
outerLoop: for serverCertificateData in serverCertificatesDataArray {
for pinnedCertificateData in pinnedCertificatesDataArray {
if serverCertificateData == pinnedCertificateData {
serverTrustIsValid = true
break outerLoop
}
}
}
}
case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost): ...
case .disableEvaluation: ...
case let .customEvaluation(closure): ...
}
return serverTrustIsValid
}
...
}
根据类型走向pinCertificates
,如果validateCertificateChain
为true
,就会验证证书链;否则验证证书数据。
Alamofire 安全认证策略的六种模式,其中最常用的有这三种:
.pinCertificates
证书验证模式、.pinPublicKeys
公钥验证模式和.disableEvaluation
不验证模式。
.performDefaultEvaluation
默认策略,只有合法证书才能通过验证
.performRevokedEvaluation
对注销证书做的一种额外设置
.pinCertificates
证书验证模式,代表客户端会将服务器返回的证书和本地保存的证书中的 所有内容 全部进行校验,如果正确,才继续执行。
.pinPublicKeys
公钥验证模式,代表客户端会将服务器返回的证书和本地保存的证书中的PublicKey
部分 进行校验,如果正确,才继续执行。
.disableEvaluation
该选项下验证一直都是通过的,无条件信任。
.customEvaluation
自定义验证,需要返回一个布尔类型的结果。