From bda4eedfb993f4e06d35befa1cfa0c1e270e4330 Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Thu, 15 Jul 2021 04:00:35 +0200 Subject: [PATCH 1/5] feat: #420 implement blacklist to disable unsafe SSL/TLS protocol versions on Android --- CHANGELOG.md | 4 +++ package.json | 2 +- plugin.xml | 1 + .../cordovahttp/CordovaHttpPlugin.java | 7 ++++ .../com/silkimen/http/TLSConfiguration.java | 19 +++++----- .../com/silkimen/http/TLSSocketFactory.java | 22 +++++++++--- test/e2e-app-template/config.xml | 1 + test/e2e-specs.js | 35 ++++++++++++++----- 8 files changed, 69 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b54596e..ee7deef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 3.2.0 + +- Feature #420: implement blacklist feature to disable SSL/TLS versions on Android (thanks mobisys Mobile Informationssysteme GmbH) + ## 3.1.1 - Fixed #372: malformed empty multipart request on Android diff --git a/package.json b/package.json index 3fd6091..434751c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-advanced-http", - "version": "3.1.1", + "version": "3.2.0", "description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning", "scripts": { "updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js", diff --git a/plugin.xml b/plugin.xml index 943bb70..c25c685 100644 --- a/plugin.xml +++ b/plugin.xml @@ -8,6 +8,7 @@ + diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java b/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java index bdec147..ce0c40c 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java @@ -47,6 +47,13 @@ public class CordovaHttpPlugin extends CordovaPlugin implements Observer { this.tlsConfiguration.setHostnameVerifier(null); this.tlsConfiguration.setTrustManagers(tmf.getTrustManagers()); + + if (this.preferences.contains("androidblacklisttlsprotocols")) { + this.tlsConfiguration.setBlacklistedProtocols( + this.preferences.getString("androidblacklisttlsprotocols", "").split(",") + ); + } + } catch (Exception e) { Log.e(TAG, "An error occured while loading system's CA certificates", e); } diff --git a/src/android/com/silkimen/http/TLSConfiguration.java b/src/android/com/silkimen/http/TLSConfiguration.java index c33df6c..ed6f0d0 100644 --- a/src/android/com/silkimen/http/TLSConfiguration.java +++ b/src/android/com/silkimen/http/TLSConfiguration.java @@ -13,9 +13,10 @@ import javax.net.ssl.TrustManager; import com.silkimen.http.TLSSocketFactory; public class TLSConfiguration { - private TrustManager[] trustManagers; - private KeyManager[] keyManagers; - private HostnameVerifier hostnameVerifier; + private TrustManager[] trustManagers = null; + private KeyManager[] keyManagers = null; + private HostnameVerifier hostnameVerifier = null; + private String[] blacklistedProtocols = {}; private SSLSocketFactory socketFactory; @@ -33,6 +34,11 @@ public class TLSConfiguration { this.socketFactory = null; } + public void setBlacklistedProtocols(String[] protocols) { + this.blacklistedProtocols = protocols; + this.socketFactory = null; + } + public HostnameVerifier getHostnameVerifier() { return this.hostnameVerifier; } @@ -46,12 +52,7 @@ public class TLSConfiguration { SSLContext context = SSLContext.getInstance("TLS"); context.init(this.keyManagers, this.trustManagers, new SecureRandom()); - - if (android.os.Build.VERSION.SDK_INT < 20) { - this.socketFactory = new TLSSocketFactory(context); - } else { - this.socketFactory = context.getSocketFactory(); - } + this.socketFactory = new TLSSocketFactory(context, this.blacklistedProtocols); return this.socketFactory; } catch (GeneralSecurityException e) { diff --git a/src/android/com/silkimen/http/TLSSocketFactory.java b/src/android/com/silkimen/http/TLSSocketFactory.java index 9bc75b1..87d8f20 100644 --- a/src/android/com/silkimen/http/TLSSocketFactory.java +++ b/src/android/com/silkimen/http/TLSSocketFactory.java @@ -5,6 +5,9 @@ import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.stream.Stream; + import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; @@ -12,9 +15,11 @@ import javax.net.ssl.SSLSocketFactory; public class TLSSocketFactory extends SSLSocketFactory { private SSLSocketFactory delegate; + private String[] blacklistedProtocols; - public TLSSocketFactory(SSLContext context) { - delegate = context.getSocketFactory(); + public TLSSocketFactory(SSLContext context, String[] blacklistedProtocols) { + this.delegate = context.getSocketFactory(); + this.blacklistedProtocols = Arrays.stream(blacklistedProtocols).map(String::trim).toArray(String[]::new); } @Override @@ -55,9 +60,18 @@ public class TLSSocketFactory extends SSLSocketFactory { } private Socket enableTLSOnSocket(Socket socket) { - if (socket != null && (socket instanceof SSLSocket)) { - ((SSLSocket) socket).setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }); + if (socket == null || !(socket instanceof SSLSocket)) { + return socket; } + + String[] supported = ((SSLSocket) socket).getSupportedProtocols(); + + String[] filtered = Arrays.stream(supported).filter( + val -> Arrays.stream(this.blacklistedProtocols).noneMatch(val::equals) + ).toArray(String[]::new); + + ((SSLSocket) socket).setEnabledProtocols(filtered); + return socket; } } diff --git a/test/e2e-app-template/config.xml b/test/e2e-app-template/config.xml index d910ef5..ae69196 100644 --- a/test/e2e-app-template/config.xml +++ b/test/e2e-app-template/config.xml @@ -27,4 +27,5 @@ + diff --git a/test/e2e-specs.js b/test/e2e-specs.js index 3f1eec8..717ac98 100644 --- a/test/e2e-specs.js +++ b/test/e2e-specs.js @@ -104,9 +104,17 @@ const helpers = { return buffer; }, + isTlsBlacklistSupported: function () { + if (window.cordova && window.cordova.platformId === 'android') { + return true; + } + + return false; + } }; const messageFactory = { + handshakeFailed: function() { return 'TLS connection could not be established: javax.net.ssl.SSLHandshakeException: Handshake failed' }, sslTrustAnchor: function () { return 'TLS connection could not be established: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.' }, invalidCertificate: function (domain) { return 'The certificate for this server is invalid. You might be connecting to a server that is pretending to be “' + domain + '” which could put your confidential information at risk.' } } @@ -1014,8 +1022,7 @@ const tests = [ before: helpers.setRawSerializer, func: function (resolve, reject, skip) { if (!helpers.isAbortSupported()) { - skip(); - return; + return skip(); } var targetUrl = 'http://httpbin.org/post'; @@ -1036,8 +1043,7 @@ const tests = [ expected: 'rejected: {"status":-8, "error": "Request ...}', func: function (resolve, reject, skip) { if (!helpers.isAbortSupported()) { - skip(); - return; + return skip(); } var url = 'https://httpbin.org/drip?duration=2&numbytes=10&code=200'; var options = { method: 'get', responseType: 'blob' }; @@ -1064,8 +1070,7 @@ const tests = [ expected: 'rejected: {"status":-8, "error": "Request ...}', func: function (resolve, reject, skip) { if (!helpers.isAbortSupported()) { - skip(); - return; + return skip(); } var sourceUrl = 'http://httpbin.org/xml'; var targetPath = cordova.file.cacheDirectory + 'test.xml'; @@ -1097,8 +1102,7 @@ const tests = [ expected: 'rejected: {"status":-8, "error": "Request ...}', func: function (resolve, reject, skip) { if (!helpers.isAbortSupported()) { - skip(); - return; + return skip(); } @@ -1148,6 +1152,21 @@ const tests = [ } } }, + { + description: 'should reject connecting to server with blacklisted SSL version #420', + expected: 'rejected: {"status":-2, ...', + func: function (resolve, reject, skip) { + if (!helpers.isTlsBlacklistSupported()) { + return skip(); + } + + cordova.plugin.http.get('https://tls-v1-0.badssl.com:1010/', {}, {}, resolve, reject); + }, + validationFunc: function (driver, result) { + result.type.should.be.equal('rejected'); + result.data.should.be.eql({ status: -2, error: messageFactory.handshakeFailed() }); + } + }, ]; if (typeof module !== 'undefined' && module.exports) { From 05abdfcd9199b163ade53310219d88b6633e2fb8 Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Thu, 15 Jul 2021 12:36:10 +0200 Subject: [PATCH 2/5] Update CHANGELOG.md Co-authored-by: Robin Hartmann --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee7deef..22cadc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 3.2.0 -- Feature #420: implement blacklist feature to disable SSL/TLS versions on Android (thanks mobisys Mobile Informationssysteme GmbH) +- Feature #420: implement blacklist feature to disable SSL/TLS versions on Android (thanks to @MobisysGmbH) ## 3.1.1 From 7eb3395aa4ac1f86af405030ef7a839281dab62e Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Thu, 15 Jul 2021 12:38:09 +0200 Subject: [PATCH 3/5] Update test/e2e-specs.js Co-authored-by: Robin Hartmann --- test/e2e-specs.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/e2e-specs.js b/test/e2e-specs.js index 717ac98..7ddde92 100644 --- a/test/e2e-specs.js +++ b/test/e2e-specs.js @@ -105,11 +105,7 @@ const helpers = { return buffer; }, isTlsBlacklistSupported: function () { - if (window.cordova && window.cordova.platformId === 'android') { - return true; - } - - return false; + return window.cordova && window.cordova.platformId === 'android'; } }; From bbd4cf01abae0e7e0d8185d532b734039d86721d Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Thu, 15 Jul 2021 12:53:12 +0200 Subject: [PATCH 4/5] refactor: apply review feddback --- plugin.xml | 2 +- src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java | 4 ++-- test/e2e-app-template/config.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin.xml b/plugin.xml index c25c685..776ce44 100644 --- a/plugin.xml +++ b/plugin.xml @@ -8,7 +8,7 @@ - + diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java b/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java index ce0c40c..becee4b 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java @@ -48,9 +48,9 @@ public class CordovaHttpPlugin extends CordovaPlugin implements Observer { this.tlsConfiguration.setHostnameVerifier(null); this.tlsConfiguration.setTrustManagers(tmf.getTrustManagers()); - if (this.preferences.contains("androidblacklisttlsprotocols")) { + if (this.preferences.contains("androidblacklistsecuresocketprotocols")) { this.tlsConfiguration.setBlacklistedProtocols( - this.preferences.getString("androidblacklisttlsprotocols", "").split(",") + this.preferences.getString("androidblacklistsecuresocketprotocols", "").split(",") ); } diff --git a/test/e2e-app-template/config.xml b/test/e2e-app-template/config.xml index ae69196..f6c6da6 100644 --- a/test/e2e-app-template/config.xml +++ b/test/e2e-app-template/config.xml @@ -27,5 +27,5 @@ - + From 060794354d85cd935340feb37c6253de6db4c749 Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Thu, 15 Jul 2021 14:19:10 +0200 Subject: [PATCH 5/5] docu: add docu for secure socket protocol blacklisting --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index d37e844..096497e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,15 @@ phonegap plugin add cordova-plugin-advanced-http cordova plugin add cordova-plugin-advanced-http ``` +### Plugin Preferences + +`AndroidBlacklistSecureSocketProtocols`: define a blacklist of secure socket protocols for Android. This preference allows you to disable protocols which are considered unsafe. You need to provide a comma-separated list of protocols ([check Android SSLSocket#protocols docu for protocol names](https://developer.android.com/reference/javax/net/ssl/SSLSocket#protocols)). + +e.g. blacklist `SSLv3` and `TLSv1`: +```xml + +``` + ## Usage ### Plain Cordova