Skip to content

Intermediate ca #460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
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);
}
}
}