You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 4基于Network库实现TCP连接升级TLS/SSL的方案问询

解决方案:基于NWConnection实现先TCP后动态TLS升级

针对你需要先建立纯TCP连接、收到服务器"TLS"触发消息后再启动TLS握手并验证自定义CA证书的场景,以下是可行的实现方案:

核心思路

NWConnection默认不支持动态修改已建立连接的协议参数,因此我们需要分两步实现:

  1. 先用BSD套接字建立纯TCP连接,完成初始消息交互
  2. 将已连接的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)

关键注意事项

  1. 避免初始TLS参数:不要一开始就给NWConnection传入TLS参数,否则会直接触发TLS握手,与服务器要求的"先TCP后TLS"流程冲突
  2. CA证书无需导入钥匙串:直接在代码中加载证书并配置自定义验证逻辑,既可控又不会污染用户系统的信任链
  3. 强制仅信任指定CA:设置SecTrustSetAnchorCertificatesOnly(true),确保只验证你提供的CA证书,避免安全风险
  4. 队列选择:网络操作尽量放在后台队列,避免阻塞主线程

内容的提问来源于stack exchange,提问作者Khang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 06:32:11