Skip to content

Commit d077d02

Browse files
authored
Merge pull request #421 from silkimen/feat/#420-implement-blacklist-to-disable-TLS-protocols-on-Android
feat: #420 implement blacklist to disable unsafe SSL/TLS protocol ver…
2 parents 9d6005a + 0607943 commit d077d02

File tree

9 files changed

+74
-22
lines changed

9 files changed

+74
-22
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 3.2.0
4+
5+
- Feature #420: implement blacklist feature to disable SSL/TLS versions on Android (thanks to @MobisysGmbH)
6+
37
## 3.1.1
48

59
- Fixed #372: malformed empty multipart request on Android

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ phonegap plugin add cordova-plugin-advanced-http
3434
cordova plugin add cordova-plugin-advanced-http
3535
```
3636

37+
### Plugin Preferences
38+
39+
`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)).
40+
41+
e.g. blacklist `SSLv3` and `TLSv1`:
42+
```xml
43+
<preference name="AndroidBlacklistSecureSocketProtocols" value="SSLv3,TLSv1" />
44+
```
45+
3746
## Usage
3847

3948
### Plain Cordova

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cordova-plugin-advanced-http",
3-
"version": "3.1.1",
3+
"version": "3.2.0",
44
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
55
"scripts": {
66
"updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js",

plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<engine name="cordova" version=">=4.0.0"/>
99
</engines>
1010
<dependency id="cordova-plugin-file" version=">=2.0.0"/>
11+
<preference name="AndroidBlacklistSecureSocketProtocols" default="SSLv3,TLSv1"/>
1112
<js-module src="www/cookie-handler.js" name="cookie-handler"/>
1213
<js-module src="www/dependency-validator.js" name="dependency-validator"/>
1314
<js-module src="www/error-codes.js" name="error-codes"/>

src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
4747

4848
this.tlsConfiguration.setHostnameVerifier(null);
4949
this.tlsConfiguration.setTrustManagers(tmf.getTrustManagers());
50+
51+
if (this.preferences.contains("androidblacklistsecuresocketprotocols")) {
52+
this.tlsConfiguration.setBlacklistedProtocols(
53+
this.preferences.getString("androidblacklistsecuresocketprotocols", "").split(",")
54+
);
55+
}
56+
5057
} catch (Exception e) {
5158
Log.e(TAG, "An error occured while loading system's CA certificates", e);
5259
}

src/android/com/silkimen/http/TLSConfiguration.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
import com.silkimen.http.TLSSocketFactory;
1414

