Compare commits

..

10 Commits

Author SHA1 Message Date
Sefa Ilkimen
0bf39650e1 release v2.0.8 2019-04-10 12:06:17 +02:00
Sefa Ilkimen
d444363a8e - fix #198: Cookie header is always passed even if there is no cookie
- some cleanup
2019-04-09 17:25:55 +02:00
Sefa Ilkimen
13976bbe43 Update change log and version number 2019-04-08 20:16:24 +02:00
Sefa Ilkimen
70c8b9bb32 Fix #201: browser implementation broken due to broken dependency 2019-04-08 20:12:36 +02:00
Sefa Ilkimen
4b964e8b7c Fix #197: iOS crashes on multiple HTTPS downloadFile() calls (reverting a8e3637) 2019-04-08 19:14:39 +02:00
Sefa Ilkimen
56f8b41f4f Fix #189: error code mappings for TLS handshake problems are not precise 2019-04-08 17:58:39 +02:00
Sefa Ilkimen
e1e720fe2d Add missing handler on browser platform 2019-04-08 17:31:38 +02:00
Sefa Ilkimen
a1b6ea94f0 Fix #200: remove string switch and use ugly if ... else 😢 to be compatible with Java target 6 2019-04-05 16:48:18 +02:00
Sefa Ilkimen
d977392a49 - some cleanup
- deprecate "setSSLCertMode" in favor of "setServerTrustMode"
2019-04-05 16:26:04 +02:00
Sefa Ilkimen
8d28f4ab80 WIP: implementing X509 client cert authentication 2019-04-05 05:22:34 +02:00
46 changed files with 633 additions and 396 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
node_modules/**
test/app-template/www/certificates/*.cer
test/e2e-app-template/www/certificates/*.cer
tags
.zedstate
npm-debug.log

View File

@@ -1,5 +1,15 @@
# Changelog
## 2.0.8
- Fixed #198: cookie header is always passed even if there is no cookie
- Fixed #201: browser implementation is broken due to broken dependency
- Fixed #197: iOS crashes when multiple request are done simultaneously (reverted a8e3637)
- Fixed #189: error code mappings are not precise
- Fixed #200: compatibility with Java 6 is broken due to string switch on Android
- :warning: **Deprecation**: Deprecated "setSSLCertMode" in favor of "setServerTrustMode"
## 2.0.7
- Fixed #195: URLs are double-encoded on Android

View File

@@ -128,13 +128,13 @@ cordova.plugin.http.clearCookies();
## Asynchronous Functions
These functions all take success and error callbacks as their last 2 arguments.
### setSSLCertMode<a name="setSSLCertMode"></a>
Set SSL Cert handling mode, being one of the following values:
### setServerTrustMode<a name="setServerTrustMode"></a>
Set server trust mode, being one of the following values:
* `default`: default SSL cert handling using system's CA certs
* `default`: default SSL trustship and hostname verification handling using system's CA certs
* `legacy`: use legacy default behavior (< 2.0.3), excluding user installed CA certs (only for Android)
* `nocheck`: disable SSL cert checking, trusting all certs (meant to be used only for testing purposes)
* `pinned`: trust only provided certs
* `nocheck`: disable SSL certificate checking and hostname verification, trusting all certs (meant to be used only for testing purposes)
* `pinned`: trust only provided certificates
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. Include your certificate in the `www/certificates` folder. All `.cer` files found there will be loaded automatically.
@@ -142,32 +142,38 @@ To use SSL pinning you must include at least one `.cer` SSL certificate in your
```js
// enable SSL pinning
cordova.plugin.http.setSSLCertMode('pinned', function() {
cordova.plugin.http.setServerTrustMode('pinned', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
// use system's default CA certs
cordova.plugin.http.setSSLCertMode('default', function() {
cordova.plugin.http.setServerTrustMode('default', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
// disable SSL cert checking, only meant for testing purposes, do NOT use in production!
cordova.plugin.http.setSSLCertMode('nocheck', function() {
cordova.plugin.http.setServerTrustMode('nocheck', function() {
console.log('success!');
}, function() {
console.log('error :(');
});
```
### setSSLCertMode (deprecated)
This function was deprecated in 2.0.8. Use ["setServerTrustMode"](#setServerTrustMode) instead.
### enableSSLPinning (obsolete)
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to enable SSL pinning (mode "pinned").
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to enable SSL pinning (mode "pinned").
### acceptAllCerts (obsolete)
This function was removed in 2.0.0. Use ["setSSLCertMode"](#setSSLCertMode) to disable checking certs (mode "nocheck").
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to disable checking certs (mode "nocheck").
### validateDomainName (obsolete)
This function was removed in v1.6.2. Domain name validation is disabled automatically when you set server trust mode to "nocheck".
### disableRedirect
If set to `true`, it won't follow redirects automatically. This defaults to false.
@@ -180,9 +186,6 @@ cordova.plugin.http.disableRedirect(true, function() {
});
```
### validateDomainName (obsolete)
This function was removed in v1.6.2. Domain name validation is disabled automatically when you set SSL cert mode to "nocheck".
### removeCookies
Remove all cookies associated with a given URL.

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-advanced-http",
"version": "2.0.7",
"version": "2.0.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-advanced-http",
"version": "2.0.7",
"version": "2.0.8",
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
"scripts": {
"updatecert": "node ./scripts/update-test-cert.js",
@@ -8,7 +8,7 @@
"testandroid": "npm run updatecert && ./scripts/build-test-app.sh --android --emulator && ./scripts/test-app.sh --android --emulator",
"testios": "npm run updatecert && ./scripts/build-test-app.sh --ios --emulator && ./scripts/test-app.sh --ios --emulator",
"testapp": "npm run testandroid && npm run testios",
"testjs": "mocha ./test/js-mocha-specs.js",
"testjs": "mocha ./test/js-specs.js",
"test": "npm run testjs && npm run testapp",
"release": "npm run test && ./scripts/release.sh"
},

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="2.0.7">
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="2.0.8">
<name>Advanced HTTP plugin</name>
<description>
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
@@ -11,6 +11,7 @@
<js-module src="www/cookie-handler.js" name="cookie-handler"/>
<js-module src="www/global-configs.js" name="global-configs"/>
<js-module src="www/helpers.js" name="helpers"/>
<js-module src="www/js-util.js" name="js-util"/>
<js-module src="www/local-storage-store.js" name="local-storage-store"/>
<js-module src="www/lodash.js" name="lodash"/>
<js-module src="www/messages.js" name="messages"/>
@@ -59,19 +60,21 @@
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.INTERNET"/>
</config-file>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaClientAuth.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpBase.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpResponse.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/http/HostnameVerifierFactory.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaServerTrust.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/http/HttpBodyDecoder.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/HttpRequest.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/JsonUtils.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/KeyChainKeyManager.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/OkConnectionFactory.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TLSConfiguration.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TLSSocketFactory.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TrustManagersFactory.java" target-dir="src/com/silkimen/http"/>
<framework src="com.squareup.okhttp3:okhttp-urlconnection:3.10.0"/>
</platform>
<platform name="browser">

View File

@@ -35,10 +35,11 @@ while :; do
shift
done
printf 'Building test app for %s\n' $PLATFORM
rm -rf $ROOT/temp
mkdir $ROOT/temp
cp -r $ROOT/test/app-template/. $ROOT/temp/
cp $ROOT/test/app-test-definitions.js $ROOT/temp/www/
cp -r $ROOT/test/e2e-app-template/. $ROOT/temp/
cp $ROOT/test/e2e-specs.js $ROOT/temp/www/
rsync -ax --exclude node_modules --exclude scripts --exclude temp --exclude test $ROOT/. $WORKINGCOPY
cd $ROOT/temp
$CDV prepare

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env bash
set -e
while getopts a:b: option; do
case "${option}" in
a) API_LEVEL=${OPTARG};;
b) BUILD_TOOLS_VERSION=${OPTARG};;
esac
done
curl http://dl.google.com/android/android-sdk_r24.4-macosx.zip -o android-sdk-macosx.zip
tar -xvf android-sdk-macosx.zip
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter platform-tools
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter build-tools-${BUILD_TOOLS_VERSION}
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter android-${API_LEVEL}
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-android-support
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-android-m2repository
echo y | ./android-sdk-macosx/tools/android update sdk --no-ui --all --filter extra-google-m2repository

View File

@@ -8,6 +8,7 @@ if [ $CI == "true" ] && ([ -z $SAUCE_USERNAME ] || [ -z $SAUCE_ACCESS_KEY ]); th
exit 0;
fi
printf 'Running e2e tests\n'
pushd $ROOT
./node_modules/.bin/mocha ./test/app-mocha-specs/test.js "$@"
./node_modules/.bin/mocha ./test/e2e-tooling/test.js "$@"
popd

View File

@@ -3,7 +3,7 @@ const https = require('https');
const path = require('path');
const SOURCE_HOST = 'httpbin.org';
const TARGET_PATH = path.join(__dirname, '../test/app-template/www/certificates/httpbin.org.cer');
const TARGET_PATH = path.join(__dirname, '../test/e2e-app-template/www/certificates/httpbin.org.cer');
const getCert = hostname => new Promise((resolve, reject) => {
const options = {

View File

@@ -0,0 +1,72 @@
package com.silkimen.cordovahttp;
import android.app.Activity;
import android.content.Context;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.util.Log;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
import org.apache.cordova.CallbackContext;
import com.silkimen.http.KeyChainKeyManager;
import com.silkimen.http.TLSConfiguration;
class CordovaClientAuth implements Runnable, KeyChainAliasCallback {
private static final String TAG = "Cordova-Plugin-HTTP";
private String mode;
private String filePath;
private Activity activity;
private Context context;
private TLSConfiguration tlsConfiguration;
private CallbackContext callbackContext;
public CordovaClientAuth(final String mode, final String filePath, final Activity activity, final Context context,
final TLSConfiguration configContainer, final CallbackContext callbackContext) {
this.mode = mode;
this.filePath = filePath;
this.activity = activity;
this.tlsConfiguration = configContainer;
this.context = context;
this.callbackContext = callbackContext;
}
@Override
public void run() {
if ("systemstore".equals(this.mode)) {
KeyChain.choosePrivateKeyAlias(this.activity, this, null, null, null, -1, null);
} else if ("file".equals(this.mode)) {
this.callbackContext.error("Not implemented, yet");
} else {
this.tlsConfiguration.setKeyManagers(null);
this.callbackContext.success();
}
}
@Override
public void alias(final String alias) {
try {
if (alias == null) {
throw new Exception("Couldn't get a consent for private key access");
}
PrivateKey key = KeyChain.getPrivateKey(this.context, alias);
X509Certificate[] chain = KeyChain.getCertificateChain(this.context, alias);
KeyManager keyManager = new KeyChainKeyManager(alias, key, chain);
this.tlsConfiguration.setKeyManagers(new KeyManager[] { keyManager });
this.callbackContext.success();
} catch (Exception e) {
Log.e(TAG, "Couldn't load private key and certificate pair for authentication", e);
this.callbackContext.error("Couldn't load private key and certificate pair for authentication");
}
}
}

View File

@@ -1,5 +1,6 @@
package com.silkimen.cordovahttp;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.net.SocketTimeoutException;
@@ -7,15 +8,14 @@ import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLException;
import com.silkimen.http.HttpBodyDecoder;
import com.silkimen.http.HttpRequest;
import com.silkimen.http.HttpRequest.HttpRequestException;
import com.silkimen.http.JsonUtils;
import com.silkimen.http.OkConnectionFactory;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
@@ -34,13 +34,11 @@ abstract class CordovaHttpBase implements Runnable {
protected JSONObject headers;
protected int timeout;
protected boolean followRedirects;
protected SSLSocketFactory customSSLSocketFactory;
protected HostnameVerifier customHostnameVerifier;
protected TLSConfiguration tlsConfiguration;
protected CallbackContext callbackContext;
public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout,
boolean followRedirects, SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
CallbackContext callbackContext) {
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
this.method = method;
this.url = url;
@@ -49,22 +47,19 @@ abstract class CordovaHttpBase implements Runnable {
this.headers = headers;
this.timeout = timeout;
this.followRedirects = followRedirects;
this.customSSLSocketFactory = customSSLSocketFactory;
this.customHostnameVerifier = customHostnameVerifier;
this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext;
}
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout,
boolean followRedirects, SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
CallbackContext callbackContext) {
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
this.method = method;
this.url = url;
this.headers = headers;
this.timeout = timeout;
this.followRedirects = followRedirects;
this.customSSLSocketFactory = customSSLSocketFactory;
this.customHostnameVerifier = customHostnameVerifier;
this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext;
}
@@ -78,10 +73,10 @@ abstract class CordovaHttpBase implements Runnable {
this.sendBody(request);
this.processResponse(request, response);
} catch (HttpRequestException e) {
if (e.getCause() instanceof SSLHandshakeException) {
if (e.getCause() instanceof SSLException) {
response.setStatus(-2);
response.setErrorMessage("SSL handshake failed: " + e.getMessage());
Log.w(TAG, "SSL handshake failed", e);
response.setErrorMessage("TLS connection could not be established: " + e.getMessage());
Log.w(TAG, "TLS connection could not be established", e);
} else if (e.getCause() instanceof UnknownHostException) {
response.setStatus(-3);
response.setErrorMessage("Host could not be resolved: " + e.getMessage());
@@ -116,20 +111,18 @@ abstract class CordovaHttpBase implements Runnable {
return new HttpRequest(this.url, this.method);
}
protected void prepareRequest(HttpRequest request) throws JSONException {
protected void prepareRequest(HttpRequest request) throws JSONException, IOException {
request.followRedirects(this.followRedirects);
request.readTimeout(this.timeout);
request.acceptCharset("UTF-8");
request.uncompress(true);
request.setConnectionFactory(new OkConnectionFactory());
if (this.customHostnameVerifier != null) {
request.setHostnameVerifier(this.customHostnameVerifier);
if (this.tlsConfiguration.getHostnameVerifier() != null) {
request.setHostnameVerifier(this.tlsConfiguration.getHostnameVerifier());
}
if (this.customSSLSocketFactory != null) {
request.setSSLSocketFactory(this.customSSLSocketFactory);
}
request.setSSLSocketFactory(this.tlsConfiguration.getTLSSocketFactory());
// setup content type before applying headers, so user can override it
this.setContentType(request);
@@ -138,16 +131,12 @@ abstract class CordovaHttpBase implements Runnable {
}
protected void setContentType(HttpRequest request) {
switch (this.serializer) {
case "json":
if ("json".equals(this.serializer)) {
request.contentType("application/json", "UTF-8");
break;
case "utf8":
} else if ("utf8".equals(this.serializer)) {
request.contentType("text/plain", "UTF-8");
break;
case "urlencoded":
} else if ("urlencoded".equals(this.serializer)) {
// intentionally left blank, because content type is set in HttpRequest.form()
break;
}
}
@@ -156,16 +145,12 @@ abstract class CordovaHttpBase implements Runnable {
return;
}
switch (this.serializer) {
case "json":
if ("json".equals(this.serializer)) {
request.send(this.data.toString());
break;
case "utf8":
} else if ("utf8".equals(this.serializer)) {
request.send(((JSONObject) this.data).getString("text"));
break;
case "urlencoded":
} else if ("urlencoded".equals(this.serializer)) {
request.form(JsonUtils.getObjectMap((JSONObject) this.data));
break;
}
}

View File

@@ -7,6 +7,7 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.HttpRequest;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.file.FileUtils;
@@ -15,12 +16,10 @@ import org.json.JSONObject;
class CordovaHttpDownload extends CordovaHttpBase {
private String filePath;
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout,
boolean followRedirects, SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
CallbackContext callbackContext) {
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super("GET", url, headers, timeout, followRedirects, customSSLSocketFactory, customHostnameVerifier,
callbackContext);
super("GET", url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
this.filePath = filePath;
}

View File

@@ -3,23 +3,21 @@ package com.silkimen.cordovahttp;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.json.JSONObject;
class CordovaHttpOperation extends CordovaHttpBase {
public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers,
int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory,
HostnameVerifier customHostnameVerifier, CallbackContext callbackContext) {
int timeout, boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super(method, url, serializer, data, headers, timeout, followRedirects, customSSLSocketFactory,
customHostnameVerifier, callbackContext);
super(method, url, serializer, data, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
}
public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
SSLSocketFactory customSSLSocketFactory, HostnameVerifier customHostnameVerifier,
CallbackContext callbackContext) {
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super(method, url, headers, timeout, followRedirects, customSSLSocketFactory, customHostnameVerifier,
callbackContext);
super(method, url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
}
}

View File

@@ -1,27 +1,8 @@
package com.silkimen.cordovahttp;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateFactory;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStore.TrustedCertificateEntry;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import com.silkimen.http.HostnameVerifierFactory;
import com.silkimen.http.TLSSocketFactory;
import com.silkimen.http.TrustManagersFactory;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
@@ -31,26 +12,32 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.res.AssetManager;
import android.util.Log;
import javax.net.ssl.TrustManagerFactory;
public class CordovaHttpPlugin extends CordovaPlugin {
private static final String TAG = "Cordova-Plugin-HTTP";
private final TrustManagersFactory trustManagersFactory = new TrustManagersFactory();
private final HostnameVerifierFactory hostnameVerifierFactory = new HostnameVerifierFactory();
private boolean followRedirects = true;
private SSLSocketFactory customSSLSocketFactory;
private HostnameVerifier customHostnameVerifier;
private TLSConfiguration tlsConfiguration;
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.tlsConfiguration = new TLSConfiguration();
try {
this.customSSLSocketFactory = this.createSocketFactory(
this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
KeyStore store = KeyStore.getInstance("AndroidCAStore");
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
store.load(null);
tmf.init(store);
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(tmf.getTrustManagers());
} catch (Exception e) {
Log.e(TAG, "An error occured while loading system's CA certificates", e);
}
@@ -64,28 +51,29 @@ public class CordovaHttpPlugin extends CordovaPlugin {
return false;
}
switch (action) {
case "get":
if ("get".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
case "post":
return this.executeHttpRequestWithData(action, args, callbackContext);
case "put":
return this.executeHttpRequestWithData(action, args, callbackContext);
case "patch":
return this.executeHttpRequestWithData(action, args, callbackContext);
case "head":
} else if ("head".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
case "delete":
} else if ("delete".equals(action)) {
return this.executeHttpRequestWithoutData(action, args, callbackContext);
case "uploadFile":
} else if ("post".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("put".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("patch".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("uploadFile".equals(action)) {
return this.uploadFile(args, callbackContext);
case "downloadFile":
} else if ("downloadFile".equals(action)) {
return this.downloadFile(args, callbackContext);
case "setSSLCertMode":
return this.setSSLCertMode(args, callbackContext);
case "disableRedirect":
} else if ("setServerTrustMode".equals(action)) {
return this.setServerTrustMode(args, callbackContext);
} else if ("setClientAuthMode".equals(action)) {
return this.setClientAuthMode(args, callbackContext);
} else if ("disableRedirect".equals(action)) {
return this.disableRedirect(args, callbackContext);
default:
} else {
return false;
}
}
@@ -98,7 +86,7 @@ public class CordovaHttpPlugin extends CordovaPlugin {
int timeout = args.getInt(2) * 1000;
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout,
this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
this.followRedirects, this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(request);
@@ -115,7 +103,7 @@ public class CordovaHttpPlugin extends CordovaPlugin {
int timeout = args.getInt(4) * 1000;
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
timeout, this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
timeout, this.followRedirects, this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(request);
@@ -129,8 +117,8 @@ public class CordovaHttpPlugin extends CordovaPlugin {
String uploadName = args.getString(3);
int timeout = args.getInt(4) * 1000;
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePath, uploadName, timeout,
this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePath, uploadName, timeout, this.followRedirects,
this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(upload);
@@ -143,42 +131,28 @@ public class CordovaHttpPlugin extends CordovaPlugin {
String filePath = args.getString(2);
int timeout = args.getInt(3) * 1000;
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout,
this.followRedirects, this.customSSLSocketFactory, this.customHostnameVerifier, callbackContext);
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout, this.followRedirects,
this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(download);
return true;
}
private boolean setSSLCertMode(final JSONArray args, final CallbackContext callbackContext) {
try {
switch (args.getString(0)) {
case "legacy":
this.customHostnameVerifier = null;
this.customSSLSocketFactory = null;
break;
case "nocheck":
this.customHostnameVerifier = this.hostnameVerifierFactory.getNoOpVerifier();
this.customSSLSocketFactory = this.createSocketFactory(this.trustManagersFactory.getNoopTrustManagers());
break;
case "pinned":
this.customHostnameVerifier = null;
this.customSSLSocketFactory = this.createSocketFactory(
this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromBundle("www/certificates")));
break;
default:
this.customHostnameVerifier = null;
this.customSSLSocketFactory = this.createSocketFactory(
this.trustManagersFactory.getPinnedTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
break;
}
private boolean setServerTrustMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
CordovaServerTrust runnable = new CordovaServerTrust(args.getString(0), this.cordova.getActivity(),
this.tlsConfiguration, callbackContext);
callbackContext.success();
} catch (Exception e) {
Log.e(TAG, "An error occured while configuring SSL cert mode", e);
callbackContext.error("An error occured while configuring SSL cert mode");
}
cordova.getThreadPool().execute(runnable);
return true;
}
private boolean setClientAuthMode(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
CordovaClientAuth runnable = new CordovaClientAuth(args.getString(0), args.getString(1), this.cordova.getActivity(),
this.cordova.getContext(), this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(runnable);
return true;
}
@@ -190,59 +164,4 @@ public class CordovaHttpPlugin extends CordovaPlugin {
return true;
}
private ArrayList<Certificate> getCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException {
ArrayList<Certificate> certList = new ArrayList<Certificate>();
KeyStore keyStore = KeyStore.getInstance(storeType);
keyStore.load(null);
Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
TrustedCertificateEntry certEntry = (TrustedCertificateEntry) keyStore.getEntry(alias, null);
Certificate cert = certEntry.getTrustedCertificate();
certList.add(cert);
}
return certList;
}
private ArrayList<Certificate> getCertsFromBundle(String path) throws GeneralSecurityException, IOException {
AssetManager assetManager = cordova.getActivity().getAssets();
String[] files = assetManager.list(path);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ArrayList<Certificate> certList = new ArrayList<Certificate>();
for (int i = 0; i < files.length; i++) {
int index = files[i].lastIndexOf('.');
if (index == -1 || !files[i].substring(index).equals(".cer")) {
continue;
}
certList.add(cf.generateCertificate(assetManager.open(path + "/" + files[i])));
}
return certList;
}
private SSLSocketFactory createSocketFactory(TrustManager[] trustManagers) throws IOException {
try {
SSLContext context = SSLContext.getInstance("TLS");
/* @TODO implement custom KeyManager */
context.init(null, trustManagers, new SecureRandom());
if (android.os.Build.VERSION.SDK_INT < 20) {
return new TLSSocketFactory(context);
} else {
return context.getSocketFactory();
}
} catch (GeneralSecurityException e) {
IOException ioException = new IOException("Security exception occured while configuring SSL context");
ioException.initCause(e);
throw ioException;
}
}
}

