Title: SSL Evaluation Fails on iPhone for React Native Expo App
Body:
I am developing a React Native Expo app that needs to configure a device with local WiFi without internet connection. The app works fine on Android by original fetch after implementing the following network_security_config.xml
:
<?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>
However, I am facing issues with iOS. I have tried using the react-native-ssl-pinning
library and rn-fetch-blob
both with and without my cert.pem
file, but the TLS check always fails.
Additionally, I attempted to write Swift code to bypass the SSL check and use the certificate, but it also fails.
Here is the error message I received using openssl
:
SSL handshake has read 601 bytes and written 465 bytes
Verification error: EE certificate key too weak
The certificate is 512 bits, which seems to be weak and self-signed. I found that apple needs 2048bit certificate.
Questions:
- Is the weak certificate the primary reason for the TLS failure on iOS?
- How can I use this certificate or bypass this issue temporarily while developing?
- Are there any best practices for handling such situations in React Native Expo for iOS?
Thank you in advance for any suggestions or guidance!
iOS Network Configuration Issue: Certificate Invalid Error [-1202]
The application requires making network requests to a local server (https://192.168.x.x/…). I have configured my Info.plist to allow arbitrary loads and have tried adding an exception domain for the local IP. Here is the current configuration for every address:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Swift Code
This is my last attempt to ignore a weak certificate for development purposes. Later, we can probably upgrade the certificates on the configured devices, but for now, I need to establish a connection to the IP address and ignore the weak certificate.
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)
}
}
}
Error Encountered
All of these configurations result in the following error:
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.}
Request for Help
How can I resolve this certificate issue and ensure my app can communicate with the local server? Is there a better approach to handle SSL pinning or trust evaluation? React Native solution could be best so I dont need manage native code at all.
Any help or guidance would be greatly appreciated.
2
Answers
Solution:
I noticed that my urlSession method was never called when it was in my own function file, so I started to investigate where the issue was occurring.
To resolve this, I added a custom
urlSession(_:didReceive:completionHandler:)
function to theURLSessionSessionDelegateProxy
class in theExpoRequestInterceptorProtocol.swift
file. This ensures that the app can properly handle server trust challenges.Here’s the code I added:
Location:
I added this function after the following code block in
node_modules/expo-modules-core/ios/DevTools/ExpoRequestInterceptorProtocol.swift
:Outcome:
With this change, the app now correctly handles server trust challenges, allowing it to connect to servers with self-signed certificates without issues. It also restores the functionality of the original React Native fetch method.
Question:
Does anyone know how I could handle certificates without modifying the Expo code? Or is this even possible? My next task is to add a certificate and specify the IP address where this function should take effect.
Issue:
When implementing a custom
URLSessionDelegate
in Expo module, delegate was being overridden by Expo’s default behavior. This resulted in your custom delegate not being utilized during network requests.Solution:
The issue arises because Expo may register its own custom
URLProtocol
classes globally, which can interfere with your customURLSessionDelegate
. To prevent this, you can explicitly clear any globally registered customURLProtocol
classes in yourURLSessionConfiguration
.Updated Code:
Here’s the relevant code snippet with the solution applied:
Explanation:
URLProtocol
classes that override your customURLSessionDelegate
. This results in your delegate not being called as expected.configuration.protocolClasses = []
in yourURLSessionConfiguration
. This clears any globally registered customURLProtocol
classes, ensuring that your delegate is used.By applying this change, you can ensure that your custom
URLSessionDelegate
handles network requests as intended, without interference from Expo’s default behavior.Important Security Note:
The CustomURLSessionDelegate class in this implementation is configured to accept all HTTPS connections, even those with self-signed or invalid certificates. This means that the delegate will trust any server, potentially exposing your application to man-in-the-middle (MITM) attacks.
If you use this code in production, make sure to implement proper security checks, such as validating the server’s certificate against a known certificate authority or pinning the server’s certificate. Accepting all server certificates without validation should be limited to development environments or controlled scenarios where you are fully aware of the security implications.
Including this note will help others understand the security risks involved and encourage them to take appropriate precautions if they intend to use the code in a production environment.