52
votes

I'm trying to communicate with https server having self-signed certificate.

I can do this from .NET application (using ServicePointManager.ServerCertificateValidationCallback event), from native iOs application (using allowsAnyHTTPSCertificateForHost) or from web browser (just need to declare that the certificate is trusted).

But I can't get it to work in react-native application (neither in Android nor in iOS simulator).

I have tried different things but still not succeed.

I know there are some similar topics there:
Ignore errors for self-signed SSL certs using the fetch API in a ReactNative App?
React Native XMLHttpRequest request fails if ssl (https) certificate is not valid
Fetch in react native wont work with ssl on android
Problems fetching data from a SSL based Server
Unable to make API calls using react-native

But they either do not contain answers or do not working (and they do not cover android programming at all). Searching other resources was not productive as well.

I believe there should be an easy way to work with self-signed certificate. Am I wrong? Does anybody know it (both for iOS and Android)?

7
Thank you for a pointing. The solution is not for self-sighed certificate literally, but we will try it.Boris WM
Right - I don't think self-signed certificates are the helpful approach, so in that regard, your question is somewhat an X/Y problem. xyproblem.info I'm not sure there is any way to get self-signed certificates to work with React Native. I think you would need to write some Objective-C to allow it, and that is just putting a nasty bandaid on the problem rather than really solving it.m59

7 Answers

52
votes

Disclaimer: This solution should be temporary and documented so that it won't stay in the production phase of the software, this is for development only.

For iOS, all you have to do is, open your xcodeproject (inside your iOS folder in RN) once you have that open, go to RCTNetwork.xcodeproj and in that project, navigate to RCTHTTPRequestHandler.m

In that file you will see a line like this:

#pragma mark - NSURLSession delegate

right after that line, add this function

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

And voila, you can now make insecure calls to your API without a valid certificate.

That should be enough, but if you are still having problems, you might need to go to your project's info.plist, left click on it and choose open as... source code.

and at the end just add

<key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>
  <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
        <key>subdomain.example.com</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>

so your file will look like this

    ...
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string></string>
  <key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>
  <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
        <key>subdomain.example.com</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>
</plist>

For a real production ready solution, https://stackoverflow.com/a/36368360/5943130 that solution is better

15
votes

I am also faced the same issue in android. Finally i found the solution for this issue.

import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.OkHttpClientProvider;
import com.facebook.react.modules.network.ReactCookieJarContainer;

import java.security.cert.CertificateException;
import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.TimeUnit;


import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;

import static android.content.ContentValues.TAG;

public class CustomClientFactory implements OkHttpClientFactory {
    private static final String TAG = "OkHttpClientFactory";
    @Override
    public OkHttpClient createNewNetworkModuleClient() {
        try {
            // Create a trust manager that does not validate certificate chains
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            // Create an ssl socket factory with our all-trusting manager
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();



            OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    .connectTimeout(0, TimeUnit.MILLISECONDS).readTimeout(0, TimeUnit.MILLISECONDS)
                    .writeTimeout(0, TimeUnit.MILLISECONDS).cookieJar(new ReactCookieJarContainer());
            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
            builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            OkHttpClient okHttpClient = builder.build();
            return okHttpClient;
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            throw new RuntimeException(e);
        }
    }

}

and inside our Android application MainApplication.java

 @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);  
    OkHttpClientProvider.setOkHttpClientFactory(new CustomClientFactory()); //add this line.
  }  

Its work for me. May be it will help to all.

6
votes

I got this working on Android by doing the following:

  1. Install the CA on your device under Settings -> Security & location > Advanced > Encryption & credentials > Install from storage. You can confirm it's installed correctly by visiting the domain through a web browser on your device. If the certificate validates, then the CA is installed.
  2. Create a network security configuration at res/xml/network_security_config.xml with the following contents. Update the domain(s).
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <!-- For React Native Hot-reloading system -->
        <!-- If you are running on a device insert your computer IP -->
        <domain includeSubdomains="true">localhost</domain>
        <domain includeSubdomains="true">your self signed domain</domain>

        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" />
        </trust-anchors>
    </domain-config>

    <base-config cleartextTrafficPermitted="false" />
</network-security-config>
  1. Reference this configuration file from your AndroidManifest.xml.
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config"
                    ... >
        ...
    </application>