View File

@@ -10,6 +10,8 @@ import java.net.URI;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.json.JSONObject;
@@ -17,12 +19,10 @@ class CordovaHttpUpload extends CordovaHttpBase {
private String filePath;
private String uploadName;
public CordovaHttpUpload(String url, JSONObject headers, String filePath, String uploadName,
int timeout, boolean followRedirects, SSLSocketFactory customSSLSocketFactory,
HostnameVerifier customHostnameVerifier, CallbackContext callbackContext) {
public CordovaHttpUpload(String url, JSONObject headers, String filePath, String uploadName, int timeout,
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super("POST", url, headers, timeout, followRedirects, customSSLSocketFactory, customHostnameVerifier,
callbackContext);
super("POST", url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
this.filePath = filePath;
this.uploadName = uploadName;
}

View File

@@ -0,0 +1,124 @@
package com.silkimen.cordovahttp;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import android.app.Activity;
import android.util.Log;
import android.content.res.AssetManager;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
class CordovaServerTrust implements Runnable {
private static final String TAG = "Cordova-Plugin-HTTP";
private final TrustManager[] noOpTrustManagers;
private final HostnameVerifier noOpVerifier;
private String mode;
private Activity activity;
private TLSConfiguration tlsConfiguration;
private CallbackContext callbackContext;
public CordovaServerTrust(final String mode, final Activity activity, final TLSConfiguration configContainer,
final CallbackContext callbackContext) {
this.mode = mode;
this.activity = activity;
this.tlsConfiguration = configContainer;
this.callbackContext = callbackContext;
this.noOpTrustManagers = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
} };
this.noOpVerifier = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
@Override
public void run() {
try {
if ("legacy".equals(this.mode)) {
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(null);
} else if ("nocheck".equals(this.mode)) {
this.tlsConfiguration.setHostnameVerifier(this.noOpVerifier);
this.tlsConfiguration.setTrustManagers(this.noOpTrustManagers);
} else if ("pinned".equals(this.mode)) {
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(this.getTrustManagers(this.getCertsFromBundle("www/certificates")));
} else {
this.tlsConfiguration.setHostnameVerifier(null);
this.tlsConfiguration.setTrustManagers(this.getTrustManagers(this.getCertsFromKeyStore("AndroidCAStore")));
}
callbackContext.success();
} catch (Exception e) {
Log.e(TAG, "An error occured while configuring SSL cert mode", e);
callbackContext.error("An error occured while configuring SSL cert mode");
}
}
private TrustManager[] getTrustManagers(KeyStore store) throws GeneralSecurityException {
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(store);
return tmf.getTrustManagers();
}
private KeyStore getCertsFromBundle(String path) throws GeneralSecurityException, IOException {
AssetManager assetManager = this.activity.getAssets();
String[] files = assetManager.list(path);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
for (int i = 0; i < files.length; i++) {
int index = files[i].lastIndexOf('.');
if (index == -1 || !files[i].substring(index).equals(".cer")) {
continue;
}
keyStore.setCertificateEntry("CA" + i, cf.generateCertificate(assetManager.open(path + "/" + files[i])));
}
return keyStore;
}
private KeyStore getCertsFromKeyStore(String storeType) throws GeneralSecurityException, IOException {
KeyStore store = KeyStore.getInstance(storeType);
store.load(null);
return store;
}
}

View File

@@ -1,20 +0,0 @@
package com.silkimen.http;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
public class HostnameVerifierFactory {
private final HostnameVerifier noOpVerifier;
public HostnameVerifierFactory() {
this.noOpVerifier = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
public HostnameVerifier getNoOpVerifier() {
return this.noOpVerifier;
}
}

View File

@@ -0,0 +1,57 @@
package com.silkimen.http;
import android.content.Context;
import android.security.KeyChain;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509ExtendedKeyManager;
public class KeyChainKeyManager extends X509ExtendedKeyManager {
private final String alias;
private final X509Certificate[] chain;
private final PrivateKey key;
public KeyChainKeyManager(String alias, PrivateKey key, X509Certificate[] chain) {
this.alias = alias;
this.key = key;
this.chain = chain;
}
@Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
return this.alias;
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return chain;
}
@Override
public PrivateKey getPrivateKey(String alias) {
return key;
}
@Override
public final String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override
public final String[] getClientAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override
public final String[] getServerAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,69 @@
package com.silkimen.http;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import com.silkimen.http.TLSSocketFactory;
public class TLSConfiguration {
private TrustManager[] trustManagers;
private KeyManager[] keyManagers;
private HostnameVerifier hostnameVerifier;
private SSLSocketFactory socketFactory;
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
}
public void setKeyManagers(KeyManager[] keyManagers) {
this.keyManagers = keyManagers;
this.socketFactory = null;
}
public void setTrustManagers(TrustManager[] trustManagers) {
this.trustManagers = trustManagers;
this.socketFactory = null;
}
public HostnameVerifier getHostnameVerifier() {
return this.hostnameVerifier;
}
public SSLSocketFactory getTLSSocketFactory() throws IOException {
if (this.socketFactory != null) {
return this.socketFactory;
}
try {
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();
}
return this.socketFactory;
} catch (GeneralSecurityException e) {
IOException ioException = new IOException("Security exception occured while configuring TLS context");
ioException.initCause(e);
throw ioException;
}
}
}

View File

@@ -28,8 +28,8 @@ public class TLSSocketFactory extends SSLSocketFactory {
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(delegate.createSocket(socket, host, port, autoClose));
}
@Override

View File

@@ -1,64 +0,0 @@
package com.silkimen.http;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class TrustManagersFactory {
private final TrustManager[] noOpTrustManager;
public TrustManagersFactory() {
this.noOpTrustManager = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// intentionally left blank
}
} };
}
public TrustManager[] getNoopTrustManagers() {
return this.noOpTrustManager;
}
public TrustManager[] getPinnedTrustManagers(ArrayList<Certificate> pinnedCerts) throws IOException {
if (pinnedCerts == null || pinnedCerts.size() == 0) {
throw new IOException("You must add at least 1 certificate in order to pin to certificates");
}
try {
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
for (int i = 0; i < pinnedCerts.size(); i++) {
keyStore.setCertificateEntry("CA" + i, pinnedCerts.get(i));
}
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
return tmf.getTrustManagers();
} catch (GeneralSecurityException e) {
IOException ioException = new IOException("Security exception configuring SSL trust managers");
ioException.initCause(e);
throw ioException;
}
}
}

