如何在macOS Swift应用中通过NWConnection实现带证书的TLS/SSL握手
基于NWConnection实现TLS加密通信
针对你在macOS应用中通过TCP连接后触发TLS握手的需求,以下是具体实现步骤和代码示例:
1. 准备服务器证书
将服务器的CA证书或自签证书(格式如.cer/.pem)添加到Xcode项目中,确保在Target的Build Phases > Copy Bundle Resources里勾选该证书,保证应用能读取到文件。
2. 加载证书并配置TLS选项
首先实现证书加载逻辑,然后自定义TLS信任评估策略——因为是局域网自签证书,系统默认的信任链验证会失败,所以需要指定只信任我们提供的服务器证书:
// 加载服务器证书 private func loadServerCertificate() -> SecCertificate? { guard let certPath = Bundle.main.path(forResource: "server", ofType: "cer"), let certData = try? Data(contentsOf: URL(fileURLWithPath: certPath)) else { return nil } return SecCertificateCreateWithData(nil, certData as CFData) } // 配置TLS选项 private func configureTLSOptions() -> NWProtocolTLS.Options { let tlsOptions = NWProtocolTLS.Options() guard let serverCert = loadServerCertificate() else { fatalError("无法加载服务器证书,请检查文件路径") } // 设置自定义信任验证逻辑 sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { (_, secTrust, completion) in guard let trust = secTrust else { completion(false) return } var trustResult = SecTrustResultType.invalid // 创建SSL策略,仅验证服务器证书 let policies = [SecPolicyCreateSSL(true, nil)] SecTrustSetPolicies(trust, policies as CFArray) // 将我们的服务器证书设为唯一信任锚点 SecTrustSetAnchorCertificates(trust, [serverCert] as CFArray) SecTrustSetAnchorCertificatesOnly(trust, true) // 执行信任评估 let status = SecTrustEvaluate(trust, &trustResult) completion(status == errSecSuccess && (trustResult == .proceed || trustResult == .unspecified)) }, .global()) return tlsOptions }
3. 修改连接流程:TCP连接后升级TLS
原TCP连接建立后,监听服务器发送的"TLS"触发消息,收到后切换到TLS加密模式:
class ConnectionManager { private var connection: NWConnection? private var isTLSActive = false private let tlsOptions = configureTLSOptions() // 启动TCP连接 func startTCPConnection(host: String, port: UInt16) { guard let port = NWEndpoint.Port(rawValue: port) else { print("端口无效") return } connection = NWConnection(host: NWEndpoint.Host(host), port: port, using: .tcp) connection?.stateUpdateHandler = { [weak self] state in switch state { case .ready: print("TCP连接成功,等待TLS触发消息") self?.startReceivingData() case .failed(let error): print("TCP连接失败: \(error.localizedDescription)") case .cancelled: print("连接已取消") default: break } } connection?.start(queue: .global()) } // 监听数据接收 private func startReceivingData() { connection?.receive(minimumIncompleteLength: 1, maximumLength: 1024) { [weak self] data, _, isComplete, error in guard let self = self else { return } if let error = error { print("接收数据错误: \(error.localizedDescription)") return } if let data = data, !data.isEmpty { guard let message = String(data: data, encoding: .utf8) else { print("无法解析收到的消息") return } if !self.isTLSActive && message.contains("TLS") { // 触发TLS握手,切换到加密连接 self.switchToTLSConnection() } else if self.isTLSActive { // 处理加密后的业务数据 self.processEncryptedData(data: data) } } if !isComplete { self.startReceivingData() } } } // 切换到TLS加密连接 private func switchToTLSConnection() { guard let connection = connection else { return } let tlsProtocol = NWProtocolTLS.definition(options: tlsOptions) // 应用协议变更,发起TLS握手 connection.applyChanges { changes in changes.renegotiate(tlsProtocol) } completionHandler: { [weak self] error in if let error = error { print("TLS握手失败: \(error.localizedDescription)") } else { print("TLS握手成功,已进入加密通信模式") self?.isTLSActive = true // 继续监听加密数据 self?.startReceivingData() } } } // 处理加密业务数据 private func processEncryptedData(data: Data) { print("收到加密数据: \(data)") // 示例:发送加密响应 let responseData = "加密响应内容".data(using: .utf8)! self.sendEncryptedData(data: responseData) } // 发送加密数据 private func sendEncryptedData(data: Data) { guard isTLSActive, let connection = connection else { return } connection.send(content: data, completion: .contentProcessed { error in if let error = error { print("发送加密数据失败: \(error.localizedDescription)") } else { print("加密数据发送成功") } }) } }
4. 使用方式
在需要连接的地方调用:
let manager = ConnectionManager() manager.startTCPConnection(host: "10.1.xxx.xx", port: 1515)
关键说明
- 信任评估:自定义验证逻辑确保只信任指定的服务器证书,避免局域网自签证书被系统拒绝
- 协议切换:通过
applyChanges和renegotiate方法实现TCP到TLS的无缝升级,无需重新建立连接 - 状态区分:用
isTLSActive标记加密状态,确保触发消息和业务数据被正确处理
内容的提问来源于stack exchange,提问作者Luu Minh
相关产品推荐
相关产品推荐

