返回

iOS 7下NSURLSession自签名SSL证书挑战与解决方案

IOS

iOS 7 下 NSURLSession 接受自签名 SSL 证书的挑战与解决

在移动应用开发中,与服务器建立安全可靠的通信至关重要。SSL/TLS 证书扮演着保障数据传输安全的关键角色。有时,出于测试或其他特殊需求,开发者会使用自签名 SSL 证书。 然而,与权威机构签发的证书不同,自签名证书默认不被系统信任。如何在 iOS 7 系统中使用 NSURLSession 接受自签名证书,是一个常见的技术挑战。 本文将深入探讨这个问题,分析其原因,并提供多种解决方案。

问题分析:iOS 7 与 iOS 8 的差异

iOS 7 与 iOS 8 在处理 SSL 证书验证的逻辑上存在细微差异,这直接导致了代码在 iOS 8 下正常工作,而在 iOS 7 下失效。问题核心在于 NSURLAuthenticationChallenge 的处理方式。 iOS 8 相对宽松,允许开发者通过 NSURLCredential(forTrust:) 直接使用服务器提供的信任对象。 但 iOS 7 在安全性方面更为严格, 需要开发者显式地验证证书链,并手动决定是否信任该证书。 错误 kCFStreamErrorDomainSSL, -9813 通常意味着 SSL 握手失败,其中一个主要原因就是证书验证不通过。

解决方案一: 禁用 SSL 证书验证 (不推荐)

最直接,但风险最高 的解决方案是完全禁用 SSL 证书验证。 这种方法仅适用于测试环境 ,在生产环境中应绝对避免。 它的原理是绕过系统对服务器证书的所有检查。

代码示例(Swift):

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
}

操作步骤:

  1. 在 URLSessionDelegate 中实现 urlSession(_:didReceive:completionHandler:) 方法。
  2. 直接使用服务器提供的 trust 对象创建凭据,并指示 URLSession 使用该凭据。

安全风险提示: 此方法完全放弃了安全性验证,使应用暴露于中间人攻击的风险中,数据传输将毫无安全保障。

解决方案二: 固定证书 (Pinning Certificate)

固定证书是一种更为安全的策略。 它的核心思想是将服务器证书的公钥或证书本身固定到应用中,在 SSL 握手过程中与服务器证书进行比对,从而防止中间人攻击。

代码示例 (Swift):

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard let serverTrust = challenge.protectionSpace.serverTrust else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    let certificateData: NSData = SecCertificateCopyData(SecTrustGetCertificateAtIndex(serverTrust, 0)!) // 获取服务器证书数据
    let localCertificatePath = Bundle.main.path(forResource: "my_certificate", ofType: "cer") // 获取本地证书路径
    guard let localCertificateData = NSData(contentsOfFile: localCertificatePath!),
          certificateData.isEqual(to: localCertificateData as Data) else {
        completionHandler(.cancelAuthenticationChallenge, nil) // 证书不匹配,取消验证
        return
    }

    completionHandler(.useCredential, URLCredential(trust: serverTrust)) // 证书匹配,使用服务器提供的信任
}

操作步骤:

  1. 导出服务器自签名证书 ( .cer 文件), 并将其添加到 Xcode 项目中。
  2. urlSession(_:didReceive:completionHandler:) 方法中:
    • 获取服务器证书数据。
    • 加载本地证书数据。
    • 比较两者是否一致,一致则信任,否则取消验证。

额外安全建议:

  • 固定证书链而非单个证书,增加安全性。
  • 定期更新应用内固定的证书,以应对证书过期或密钥泄露的情况。

解决方案三: 评估服务器信任 (Evaluate Server Trust)

更精细的控制方法是手动评估服务器的信任链。 iOS 提供了 Security 框架,允许开发者获取服务器证书信息,并进行自定义的验证逻辑。

代码示例 (Swift):

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard let serverTrust = challenge.protectionSpace.serverTrust else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    let policy = SecPolicyCreateBasicX509()
    SecTrustSetPolicies(serverTrust, policy)

    var error: CFError?
    let isTrusted = SecTrustEvaluateWithError(serverTrust, &error)

    if isTrusted {
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
    } else {
        if let error = error {
                print("证书验证失败: \(error)") // 打印详细错误信息
            }
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

操作步骤:

  1. 获取 challenge.protectionSpace.serverTrust
  2. 创建基本的 X509 验证策略。
  3. 使用 SecTrustEvaluateWithError 评估信任链。
  4. 根据评估结果决定是否信任。

安全建议:

  • 仔细检查证书的有效期、域名是否匹配等信息,确保其合法性。
  • 可以进一步检查证书链是否完整,是否由可信的根证书颁发机构签发。

选择合适的方案

选择哪种方案取决于具体的应用场景和安全需求。 如果仅仅是开发测试环境,可以临时采用方案一。 但为了保证生产环境的安全,强烈建议使用方案二或方案三。固定证书(方案二)相对简单易行, 而评估服务器信任(方案三)提供了更大的灵活性和控制力,允许开发者实现更复杂的验证逻辑。

结语

处理自签名 SSL 证书需要对安全性和风险有充分的认识。 iOS 7 对证书验证的严格要求体现了对安全性的重视。 开发者应该根据实际需求选择合适的解决方案,并在确保安全的前提下完成网络通信。 记住,安全性是应用开发中不可或缺的一环, 任何疏忽都可能导致严重后果。

相关资源

希望以上信息能帮助你解决在 iOS 7 下使用 NSURLSession 接受自签名 SSL 证书的问题。 代码示例仅供参考, 实际应用中需要根据具体情况进行调整。