View File

@@ -1,7 +1,7 @@
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var cordovaProxy = require('cordova/exec/proxy');
var helpers = require(pluginId + '.helpers');
var jsUtil = require(pluginId + '.js-util');
function serializeJsonData(data) {
try {
@@ -29,7 +29,7 @@ function serializeParams(params) {
if (params === null) return '';
return Object.keys(params).map(function(key) {
if (helpers.getTypeOf(params[key]) === 'Array') {
if (jsUtil.getTypeOf(params[key]) === 'Array') {
return serializeArray(key, params[key]);
}
@@ -56,7 +56,7 @@ function createXhrSuccessObject(xhr) {
return {
url: xhr.responseURL,
status: xhr.status,
data: helpers.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response,
data: jsUtil.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response,
headers: deserializeResponseHeaders(xhr.getAllResponseHeaders())
};
}
@@ -65,7 +65,7 @@ function createXhrFailureObject(xhr) {
var obj = {};
obj.headers = xhr.getAllResponseHeaders();
obj.error = helpers.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response;
obj.error = jsUtil.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response;
obj.error = obj.error || 'advanced-http: please check browser console for error messages';
if (xhr.responseURL) obj.url = xhr.responseURL;
@@ -184,8 +184,11 @@ var browserInterface = {
downloadFile: function (success, failure, opts) {
return failure('advanced-http: function "downloadFile" not supported on browser platform');
},
setSSLCertMode: function (success, failure, opts) {
return failure('advanced-http: function "setSSLCertMode" not supported on browser platform');
setServerTrustMode: function (success, failure, opts) {
return failure('advanced-http: function "setServerTrustMode" not supported on browser platform');
},
setClientAuthMode: function (success, failure, opts) {
return failure('advanced-http: function "setClientAuthMode" not supported on browser platform');
},
disableRedirect: function (success, failure, opts) {
return failure('advanced-http: function "disableRedirect" not supported on browser platform');

View File

@@ -4,7 +4,7 @@
@interface CordovaHttpPlugin : CDVPlugin
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command;
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command;
- (void)disableRedirect:(CDVInvokedUrlCommand*)command;
- (void)post:(CDVInvokedUrlCommand*)command;
- (void)get:(CDVInvokedUrlCommand*)command;

View File

@@ -20,12 +20,10 @@
@implementation CordovaHttpPlugin {
AFSecurityPolicy *securityPolicy;
bool redirect;
AFHTTPSessionManager *manager;
}
- (void)pluginInitialize {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
manager = [AFHTTPSessionManager manager];
redirect = true;
}
@@ -106,8 +104,12 @@
case -1009:
// no connection
return [NSNumber numberWithInt:-6];
case -1202:
// untrusted SSL certificate
case -1200: // secure connection failed
case -1201: // certificate has bad date
case -1202: // certificate untrusted
case -1203: // certificate has unknown root
case -1204: // certificate is not yet valid
// configuring SSL failed
return [NSNumber numberWithInt:-2];
default:
return [NSNumber numberWithInt:-1];
@@ -126,7 +128,7 @@
return headerFieldsCopy;
}
- (void)setSSLCertMode:(CDVInvokedUrlCommand*)command {
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command {
NSString *certMode = [command.arguments objectAtIndex:0];
if ([certMode isEqualToString: @"default"] || [certMode isEqualToString: @"legacy"]) {
@@ -162,6 +164,7 @@
}
- (void)get:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -201,6 +204,7 @@
}
- (void)head:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -240,6 +244,7 @@
}
- (void)delete:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -279,6 +284,7 @@
}
- (void)post:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -320,6 +326,7 @@
}
- (void)put:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -361,6 +368,7 @@
}
- (void)patch:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -402,6 +410,7 @@
}
- (void)uploadFile:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
@@ -456,6 +465,7 @@
}
- (void)downloadFile:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];

View File

@@ -15,7 +15,7 @@
<button id="nextBtn">Run next test</button>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="app-test-definitions.js"></script>
<script type="text/javascript" src="e2e-specs.js"></script>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

View File

@@ -1,14 +1,14 @@
const hooks = {
onBeforeEachTest: function(done) {
cordova.plugin.http.clearCookies();
helpers.setDefaultCertMode(done);
helpers.setDefaultServerTrustMode(done);
}
};
const helpers = {
setDefaultCertMode: function(done) { cordova.plugin.http.setSSLCertMode('default', done, done); },
setNoCheckCertMode: function(done) { cordova.plugin.http.setSSLCertMode('nocheck', done, done); },
setPinnedCertMode: function(done) { cordova.plugin.http.setSSLCertMode('pinned', done, done); },
setDefaultServerTrustMode: function(done) { cordova.plugin.http.setServerTrustMode('default', done, done); },
setNoCheckServerTrustMode: function(done) { cordova.plugin.http.setServerTrustMode('nocheck', done, done); },
setPinnedServerTrustMode: function(done) { cordova.plugin.http.setServerTrustMode('pinned', done, done); },
setJsonSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('json')); },
setUtf8StringSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('utf8')); },
setUrlEncodedSerializer: function(done) { done(cordova.plugin.http.setDataSerializer('urlencoded')); },
@@ -38,7 +38,7 @@ const helpers = {
};
const messageFactory = {
sslTrustAnchor: function() { return 'SSL handshake failed: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.' },
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.' }
}
@@ -91,7 +91,7 @@ const tests = [
{
description: 'should accept bad cert (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.setNoCheckCertMode,
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.get('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
@@ -101,7 +101,7 @@ const tests = [
{
description: 'should accept bad cert (PUT)',
expected: 'rejected: {"status":405, ... // will be rejected because PUT is not allowed',
before: helpers.setNoCheckCertMode,
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.put('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
@@ -111,7 +111,7 @@ const tests = [
{
description: 'should accept bad cert (POST)',
expected: 'rejected: {"status":405, ... // will be rejected because POST is not allowed',
before: helpers.setNoCheckCertMode,
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.post('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
@@ -121,7 +121,7 @@ const tests = [
{
description: 'should accept bad cert (PATCH)',
expected: 'rejected: {"status":405, ... // will be rejected because PATCH is not allowed',
before: helpers.setNoCheckCertMode,
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.patch('https://self-signed.badssl.com/', { test: 'testString' }, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
@@ -131,7 +131,7 @@ const tests = [
{
description: 'should accept bad cert (DELETE)',
expected: 'rejected: {"status":405, ... // will be rejected because DELETE is not allowed',
before: helpers.setNoCheckCertMode,
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.delete('https://self-signed.badssl.com/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('rejected');
@@ -141,7 +141,7 @@ const tests = [
{
description: 'should fetch data from http://httpbin.org/ (GET)',
expected: 'resolved: {"status":200, ...',
before: helpers.setNoCheckCertMode,
before: helpers.setNoCheckServerTrustMode,
func: function(resolve, reject) { cordova.plugin.http.get('http://httpbin.org/', {}, {}, resolve, reject); },
validationFunc: function(driver, result) {
result.type.should.be.equal('resolved');
@@ -383,8 +383,7 @@ const tests = [
JSON
.parse(result.data.data)
.headers
.Cookie
.should.be.equal('');
.should.not.have.property('Cookie');
}
},
{
@@ -468,7 +467,7 @@ const tests = [
{
description: 'should pin SSL cert correctly (GET)',
expected: 'resolved: {"status": 200 ...',
before: helpers.setPinnedCertMode,
before: helpers.setPinnedServerTrustMode,
func: function(resolve, reject) {
cordova.plugin.http.get('https://httpbin.org', {}, {}, resolve, reject);
},
@@ -480,7 +479,7 @@ const tests = [
{
description: 'should reject when pinned cert does not match received server cert (GET)',
expected: 'rejected: {"status": -2 ...',
before: helpers.setPinnedCertMode,
before: helpers.setPinnedServerTrustMode,
func: function(resolve, reject) {
cordova.plugin.http.get('https://sha512.badssl.com/', {}, {}, resolve, reject);
},

View File

@@ -4,7 +4,7 @@ const wd = require('wd');
const apps = require('./helpers/apps');
const caps = Object.assign({}, require('./helpers/caps'));
const serverConfig = require('./helpers/server');
const testDefinitions = require('../app-test-definitions');
const testDefinitions = require('../e2e-specs');
const pkgjson = require('../../package.json');
describe('Advanced HTTP', function() {

View File

@@ -10,12 +10,13 @@ describe('Advanced HTTP public interface', function () {
const getDependenciesBlueprint = () => {
const messages = require('../www/messages');
const globalConfigs = require('../www/global-configs');
const jsUtil = require('../www/js-util');
const ToughCookie = require('../www/umd-tough-cookie');
const lodash = require('../www/lodash');
const WebStorageCookieStore = require('../www/local-storage-store')(ToughCookie, lodash);
const cookieHandler = require('../www/cookie-handler')(null, ToughCookie, WebStorageCookieStore);
const helpers = require('../www/helpers')(cookieHandler, messages);
const urlUtil = require('../www/url-util')(helpers);
const helpers = require('../www/helpers')(jsUtil, cookieHandler, messages);
const urlUtil = require('../www/url-util')(jsUtil);
return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs };
};
@@ -131,8 +132,8 @@ describe('Advanced HTTP public interface', function () {
});
describe('URL util', function () {
const helpers = require('../www/helpers')(null, null);
const util = require('../www/url-util')(helpers);
const jsUtil = require('../www/js-util');
const util = require('../www/url-util')(jsUtil);
it('parses URL with protocol, hostname and path correctly', () => {
util.parseUrl('http://ilkimen.net/test').should.include({
@@ -234,3 +235,30 @@ describe('URL util', function () {
.should.equal('http://ilkimen.net/?myParam=myValue&param1=value1#myHash');
});
});
describe('Common helpers', function () {
describe('mergeHeaders(globalHeaders, localHeaders)', function () {
const init = require('../www/helpers');
init.debug = true;
const helpers = init(null, null, null);
it('merges empty header sets correctly', () => {
helpers.mergeHeaders({}, {}).should.eql({});
});
});
describe('getCookieHeader(url)', function () {
it('resolves cookie header correctly when no cookie is set #198', () => {
const helpers = require('../www/helpers')(null, { getCookieString: () => '' }, null);
helpers.getCookieHeader('http://ilkimen.net').should.eql({});
});
it('resolves cookie header correctly when a cookie is set', () => {
const helpers = require('../www/helpers')(null, { getCookieString: () => 'cookie=value' }, null);
helpers.getCookieHeader('http://ilkimen.net').should.eql({ Cookie: 'cookie=value' });
});
});
})

View File

@@ -7,12 +7,13 @@ var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var exec = require('cordova/exec');
var messages = require(pluginId + '.messages');
var globalConfigs = require(pluginId + '.global-configs');
var jsUtil = require(pluginId + '.js-util');
var ToughCookie = require(pluginId + '.tough-cookie');
var lodash = require(pluginId + '.lodash');
var WebStorageCookieStore = require(pluginId + '.local-storage-store')(ToughCookie, lodash);
var cookieHandler = require(pluginId + '.cookie-handler')(window.localStorage, ToughCookie, WebStorageCookieStore);
var helpers = require(pluginId + '.helpers')(cookieHandler, messages);
var urlUtil = require(pluginId + '.url-util')(helpers);
var helpers = require(pluginId + '.helpers')(jsUtil, cookieHandler, messages);
var urlUtil = require(pluginId + '.url-util')(jsUtil);
var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs);
module.exports = publicInterface;

View File

@@ -1,13 +1,14 @@
module.exports = function init(cookieHandler, messages) {
module.exports = function init(jsUtil, cookieHandler, messages) {
var validSerializers = ['urlencoded', 'json', 'utf8'];
var validCertModes = ['default', 'nocheck', 'pinned', 'legacy'];
var validClientAuthModes = ['none', 'systemstore', 'file'];
var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download'];
return {
var interface = {
b64EncodeUnicode: b64EncodeUnicode,
getTypeOf: getTypeOf,
checkSerializer: checkSerializer,
checkSSLCertMode: checkSSLCertMode,
checkClientAuthMode: checkClientAuthMode,
checkForBlacklistedHeaderKey: checkForBlacklistedHeaderKey,
checkForInvalidHeaderValue: checkForInvalidHeaderValue,
injectCookieHandler: injectCookieHandler,
@@ -18,6 +19,24 @@ module.exports = function init(cookieHandler, messages) {
handleMissingOptions: handleMissingOptions
};
// expose all functions for testing purposes
if (init.debug) {
interface.mergeHeaders = mergeHeaders;
interface.checkForValidStringValue = checkForValidStringValue;
interface.checkKeyValuePairObject = checkKeyValuePairObject;
interface.checkHttpMethod = checkHttpMethod;
interface.checkTimeoutValue = checkTimeoutValue;
interface.checkHeadersObject = checkHeadersObject;
interface.checkParamsObject = checkParamsObject;
interface.resolveCookieString = resolveCookieString;
interface.createFileEntry = createFileEntry;
interface.getCookieHeader = getCookieHeader;
interface.getMatchingHostHeaders = getMatchingHostHeaders;
interface.getAllowedDataTypes = getAllowedDataTypes;
}
return interface;
// Thanks Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
@@ -41,7 +60,7 @@ module.exports = function init(cookieHandler, messages) {
}
function checkForValidStringValue(list, value, onInvalidValueMessage) {
if (getTypeOf(value) !== 'String') {
if (jsUtil.getTypeOf(value) !== 'String') {
throw new Error(onInvalidValueMessage + ' ' + list.join(', '));
}
@@ -55,14 +74,14 @@ module.exports = function init(cookieHandler, messages) {
}
function checkKeyValuePairObject(obj, allowedChildren, onInvalidValueMessage) {
if (getTypeOf(obj) !== 'Object') {
if (jsUtil.getTypeOf(obj) !== 'Object') {
throw new Error(onInvalidValueMessage);
}
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
if (allowedChildren.indexOf(getTypeOf(obj[keys[i]])) === -1) {
if (allowedChildren.indexOf(jsUtil.getTypeOf(obj[keys[i]])) === -1) {
throw new Error(onInvalidValueMessage);
}
}
@@ -82,6 +101,10 @@ module.exports = function init(cookieHandler, messages) {
return checkForValidStringValue(validCertModes, mode, messages.INVALID_SSL_CERT_MODE);
}
function checkClientAuthMode(mode) {
return checkForValidStringValue(validClientAuthModes, mode, messages.INVALID_CLIENT_AUTH_MODE);
}
function checkForBlacklistedHeaderKey(key) {
if (key.toLowerCase() === 'cookie') {
throw new Error(messages.ADDING_COOKIES_NOT_SUPPORTED);
@@ -91,7 +114,7 @@ module.exports = function init(cookieHandler, messages) {
}
function checkForInvalidHeaderValue(value) {
if (getTypeOf(value) !== 'String') {
if (jsUtil.getTypeOf(value) !== 'String') {
throw new Error(messages.INVALID_HEADERS_VALUE);
}
@@ -99,7 +122,7 @@ module.exports = function init(cookieHandler, messages) {
}
function checkTimeoutValue(timeout) {
if (getTypeOf(timeout) !== 'Number' || timeout < 0) {
if (jsUtil.getTypeOf(timeout) !== 'Number' || timeout < 0) {
throw new Error(messages.INVALID_TIMEOUT_VALUE);
}
@@ -153,7 +176,13 @@ module.exports = function init(cookieHandler, messages) {
}
function getCookieHeader(url) {
return { Cookie: cookieHandler.getCookieString(url) };
var cookieString = cookieHandler.getCookieString(url);
if (cookieString.length) {
return { Cookie: cookieHandler.getCookieString(url) };
}
return {};
}
function getMatchingHostHeaders(url, headersList) {
@@ -174,30 +203,6 @@ module.exports = function init(cookieHandler, messages) {
return mergedHeaders;
}
// typeof is not working reliably in JS
function getTypeOf(object) {
switch (Object.prototype.toString.call(object)) {
case '[object Array]':
return 'Array';
case '[object Boolean]':
return 'Boolean';
case '[object Function]':
return 'Function';
case '[object Null]':
return 'Null';
case '[object Number]':
return 'Number';
case '[object Object]':
return 'Object';
case '[object String]':
return 'String';
case '[object Undefined]':
return 'Undefined';
default:
return 'Unknown';
}
}
function getAllowedDataTypes(dataSerializer) {
switch (dataSerializer) {
case 'utf8':
@@ -210,7 +215,7 @@ module.exports = function init(cookieHandler, messages) {
}
function getProcessedData(data, dataSerializer) {
var currentDataType = getTypeOf(data);
var currentDataType = jsUtil.getTypeOf(data);
var allowedDataTypes = getAllowedDataTypes(dataSerializer);
if (allowedDataTypes.indexOf(currentDataType) === -1) {
@@ -225,11 +230,11 @@ module.exports = function init(cookieHandler, messages) {
}
function handleMissingCallbacks(successFn, failFn) {
if (getTypeOf(successFn) !== 'Function') {
if (jsUtil.getTypeOf(successFn) !== 'Function') {
throw new Error(messages.MANDATORY_SUCCESS);
}
if (getTypeOf(failFn) !== 'Function') {
if (jsUtil.getTypeOf(failFn) !== 'Function') {
throw new Error(messages.MANDATORY_FAIL);
}
}
@@ -243,7 +248,7 @@ module.exports = function init(cookieHandler, messages) {
timeout: checkTimeoutValue(options.timeout || globals.timeout),
headers: checkHeadersObject(options.headers || {}),
params: checkParamsObject(options.params || {}),
data: getTypeOf(options.data) === 'Undefined' ? null : options.data,
data: jsUtil.getTypeOf(options.data) === 'Undefined' ? null : options.data,
filePath: options.filePath || '',
name: options.name || ''
};

26
www/js-util.js Normal file
View File

@@ -0,0 +1,26 @@
module.exports = {
// typeof is not working reliably in JS
getTypeOf: function (object) {
switch (Object.prototype.toString.call(object)) {
case '[object Array]':
return 'Array';
case '[object Boolean]':
return 'Boolean';
case '[object Function]':
return 'Function';
case '[object Null]':
return 'Null';
case '[object Number]':
return 'Number';
case '[object Object]':
return 'Object';
case '[object String]':
return 'String';
case '[object Undefined]':
return 'Undefined';
default:
return 'Unknown';
}
}
}

View File

@@ -6,6 +6,7 @@ module.exports = {
INVALID_HTTP_METHOD: 'advanced-http: invalid HTTP method, supported methods are:',
INVALID_DATA_SERIALIZER: 'advanced-http: invalid serializer, supported serializers are:',
INVALID_SSL_CERT_MODE: 'advanced-http: invalid SSL cert mode, supported modes are:',
INVALID_CLIENT_AUTH_MODE: 'advanced-http: invalid client certificate authentication mode, supported modes are:',
INVALID_HEADERS_VALUE: 'advanced-http: header values must be strings',
INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value',
INVALID_PARAMS_VALUE: 'advanced-http: invalid params object, needs to be an object with strings'

View File

@@ -12,7 +12,10 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
getCookieString: getCookieString,
getRequestTimeout: getRequestTimeout,
setRequestTimeout: setRequestTimeout,
setSSLCertMode: setSSLCertMode,
// for being backward compatible
setSSLCertMode: setServerTrustMode,
setServerTrustMode: setServerTrustMode,
setClientAuthMode: setClientAuthMode,
disableRedirect: disableRedirect,
sendRequest: sendRequest,
post: post,
@@ -88,11 +91,34 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
globalConfigs.timeout = timeout;
}
function setSSLCertMode(mode, success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'setSSLCertMode', [helpers.checkSSLCertMode(mode)]);
function setServerTrustMode(mode, success, failure) {
helpers.handleMissingCallbacks(success, failure);
return exec(success, failure, 'CordovaHttpPlugin', 'setServerTrustMode', [helpers.checkSSLCertMode(mode)]);
}
function setClientAuthMode() {
// filePath is an optional param
var mode = arguments[0];
var success = arguments[1];
var failure = arguments[2];
var filePath = null;
if (arguments.length === 4) {
mode = arguments[0];
filePath = arguments[1];
success = arguments[2];
failure = arguments[3];
}
helpers.handleMissingCallbacks(success, failure);
return exec(success, failure, 'CordovaHttpPlugin', 'setClientAuthMode', [helpers.checkClientAuthMode(mode), filePath]);
}
function disableRedirect(disable, success, failure) {
helpers.handleMissingCallbacks(success, failure);
return exec(success, failure, 'CordovaHttpPlugin', 'disableRedirect', [!!disable]);
}

View File

@@ -1,4 +1,4 @@
module.exports = function init(helpers) {
module.exports = function init(jsUtil) {
return {
parseUrl: parseUrl,
appendQueryParamsString: appendQueryParamsString,
@@ -48,10 +48,10 @@ module.exports = function init(helpers) {
var identifier = parentKey.length ? parentKey + '[' + key + ']' : key;
if (helpers.getTypeOf(object[key]) === 'Array') {
if (jsUtil.getTypeOf(object[key]) === 'Array') {
parts.push(serializeArray(identifier, object[key], encode));
continue;
} else if (helpers.getTypeOf(object[key]) === 'Object') {
} else if (jsUtil.getTypeOf(object[key]) === 'Object') {
parts.push(serializeObject(identifier, object[key], encode));
continue;
}
@@ -66,10 +66,10 @@ module.exports = function init(helpers) {
var parts = [];
for (var i = 0; i < array.length; ++i) {
if (helpers.getTypeOf(array[i]) === 'Array') {
if (jsUtil.getTypeOf(array[i]) === 'Array') {
parts.push(serializeArray(parentKey + '[]', array[i], encode));
continue;
} else if (helpers.getTypeOf(array[i]) === 'Object') {
} else if (jsUtil.getTypeOf(array[i]) === 'Object') {
parts.push(serializeObject(parentKey + '[]' + array[i], encode));
continue;
}