Skip to content

Commit 96f45d7

Browse files
author
Sefa Ilkimen
committed
feature silkimen#103: implement HTTP SSL cert modes
1 parent 60189a6 commit 96f45d7

File tree

10 files changed

+112
-99
lines changed

10 files changed

+112
-99
lines changed

CHANGELOG.md

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

3+
## 2.0.0
4+
5+
- Feature #103: implement HTTP SSL cert modes
6+
7+
- :warning: **Breaking Change**: Removed "enableSSLPinning" and "acceptAllCerts", use "setSSLCertMode" instead.
8+
39
## 1.11.1
410

511
- Fixed #92: headers not deserialized on platform "browser"

README.md

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,32 +141,46 @@ cordova.plugin.http.clearCookies();
141141
## Asynchronous Functions
142142
These functions all take success and error callbacks as their last 2 arguments.
143143

144-
### enableSSLPinning
145-
Enable or disable SSL pinning. This defaults to false.
144+
### setSSLCertMode<a name="setSSLCertMode"></a>
145+
Set SSL Cert handling mode, being one of the following values:
146+
147+
* `default`: default SSL cert handling using system's CA certs
148+
* `nocheck`: disable SSL cert checking, trusting all certs (meant to be used only for testing purposes)
149+
* `pinned`: trust only provided certs
146150

147151
To use SSL pinning you must include at least one .cer SSL certificate in your app project. You can pin to your server certificate or to one of the issuing CA certificates. For ios include your certificate in the root level of your bundle (just add the .cer file to your project/target at the root level). For android include your certificate in your project's platforms/android/assets folder. In both cases all .cer files found will be loaded automatically. If you only have a .pem certificate see this [stackoverflow answer](http://stackoverflow.com/a/16583429/3182729). You want to convert it to a DER encoded certificate with a .cer extension.
148152

149153
As an alternative, you can store your .cer files in the www/certificates folder.
150154

151155
```js
152-
cordova.plugin.http.enableSSLPinning(true, function() {
156+
// enable SSL pinning
157+
cordova.plugin.http.setSSLCertMode('pinned', function() {
153158
console.log('success!');
154159
}, function() {
155160
console.log('error :(');
156161
});
157-
```
158162

159-
### acceptAllCerts
160-
Accept all SSL certificates. Or disable accepting all certificates. This defaults to false.
163+
// use system's default CA certs
164+
cordova.plugin.http.setSSLCertMode('default', function() {
165+
console.log('success!');
166+
}, function() {
167+
console.log('error :(');
168+
});
161169

162-
```js
163-
cordova.plugin.http.acceptAllCerts(true, function() {
170+
// disable SSL cert checking, only meant for testing purposes, do NOT use in production!
171+
cordova.plugin.http.setSSLCertMode('nocheck', function() {
164172
console.log('success!');
165173
}, function() {
166174
console.log('error :(');
167175
});
168176
```
169177

178+
### enableSSLPinning
179+
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to enable SSL pinning (mode "pinned").
180+
181+
### acceptAllCerts
182+
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to disable checking certs (mode "nocheck").
183+
170184
### disableRedirect
171185
If set to `true`, it won't follow redirects automatically. This defaults to false.
172186

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": "1.11.1",
3+
"version": "2.0.0",
44
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
55
"scripts": {
66
"buildbrowser": "./scripts/build-test-app.sh --browser",

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

Lines changed: 43 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -86,25 +86,25 @@ else if (action.equals("delete")) {
8686
CordovaHttpHead head = new CordovaHttpHead(urlString, params, headers, timeoutInMilliseconds, callbackContext);
8787

8888
cordova.getThreadPool().execute(head);
89-
} else if (action.equals("enableSSLPinning")) {
90-
try {
91-
boolean enable = args.getBoolean(0);
92-
this.enableSSLPinning(enable);
93-
callbackContext.success();
94-
} catch(Exception e) {
95-
e.printStackTrace();
96-
callbackContext.error("There was an error setting up ssl pinning");
97-
}
98-
} else if (action.equals("acceptAllCerts")) {
99-
boolean accept = args.getBoolean(0);
89+
} else if (action.equals("setSSLCertMode")) {
90+
String mode = args.getString(0);
10091

101-
if (accept) {
102-
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_TRUSTALL);
103-
} else {
104-
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_DEFAULT);
92+
if (mode.equals("default")) {
93+
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_DEFAULT);
94+
callbackContext.success();
95+
} else if (mode.equals("nocheck")) {
96+
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_TRUSTALL);
97+
callbackContext.success();
98+
} else if (mode.equals("pinned")) {
99+
try {
100+
this.loadSSLCerts();
101+
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_PINNED);
102+
callbackContext.success();
103+
} catch(Exception e) {
104+
e.printStackTrace();
105+
callbackContext.error("There was an error setting up ssl pinning");
106+
}
105107
}
106-
107-
callbackContext.success();
108108
} else if (action.equals("uploadFile")) {
109109
String urlString = args.getString(0);
110110
Object params = args.get(1);
@@ -125,50 +125,44 @@ else if (action.equals("delete")) {
125125

126126
cordova.getThreadPool().execute(download);
127127
} else if (action.equals("disableRedirect")) {
128-
boolean disable = args.getBoolean(0);
129-
CordovaHttp.disableRedirect(disable);
130-
callbackContext.success();
128+
boolean disable = args.getBoolean(0);
129+
CordovaHttp.disableRedirect(disable);
130+
callbackContext.success();
131131
} else {
132132
return false;
133133
}
134134
return true;
135135
}
136136

