iOS客户端连接本地Mosquitto MQTT Broker时出现TLS版本错误
问题:iOS客户端通过TLS连接Mosquitto MQTT Broker遇SSL版本错误及连接重置
问题现象
客户端日志报错:
[connection] nw_socket_handle_socket_event [C1.1.1.1:1] Socket SO_ERROR [54: Connection reset by peer]
Broker日志报错:
OpenSSL Error[0]: error:1408F10B:SSL routines:ssl3_get_record:wrong version number
Clientdisconnected: Protocol error.
配置信息
Mosquitto配置(mosquitto.conf)
... listener 8883 protocol mqtt allow_anonymous true require_certificate true cafile /opt/homebrew/etc/mosquitto/certs/ca.crt certfile /opt/homebrew/etc/mosquitto/certs/server.crt keyfile /opt/homebrew/etc/mosquitto/certs/server.key ...
证书创建步骤
openssl genrsa -out ca.key 2048 openssl req -new -x509 -key ca.key -out ca.crt (CN=CA) openssl genrsa -out server.key 2048 openssl req -new -sha256 -out server.csr -key server.key (CN=localhost) openssl x509 -req -sha256 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt openssl genrsa -out client.key 2048 openssl req -new -sha256 -out client.csr -key client.key (CN=localhost) openssl x509 -req -sha256 -days 365 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca.crt openssl x509 -inform pem -in ca.crt -outform der -out root.der
iOS客户端代码
证书解析代码
public static func importIdentities(from url: URL, password: String) throws -> [SecIdentity] { let p12Data = try Data(contentsOf: url) let options: [String: String] = [kSecImportExportPassphrase as String: password] var rawItems: CFArray? let result = DispatchQueue.global().sync { SecPKCS12Import(p12Data as CFData, options as CFDictionary, &rawItems) } guard result == errSecSuccess else { if result == errSecAuthFailed { throw Error.invalidPassword } else { throw Error.invalidData } } guard let items = rawItems as? [[CFString: SecIdentity]] else { throw Error.invalidData } return items.compactMap({ $0[kSecImportItemIdentity] }) } public static func importCertificate(from url: URL) throws -> SecCertificate { let certificateData = try Data(contentsOf: url) as CFData guard let certificate = SecCertificateCreateWithData(nil, certificateData) else { throw Error.invalidData } return certificate }
TLS配置代码
func getNWProtocolTLSOptions() -> NWProtocolTLS.Options { let options = NWProtocolTLS.Options() sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, minimumTLSVersion) if let maximumTLSVersion = maximumTLSVersion { sec_protocol_options_set_max_tls_protocol_version(options.securityProtocolOptions, maximumTLSVersion) } if let clientIdentity = clientIdentity, let secClientIdentity = sec_identity_create(clientIdentity) { sec_protocol_options_set_local_identity(options.securityProtocolOptions, secClientIdentity) } securityProtocols.forEach { sec_protocol_options_add_tls_application_protocol(options.securityProtocolOptions, $0) } if trustRootCertificates != nil { sec_protocol_options_set_verify_block(options.securityProtocolOptions, { _, sec_trust, sec_protocol_verify_complete in let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue() if let trustRootCertificates = trustRootCertificates { SecTrustSetAnchorCertificates(trust, trustRootCertificates as CFArray) } SecTrustEvaluateAsyncWithError(trust, queue) { _, result, error in if let error = error { log.error("Trust failed: \(error.localizedDescription)") } sec_protocol_verify_complete(result) } }, queue) } return options }
可能的原因及排查方向
- TLS版本不匹配:检查客户端
minimumTLSVersion和maximumTLSVersion设置,Mosquitto 2.0.15默认支持TLSv1.2及以上版本。如果客户端设置了过低(如SSLv3)或过高版本,会导致握手失败。建议将客户端最小版本设为tls_protocol_version_tls12,最大版本设为tls_protocol_version_tls13(或不限制最大版本)。同时可以在Mosquitto配置的listener 8883下添加tls_version tlsv1.2,强制Broker使用指定TLS版本。 - 应用层协议冗余配置:代码中
securityProtocols添加的协议可能干扰MQTT的TLS握手。MQTT over TLS不需要额外添加应用层协议(如h2),建议注释掉securityProtocols.forEach这段代码后重试。 - 客户端证书未正确发送:Mosquitto配置了
require_certificate true,需确保客户端在TLS握手时正确传入了clientIdentity。检查importIdentities方法是否成功获取到SecIdentity,以及clientIdentity是否正确传递到TLS配置中。 - 信任锚配置问题:确认
trustRootCertificates是否包含了正确的CA证书(root.der对应的SecCertificate),如果CA证书未正确设置,会导致信任验证失败,触发连接断开。
内容的提问来源于stack exchange,提问作者doovers
相关产品推荐
相关产品推荐

