阅读 51

Alamofire(五) 安全认证之HTTPS证书的使用

前言

这篇文章主要有几个目的:

  1. 简单了解HTTPS和HTTP
  2. 熟悉Alamofire是如何使用HTTPS证书请求网络的
  3. 了解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,全局搜索找到它是在SessionDelegateTaskDelegate都有调用
 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方法去验证是否成功

总结

  1. 创建ServerTrustPolicyManager策略管理者,设置认证策略ServerTrustPolicy
  2. 获取HTTPS服务器的serverTrusthost
  3. 调用ServerTrustPolicyManager策略管理者里的evaluate验证方法
  4. 根据返回结果创建URLCredential
  5. 通过completionHandler(disposition, credential)回调告诉外界的结果
关注下面的标签,发现更多相似文章
评论