Skip to content
12 changes: 12 additions & 0 deletions android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ class RNFetchBlobConfig {
public Boolean followRedirect = true;
public ReadableArray binaryContentTypes = null;

public Boolean nqmTrustSystem;
public ReadableArray nqmCACertificate;
public String nqmClientCertificate;
public String nqmClientCertificatePassword;

RNFetchBlobConfig(ReadableMap options) {
if(options == null)
return;
Expand Down Expand Up @@ -46,6 +51,13 @@ class RNFetchBlobConfig {
if(options.hasKey("timeout")) {
this.timeout = options.getInt("timeout");
}

// Default to not using OS trust store.
this.nqmTrustSystem = options.hasKey("nqmTrustSystem") ? options.getBoolean("nqmTrustSystem") : false;
// Cache the passed certificate data.
this.nqmCACertificate = options.hasKey("nqmCACertificate") ? options.getArray("nqmCACertificate") : null;
this.nqmClientCertificate = options.hasKey("nqmClientCertificate") ? options.getString("nqmClientCertificate") : null;
this.nqmClientCertificatePassword = options.hasKey("nqmClientCertificatePassword") ? options.getString("nqmClientCertificatePassword") : null;
}

}
6 changes: 4 additions & 2 deletions android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,10 @@ else if(this.options.fileCache)
OkHttpClient.Builder clientBuilder;

try {
// use trusty SSL socket
if (this.options.trusty) {
if (this.options.nqmClientCertificate != null) {
clientBuilder = RNFetchBlobUtils.getClientCertCAOkHttpClient(this.options.nqmTrustSystem, this.options.nqmCACertificate, this.options.nqmClientCertificate, this.options.nqmClientCertificatePassword, client);
} else if (this.options.trusty) {
// use trusty SSL socket
clientBuilder = RNFetchBlobUtils.getUnsafeOkHttpClient(client);
} else {
clientBuilder = client.newBuilder();
Expand Down
95 changes: 95 additions & 0 deletions android/src/main/java/com/RNFetchBlob/RNFetchBlobTrustManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.RNFetchBlob;

import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.*;
import java.util.Arrays;
import java.util.List;

/**
* A custom X509TrustManager implementation that trusts a specified server certificate in addition
* to those that are in the system TrustStore.
*/
public class RNFetchBlobTrustManager implements X509TrustManager {

private final X509TrustManager originalX509TrustManager;
private Boolean trustSystem;
private final KeyStore trustStore;

/**
* @param trustStore A KeyStore containing the server certificate that should be trusted
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
*/
public RNFetchBlobTrustManager(boolean trustSystem, KeyStore trustStore) throws NoSuchAlgorithmException, KeyStoreException {
this.trustSystem = trustSystem;
this.trustStore = trustStore;

TrustManagerFactory originalTrustManagerFactory = TrustManagerFactory.getInstance("X509");
originalTrustManagerFactory.init((KeyStore) null);

TrustManager[] originalTrustManagers = originalTrustManagerFactory.getTrustManagers();
this.originalX509TrustManager = (X509TrustManager) originalTrustManagers[0];
}

/**
* No-op. Never invoked by client, only used in server-side implementations
* @return
*/
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}

/**
* No-op. Never invoked by client, only used in server-side implementations
* @return
*/
public void checkClientTrusted(X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {
}

private void doCustomCheck(X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {
try {
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
CertificateFactory factory = CertificateFactory.getInstance("X509");
CertPath certPath = factory.generateCertPath(Arrays.asList(chain));
PKIXParameters params = new PKIXParameters(this.trustStore);
params.setRevocationEnabled(false);
validator.validate(certPath, params);
} catch(Exception ex) {
throw new java.security.cert.CertificateException(ex.getMessage());
}
}

/**
* Given the partial or complete certificate chain provided by the peer,
* build a certificate path to a trusted root and return if it can be validated and is trusted
* for client SSL authentication based on the authentication type. The authentication type is
* determined by the actual certificate used. For instance, if RSAPublicKey is used, the authType should be "RSA".
* Checking is case-sensitive.
* If `trustSystem` is set, defers to the default trust manager first, checks the cert supplied in the ctor if
* that fails.
* @param chain the server's certificate chain
* @param authType the authentication type based on the client certificate
* @throws java.security.cert.CertificateException
*/
public void checkServerTrusted(X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {
if (this.trustSystem) {
try {
this.originalX509TrustManager.checkServerTrusted(chain, authType);
} catch(CertificateException originalException) {
try {
this.doCustomCheck(chain, authType);
} catch(Exception ex) {
throw originalException;
}
}
} else {
this.doCustomCheck(chain, authType);
}
}
}
137 changes: 137 additions & 0 deletions android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
package com.RNFetchBlob;

import android.content.res.AssetManager;
import android.content.Context;
import android.util.Log;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.io.*;

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 javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;

import android.util.Base64;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import okhttp3.OkHttpClient;

import com.facebook.react.bridge.ReadableArray;

public class RNFetchBlobUtils {

Expand Down Expand Up @@ -93,4 +106,128 @@ public boolean verify(String hostname, SSLSession session) {
throw new RuntimeException(e);
}
}

/**
* Produces a KeyStore from a String containing a PEM certificate (typically, the server's CA certificate)
* @param certificateString A String containing the PEM-encoded certificate
* @return a KeyStore (to be used as a trust store) that contains the certificate
* @throws Exception
*/
private static KeyStore loadPEMTrustStore(ReadableArray caCertificates) throws Exception {

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null);

CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

for(int i = 0; i< caCertificates.size();i++) {
String certificateString = caCertificates.getString(i);
byte[] der = loadPemCertificate(new ByteArrayInputStream(certificateString.getBytes()));
ByteArrayInputStream derInputStream = new ByteArrayInputStream(der);
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
String alias = cert.getSubjectX500Principal().getName();

trustStore.setCertificateEntry(alias, cert);
}

return trustStore;
}

/**
* Reads and decodes a base-64 encoded DER certificate (a .pem certificate), typically the server's CA cert.
* @param certificateStream an InputStream from which to read the cert
* @return a byte[] containing the decoded certificate
* @throws IOException
*/
private static byte[] loadPemCertificate(InputStream certificateStream) throws IOException {

byte[] der = null;
BufferedReader br = null;

try {
StringBuilder buf = new StringBuilder();
br = new BufferedReader(new InputStreamReader(certificateStream));

String line = br.readLine();
while(line != null) {
if(!line.startsWith("--")){
buf.append(line);
}
line = br.readLine();
}

String pem = buf.toString();
der = Base64.decode(pem, Base64.DEFAULT);

} finally {
if(br != null) {
br.close();
}
}

return der;
}

/**
* Produces a KeyStore from a PKCS12 (.p12) certificate file, typically the client certificate
* @param p12Base64 The base64-encoded p12 file.
* @param clientCertPassword Password for the certificate
* @return A KeyStore containing the certificate from the certificateFile
* @throws Exception
*/
private static KeyStore loadPKCS12KeyStore(String p12Base64, String clientCertPassword) throws Exception {
KeyStore keyStore = null;
byte[] p12Decoded = Base64.decode(p12Base64, Base64.DEFAULT);
InputStream fis = new ByteArrayInputStream(p12Decoded);
try {
keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(fis, clientCertPassword.toCharArray());
} finally {
try {
if(fis != null) {
fis.close();
}
} catch(IOException ex) {
// ignore
}
}
return keyStore;
}

public static OkHttpClient.Builder getClientCertCAOkHttpClient(boolean trustSystem, ReadableArray caCertificates, String p12Base64ClientCertificate, String clientCertificatePassword, OkHttpClient client) {
try {
Log.i("TOBY", "in getClientCertCAOkHttpClient");

// Create a trust store from the CA certificates.
KeyStore trustStore = loadPEMTrustStore(caCertificates);
TrustManager[] trustManagers = {new RNFetchBlobTrustManager(trustSystem, trustStore)};

// Load the client certificate.
KeyStore keyStore = loadPKCS12KeyStore(p12Base64ClientCertificate, clientCertificatePassword);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, clientCertificatePassword.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();

// Create a context using the custom key and trust managers.
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);

// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

OkHttpClient.Builder builder = client.newBuilder();
builder.sslSocketFactory(sslSocketFactory);

// builder.hostnameVerifier(new HostnameVerifier() {
// @Override
// public boolean verify(String hostname, SSLSession session) {
// return true;
// }
// });

return builder;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}