Swift 4基于Network库实现TCP连接升级TLS/SSL的方案问询
解决方案:基于NWConnection实现先TCP后动态TLS升级
针对你需要先建立纯TCP连接、收到服务器"TLS"触发消息后再启动TLS握手并验证自定义CA证书的场景,以下是可行的实现方案:
核心思路
NWConnection默认不支持动态修改已建立连接的协议参数,因此我们需要分两步实现:
- 先用BSD套接字建立纯TCP连接,完成初始消息交互
- 将已连接的BSD套接字转交给NWConnection,同时配置TLS选项启动握手
完整实现代码
1. 封装TLS升级逻辑类
import Foundation import Network class TLSConnectionManager { private var tcpSocket: Int32 = -1 private var tlsConnection: NWConnection? // 建立初始纯TCP连接 func connect(toHost host: String, port: UInt16) { // 创建BSD TCP套接字 tcpSocket = socket(AF_INET, SOCK_STREAM, 0) guard tcpSocket != -1 else { print("创建TCP套接字失败: \(errno)") return } // 解析服务器地址 var serverAddr = sockaddr_in() serverAddr.sin_family = sa_family_t(AF_INET) serverAddr.sin_port = port.bigEndian guard inet_pton(AF_INET, host, &serverAddr.sin_addr) == 1 else { print("解析服务器地址失败") close(tcpSocket) return } // 发起TCP连接 let addrLen = socklen_t(MemoryLayout<sockaddr_in>.size) guard connect(tcpSocket, sockaddr_cast(&serverAddr), addrLen) == 0 else { print("TCP连接失败: \(errno)") close(tcpSocket) return } // 开始接收服务器消息 startTCPMessageReceiving() } // 接收TCP消息,等待TLS触发指令 private func startTCPMessageReceiving() { DispatchQueue.global().async { [weak self] in guard let self = self else { return } var buffer = [UInt8](repeating: 0, count: 4096) while true { let bytesRead = recv(self.tcpSocket, &buffer, buffer.count, 0) guard bytesRead > 0 else { print("TCP接收中断或失败: \(errno)") close(self.tcpSocket) return } let receivedMessage = String(bytes: buffer[0..<bytesRead], encoding: .utf8) ?? "" if receivedMessage.contains("TLS") { // 收到触发指令,启动TLS升级 self.upgradeToTLS() break } else { // 处理其他TCP消息 print("收到TCP消息: \(receivedMessage)") } } } } // 升级到TLS连接 private func upgradeToTLS() { guard let tlsOptions = createCustomTLSOptions() else { close(tcpSocket) return } // 基于已有的TCP套接字创建带TLS的NWConnection let tlsParams = NWParameters(tls: tlsOptions, tcp: .init()) tlsConnection = NWConnection(from: tcpSocket, using: tlsParams) // 监听TLS连接状态 tlsConnection?.stateUpdateHandler = { [weak self] state in switch state { case .ready: print("TLS握手成功,已切换到加密通信") self?.startTLSMessageReceiving() case .failed(let error): print("TLS握手失败: \(error.localizedDescription)") self?.cleanup() default: break } } tlsConnection?.start(queue: .global(qos: .background)) } // 配置自定义TLS选项(加载CA证书+自定义验证逻辑) private func createCustomTLSOptions() -> NWProtocolTLS.Options? { let tlsOptions = NWProtocolTLS.Options() // 设置TLS版本范围 sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12) sec_protocol_options_set_max_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv13) // 加载本地CA证书(支持.der/.cer格式) guard let caCertURL = Bundle.main.url(forResource: "enterprise_der", withExtension: "cer"), let caCertData = try? Data(contentsOf: caCertURL), let caCert = SecCertificateCreateWithData(kCFAllocatorDefault, caCertData as CFData) else { print("加载CA证书失败") return nil } // 设置自定义证书验证逻辑 sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { metadata, trust, completion in // 配置仅信任指定CA证书 let trustPolicy = SecPolicyCreateBasicX509() var trustRef = trust SecTrustSetAnchorCertificates(trustRef, [caCert] as CFArray) SecTrustSetAnchorCertificatesOnly(trustRef, true) // 执行信任评估 var trustResult = SecTrustResultType.invalid let evaluateStatus = SecTrustEvaluate(trustRef, &trustResult) // 返回验证结果 completion(evaluateStatus == errSecSuccess && (trustResult == .unspecified || trustResult == .proceed)) }, .global(qos: .background)) return tlsOptions } // 接收TLS加密消息 private func startTLSMessageReceiving() { tlsConnection?.receive(minimumIncompleteLength: 1, maximumLength: 4096) { [weak self] data, context, isComplete, error in if let data = data, let message = String(data: data, encoding: .utf8) { print("收到TLS加密消息: \(message)") self?.startTLSMessageReceiving() } else if let error = error { print("TLS接收失败: \(error.localizedDescription)") self?.cleanup() } } } // 资源清理 private func cleanup() { if tcpSocket != -1 { close(tcpSocket) } tlsConnection?.cancel() } deinit { cleanup() } } // 辅助类型转换函数 private func sockaddr_cast(_ ptr: UnsafeMutablePointer<sockaddr_in>) -> UnsafeMutablePointer<sockaddr> { return UnsafeMutableRawPointer(ptr).assumingMemoryBound(to: sockaddr.self) }
2. 使用方式
// 初始化管理器并发起连接 let connectionManager = TLSConnectionManager() connectionManager.connect(toHost: "X.X.X.X", port: YYYY)
关键注意事项
- 避免初始TLS参数:不要一开始就给NWConnection传入TLS参数,否则会直接触发TLS握手,与服务器要求的"先TCP后TLS"流程冲突
- CA证书无需导入钥匙串:直接在代码中加载证书并配置自定义验证逻辑,既可控又不会污染用户系统的信任链
- 强制仅信任指定CA:设置
SecTrustSetAnchorCertificatesOnly(true),确保只验证你提供的CA证书,避免安全风险 - 队列选择:网络操作尽量放在后台队列,避免阻塞主线程
内容的提问来源于stack exchange,提问作者Khang
相关产品推荐
相关产品推荐

