如何区分Alamofire证书钉扎失败是MITM攻击还是证书过期
Alamofire 5 证书钉扎两类错误区分实现方案
核心实现逻辑
原生PinnedCertificatesTrustEvaluator会直接合并所有校验失败为certificatePinningFailed错误,所以需要拆分两个校验步骤:
- 第一步:先走系统原生的X.509证书信任链校验,判断当前服务端证书本身是否合法可信,这一步失败直接判定为证书不可信(MITM攻击/服务端证书异常)
- 第二步:系统校验通过后,再执行本地钉扎证书的匹配校验,这一步失败说明服务端证书已经合法更新,但本地App内置的钉扎证书过期,判定为钉扎证书过期(App版本过旧需更新)
自定义信任评估器代码
import Alamofire import Security enum CustomTrustError: Error { case certificateUntrusted // 证书本身不可信,对应MITM/服务端异常 case pinnedCertificateExpired // 钉扎证书过期,对应App需更新 } class CustomPinnedCertEvaluator: ServerTrustEvaluating { private let pinnedCertificates: [SecCertificate] private let performDefaultValidation: Bool init(pinnedCertificates: [SecCertificate], performDefaultValidation: Bool = true) { self.pinnedCertificates = pinnedCertificates self.performDefaultValidation = performDefaultValidation } func evaluate(_ trust: SecTrust, forHost host: String) throws { // 第一步:执行系统默认信任校验,判断证书本身是否合法 if performDefaultValidation { let policy = SecPolicyCreateSSL(true, host as CFString) SecTrustSetPolicies(trust, policy) var error: CFError? let isSystemTrusted = SecTrustEvaluateWithError(trust, &error) guard isSystemTrusted else { throw CustomTrustError.certificateUntrusted } } // 第二步:校验服务端证书是否和本地钉扎证书匹配 let serverCertificates = (0..<SecTrustGetCertificateCount(trust)).compactMap { index in SecTrustGetCertificateAtIndex(trust, index) } let pinMatch = serverCertificates.contains { serverCert in pinnedCertificates.contains { pinnedCert in SecCertificateIsEqual(serverCert, pinnedCert) } } guard pinMatch else { throw CustomTrustError.pinnedCertificateExpired } } }
评估器使用配置
初始化Alamofire Session的时候替换默认的评估器即可:
// 加载本地内置的钉扎证书(cer/crt格式都支持) let pinnedCerts = Bundle.main.certificates(withExtensions: ["cer", "crt"]) let evaluator = CustomPinnedCertEvaluator(pinnedCertificates: pinnedCerts) let session = Session( serverTrustManager: ServerTrustManager( evaluators: ["你的业务域名.com": evaluator] ) )
错误捕获与提示逻辑
请求失败时解析错误类型给出对应提示:
session.request("你的接口地址").response { response in switch response.result { case .success(let data): // 正常处理业务逻辑 break case .failure(let error): if let customError = error.underlyingError as? CustomTrustError { switch customError { case .certificateUntrusted: // 提示当前网络存在风险,建议切换正规网络 print("当前网络环境不安全,疑似存在中间人攻击") case .pinnedCertificateExpired: // 提示当前App版本过旧,需要更新到最新版本 print("当前App版本已不支持服务端安全校验,请更新到最新版本") } } else { // 处理超时、无网络等普通网络错误 } } }
内容的提问来源于stack exchange,提问作者Fernando
相关产品推荐
相关产品推荐

