Alamofire(五) 安全认证之HTTPS证书的使用
2019-08-28 本文已影响0人
伤心的EasyMan
前言
这篇文章主要有几个目的:
- 简单了解HTTPS和HTTP
- 熟悉
Alamofire
是如何使用HTTPS证书请求网络的 - 了解
Alamofire
使用证书的实现过程
为什么要使用HTTPS
HTTP的缺点:
- 通信使用明文(不加密),内容可能被窃听
- 无法证明报文的完整性,所以可能遭篡改
- 不验证通信方的身份,因此有可能遭遇伪装
- HTTP协议无法验证通信方身份,任何人都可以伪造虚假服务器欺骗用户,实现“钓鱼欺诈”,用户无法察觉。
HTTPS的优势:
- 数据隐私性:内容经过对称加密,每个连接生成一个唯一的加密密钥
- 数据完整性:内容传输经过完整性校验
-
身份认证:第三方无法伪造服务端(客户端)身份
HTTPS加密流程
HTTPS = HTTP + SSL/TLS
HTTPS采用的是混合加密方式
SSL/TLS = 非对称加密 + 对称加密 + 散列算法
image
Alamofire中验证HTTPS证书
下面是一段使用了证书验证的代码,这里使用了.pinCertificates
验证证书的方式
func trustSessionManager() -> SessionManager{
let serverTrustPlolicies:[String: ServerTrustPolicy] = [
hostUrl: .pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true)
]
let sessionManger = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPlolicies))
return sessionManger
}
首先来看看安全验证有哪几种,ServerTrustPolicy
是一个枚举类型,里面常用的是pinCertificates
验证证书,pinPublicKeys
验证公钥,disableEvaluation
取消验证,按照不同的需求去使用这些枚举类型
public enum ServerTrustPolicy {
case performDefaultEvaluation(validateHost: Bool)
case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
case disableEvaluation
case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
}
我在外界使用了pinCertificates
的验证方式,那么证书是怎么获取的呢?
通过注释了解到它是返回bundle
里的所有证书
/// Returns all certificates within the given bundle with a `.cer` file extension.
///
/// - parameter bundle: The bundle to search for all `.cer` files.
///
/// - returns: All certificates within the given bundle.
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
}
同理,pinPublicKeys
方式可以直接获取所有的公钥publicKeys
public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {
var publicKeys: [SecKey] = []
for certificate in certificates(in: bundle) {
if let publicKey = publicKey(for: certificate) {
publicKeys.append(publicKey)
}
}
return publicKeys
}
证书验证方法
public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
var serverTrustIsValid = false
switch self {
case let .performDefaultEvaluation(validateHost):
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, policy)
serverTrustIsValid = trustIsValid(serverTrust)
case let .performRevokedEvaluation(validateHost, revocationFlags):
let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
let revokedPolicy = SecPolicyCreateRevocation(revocationFlags)
SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef)
serverTrustIsValid = trustIsValid(serverTrust)
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):
var certificateChainEvaluationPassed = true
if validateCertificateChain {
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, policy)
certificateChainEvaluationPassed = trustIsValid(serverTrust)
}
if certificateChainEvaluationPassed {
outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {
for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
if serverPublicKey.isEqual(pinnedPublicKey) {
serverTrustIsValid = true
break outerLoop
}
}
}
}
case .disableEvaluation:
serverTrustIsValid = true
case let .customEvaluation(closure):
serverTrustIsValid = closure(serverTrust, host)
}
return serverTrustIsValid
}
- 用switch根据不同的验证方式做不同的验证操作
- 我们找到了验证的核心方法
evaluate
,全局搜索找到它是在SessionDelegate
和TaskDelegate
都有调用
open func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
///其余代码省略
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
}
}
}
completionHandler(disposition, credential)
}
可以看到serverTrustPolicy.evaluate(serverTrust, forHost: host)
,在这里调用了我们上面看到的evaluate
方法去验证是否成功
总结
- 创建
ServerTrustPolicyManager
策略管理者,设置认证策略ServerTrustPolicy
- 获取HTTPS服务器的
serverTrust
和host
- 调用
ServerTrustPolicyManager
策略管理者里的evaluate
验证方法 - 根据返回结果创建
URLCredential
- 通过
completionHandler(disposition, credential)
回调告诉外界的结果