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) {