</manifest>
5
votes

This is happened due to self signed certificate used for encryption.Due to security reasons in android it demands C A authority signed or trusted certificates

use this plugin to avoid this.

https://www.npmjs.com/package/react-native-fetch-blob

RNFetchBlob.config({
  trusty : true
})
.then('GET', 'https://xxxxx.com')
.then((resp) => {
  // ...
})

Add config trusty as true to trust the certificate when you POST or GET API's

3
votes

Piggybacking of @Santiago Jimenez Wilson's answer here.

Obviously patching react-native itself is pretty dirty so we took the suggested override and extracted it into a category.

Just create a new file called RCTHTTPRequestHandler+yourPatchName.m somewhere in your project:

//
//  RCTHTTPRequestHandler+yourPatchName
//

#import "RCTBridgeModule.h"
#import "RCTHTTPRequestHandler.h"

@implementation RCTHTTPRequestHandler(yourPatchName)

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
@end

Next step would be to then differ between dev and prod and only overload the method for dev.

2
votes

Just to add information for users looking for Android Solution. As react-native do not handle SSL Error by default. There is a simple approach to Run your WebView for the websites that must be connected through "https" instead of "http".

I am assuming you have already installed the react-native-webview module using NPM if no then please google.

Once you have "react-native-webview" module inside "node_modules" folder. Go inside ".\node_modules >> react-native-webview >> android >> src >> main >> java >> com >> reactnativecommunity >> webview"

Open "RNCWebViewManager.java" File in Text Editor and Add Below Code

In import section add these two dependencies

....
import android.webkit.SslErrorHandler;
import android.net.http.SslError;
....

Now Search for Below "class" inside same file protected static class RNCWebViewClient extends WebViewClient

And add this method

@Override
public void onReceivedSslError(WebView webView, SslErrorHandler handler, SslError 
error) {
    if (error.toString() == "piglet")
        handler.cancel();
    else
        handler.proceed(); // Ignore SSL certificate errors
}

Next Save the file and Build your Project. It would not show Blank page now and handle the Invalid SSL Error.

Note:

  1. Solution works only if you are using "WebView" from "react-native-webview" instead of deprecated "WebView" from "react-native"
  2. Make sure you have already linked your "react-native" with "react-native-webview" else it would not be included inside your android project
  3. There might be version Error in "RNCWebViewModule", "RNCWebViewManager", "RNCWebViewFileProvider" classes (will be visible once you properly build your project using react-native run-android inside AndroidStudio after opening your android project using import) that you can easily fix using AndroidStudio
0
votes

Second answer is for android, and it is working fine. so use that for android. for ios actual answer is correct. but its hard to find the RCTNetwork.xcodeproj and also changes will gone if you delete and add the npm modules. so it shard to maintain.

so i have created a javascript for patching. just executing the below script with node js will patch. passing the argument -r will remove the patch.

 * run this script normally for patching,
 * pass -r argument at the end of the command for removing the patch
 * ex: 
 * patching:         $ node patch_ssl_bypass.js
 * removing patch:   $ node patch_ssl_bypass.js -r
 */

var fs = require('fs');

const isRemove = process.argv[process.argv.length-1] == '-r';

const file =
  'node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm';
const delemeter = '#pragma mark - NSURLSession delegate';
const code = `
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
  completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
`;
console.log('#############   Reading File   ###############');
fs.readFile(file, 'utf8', function(error, data) {
  if (error) {
    console.log('#############  error reading file  ###############');
    console.error(error);
    return;
  }
  if (data.indexOf(code) < 0 && !isRemove) {
    console.log('#############  Patch is not done.  ###############');
    console.log('#############  Patching file  ###############');
    var parts = data.split(delemeter);
    var newCodeBlock = parts[0] + delemeter + '\n' + code + '\n'+parts[1];
    fs.writeFile(file,newCodeBlock,function(){
        console.log('#############  Successfully patched file  ###############');
        console.log('#############  re build the ios project  ###############');
    })
  }else{
      if (isRemove){
          var updatedCode = data.replace(code,'');
        fs.writeFile(file,updatedCode,function(){
            console.log('#############  Successfully removed patch  ###############');
            console.log('#############  re build the ios project  ###############');
        })
      }else{
        console.log('#############  File already patched. No need again  ###############');
      }
  }
});