From d4fce7751da2ec48e87fdf55f3a764a157a7f49d Mon Sep 17 00:00:00 2001 From: Andrew Stephan Date: Wed, 26 Mar 2014 11:56:49 -0400 Subject: [PATCH] SSL pinning now finds any cer files in the assets folder. Now can allow invalid certs --- plugin.xml | 2 + .../synconset/CordovaHTTP/CordovaHTTP.java | 82 ++++++++++++++----- .../com/synconset/CordovaHTTP/HTTP.java | 9 +- .../com/synconset/CordovaHTTP/HTTPGet.java | 5 +- .../com/synconset/CordovaHTTP/HTTPPost.java | 14 +++- .../VeryTrustingHostnameVerifier.java | 11 +++ .../CordovaHTTP/VeryTrustingTrustManager.java | 18 ++++ src/ios/CordovaHTTP.m | 24 ++---- www/cordovaHTTP.js | 14 +--- 9 files changed, 123 insertions(+), 56 deletions(-) create mode 100644 src/android/com/synconset/CordovaHTTP/VeryTrustingHostnameVerifier.java create mode 100644 src/android/com/synconset/CordovaHTTP/VeryTrustingTrustManager.java diff --git a/plugin.xml b/plugin.xml index 239893d..aedd4f1 100644 --- a/plugin.xml +++ b/plugin.xml @@ -81,5 +81,7 @@ + + \ No newline at end of file diff --git a/src/android/com/synconset/CordovaHTTP/CordovaHTTP.java b/src/android/com/synconset/CordovaHTTP/CordovaHTTP.java index 9d4250d..e50bcb5 100644 --- a/src/android/com/synconset/CordovaHTTP/CordovaHTTP.java +++ b/src/android/com/synconset/CordovaHTTP/CordovaHTTP.java @@ -15,9 +15,12 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Iterator; +import java.util.ArrayList; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.HostnameVerifier; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaInterface; @@ -27,12 +30,15 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import android.content.res.AssetManager; import android.util.Base64; +import android.util.Log; public class CordovaHTTP extends CordovaPlugin { private static final String TAG = "CordovaHTTP"; private SSLContext sslContext; + private HostnameVerifier hostnameVerifier; private JSONObject globalHeaders; @Override @@ -40,6 +46,7 @@ public class CordovaHTTP extends CordovaPlugin { super.initialize(cordova, webView); this.globalHeaders = new JSONObject(); this.sslContext = null; + this.hostnameVerifier = null; } @Override @@ -49,28 +56,34 @@ public class CordovaHTTP extends CordovaPlugin { JSONObject params = args.getJSONObject(1); JSONObject headers = args.getJSONObject(2); this.addToJSONObject(headers, this.globalHeaders); - HTTPGet get = new HTTPGet(urlString, params, headers, this.sslContext, callbackContext); + HTTPGet get = new HTTPGet(urlString, params, headers, this.sslContext, this.hostnameVerifier, callbackContext); cordova.getThreadPool().execute(get); } else if (action.equals("post")) { String urlString = args.getString(0); JSONObject params = args.getJSONObject(1); JSONObject headers = args.getJSONObject(2); this.addToJSONObject(headers, this.globalHeaders); - HTTPPost post = new HTTPPost(urlString, params, headers, this.sslContext, callbackContext); + HTTPPost post = new HTTPPost(urlString, params, headers, this.sslContext, this.hostnameVerifier, callbackContext); cordova.getThreadPool().execute(post); } else if (action.equals("setAuthorizationHeaderWithUsernameAndPassword")) { String username = args.getString(0); String password = args.getString(1); this.setAuthorizationHeaderWithUsernameAndPassword(username, password); callbackContext.success(); - } else if (action.equals("setSSLPinningMode")) { - int mode = args.getInt(0); + } else if (action.equals("enableSSLPinning")) { try { - this.setSSLPinningMode(mode); + this.enableSSLPinning(); callbackContext.success(); } catch(Exception e) { callbackContext.error("There was an error setting up ssl pinning"); } + } else if (action.equals("allowInvalidCertificates")) { + try { + boolean allow = args.getBoolean(0); + this.allowInvalidCertificates(allow); + } catch(Exception e) { + callbackContext.error("There was an error allowing or disallowing invalide certificates"); + } } else { return false; } @@ -83,26 +96,40 @@ public class CordovaHTTP extends CordovaPlugin { globalHeaders.put("Authorization", loginInfo); } - private void setSSLPinningMode(int mode) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException { - // Load CAs from an InputStream - // (could be from a resource or ByteArrayInputStream or ...) - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - // From https://www.washington.edu/itconnect/security/ca/load-der.crt - InputStream in = cordova.getActivity().getAssets().open("PCA-3G5.cer"); - InputStream caInput = new BufferedInputStream(in); - Certificate ca; - try { - ca = cf.generateCertificate(caInput); - System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); - } finally { - caInput.close(); - } - + private void enableSSLPinning() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException { // Create a KeyStore containing our trusted CAs String keyStoreType = KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); - keyStore.setCertificateEntry("ca", ca); + + + AssetManager assetManager = cordova.getActivity().getAssets(); + String[] files = assetManager.list(""); + int index; + ArrayList cerFiles = new ArrayList(); + for (int i = 0; i < files.length; i++) { + index = files[i].lastIndexOf('.'); + if (index != -1) { + if (files[i].substring(index).equals(".cer")) { + cerFiles.add(files[i]); + } + } + } + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + for (int i = 0; i < cerFiles.size(); i++) { + InputStream in = cordova.getActivity().getAssets().open(cerFiles.get(i)); + InputStream caInput = new BufferedInputStream(in); + Certificate ca; + try { + ca = cf.generateCertificate(caInput); + System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); + } finally { + caInput.close(); + } + + keyStore.setCertificateEntry(cerFiles.get(i), ca); + } // Create a TrustManager that trusts the CAs in our KeyStore String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); @@ -112,6 +139,19 @@ public class CordovaHTTP extends CordovaPlugin { // Create an SSLContext that uses our TrustManager sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null); + hostnameVerifier = null; + } + + private void allowInvalidCertificates(boolean allow) throws NoSuchAlgorithmException, KeyManagementException { + if (allow) { + VeryTrustingTrustManager vttm = new VeryTrustingTrustManager(); + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{vttm}, null); + hostnameVerifier = new VeryTrustingHostnameVerifier(); + } else { + sslContext = null; + hostnameVerifier = null; + } } private void addToJSONObject(JSONObject object, JSONObject objectToAdd) throws JSONException { diff --git a/src/android/com/synconset/CordovaHTTP/HTTP.java b/src/android/com/synconset/CordovaHTTP/HTTP.java index 66dfe30..abefd70 100644 --- a/src/android/com/synconset/CordovaHTTP/HTTP.java +++ b/src/android/com/synconset/CordovaHTTP/HTTP.java @@ -16,6 +16,7 @@ import java.io.BufferedReader; import java.net.URLConnection; import javax.net.ssl.SSLContext; +import javax.net.ssl.HostnameVerifier; import java.util.Iterator; @@ -30,14 +31,16 @@ public class HTTP { private JSONObject params; private JSONObject headers; private SSLContext sslContext; + private HostnameVerifier hostnameVerifier; private CallbackContext callbackContext; - public HTTP(String urlString, JSONObject params, JSONObject headers, SSLContext sslContext, CallbackContext callbackContext) { + public HTTP(String urlString, JSONObject params, JSONObject headers, SSLContext sslContext, HostnameVerifier hostnameVerifier, CallbackContext callbackContext) { this.urlString = urlString; this.params = params; this.headers = headers; this.sslContext = sslContext; this.callbackContext = callbackContext; + this.hostnameVerifier = hostnameVerifier; } protected String getUrlString() { @@ -68,6 +71,10 @@ public class HTTP { return this.sslContext; } + protected HostnameVerifier getHostnameVerifier() { + return this.hostnameVerifier; + } + protected CallbackContext getCallbackContext() { return this.callbackContext; } diff --git a/src/android/com/synconset/CordovaHTTP/HTTPGet.java b/src/android/com/synconset/CordovaHTTP/HTTPGet.java index 5cd4386..a7ac1a9 100644 --- a/src/android/com/synconset/CordovaHTTP/HTTPGet.java +++ b/src/android/com/synconset/CordovaHTTP/HTTPGet.java @@ -10,6 +10,7 @@ import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; +import javax.net.ssl.HostnameVerifier; import org.apache.cordova.CallbackContext; import org.json.JSONException; @@ -18,8 +19,8 @@ import org.json.JSONObject; import android.util.Log; public class HTTPGet extends HTTP implements Runnable { - public HTTPGet(String urlString, JSONObject params, JSONObject headers, SSLContext sslContext, CallbackContext callbackContext) { - super(urlString, params, headers, sslContext, callbackContext); + public HTTPGet(String urlString, JSONObject params, JSONObject headers, SSLContext sslContext, HostnameVerifier hostnameVerifier, CallbackContext callbackContext) { + super(urlString, params, headers, sslContext, hostnameVerifier, callbackContext); } @Override diff --git a/src/android/com/synconset/CordovaHTTP/HTTPPost.java b/src/android/com/synconset/CordovaHTTP/HTTPPost.java index 3f3b9ef..8cc4bed 100644 --- a/src/android/com/synconset/CordovaHTTP/HTTPPost.java +++ b/src/android/com/synconset/CordovaHTTP/HTTPPost.java @@ -13,6 +13,7 @@ import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; +import javax.net.ssl.HostnameVerifier; import org.apache.cordova.CallbackContext; import org.json.JSONException; @@ -21,8 +22,8 @@ import org.json.JSONObject; import android.util.Log; public class HTTPPost extends HTTP implements Runnable { - public HTTPPost(String urlString, JSONObject params, JSONObject headers, SSLContext sslContext, CallbackContext callbackContext) { - super(urlString, params, headers, sslContext, callbackContext); + public HTTPPost(String urlString, JSONObject params, JSONObject headers, SSLContext sslContext, HostnameVerifier hostnameVerifier, CallbackContext callbackContext) { + super(urlString, params, headers, sslContext, hostnameVerifier, callbackContext); } @Override @@ -35,7 +36,14 @@ public class HTTPPost extends HTTP implements Runnable { try { URL url = new URL(urlString); conn = (HttpsURLConnection)url.openConnection(); - conn.setSSLSocketFactory(this.getSSLContext().getSocketFactory()); + HostnameVerifier hostnameVerifier = this.getHostnameVerifier(); + if (hostnameVerifier != null) { + conn.setHostnameVerifier(hostnameVerifier); + } + SSLContext context = this.getSSLContext(); + if (context != null) { + conn.setSSLSocketFactory(this.getSSLContext().getSocketFactory()); + } conn.setRequestMethod("POST"); conn.setDoInput(true); conn.setDoOutput(true); diff --git a/src/android/com/synconset/CordovaHTTP/VeryTrustingHostnameVerifier.java b/src/android/com/synconset/CordovaHTTP/VeryTrustingHostnameVerifier.java new file mode 100644 index 0000000..9368ea2 --- /dev/null +++ b/src/android/com/synconset/CordovaHTTP/VeryTrustingHostnameVerifier.java @@ -0,0 +1,11 @@ +package com.synconset; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +public class VeryTrustingHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } +} \ No newline at end of file diff --git a/src/android/com/synconset/CordovaHTTP/VeryTrustingTrustManager.java b/src/android/com/synconset/CordovaHTTP/VeryTrustingTrustManager.java new file mode 100644 index 0000000..f711946 --- /dev/null +++ b/src/android/com/synconset/CordovaHTTP/VeryTrustingTrustManager.java @@ -0,0 +1,18 @@ +package com.synconset; + +import java.security.cert.X509Certificate; +import java.security.cert.CertificateException; +import javax.net.ssl.X509TrustManager; + +public class VeryTrustingTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException, IllegalArgumentException { } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException, IllegalArgumentException { } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} \ No newline at end of file diff --git a/src/ios/CordovaHTTP.m b/src/ios/CordovaHTTP.m index e747c66..b6630c1 100644 --- a/src/ios/CordovaHTTP.m +++ b/src/ios/CordovaHTTP.m @@ -5,7 +5,7 @@ @implementation CordovaHTTP -- (void) setAuthorizationHeaderWithUsernameAndPassword:(CDVInvokedUrlCommand*)command { +- (void)setAuthorizationHeaderWithUsernameAndPassword:(CDVInvokedUrlCommand*)command { NSString *username = [command.arguments objectAtIndex:0]; NSString *password = [command.arguments objectAtIndex:1]; @@ -15,7 +15,7 @@ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } -- (void) setHeader:(CDVInvokedUrlCommand*)command { +- (void)setHeader:(CDVInvokedUrlCommand*)command { NSString *header = [command.arguments objectAtIndex:0]; NSString *value = [command.arguments objectAtIndex:1]; @@ -25,24 +25,10 @@ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } -- (void) setSSLPinningMode:(CDVInvokedUrlCommand*)command { - CDVPluginResult* pluginResult = nil; - int pinningMode = [[command.arguments objectAtIndex:0] integerValue]; - - if (pinningMode == 0) { - [HTTPManager sharedClient].securityPolicy.SSLPinningMode = AFSSLPinningModeNone; - } else if (pinningMode == 1) { - [HTTPManager sharedClient].securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; - } else if (pinningMode == 2) { - [HTTPManager sharedClient].securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey]; - } else { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Requested SSL Pinning Mode is Unknown"]; - } - - if (pluginResult == nil) { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; - } +- (void)enableSSLPinning:(CDVInvokedUrlCommand*)command { + [HTTPManager sharedClient].securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } diff --git a/www/cordovaHTTP.js b/www/cordovaHTTP.js index ebf364f..f8155b1 100644 --- a/www/cordovaHTTP.js +++ b/www/cordovaHTTP.js @@ -7,19 +7,14 @@ var exec = require('cordova/exec'); var http = { - SSLPinningMode: { - None: 0, - Certificate: 1, - PublicKey: 2 - }, setAuthorizationHeaderWithUsernameAndPassword: function(username, password, success, failure) { return exec(success, failure, "CordovaHTTP", "setAuthorizationHeaderWithUsernameAndPassword", [username, password]); }, setHeader: function(header, value, success, failure) { return exec(success, failure, "CordovaHTTP", "setHeader", [header, value]); }, - setSSLPinningMode: function(mode, success, failure) { - return exec(success, failure, "CordovaHTTP", "setSSLPinningMode", [mode]); + enableSSLPinning: function(success, failure) { + return exec(success, failure, "CordovaHTTP", "enableSSLPinning", []); }, validateEntireCertificateChain: function(validateChain, success, failure) { return exec(success, failure, "CordovaHTTP", "validateEntireCertificateChain", [validateChain]); @@ -114,15 +109,14 @@ if (angular) { } var cordovaHTTP = { - SSLPinningMode: http.SSLPinningMode, setAuthorizationHeaderWithUsernameAndPassword: function(username, password) { return makePromise(http.setAuthorizationHeaderWithUsernameAndPassword, [username, password]); }, setHeader: function(header, value) { return makePromise(http.setHeader, [header, value]); }, - setSSLPinningMode: function(mode) { - return makePromise(http.setSSLPinningMode, [mode]); + enableSSLPinning: function() { + return makePromise(http.enableSSLPinning, []); }, validateEntireCertificateChain: function(validateChain) { return makePromise(http.validateEntireCertificateChain, [validateChain]);