From 7992bd0991003870885b4ab26f1dc01a657307e6 Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Fri, 22 Mar 2019 04:06:50 +0100 Subject: [PATCH] WIP: started re-implementing SSL cert modes --- plugin.xml | 2 + .../silkimen/cordovahttp/CordovaHttpBase.java | 13 +- .../cordovahttp/CordovaHttpDownload.java | 6 +- .../cordovahttp/CordovaHttpOperation.java | 10 +- .../cordovahttp/CordovaHttpPlugin.java | 140 ++++++++++-------- .../cordovahttp/CordovaHttpUpload.java | 6 +- .../silkimen/http/TrustManagersFactory.java | 11 +- 7 files changed, 116 insertions(+), 72 deletions(-) diff --git a/plugin.xml b/plugin.xml index 21b964e..96d0379 100644 --- a/plugin.xml +++ b/plugin.xml @@ -65,6 +65,8 @@ + + diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java b/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java index badba56..87d08d0 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java @@ -8,6 +8,7 @@ import java.net.UnknownHostException; import java.nio.ByteBuffer; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSocketFactory; import com.silkimen.http.HttpBodyDecoder; import com.silkimen.http.HttpRequest; @@ -32,9 +33,10 @@ abstract class CordovaHttpBase implements Runnable { protected JSONObject headers; protected int timeout; protected boolean followRedirects; + protected SSLSocketFactory customSSLSocketFactory; protected CallbackContext callbackContext; - public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout, boolean followRedirects, + public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory, CallbackContext callbackContext) { this.method = method; @@ -44,10 +46,12 @@ abstract class CordovaHttpBase implements Runnable { this.headers = headers; this.timeout = timeout; this.followRedirects = followRedirects; + this.customSSLSocketFactory = customSSLSocketFactory; this.callbackContext = callbackContext; } - public CordovaHttpBase(String method, String url, JSONObject params, JSONObject headers, int timeout, boolean followRedirects, + + public CordovaHttpBase(String method, String url, JSONObject params, JSONObject headers, int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory, CallbackContext callbackContext) { this.method = method; @@ -56,6 +60,7 @@ abstract class CordovaHttpBase implements Runnable { this.headers = headers; this.timeout = timeout; this.followRedirects = followRedirects; + this.customSSLSocketFactory = customSSLSocketFactory; this.callbackContext = callbackContext; } @@ -116,6 +121,10 @@ abstract class CordovaHttpBase implements Runnable { request.acceptCharset("UTF-8"); request.uncompress(true); + if (this.customSSLSocketFactory != null) { + request.setSSLSocketFactory(this.customSSLSocketFactory); + } + // setup content type before applying headers, so user can override it this.setContentType(request); diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java b/src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java index d17face..60c830e 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java @@ -3,6 +3,8 @@ package com.silkimen.cordovahttp; import java.io.File; import java.net.URI; +import javax.net.ssl.SSLSocketFactory; + import com.silkimen.http.HttpRequest; import org.apache.cordova.CallbackContext; @@ -13,9 +15,9 @@ class CordovaHttpDownload extends CordovaHttpBase { private String filePath; public CordovaHttpDownload(String url, JSONObject params, JSONObject headers, String filePath, int timeout, - boolean followRedirects, CallbackContext callbackContext) { + boolean followRedirects, SSLSocketFactory customSSLSocketFactory, CallbackContext callbackContext) { - super("GET", url, params, headers, timeout, followRedirects, callbackContext); + super("GET", url, params, headers, timeout, followRedirects, customSSLSocketFactory, callbackContext); this.filePath = filePath; } diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java b/src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java index 3e9fe82..a40edf1 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java @@ -1,18 +1,20 @@ package com.silkimen.cordovahttp; +import javax.net.ssl.SSLSocketFactory; + import org.apache.cordova.CallbackContext; import org.json.JSONObject; class CordovaHttpOperation extends CordovaHttpBase { public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers, - int timeout, boolean followRedirects, CallbackContext callbackContext) { + int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory, CallbackContext callbackContext) { - super(method, url, serializer, data, headers, timeout, followRedirects, callbackContext); + super(method, url, serializer, data, headers, timeout, followRedirects, customSSLSocketFactory, callbackContext); } public CordovaHttpOperation(String method, String url, JSONObject params, JSONObject headers, int timeout, - boolean followRedirects, CallbackContext callbackContext) { + boolean followRedirects, SSLSocketFactory customSSLSocketFactory, CallbackContext callbackContext) { - super(method, url, params, headers, timeout, followRedirects, callbackContext); + super(method, url, params, headers, timeout, followRedirects, customSSLSocketFactory, callbackContext); } } diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java b/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java index 668ac09..8706181 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java @@ -4,38 +4,50 @@ import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; +import java.security.cert.CertificateFactory; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStore.TrustedCertificateEntry; +import java.security.SecureRandom; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Enumeration; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +import com.silkimen.http.TLSSocketFactory; +import com.silkimen.http.TrustManagersFactory; + import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaWebView; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import android.util.Log; import android.content.res.AssetManager; +import android.util.Log; public class CordovaHttpPlugin extends CordovaPlugin { private static final String TAG = "Cordova-Plugin-HTTP"; - private static boolean followRedirects = true; + private boolean followRedirects = true; + private TrustManagersFactory trustManagersFactory; + private SSLSocketFactory customSSLSocketFactory; @Override public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); + this.trustManagersFactory = new TrustManagersFactory(); + try { - // HttpRequest.clearCerts(); - this.pinSSLCertsFromCAStore(); + this.customSSLSocketFactory = this.createSocketFactory( + this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromKeyStore("AndroidCAStore"))); } catch (Exception e) { Log.e(TAG, "An error occured while loading system's CA certificates", e); } @@ -84,7 +96,7 @@ public class CordovaHttpPlugin extends CordovaPlugin { int timeout = args.getInt(3) * 1000; CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, params, headers, timeout, - followRedirects, callbackContext); + this.followRedirects, this.customSSLSocketFactory, callbackContext); cordova.getThreadPool().execute(request); @@ -101,7 +113,7 @@ public class CordovaHttpPlugin extends CordovaPlugin { int timeout = args.getInt(4) * 1000; CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers, - timeout, followRedirects, callbackContext); + timeout, this.followRedirects, this.customSSLSocketFactory, callbackContext); cordova.getThreadPool().execute(request); @@ -117,7 +129,7 @@ public class CordovaHttpPlugin extends CordovaPlugin { int timeout = args.getInt(5) * 1000; CordovaHttpUpload upload = new CordovaHttpUpload(url, params, headers, filePath, uploadName, timeout, - followRedirects, callbackContext); + this.followRedirects, this.customSSLSocketFactory, callbackContext); cordova.getThreadPool().execute(upload); @@ -131,91 +143,101 @@ public class CordovaHttpPlugin extends CordovaPlugin { String filePath = args.getString(3); int timeout = args.getInt(4) * 1000; - CordovaHttpDownload download = new CordovaHttpDownload(url, params, headers, filePath, timeout, followRedirects, - callbackContext); + CordovaHttpDownload download = new CordovaHttpDownload(url, params, headers, filePath, timeout, + this.followRedirects, this.customSSLSocketFactory, callbackContext); cordova.getThreadPool().execute(download); return true; } - private boolean setSSLCertMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException { - String mode = args.getString(0); - - // HttpRequest.clearCerts(); - - if (mode.equals("legacy")) { - // HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_DEFAULT); - callbackContext.success(); - } else if (mode.equals("nocheck")) { - // HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_TRUSTALL); - callbackContext.success(); - } else if (mode.equals("pinned")) { - try { - this.loadSSLCertsFromBundle(); - // HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_PINNED); - callbackContext.success(); - } catch (Exception e) { - e.printStackTrace(); - callbackContext.error("There was an error setting up ssl pinning"); - } - } else if (mode.equals("default")) { - try { - this.pinSSLCertsFromCAStore(); - callbackContext.success(); - } catch (Exception e) { - e.printStackTrace(); - callbackContext.error("There was an error loading system's CA certificates"); + private boolean setSSLCertMode(final JSONArray args, final CallbackContext callbackContext) { + try { + switch (args.getString(0)) { + case "legacy": + this.customSSLSocketFactory = null; + break; + case "nocheck": + /* @TODO host name verification */ + this.customSSLSocketFactory = this.createSocketFactory(this.trustManagersFactory.getNoopTrustManagers()); + break; + case "pinned": + this.customSSLSocketFactory = this.createSocketFactory( + this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromBundle("www/certificates/"))); + break; + default: + this.customSSLSocketFactory = this.createSocketFactory( + this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromKeyStore("AndroidCAStore"))); + break; } + + callbackContext.success(); + } catch (Exception e) { + Log.e(TAG, "An error occured while configuring SSL cert mode", e); + callbackContext.error("An error occured while configuring SSL cert mode"); } return true; } private boolean disableRedirect(final JSONArray args, final CallbackContext callbackContext) throws JSONException { - followRedirects = !args.getBoolean(0); + this.followRedirects = !args.getBoolean(0); callbackContext.success(); return true; } - private void pinSSLCertsFromCAStore() throws GeneralSecurityException, IOException { - this.loadSSLCertsFromKeyStore("AndroidCAStore"); - // HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_PINNED); - } + private ArrayList getCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException { + ArrayList certList = new ArrayList(); + KeyStore keyStore = KeyStore.getInstance(storeType); + keyStore.load(null); - private void loadSSLCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException { - KeyStore ks = KeyStore.getInstance(storeType); - ks.load(null); - Enumeration aliases = ks.aliases(); + Enumeration aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); - TrustedCertificateEntry certEntry = (TrustedCertificateEntry) ks.getEntry(alias, null); + TrustedCertificateEntry certEntry = (TrustedCertificateEntry) keyStore.getEntry(alias, null); Certificate cert = certEntry.getTrustedCertificate(); - // HttpRequest.addCert(cert); + certList.add(cert); } + + return certList; } - private void loadSSLCertsFromBundle() throws GeneralSecurityException, IOException { + private ArrayList getCertsFromBundle(String path) throws GeneralSecurityException, IOException { AssetManager assetManager = cordova.getActivity().getAssets(); - String[] files = assetManager.list("www/certificates"); - ArrayList cerFiles = new ArrayList(); + String[] files = assetManager.list(path); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + ArrayList certList = new ArrayList(); for (int i = 0; i < files.length; i++) { int index = files[i].lastIndexOf('.'); - if (index != -1) { - if (files[i].substring(index).equals(".cer")) { - cerFiles.add("www/certificates/" + files[i]); - } + + if (index == -1 || !files[i].substring(index).equals(".cer")) { + continue; } + + certList.add(cf.generateCertificate(assetManager.open(path + files[i]))); } - for (int i = 0; i < cerFiles.size(); i++) { - InputStream in = cordova.getActivity().getAssets().open(cerFiles.get(i)); - InputStream caInput = new BufferedInputStream(in); - // HttpRequest.addCert(caInput); + return certList; + } + + private SSLSocketFactory createSocketFactory(TrustManager[] trustManagers) throws IOException { + try { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, trustManagers, new SecureRandom()); + + if (android.os.Build.VERSION.SDK_INT < 20) { + return new TLSSocketFactory(context); + } else { + return context.getSocketFactory(); + } + } catch (GeneralSecurityException e) { + IOException ioException = new IOException("Security exception occured while configuring SSL context"); + ioException.initCause(e); + throw ioException; } } } diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java b/src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java index c6b6b80..fff3390 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java @@ -7,6 +7,8 @@ import com.silkimen.http.HttpRequest; import java.io.File; import java.net.URI; +import javax.net.ssl.SSLSocketFactory; + import org.apache.cordova.CallbackContext; import org.json.JSONObject; @@ -15,9 +17,9 @@ class CordovaHttpUpload extends CordovaHttpBase { private String uploadName; public CordovaHttpUpload(String url, JSONObject params, JSONObject headers, String filePath, String uploadName, - int timeout, boolean followRedirects, CallbackContext callbackContext) { + int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory, CallbackContext callbackContext) { - super("POST", url, params, headers, timeout, followRedirects, callbackContext); + super("POST", url, params, headers, timeout, followRedirects, customSSLSocketFactory, callbackContext); this.filePath = filePath; this.uploadName = uploadName; } diff --git a/src/android/com/silkimen/http/TrustManagersFactory.java b/src/android/com/silkimen/http/TrustManagersFactory.java index 943abde..ca9fa50 100644 --- a/src/android/com/silkimen/http/TrustManagersFactory.java +++ b/src/android/com/silkimen/http/TrustManagersFactory.java @@ -1,9 +1,13 @@ package com.silkimen.http; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.util.ArrayList; +import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; @@ -17,11 +21,11 @@ public class TrustManagersFactory { } public void checkClientTrusted(X509Certificate[] chain, String authType) { - // Intentionally left blank + // intentionally left blank } public void checkServerTrusted(X509Certificate[] chain, String authType) { - // Intentionally left blank + // intentionally left blank } } }; } @@ -53,7 +57,8 @@ public class TrustManagersFactory { } catch (GeneralSecurityException e) { IOException ioException = new IOException("Security exception configuring SSL trust managers"); ioException.initCause(e); - throw new HttpRequestException(ioException); + + throw ioException; } } }