标题:React Native Expo 应用程序在 iPhone 上的 SSL 评估失败
身体:
我正在开发一个 React Native Expo 应用程序,需要在没有互联网连接的情况下使用本地 WiFi 配置设备。在实现以下
network_security_config.xml
后,该应用程序通过原始 fetch 在 Android 上运行良好:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config>
<trust-anchors>
<certificates src="user" />
<certificates src="system" />
</trust-anchors>
</base-config>
<domain-config>
<domain includeSubdomains="true">192.168.x.x</domain>
<trust-anchors>
<certificates src="@raw/mycert"
tools:ignore="NetworkSecurityConfig" />
</trust-anchors>
</domain-config>
</network-security-config>
但是,我在使用 iOS 时遇到了问题。我尝试过使用
react-native-ssl-pinning
库和 rn-fetch-blob
(带或不带 cert.pem
文件),但 TLS 检查总是失败。
此外,我尝试编写Swift代码来绕过SSL检查并使用证书,但也失败了。
这是我使用
openssl
收到的错误消息:
SSL handshake has read 601 bytes and written 465 bytes
Verification error: EE certificate key too weak
证书是512位,看起来很弱并且是自签名的。我发现苹果需要2048位证书。
问题:
预先感谢您的任何建议或指导!
应用程序需要向本地服务器发出网络请求(https://192.168.x.x/...)。我已将 Info.plist 配置为允许任意加载,并尝试为本地 IP 添加例外域。这是每个地址的当前配置:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
这是我出于开发目的而忽略弱证书的最后一次尝试。稍后,我们可能可以升级已配置设备上的证书,但现在,我需要建立到 IP 地址的连接并忽略弱证书。
import Foundation
@objc(RNSecureRequest)
class RNSecureRequest: NSObject {
@objc(performDebugRequest:reject:)
func performDebugRequest(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
guard let url = URL(string: "https://192.168.x.x/init/...data") else {
reject("Invalid URL", "The URL provided is invalid", nil)
return
}
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
let task = session.dataTask(with: url) { data, response, error in
if let error = error {
reject("Request Error", error.localizedDescription, error)
return
}
guard let data = data else {
reject("No Data", "No data received from the server", nil)
return
}
let responseString = String(data: data, encoding: .utf8)
resolve(responseString)
}
task.resume()
}
}
extension RNSecureRequest: URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if let serverTrust = challenge.protectionSpace.serverTrust {
// Create a policy to allow the connection to proceed despite the weak certificate.
let policy = SecPolicyCreateSSL(true, challenge.protectionSpace.host as CFString)
SecTrustSetPolicies(serverTrust, policy)
// Evaluate the trust object using the modern API.
var error: CFError?
let isServerTrusted = SecTrustEvaluateWithError(serverTrust, &error)
if isServerTrusted {
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
} else {
// Even if the evaluation fails, you can bypass it if needed.
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
}
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}
所有这些配置都会导致以下错误:
Connection 15: default TLS Trust evaluation failed(-9807)
Connection 15: TLS Trust encountered error 3:-9807
Connection 15: encountered error(3:-9807)
Task <F30FCF7F-8C61-42D7-8F64-DF19C7D426DF>.<4> HTTP load failed, 0/0 bytes (error code: -1202 [3:-9807])
Task <F30FCF7F-8C61-42D7-8F64-DF19C7D426DF>.<4> finished with error [-1202] Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.x.x” which could put your confidential information at risk." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
"<cert(0x14703a800) s: DeviceDrive i: DeviceDrive>"
), NSErrorClientCertificateStateKey=0, NSErrorFailingURLKey=https://192.168.x.x/init?ssid=&pwd=&token=url=data, NSErrorFailingURLStringKey=https://192.168.x.x/init?ssid=&pwd=&token=url=data, NSUnderlyingError=0x30118c5d0 {Error Domain=kCFErrorDomainCFNetwork Code=-1202 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x302ec0dc0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9807, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9807, kCFStreamPropertySSLPeerCertificates=(
"<cert(0x14703a800) s: DeviceDrive i: DeviceDrive>"
)}}, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <F30FCF7F-8C61-42D7-8F64-DF19C7D426DF>.<4>"
), _kCFStreamErrorCodeKey=-9807, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <F30FCF7F-8C61-42D7-8F64-DF19C7D426DF>.<4>, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x302ec0dc0>, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.x.x” which could put your confidential information at risk.}
如何解决此证书问题并确保我的应用程序可以与本地服务器通信?是否有更好的方法来处理 SSL 固定或信任评估? React Native 解决方案可能是最好的,所以我根本不需要管理本机代码。
任何帮助或指导将不胜感激。
解决方案:
我注意到我的 urlSession 方法在我自己的函数文件中时从未被调用,因此我开始调查问题发生的位置。
为了解决此问题,我向
urlSession(_:didReceive:completionHandler:)
文件中的 URLSessionSessionDelegateProxy
类添加了自定义 ExpoRequestInterceptorProtocol.swift
函数。这确保应用程序可以正确处理服务器信任挑战。
这是我添加的代码:
// MARK: - Custom URL Session Delegate Function
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if let serverTrust = challenge.protectionSpace.serverTrust {
let credential = URLCredential(trust: serverTrust)
print("Accepting server trust in URLSessionSessionDelegateProxy")
completionHandler(.useCredential, credential)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
地点:
我在
node_modules/expo-modules-core/ios/DevTools/ExpoRequestInterceptorProtocol.swift
中的以下代码块之后添加了此功能:
/private class URLSessionSessionDelegateProxy: NSObject, URLSessionDataDelegate {
private var requestIdProvider = RequestIdProvider()
private var delegateMap: [String: URLSessionDataDelegate] = [:]
private let dispatchQueue = ExpoRequestCdpInterceptor.shared.dispatchQueue
结果:
通过此更改,该应用程序现在可以正确处理服务器信任挑战,使其能够毫无问题地连接到具有自签名证书的服务器。它还恢复了原始 React Native fetch 方法的功能。
问题:
有谁知道如何在不修改Expo代码的情况下处理证书?或者这有可能吗?我的下一个任务是添加证书并指定此功能应生效的 IP 地址。