137-
private void enableSSLPinning(boolean enable) throws GeneralSecurityException, IOException {
138-
if (enable) {
139-
AssetManager assetManager = cordova.getActivity().getAssets();
140-
String[] files = assetManager.list("");
141-
int index;
142-
ArrayList<String> cerFiles = new ArrayList<String>();
143-
for (int i = 0; i < files.length; i++) {
144-
index = files[i].lastIndexOf('.');
145-
if (index != -1) {
146-
if (files[i].substring(index).equals(".cer")) {
147-
cerFiles.add(files[i]);
148-
}
149-
}
150-
}
151-
152-
// scan the www/certificates folder for .cer files as well
153-
files = assetManager.list("www/certificates");
154-
for (int i = 0; i < files.length; i++) {
155-
index = files[i].lastIndexOf('.');
156-
if (index != -1) {
137+
private void loadSSLCerts() throws GeneralSecurityException, IOException {
138+
AssetManager assetManager = cordova.getActivity().getAssets();
139+
String[] files = assetManager.list("");
140+
int index;
141+
ArrayList<String> cerFiles = new ArrayList<String>();
142+
for (int i = 0; i < files.length; i++) {
143+
index = files[i].lastIndexOf('.');
144+
if (index != -1) {
157145
if (files[i].substring(index).equals(".cer")) {
158-
cerFiles.add("www/certificates/" + files[i]);
146+
cerFiles.add(files[i]);
159147
}
160-
}
161148
}
149+
}
162150

163-
for (int i = 0; i < cerFiles.size(); i++) {
164-
InputStream in = cordova.getActivity().getAssets().open(cerFiles.get(i));
165-
InputStream caInput = new BufferedInputStream(in);
166-
HttpRequest.addCert(caInput);
151+
// scan the www/certificates folder for .cer files as well
152+
files = assetManager.list("www/certificates");
153+
for (int i = 0; i < files.length; i++) {
154+
index = files[i].lastIndexOf('.');
155+
if (index != -1) {
156+
if (files[i].substring(index).equals(".cer")) {
157+
cerFiles.add("www/certificates/" + files[i]);
167158
}
159+
}
160+
}
168161

169-
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_PINNED);
170-
} else {
171-
HttpRequest.setSSLCertMode(HttpRequest.CERT_MODE_DEFAULT);
162+
for (int i = 0; i < cerFiles.size(); i++) {
163+
InputStream in = cordova.getActivity().getAssets().open(cerFiles.get(i));
164+
InputStream caInput = new BufferedInputStream(in);
165+
HttpRequest.addCert(caInput);
172166
}
173167
}
174168
}

src/ios/CordovaHttpPlugin.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
@interface CordovaHttpPlugin : CDVPlugin
66

7-
- (void)enableSSLPinning:(CDVInvokedUrlCommand*)command;
8-
- (void)acceptAllCerts:(CDVInvokedUrlCommand*)command;
7+
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command;
98
- (void)disableRedirect:(CDVInvokedUrlCommand*)command;
109
- (void)post:(CDVInvokedUrlCommand*)command;
1110
- (void)get:(CDVInvokedUrlCommand*)command;

src/ios/CordovaHttpPlugin.m

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -120,23 +120,31 @@ - (NSMutableDictionary*)copyHeaderFields:(NSDictionary *)headerFields {
120120
return headerFieldsCopy;
121121
}
122122

123-
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager {
124-
[manager.requestSerializer setTimeoutInterval:timeout];
125-
}
123+
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command {
124+
NSString *certMode = [command.arguments objectAtIndex:0];
126125

127-
- (void)enableSSLPinning:(CDVInvokedUrlCommand*)command {
128-
bool enable = [[command.arguments objectAtIndex:0] boolValue];
129-
130-
if (enable) {
131-
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
132-
} else {
126+
if ([certMode isEqualToString: @"default"]) {
127+
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
128+
securityPolicy.allowInvalidCertificates = NO;
129+
securityPolicy.validatesDomainName = YES;
130+
} else if ([certMode isEqualToString: @"nocheck"]) {
133131
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
132+
securityPolicy.allowInvalidCertificates = YES;
133+
securityPolicy.validatesDomainName = NO;
134+
} else if ([certMode isEqualToString: @"pinned"]) {
135+
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
136+
securityPolicy.allowInvalidCertificates = NO;
137+
securityPolicy.validatesDomainName = YES;
134138
}
135139

136140
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
137141
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
138142
}
139143

144+
- (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager {
145+
[manager.requestSerializer setTimeoutInterval:timeout];
146+
}
147+
140148
- (void)disableRedirect:(CDVInvokedUrlCommand*)command {
141149
CDVPluginResult* pluginResult = nil;
142150
bool disable = [[command.arguments objectAtIndex:0] boolValue];
@@ -147,17 +155,6 @@ - (void)disableRedirect:(CDVInvokedUrlCommand*)command {
147155
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
148156
}
149157

150-
- (void)acceptAllCerts:(CDVInvokedUrlCommand*)command {
151-
CDVPluginResult* pluginResult = nil;
152-
bool allow = [[command.arguments objectAtIndex:0] boolValue];
153-
154-
securityPolicy.allowInvalidCertificates = allow;
155-
securityPolicy.validatesDomainName = !allow;
156-
157-
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
158-
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
159-
}
160-
161158
- (void)post:(CDVInvokedUrlCommand*)command {
162159
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
163160
manager.securityPolicy = securityPolicy;

test/app-test-definitions.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
const hooks = {
22
onBeforeEachTest: function(done) {
33
cordova.plugin.http.clearCookies();
4-
cordova.plugin.http.acceptAllCerts(false, function() {
5-
cordova.plugin.http.enableSSLPinning(false, done, done);
6-
}, done);
4+
helpers.setDefaultCertMode(done);
75
}
86
};
97

108
const helpers = {
11-
acceptAllCerts: function(done) { cordova.plugin.http.acceptAllCerts(true, done, done); },
12-
enableSSLPinning: function(done) { cordova.plugin.http.enableSSLPinning(true, done, done); },
9+
setDefaultCertMode: function(done) { cordova.plugin.http.setSSLCertMode('default', done, done); },
10+
setNoCheckCertMode: function(done) { cordova.plugin.http.setSSLCertMode('nocheck', done, done); },
11+
setPinnedCertMode: function(done) { cordova.plugin.http.setSSLCertMode('pinned', done, done); },
1312
setJsonSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('json')); },
1413
setUtf8StringSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('utf8')); },
1514
setUrlEncodedSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('urlencoded')); },
@@ -82,7 +81,7 @@ const tests = [
8281
},{
8382
description: 'should accept bad cert (GET)',
8483
expected: 'resolved: {"status":200, ...',
85-
before: helpers.acceptAllCerts,
84+
before: helpers.setNoCheckCertMode,
8685
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
8786
validationFunc: function(driver, result) {
8887
result.type.should.be.equal('resolved');
@@ -91,7 +90,7 @@ const tests = [
9190
},{
9291
description: 'should accept bad cert (PUT)',
9392
expected: 'rejected: {"status":405, ... // will be rejected because PUT is not allowed',
94-
before: helpers.acceptAllCerts,
93+
before: helpers.setNoCheckCertMode,
9594
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
9695
validationFunc: function(driver, result) {
9796
result.type.should.be.equal('rejected');
@@ -100,7 +99,7 @@ const tests = [
10099
},{
101100
description: 'should accept bad cert (POST)',
102101
expected: 'rejected: {"status":405, ... // will be rejected because POST is not allowed',
103-
before: helpers.acceptAllCerts,
102+
before: helpers.setNoCheckCertMode,
104103
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
105104
validationFunc: function(driver, result) {
106105
result.type.should.be.equal('rejected');
@@ -109,7 +108,7 @@ const tests = [
109108
},{
110109
description: 'should accept bad cert (PATCH)',
111110
expected: 'rejected: {"status":405, ... // will be rejected because PATCH is not allowed',
112-
before: helpers.acceptAllCerts,
111+
before: helpers.setNoCheckCertMode,
113112
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
114113
validationFunc: function(driver, result) {
115114
result.type.should.be.equal('rejected');
@@ -118,7 +117,7 @@ const tests = [
118117
},{
119118
description: 'should accept bad cert (DELETE)',
120119
expected: 'rejected: {"status":405, ... // will be rejected because DELETE is not allowed',
121-
before: helpers.acceptAllCerts,
120+
before: helpers.setNoCheckCertMode,
122121
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
123122
validationFunc: function(driver, result) {
124123
result.type.should.be.equal('rejected');
@@ -127,7 +126,7 @@ const tests = [
127126
},{
128127
description: 'should fetch data from http://httpbin.org/ (GET)',
129128
expected: 'resolved: {"status":200, ...',
130-
before: helpers.acceptAllCerts,
129+
before: helpers.setNoCheckCertMode,
131130
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/', {}, {}, resolve, reject); },
132131
validationFunc: function(driver, result) {
133132
result.type.should.be.equal('resolved');
@@ -430,7 +429,7 @@ const tests = [
430429
},{
431430
description: 'should pin SSL cert correctly (GET)',
432431
expected: 'resolved: {"status": 200 ...',
433-
before: helpers.enableSSLPinning,
432+
before: helpers.setPinnedCertMode,
434433
func: function(resolve, reject) {
435434
cordova.plugin.http.get('https://httpbin.org', {}, {}, resolve, reject);
436435
},
@@ -440,7 +439,7 @@ const tests = [
440439
},{
441440
description: 'should reject when pinned cert does not match received server cert (GET)',
442441
expected: 'rejected: {"status": -1 ...',
443-
before: helpers.enableSSLPinning,
442+
before: helpers.setPinnedCertMode,
444443
func: function(resolve, reject) {
445444
cordova.plugin.http.get('https://sha512.badssl.com/', {}, {}, resolve, reject);
446445
},

www/advanced-http.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,11 @@ var publicInterface = {
6767
setRequestTimeout: function (timeout) {
6868
globalConfigs.timeout = timeout;
6969
},
70-
enableSSLPinning: function (enable, success, failure) {
71-
return exec(success, failure, 'CordovaHttpPlugin', 'enableSSLPinning', [ enable ]);
72-
},
73-
acceptAllCerts: function (allow, success, failure) {
74-
return exec(success, failure, 'CordovaHttpPlugin', 'acceptAllCerts', [ allow ]);
70+
setSSLCertMode: function (mode, success, failure) {
71+
return exec(success, failure, 'CordovaHttpPlugin', 'setSSLCertMode', [ helpers.checkSSLCertMode(mode) ]);
7572
},
7673
disableRedirect: function (disable, success, failure) {
77-
return exec(success, failure, 'CordovaHttpPlugin', 'disableRedirect', [ disable ]);
74+
return exec(success, failure, 'CordovaHttpPlugin', 'disableRedirect', [ !!disable ]);
7875
},
7976
sendRequest: function (url, options, success, failure) {
8077
helpers.handleMissingCallbacks(success, failure);

www/helpers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ var cookieHandler = require(pluginId + '.cookie-handler');
33
var messages = require(pluginId + '.messages');
44

55
var validSerializers = [ 'urlencoded', 'json', 'utf8' ];
6+
var validCertModes = [ 'default', 'nocheck', 'pinned' ];
67
var validHttpMethods = [ 'get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download' ];
78

89
module.exports = {
910
b64EncodeUnicode: b64EncodeUnicode,
1011
getTypeOf: getTypeOf,
1112
checkSerializer: checkSerializer,
13+
checkSSLCertMode: checkSSLCertMode,
1214
checkForBlacklistedHeaderKey: checkForBlacklistedHeaderKey,
1315
checkForInvalidHeaderValue: checkForInvalidHeaderValue,
1416
injectCookieHandler: injectCookieHandler,
@@ -79,6 +81,10 @@ function checkSerializer(serializer) {
7981
return checkForValidStringValue(validSerializers, serializer, messages.INVALID_DATA_SERIALIZER);
8082
}
8183

84+
function checkSSLCertMode(mode) {
85+
return checkForValidStringValue(validCertModes, mode, messages.INVALID_SSL_CERT_MODE);
86+
}
87+
8288
function checkForBlacklistedHeaderKey(key) {
8389
if (key.toLowerCase() === 'cookie') {
8490
throw new Error(messages.ADDING_COOKIES_NOT_SUPPORTED);

0 commit comments

Comments
 (0)