1515
public class TLSConfiguration {
16-
private TrustManager[] trustManagers;
17-
private KeyManager[] keyManagers;
18-
private HostnameVerifier hostnameVerifier;
16+
private TrustManager[] trustManagers = null;
17+
private KeyManager[] keyManagers = null;
18+
private HostnameVerifier hostnameVerifier = null;
19+
private String[] blacklistedProtocols = {};
1920

2021
private SSLSocketFactory socketFactory;
2122

@@ -33,6 +34,11 @@ public void setTrustManagers(TrustManager[] trustManagers) {
3334
this.socketFactory = null;
3435
}
3536

37+
public void setBlacklistedProtocols(String[] protocols) {
38+
this.blacklistedProtocols = protocols;
39+
this.socketFactory = null;
40+
}
41+
3642
public HostnameVerifier getHostnameVerifier() {
3743
return this.hostnameVerifier;
3844
}
@@ -46,12 +52,7 @@ public SSLSocketFactory getTLSSocketFactory() throws IOException {
4652
SSLContext context = SSLContext.getInstance("TLS");
4753

4854
context.init(this.keyManagers, this.trustManagers, new SecureRandom());
49-
50-
if (android.os.Build.VERSION.SDK_INT < 20) {
51-
this.socketFactory = new TLSSocketFactory(context);
52-
} else {
53-
this.socketFactory = context.getSocketFactory();
54-
}
55+
this.socketFactory = new TLSSocketFactory(context, this.blacklistedProtocols);
5556

5657
return this.socketFactory;
5758
} catch (GeneralSecurityException e) {

src/android/com/silkimen/http/TLSSocketFactory.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@
55
import java.net.Socket;
66
import java.net.UnknownHostException;
77

8+
import java.util.Arrays;
9+
import java.util.stream.Stream;
10+
811
import javax.net.ssl.SSLContext;
912
import javax.net.ssl.SSLSocket;
1013
import javax.net.ssl.SSLSocketFactory;
1114

1215
public class TLSSocketFactory extends SSLSocketFactory {
1316

1417
private SSLSocketFactory delegate;
18+
private String[] blacklistedProtocols;
1519

16-
public TLSSocketFactory(SSLContext context) {
17-
delegate = context.getSocketFactory();
20+
public TLSSocketFactory(SSLContext context, String[] blacklistedProtocols) {
21+
this.delegate = context.getSocketFactory();
22+
this.blacklistedProtocols = Arrays.stream(blacklistedProtocols).map(String::trim).toArray(String[]::new);
1823
}
1924

2025
@Override
@@ -55,9 +60,18 @@ public Socket createSocket(InetAddress address, int port, InetAddress localAddre
5560
}
5661

5762
private Socket enableTLSOnSocket(Socket socket) {
58-
if (socket != null && (socket instanceof SSLSocket)) {
59-
((SSLSocket) socket).setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" });
63+
if (socket == null || !(socket instanceof SSLSocket)) {
64+
return socket;
6065
}
66+
67+
String[] supported = ((SSLSocket) socket).getSupportedProtocols();
68+
69+
String[] filtered = Arrays.stream(supported).filter(
70+
val -> Arrays.stream(this.blacklistedProtocols).noneMatch(val::equals)
71+
).toArray(String[]::new);
72+
73+
((SSLSocket) socket).setEnabledProtocols(filtered);
74+
6175
return socket;
6276
}
6377
}

test/e2e-app-template/config.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@
2727
<allow-intent href="itms-apps:*" />
2828
</platform>
2929
<preference name="AndroidPersistentFileLocation" value="Internal" />
30+
<preference name="AndroidBlacklistSecureSocketProtocols" value="SSLv3,TLSv1" />
3031
</widget>

test/e2e-specs.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,13 @@ const helpers = {
104104

105105
return buffer;
106106
},
107+
isTlsBlacklistSupported: function () {
108+
return window.cordova && window.cordova.platformId === 'android';
109+
}
107110
};
108111

109112
const messageFactory = {
113+
handshakeFailed: function() { return 'TLS connection could not be established: javax.net.ssl.SSLHandshakeException: Handshake failed' },
110114
sslTrustAnchor: function () { return 'TLS connection could not be established: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.' },
111115
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.' }
112116
}
@@ -1014,8 +1018,7 @@ const tests = [
10141018
before: helpers.setRawSerializer,
10151019
func: function (resolve, reject, skip) {
10161020
if (!helpers.isAbortSupported()) {
1017-
skip();
1018-
return;
1021+
return skip();
10191022
}
10201023

10211024
var targetUrl = 'http://httpbin.org/post';
@@ -1036,8 +1039,7 @@ const tests = [
10361039
expected: 'rejected: {"status":-8, "error": "Request ...}',
10371040
func: function (resolve, reject, skip) {
10381041
if (!helpers.isAbortSupported()) {
1039-
skip();
1040-
return;
1042+
return skip();
10411043
}
10421044
var url = 'https://httpbin.org/drip?duration=2&numbytes=10&code=200';
10431045
var options = { method: 'get', responseType: 'blob' };
@@ -1064,8 +1066,7 @@ const tests = [
10641066
expected: 'rejected: {"status":-8, "error": "Request ...}',
10651067
func: function (resolve, reject, skip) {
10661068
if (!helpers.isAbortSupported()) {
1067-
skip();
1068-
return;
1069+
return skip();
10691070
}
10701071
var sourceUrl = 'http://httpbin.org/xml';
10711072
var targetPath = cordova.file.cacheDirectory + 'test.xml';
@@ -1097,8 +1098,7 @@ const tests = [
10971098
expected: 'rejected: {"status":-8, "error": "Request ...}',
10981099
func: function (resolve, reject, skip) {
10991100
if (!helpers.isAbortSupported()) {
1100-
skip();
1101-
return;
1101+
return skip();
11021102
}
11031103

11041104

@@ -1148,6 +1148,21 @@ const tests = [
11481148
}
11491149
}
11501150
},
1151+
{
1152+
description: 'should reject connecting to server with blacklisted SSL version #420',
1153+
expected: 'rejected: {"status":-2, ...',
1154+
func: function (resolve, reject, skip) {
1155+
if (!helpers.isTlsBlacklistSupported()) {
1156+
return skip();
1157+
}
1158+
1159+
cordova.plugin.http.get('https://tls-v1-0.badssl.com:1010/', {}, {}, resolve, reject);
1160+
},
1161+
validationFunc: function (driver, result) {
1162+
result.type.should.be.equal('rejected');
1163+
result.data.should.be.eql({ status: -2, error: messageFactory.handshakeFailed() });
1164+
}
1165+
},
11511166
];
11521167

11531168
if (typeof module !== 'undefined' && module.exports) {

0 commit comments

Comments
 (0)