mirror of https://github.com/LeOS-GSI/LeOS-Genesis
parent
99a04ecfb1
commit
51b93b6e14
|
@ -143,4 +143,5 @@ dependencies {
|
||||||
implementation 'com.github.instacart.truetime-android:library-extension-rx:3.3'
|
implementation 'com.github.instacart.truetime-android:library-extension-rx:3.3'
|
||||||
implementation files('libs/httpclientandroidlib-1.2.1.jar')
|
implementation files('libs/httpclientandroidlib-1.2.1.jar')
|
||||||
implementation 'net.zetetic:android-database-sqlcipher:4.4.3@aar'
|
implementation 'net.zetetic:android-database-sqlcipher:4.4.3@aar'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,16 +221,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities="com.darkweb.genesissearchengine.fileprovider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/provider_paths" />
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="com.darkweb.genesissearchengine.provider"
|
android:authorities="com.darkweb.genesissearchengine.provider"
|
||||||
|
|
|
@ -35,6 +35,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.widget.NestedScrollView;
|
import androidx.core.widget.NestedScrollView;
|
||||||
import androidx.fragment.app.FragmentContainerView;
|
import androidx.fragment.app.FragmentContainerView;
|
||||||
|
@ -703,6 +704,8 @@ public class homeController extends AppCompatActivity implements ComponentCallba
|
||||||
if(!status.sSettingIsAppStarted){
|
if(!status.sSettingIsAppStarted){
|
||||||
Intent intent = new Intent(getApplicationContext(), OrbotService.class);
|
Intent intent = new Intent(getApplicationContext(), OrbotService.class);
|
||||||
stopService(intent);
|
stopService(intent);
|
||||||
|
}else {
|
||||||
|
NotificationManagerCompat.from(this).cancelAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1389,6 +1392,7 @@ public class homeController extends AppCompatActivity implements ComponentCallba
|
||||||
|
|
||||||
if(status.sSettingIsAppStarted && !status.mThemeApplying){
|
if(status.sSettingIsAppStarted && !status.mThemeApplying){
|
||||||
if(mGeckoClient.getSession().wasPreviousErrorPage()){
|
if(mGeckoClient.getSession().wasPreviousErrorPage()){
|
||||||
|
pluginController.getInstance().onOrbotInvoke(null, pluginEnums.eOrbotManager.M_NEW_CIRCUIT);
|
||||||
mGeckoClient.onReload(mGeckoView, this);
|
mGeckoClient.onReload(mGeckoView, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -18,26 +17,28 @@ import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
|
import com.darkweb.genesissearchengine.netcipher.client.StrongHttpsClient;
|
||||||
import com.example.myapplication.R;
|
import com.example.myapplication.R;
|
||||||
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
|
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
|
||||||
import org.torproject.android.service.util.Prefs;
|
import org.torproject.android.service.util.Prefs;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.util.Objects;
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||||
|
import ch.boye.httpclientandroidlib.client.methods.HttpGet;
|
||||||
import static java.lang.Thread.sleep;
|
import static java.lang.Thread.sleep;
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,8 +54,8 @@ public class localFileDownloader extends AsyncTask<String, Integer, String> {
|
||||||
private String PROXY_ADDRESS = "localhost";
|
private String PROXY_ADDRESS = "localhost";
|
||||||
private int PROXY_PORT = 9050;
|
private int PROXY_PORT = 9050;
|
||||||
|
|
||||||
private int mID = 123;
|
private int mID;
|
||||||
private String mFileName="";
|
private String mFileName;
|
||||||
private float mTotalByte;
|
private float mTotalByte;
|
||||||
private float mDownloadByte;
|
private float mDownloadByte;
|
||||||
private String mURL;
|
private String mURL;
|
||||||
|
@ -119,6 +120,7 @@ public class localFileDownloader extends AsyncTask<String, Integer, String> {
|
||||||
@Override
|
@Override
|
||||||
protected String doInBackground(String... f_url) {
|
protected String doInBackground(String... f_url) {
|
||||||
int count;
|
int count;
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
try {
|
try {
|
||||||
URL url = new URL(f_url[0]);
|
URL url = new URL(f_url[0]);
|
||||||
Proxy proxy = new Proxy(Proxy.Type.SOCKS, InetSocketAddress.createUnresolved(PROXY_ADDRESS, PROXY_PORT));
|
Proxy proxy = new Proxy(Proxy.Type.SOCKS, InetSocketAddress.createUnresolved(PROXY_ADDRESS, PROXY_PORT));
|
||||||
|
@ -163,7 +165,54 @@ public class localFileDownloader extends AsyncTask<String, Integer, String> {
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
onCancel();
|
onCancel();
|
||||||
}
|
}
|
||||||
|
}else {
|
||||||
|
try {
|
||||||
|
String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%";
|
||||||
|
String urlEncoded = Uri.encode(f_url[0], ALLOWED_URI_CHARS);
|
||||||
|
|
||||||
|
StrongHttpsClient httpclient = new StrongHttpsClient(context);
|
||||||
|
|
||||||
|
httpclient.useProxy(true, "SOCKS", "127.0.0.1", 9050);
|
||||||
|
|
||||||
|
HttpGet httpget = new HttpGet(urlEncoded);
|
||||||
|
HttpResponse response = httpclient.execute(httpget);
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append(response.getStatusLine()).append("\n\n");
|
||||||
|
|
||||||
|
InputStream mStream = response.getEntity().getContent();
|
||||||
|
|
||||||
|
output = new FileOutputStream(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()+"/"+mFileName));
|
||||||
|
byte[] data = new byte[100000];
|
||||||
|
|
||||||
|
long total = 0;
|
||||||
|
|
||||||
|
mTotalByte = response.getEntity().getContentLength();
|
||||||
|
int read;
|
||||||
|
while ((read = mStream.read(data)) != -1) {
|
||||||
|
total += read;
|
||||||
|
int cur = (int) ((total * 100) / response.getEntity().getContentLength());
|
||||||
|
mDownloadByte = cur;
|
||||||
|
publishProgress(Math.min(cur, 100));
|
||||||
|
if (Math.min(cur, 100) > 98) {
|
||||||
|
sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("currentProgress", "currentProgress: " + Math.min(cur, 100) + "\n " + cur);
|
||||||
|
output.write(data, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
build.setContentText("saving file");
|
||||||
|
build.setSmallIcon(android.R.drawable.stat_sys_download);
|
||||||
|
mNotifyManager.notify(mID, build.build());
|
||||||
|
|
||||||
|
output.flush();
|
||||||
|
output.close();
|
||||||
|
mStream.close();
|
||||||
|
}catch (Exception ex){
|
||||||
|
Log.d("sda", "dsa");
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,358 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2014-2016 Hans-Christoph Steiner
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.darkweb.genesissearchengine.netcipher.client.TlsOnlySocketFactory;
|
||||||
|
import com.darkweb.genesissearchengine.netcipher.proxy.OrbotHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
|
||||||
|
public class NetCipher {
|
||||||
|
private static final String TAG = "NetCipher";
|
||||||
|
|
||||||
|
private NetCipher() {
|
||||||
|
// this is a utility class with only static methods
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static Proxy ORBOT_HTTP_PROXY = new Proxy(Proxy.Type.HTTP,
|
||||||
|
new InetSocketAddress("127.0.0.1", 8118));
|
||||||
|
|
||||||
|
private static Proxy proxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the global HTTP proxy for all new {@link HttpURLConnection}s and
|
||||||
|
* {@link HttpsURLConnection}s that are created after this is called.
|
||||||
|
* <p/>
|
||||||
|
* {@link #useTor()} will override this setting. Traffic must be directed
|
||||||
|
* to Tor using the proxy settings, and Orbot has its own proxy settings
|
||||||
|
* for connections that need proxies to work. So if "use Tor" is enabled,
|
||||||
|
* as tested by looking for the static instance of Proxy, then no other
|
||||||
|
* proxy settings are allowed to override the current Tor proxy.
|
||||||
|
*
|
||||||
|
* @param host the IP address for the HTTP proxy to use globally
|
||||||
|
* @param port the port number for the HTTP proxy to use globally
|
||||||
|
*/
|
||||||
|
public static void setProxy(String host, int port) {
|
||||||
|
if (!TextUtils.isEmpty(host) && port > 0) {
|
||||||
|
InetSocketAddress isa = new InetSocketAddress(host, port);
|
||||||
|
setProxy(new Proxy(Proxy.Type.HTTP, isa));
|
||||||
|
} else if (NetCipher.proxy != ORBOT_HTTP_PROXY) {
|
||||||
|
setProxy(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the global HTTP proxy for all new {@link HttpURLConnection}s and
|
||||||
|
* {@link HttpsURLConnection}s that are created after this is called.
|
||||||
|
* <p/>
|
||||||
|
* {@link #useTor()} will override this setting. Traffic must be directed
|
||||||
|
* to Tor using the proxy settings, and Orbot has its own proxy settings
|
||||||
|
* for connections that need proxies to work. So if "use Tor" is enabled,
|
||||||
|
* as tested by looking for the static instance of Proxy, then no other
|
||||||
|
* proxy settings are allowed to override the current Tor proxy.
|
||||||
|
*
|
||||||
|
* @param proxy the HTTP proxy to use globally
|
||||||
|
*/
|
||||||
|
public static void setProxy(Proxy proxy) {
|
||||||
|
if (proxy != null && NetCipher.proxy == ORBOT_HTTP_PROXY) {
|
||||||
|
Log.w(TAG, "useTor is enabled, ignoring new proxy settings!");
|
||||||
|
} else {
|
||||||
|
NetCipher.proxy = proxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently active global HTTP {@link Proxy}.
|
||||||
|
*
|
||||||
|
* @return the active HTTP {@link Proxy}
|
||||||
|
*/
|
||||||
|
public static Proxy getProxy() {
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the global HTTP proxy for all new {@link HttpURLConnection}s and
|
||||||
|
* {@link HttpsURLConnection}s that are created after this is called. This
|
||||||
|
* returns things to the default, proxy-less state.
|
||||||
|
*/
|
||||||
|
public static void clearProxy() {
|
||||||
|
setProxy(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Orbot as the global HTTP proxy for all new {@link HttpURLConnection}
|
||||||
|
* s and {@link HttpsURLConnection}s that are created after this is called.
|
||||||
|
* This overrides all future calls to {@link #setProxy(Proxy)}, except to
|
||||||
|
* clear the proxy, e.g. {@code #setProxy(null)} or {@link #clearProxy()}.
|
||||||
|
* <p/>
|
||||||
|
* Traffic must be directed to Tor using the proxy settings, and Orbot has its
|
||||||
|
* own proxy settings for connections that need proxies to work. So if "use
|
||||||
|
* Tor" is enabled, as tested by looking for the static instance of Proxy,
|
||||||
|
* then no other proxy settings are allowed to override the current Tor proxy.
|
||||||
|
*/
|
||||||
|
public static void useTor() {
|
||||||
|
setProxy(ORBOT_HTTP_PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link TlsOnlySocketFactory} from NetCipher.
|
||||||
|
*
|
||||||
|
* @see HttpsURLConnection#setDefaultSSLSocketFactory(SSLSocketFactory)
|
||||||
|
*/
|
||||||
|
public static TlsOnlySocketFactory getTlsOnlySocketFactory() {
|
||||||
|
return getTlsOnlySocketFactory(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link TlsOnlySocketFactory} from NetCipher, and specify whether
|
||||||
|
* it should use a more compatible, but less strong, suite of ciphers.
|
||||||
|
*
|
||||||
|
* @see HttpsURLConnection#setDefaultSSLSocketFactory(SSLSocketFactory)
|
||||||
|
*/
|
||||||
|
public static TlsOnlySocketFactory getTlsOnlySocketFactory(boolean compatible) {
|
||||||
|
SSLContext sslcontext;
|
||||||
|
try {
|
||||||
|
sslcontext = SSLContext.getInstance("TLSv1");
|
||||||
|
sslcontext.init(null, null, null);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
} catch (KeyManagementException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
return new TlsOnlySocketFactory(sslcontext.getSocketFactory(), compatible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link URL}, and specify whether
|
||||||
|
* it should use a more compatible, but less strong, suite of ciphers.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @param compatible
|
||||||
|
* @return the {@code url} in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(URL url, boolean compatible)
|
||||||
|
throws IOException {
|
||||||
|
// .onion addresses only work via Tor, so force Tor for all of them
|
||||||
|
Proxy proxy = NetCipher.proxy;
|
||||||
|
if (OrbotHelper.isOnionAddress(url))
|
||||||
|
proxy = ORBOT_HTTP_PROXY;
|
||||||
|
|
||||||
|
HttpURLConnection connection;
|
||||||
|
if (proxy != null) {
|
||||||
|
connection = (HttpURLConnection) url.openConnection(proxy);
|
||||||
|
} else {
|
||||||
|
connection = (HttpURLConnection) url.openConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection instanceof HttpsURLConnection) {
|
||||||
|
HttpsURLConnection httpsConnection = ((HttpsURLConnection) connection);
|
||||||
|
SSLSocketFactory tlsOnly = getTlsOnlySocketFactory(compatible);
|
||||||
|
httpsConnection.setSSLSocketFactory(tlsOnly);
|
||||||
|
if (Build.VERSION.SDK_INT < 16) {
|
||||||
|
httpsConnection.setHostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a URL {@link String} using the best
|
||||||
|
* TLS configuration available on the device.
|
||||||
|
*
|
||||||
|
* @param urlString
|
||||||
|
* @return the URL in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(String urlString) throws IOException {
|
||||||
|
URL url = new URL(urlString.replaceFirst("^[Hh][Tt][Tt][Pp]:", "https:"));
|
||||||
|
return getHttpsURLConnection(url, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link Uri} using the best TLS
|
||||||
|
* configuration available on the device.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @return the {@code uri} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(Uri uri) throws IOException {
|
||||||
|
return getHttpsURLConnection(uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link URI} using the best TLS
|
||||||
|
* configuration available on the device.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @return the {@code uri} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(URI uri) throws IOException {
|
||||||
|
if (TextUtils.equals(uri.getScheme(), "https"))
|
||||||
|
return getHttpsURLConnection(uri.toURL(), false);
|
||||||
|
else
|
||||||
|
// otherwise force scheme to https
|
||||||
|
return getHttpsURLConnection(uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link URL} using the best TLS
|
||||||
|
* configuration available on the device.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(URL url) throws IOException {
|
||||||
|
return getHttpsURLConnection(url, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link URL} using a more
|
||||||
|
* compatible, but less strong, suite of ciphers.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getCompatibleHttpsURLConnection(URL url) throws IOException {
|
||||||
|
return getHttpsURLConnection(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link URL}, and specify whether
|
||||||
|
* it should use a more compatible, but less strong, suite of ciphers.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @param compatible
|
||||||
|
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(URL url, boolean compatible)
|
||||||
|
throws IOException {
|
||||||
|
// use default method, but enforce a HttpsURLConnection
|
||||||
|
HttpURLConnection connection = getHttpURLConnection(url, compatible);
|
||||||
|
if (connection instanceof HttpsURLConnection) {
|
||||||
|
return (HttpsURLConnection) connection;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("not an HTTPS connection!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link URL}. If the connection is
|
||||||
|
* {@code https://}, it will use a more compatible, but less strong, TLS
|
||||||
|
* configuration.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getCompatibleHttpURLConnection(URL url) throws IOException {
|
||||||
|
return getHttpURLConnection(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a URL {@link String}. If it is an
|
||||||
|
* {@code https://} link, then this will use the best TLS configuration
|
||||||
|
* available on the device.
|
||||||
|
*
|
||||||
|
* @param urlString
|
||||||
|
* @return the URL in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(String urlString) throws IOException {
|
||||||
|
return getHttpURLConnection(new URL(urlString));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link Uri}. If it is an
|
||||||
|
* {@code https://} link, then this will use the best TLS configuration
|
||||||
|
* available on the device.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @return the {@code uri} in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(Uri uri) throws IOException {
|
||||||
|
return getHttpURLConnection(uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link URI}. If it is an
|
||||||
|
* {@code https://} link, then this will use the best TLS configuration
|
||||||
|
* available on the device.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @return the {@code uri} in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(URI uri) throws IOException {
|
||||||
|
return getHttpURLConnection(uri.toURL());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link URL}. If it is an
|
||||||
|
* {@code https://} link, then this will use the best TLS configuration
|
||||||
|
* available on the device.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @return the {@code url} in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(URL url) throws IOException {
|
||||||
|
return (HttpURLConnection) getHttpURLConnection(url, false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,255 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.client;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.HttpHost;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.HttpHostConnectException;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.OperatedClientConnection;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.SchemeSocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.SocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.impl.conn.DefaultClientConnectionOperator;
|
||||||
|
import ch.boye.httpclientandroidlib.params.HttpParams;
|
||||||
|
import ch.boye.httpclientandroidlib.protocol.HttpContext;
|
||||||
|
|
||||||
|
public class SocksAwareClientConnOperator extends DefaultClientConnectionOperator {
|
||||||
|
|
||||||
|
private static final int CONNECT_TIMEOUT_MILLISECONDS = 60000;
|
||||||
|
private static final int READ_TIMEOUT_MILLISECONDS = 60000;
|
||||||
|
|
||||||
|
private HttpHost mProxyHost;
|
||||||
|
private String mProxyType;
|
||||||
|
private SocksAwareProxyRoutePlanner mRoutePlanner;
|
||||||
|
|
||||||
|
public SocksAwareClientConnOperator(SchemeRegistry registry,
|
||||||
|
HttpHost proxyHost,
|
||||||
|
String proxyType,
|
||||||
|
SocksAwareProxyRoutePlanner proxyRoutePlanner) {
|
||||||
|
super(registry);
|
||||||
|
|
||||||
|
mProxyHost = proxyHost;
|
||||||
|
mProxyType = proxyType;
|
||||||
|
mRoutePlanner = proxyRoutePlanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void openConnection(
|
||||||
|
final OperatedClientConnection conn,
|
||||||
|
final HttpHost target,
|
||||||
|
final InetAddress local,
|
||||||
|
final HttpContext context,
|
||||||
|
final HttpParams params) throws IOException {
|
||||||
|
if (mProxyHost != null) {
|
||||||
|
if (mProxyType != null && mProxyType.equalsIgnoreCase("socks")) {
|
||||||
|
Log.d("StrongHTTPS", "proxying using SOCKS");
|
||||||
|
openSocksConnection(mProxyHost, conn, target, local, context, params);
|
||||||
|
} else {
|
||||||
|
Log.d("StrongHTTPS", "proxying with: " + mProxyType);
|
||||||
|
openNonSocksConnection(conn, target, local, context, params);
|
||||||
|
}
|
||||||
|
} else if (mRoutePlanner != null) {
|
||||||
|
if (mRoutePlanner.isProxy(target)) {
|
||||||
|
// HTTP proxy, already handled by the route planner system
|
||||||
|
Log.d("StrongHTTPS", "proxying using non-SOCKS");
|
||||||
|
openNonSocksConnection(conn, target, local, context, params);
|
||||||
|
} else {
|
||||||
|
// Either SOCKS or direct
|
||||||
|
HttpHost proxy = mRoutePlanner.determineRequiredProxy(target, null, context);
|
||||||
|
if (proxy == null) {
|
||||||
|
Log.d("StrongHTTPS", "not proxying");
|
||||||
|
openNonSocksConnection(conn, target, local, context, params);
|
||||||
|
} else if (mRoutePlanner.isSocksProxy(proxy)) {
|
||||||
|
Log.d("StrongHTTPS", "proxying using SOCKS");
|
||||||
|
openSocksConnection(proxy, conn, target, local, context, params);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Non-SOCKS proxy returned");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("StrongHTTPS", "not proxying");
|
||||||
|
openNonSocksConnection(conn, target, local, context, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openNonSocksConnection(
|
||||||
|
final OperatedClientConnection conn,
|
||||||
|
final HttpHost target,
|
||||||
|
final InetAddress local,
|
||||||
|
final HttpContext context,
|
||||||
|
final HttpParams params) throws IOException {
|
||||||
|
if (conn == null) {
|
||||||
|
throw new IllegalArgumentException("Connection must not be null.");
|
||||||
|
}
|
||||||
|
if (target == null) {
|
||||||
|
throw new IllegalArgumentException("Target host must not be null.");
|
||||||
|
}
|
||||||
|
// local address may be null
|
||||||
|
// @@@ is context allowed to be null?
|
||||||
|
if (params == null) {
|
||||||
|
throw new IllegalArgumentException("Parameters must not be null.");
|
||||||
|
}
|
||||||
|
if (conn.isOpen()) {
|
||||||
|
throw new IllegalArgumentException("Connection must not be open.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
|
||||||
|
final SocketFactory sf = schm.getSocketFactory();
|
||||||
|
|
||||||
|
Socket sock = sf.createSocket();
|
||||||
|
conn.opening(sock, target);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Socket connsock = sf.connectSocket(sock, target.getHostName(),
|
||||||
|
schm.resolvePort(target.getPort()),
|
||||||
|
local, 0, params);
|
||||||
|
|
||||||
|
if (sock != connsock) {
|
||||||
|
sock = connsock;
|
||||||
|
conn.opening(sock, target);
|
||||||
|
}
|
||||||
|
} catch (ConnectException ex) {
|
||||||
|
throw new HttpHostConnectException(target, ex);
|
||||||
|
}
|
||||||
|
prepareSocket(sock, context, params);
|
||||||
|
conn.openCompleted(sf.isSecure(sock), params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derived from the original DefaultClientConnectionOperator.java in Apache HttpClient 4.2
|
||||||
|
private void openSocksConnection(
|
||||||
|
final HttpHost proxy,
|
||||||
|
final OperatedClientConnection conn,
|
||||||
|
final HttpHost target,
|
||||||
|
final InetAddress local,
|
||||||
|
final HttpContext context,
|
||||||
|
final HttpParams params) throws IOException {
|
||||||
|
Socket socket = null;
|
||||||
|
Socket sslSocket = null;
|
||||||
|
try {
|
||||||
|
if (conn == null || target == null || params == null) {
|
||||||
|
throw new IllegalArgumentException("Required argument may not be null");
|
||||||
|
}
|
||||||
|
if (conn.isOpen()) {
|
||||||
|
throw new IllegalStateException("Connection must not be open");
|
||||||
|
}
|
||||||
|
|
||||||
|
Scheme scheme = schemeRegistry.getScheme(target.getSchemeName());
|
||||||
|
SchemeSocketFactory schemeSocketFactory = scheme.getSchemeSocketFactory();
|
||||||
|
|
||||||
|
int port = scheme.resolvePort(target.getPort());
|
||||||
|
String host = target.getHostName();
|
||||||
|
|
||||||
|
// Perform explicit SOCKS4a connection request. SOCKS4a supports remote host name resolution
|
||||||
|
// (i.e., Tor resolves the hostname, which may be an onion address).
|
||||||
|
// The Android (Apache Harmony) Socket class appears to support only SOCKS4 and throws an
|
||||||
|
// exception on an address created using INetAddress.createUnresolved() -- so the typical
|
||||||
|
// technique for using Java SOCKS4a/5 doesn't appear to work on Android:
|
||||||
|
// https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/java/net/PlainSocketImpl.java
|
||||||
|
// See also: http://www.mit.edu/~foley/TinFoil/src/tinfoil/TorLib.java, for a similar implementation
|
||||||
|
|
||||||
|
// From http://en.wikipedia.org/wiki/SOCKS#SOCKS4a:
|
||||||
|
//
|
||||||
|
// field 1: SOCKS version number, 1 byte, must be 0x04 for this version
|
||||||
|
// field 2: command code, 1 byte:
|
||||||
|
// 0x01 = establish a TCP/IP stream connection
|
||||||
|
// 0x02 = establish a TCP/IP port binding
|
||||||
|
// field 3: network byte order port number, 2 bytes
|
||||||
|
// field 4: deliberate invalid IP address, 4 bytes, first three must be 0x00 and the last one must not be 0x00
|
||||||
|
// field 5: the user ID string, variable length, terminated with a null (0x00)
|
||||||
|
// field 6: the domain name of the host we want to contact, variable length, terminated with a null (0x00)
|
||||||
|
|
||||||
|
|
||||||
|
socket = new Socket();
|
||||||
|
conn.opening(socket, target);
|
||||||
|
socket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);
|
||||||
|
socket.connect(new InetSocketAddress(proxy.getHostName(), proxy.getPort()), CONNECT_TIMEOUT_MILLISECONDS);
|
||||||
|
|
||||||
|
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
|
||||||
|
outputStream.write((byte) 0x04);
|
||||||
|
outputStream.write((byte) 0x01);
|
||||||
|
outputStream.writeShort((short) port);
|
||||||
|
outputStream.writeInt(0x01);
|
||||||
|
outputStream.write((byte) 0x00);
|
||||||
|
outputStream.write(host.getBytes());
|
||||||
|
outputStream.write((byte) 0x00);
|
||||||
|
|
||||||
|
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
|
||||||
|
if (inputStream.readByte() != (byte) 0x00 || inputStream.readByte() != (byte) 0x5a) {
|
||||||
|
throw new IOException("SOCKS4a connect failed");
|
||||||
|
}
|
||||||
|
inputStream.readShort();
|
||||||
|
inputStream.readInt();
|
||||||
|
|
||||||
|
if (schemeSocketFactory instanceof SSLSocketFactory) {
|
||||||
|
sslSocket = ((SSLSocketFactory) schemeSocketFactory).createLayeredSocket(socket, host, port, params);
|
||||||
|
conn.opening(sslSocket, target);
|
||||||
|
sslSocket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);
|
||||||
|
prepareSocket(sslSocket, context, params);
|
||||||
|
conn.openCompleted(schemeSocketFactory.isSecure(sslSocket), params);
|
||||||
|
} else {
|
||||||
|
conn.opening(socket, target);
|
||||||
|
socket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);
|
||||||
|
prepareSocket(socket, context, params);
|
||||||
|
conn.openCompleted(schemeSocketFactory.isSecure(socket), params);
|
||||||
|
}
|
||||||
|
// TODO: clarify which connection throws java.net.SocketTimeoutException?
|
||||||
|
} catch (IOException e) {
|
||||||
|
try {
|
||||||
|
if (sslSocket != null) {
|
||||||
|
sslSocket.close();
|
||||||
|
}
|
||||||
|
if (socket != null) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSecureConnection(
|
||||||
|
final OperatedClientConnection conn,
|
||||||
|
final HttpHost target,
|
||||||
|
final HttpContext context,
|
||||||
|
final HttpParams params) throws IOException {
|
||||||
|
if (mProxyHost != null && mProxyType.equalsIgnoreCase("socks"))
|
||||||
|
throw new RuntimeException("operation not supported");
|
||||||
|
else
|
||||||
|
super.updateSecureConnection(conn, target, context, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InetAddress[] resolveHostname(final String host) throws UnknownHostException {
|
||||||
|
if (mProxyHost != null && mProxyType.equalsIgnoreCase("socks"))
|
||||||
|
throw new RuntimeException("operation not supported");
|
||||||
|
else
|
||||||
|
return super.resolveHostname(host);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.client;
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.HttpException;
|
||||||
|
import ch.boye.httpclientandroidlib.HttpHost;
|
||||||
|
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.SchemePortResolver;
|
||||||
|
import ch.boye.httpclientandroidlib.impl.conn.DefaultRoutePlanner;
|
||||||
|
import ch.boye.httpclientandroidlib.protocol.HttpContext;
|
||||||
|
|
||||||
|
public abstract class SocksAwareProxyRoutePlanner extends DefaultRoutePlanner {
|
||||||
|
public SocksAwareProxyRoutePlanner(SchemePortResolver schemePortResolver) {
|
||||||
|
super(schemePortResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpHost determineProxy(
|
||||||
|
HttpHost target,
|
||||||
|
HttpRequest request,
|
||||||
|
HttpContext context) throws HttpException {
|
||||||
|
HttpHost proxy = determineRequiredProxy(target, request, context);
|
||||||
|
if (isSocksProxy(proxy))
|
||||||
|
proxy = null;
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the proxy required for the provided target.
|
||||||
|
*
|
||||||
|
* @param target see {@link #determineProxy(HttpHost, HttpRequest, HttpContext) determineProxy()}
|
||||||
|
* @param request see {@link #determineProxy(HttpHost, HttpRequest, HttpContext) determineProxy()}.
|
||||||
|
* Will be null when called from {@link SocksAwareClientConnOperator} to
|
||||||
|
* determine if target requires a SOCKS proxy, so don't rely on it in this case.
|
||||||
|
* @param context see {@link #determineProxy(HttpHost, HttpRequest, HttpContext) determineProxy()}
|
||||||
|
* @return the proxy required for this target, or null if should connect directly.
|
||||||
|
*/
|
||||||
|
protected abstract HttpHost determineRequiredProxy(
|
||||||
|
HttpHost target,
|
||||||
|
HttpRequest request,
|
||||||
|
HttpContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the provided target is a proxy we define.
|
||||||
|
*
|
||||||
|
* @param target to check
|
||||||
|
* @return true if this is a proxy, false otherwise
|
||||||
|
*/
|
||||||
|
protected abstract boolean isProxy(HttpHost target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the provided target is a SOCKS proxy we define.
|
||||||
|
*
|
||||||
|
* @param target to check
|
||||||
|
* @return true if this target is a SOCKS proxy, false otherwise.
|
||||||
|
*/
|
||||||
|
protected abstract boolean isSocksProxy(HttpHost target);
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
|
||||||
|
public interface StrongBuilder<T extends StrongBuilder, C> {
|
||||||
|
/**
|
||||||
|
* Callback to get a connection handed to you for use,
|
||||||
|
* already set up for NetCipher.
|
||||||
|
*
|
||||||
|
* @param <C> the type of connection created by this builder
|
||||||
|
*/
|
||||||
|
interface Callback<C> {
|
||||||
|
/**
|
||||||
|
* Called when the NetCipher-enhanced connection is ready
|
||||||
|
* for use.
|
||||||
|
*
|
||||||
|
* @param connection the connection
|
||||||
|
*/
|
||||||
|
void onConnected(C connection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if we tried to connect through to Orbot but failed
|
||||||
|
* for some reason
|
||||||
|
*
|
||||||
|
* @param e the reason
|
||||||
|
*/
|
||||||
|
void onConnectionException(Exception e);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if our attempt to get a status from Orbot failed
|
||||||
|
* after a defined period of time. See statusTimeout() on
|
||||||
|
* OrbotInitializer.
|
||||||
|
*/
|
||||||
|
void onTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if you requested validation that we are connecting
|
||||||
|
* through Tor, and while we were able to connect to Orbot, that
|
||||||
|
* validation failed.
|
||||||
|
*/
|
||||||
|
void onInvalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this to configure the Tor proxy from the results
|
||||||
|
* returned by Orbot, using the best available proxy
|
||||||
|
* (SOCKS if possible, else HTTP)
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withBestProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if this builder supports HTTP proxies, false
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
boolean supportsHttpProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this to configure the Tor proxy from the results
|
||||||
|
* returned by Orbot, using the HTTP proxy.
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withHttpProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if this builder supports SOCKS proxies, false
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
boolean supportsSocksProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this to configure the Tor proxy from the results
|
||||||
|
* returned by Orbot, using the SOCKS proxy.
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withSocksProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies your own custom TrustManagers, such as for
|
||||||
|
* replacing the stock keystore support with a custom
|
||||||
|
* keystore.
|
||||||
|
*
|
||||||
|
* @param trustManagers the TrustManagers to use
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withTrustManagers(TrustManager[] trustManagers)
|
||||||
|
throws NoSuchAlgorithmException, KeyManagementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this if you want a weaker set of supported ciphers,
|
||||||
|
* because you are running into compatibility problems with
|
||||||
|
* some server due to a cipher mismatch. The better solution
|
||||||
|
* is to fix the server.
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withWeakCiphers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this if you want the builder to confirm that we are
|
||||||
|
* communicating over Tor, by reaching out to a Tor test
|
||||||
|
* server and confirming our connection status. By default,
|
||||||
|
* this is skipped. Adding this check adds security, but it
|
||||||
|
* has the chance of false negatives (e.g., we cannot reach
|
||||||
|
* that Tor server for some reason).
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withTorValidation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a connection, applying the configuration already
|
||||||
|
* specified in the builder.
|
||||||
|
*
|
||||||
|
* @param status status Intent from OrbotInitializer
|
||||||
|
* @return the connection
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
C build(Intent status) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous version of build(), one that uses OrbotInitializer
|
||||||
|
* internally to get the status and checks the validity of the Tor
|
||||||
|
* connection (if requested). Note that your callback methods may
|
||||||
|
* be invoked on any thread; do not assume that they will be called
|
||||||
|
* on any particular thread.
|
||||||
|
*
|
||||||
|
* @param callback Callback to get a connection handed to you
|
||||||
|
* for use, already set up for NetCipher
|
||||||
|
*/
|
||||||
|
void build(Callback<C> callback);
|
||||||
|
}
|
|
@ -0,0 +1,282 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
* Copyright 2015 str4d
|
||||||
|
* Portions Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import com.darkweb.genesissearchengine.netcipher.proxy.OrbotHelper;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an HttpUrlConnection that connects via Tor through
|
||||||
|
* Orbot.
|
||||||
|
*/
|
||||||
|
abstract public class
|
||||||
|
StrongBuilderBase<T extends StrongBuilderBase, C>
|
||||||
|
implements StrongBuilder<T, C> {
|
||||||
|
/**
|
||||||
|
* Performs an HTTP GET request using the supplied connection
|
||||||
|
* to a supplied URL, returning the String response or
|
||||||
|
* throws an Exception (e.g., cannot reach the server).
|
||||||
|
* This is used as part of validating the Tor connection.
|
||||||
|
*
|
||||||
|
* @param status the status Intent we got back from Orbot
|
||||||
|
* @param connection a connection of the type for the builder
|
||||||
|
* @param url an public Web page
|
||||||
|
* @return the String response from the GET request
|
||||||
|
*/
|
||||||
|
abstract protected String get(Intent status, C connection, String url)
|
||||||
|
throws Exception;
|
||||||
|
|
||||||
|
final static String TOR_CHECK_URL="https://check.torproject.org/api/ip";
|
||||||
|
private final static String PROXY_HOST="127.0.0.1";
|
||||||
|
protected final Context ctxt;
|
||||||
|
protected Proxy.Type proxyType;
|
||||||
|
protected SSLContext sslContext=null;
|
||||||
|
protected boolean useWeakCiphers=false;
|
||||||
|
protected boolean validateTor=false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard constructor.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the StrongBuilderBase
|
||||||
|
* will hold onto the Application singleton
|
||||||
|
*/
|
||||||
|
public StrongBuilderBase(Context ctxt) {
|
||||||
|
this.ctxt=ctxt.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor.
|
||||||
|
*
|
||||||
|
* @param original builder to clone
|
||||||
|
*/
|
||||||
|
public StrongBuilderBase(StrongBuilderBase original) {
|
||||||
|
this.ctxt=original.ctxt;
|
||||||
|
this.proxyType=original.proxyType;
|
||||||
|
this.sslContext=original.sslContext;
|
||||||
|
this.useWeakCiphers=original.useWeakCiphers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withBestProxy() {
|
||||||
|
if (supportsSocksProxy()) {
|
||||||
|
return(withSocksProxy());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return(withHttpProxy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean supportsHttpProxy() {
|
||||||
|
return(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withHttpProxy() {
|
||||||
|
proxyType=Proxy.Type.HTTP;
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean supportsSocksProxy() {
|
||||||
|
return(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withSocksProxy() {
|
||||||
|
proxyType=Proxy.Type.SOCKS;
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withTrustManagers(TrustManager[] trustManagers)
|
||||||
|
throws NoSuchAlgorithmException, KeyManagementException {
|
||||||
|
|
||||||
|
sslContext=SSLContext.getInstance("TLSv1");
|
||||||
|
sslContext.init(null, trustManagers, null);
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withWeakCiphers() {
|
||||||
|
useWeakCiphers=true;
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withTorValidation() {
|
||||||
|
validateTor=true;
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SSLContext getSSLContext() {
|
||||||
|
return(sslContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSocksPort(Intent status) {
|
||||||
|
if (status.getStringExtra(OrbotHelper.EXTRA_STATUS)
|
||||||
|
.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
return(status.getIntExtra(OrbotHelper.EXTRA_PROXY_PORT_SOCKS,
|
||||||
|
9050));
|
||||||
|
}
|
||||||
|
|
||||||
|
return(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHttpPort(Intent status) {
|
||||||
|
if (status.getStringExtra(OrbotHelper.EXTRA_STATUS)
|
||||||
|
.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
return(status.getIntExtra(OrbotHelper.EXTRA_PROXY_PORT_HTTP,
|
||||||
|
8118));
|
||||||
|
}
|
||||||
|
|
||||||
|
return(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SSLSocketFactory buildSocketFactory() {
|
||||||
|
if (sslContext==null) {
|
||||||
|
return(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
SSLSocketFactory result=
|
||||||
|
new TlsOnlySocketFactory(sslContext.getSocketFactory(),
|
||||||
|
useWeakCiphers);
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Proxy buildProxy(Intent status) {
|
||||||
|
Proxy result=null;
|
||||||
|
|
||||||
|
if (status.getStringExtra(OrbotHelper.EXTRA_STATUS)
|
||||||
|
.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
if (proxyType==Proxy.Type.SOCKS) {
|
||||||
|
result=new Proxy(Proxy.Type.SOCKS,
|
||||||
|
new InetSocketAddress(PROXY_HOST, getSocksPort(status)));
|
||||||
|
}
|
||||||
|
else if (proxyType==Proxy.Type.HTTP) {
|
||||||
|
result=new Proxy(Proxy.Type.HTTP,
|
||||||
|
new InetSocketAddress(PROXY_HOST, getHttpPort(status)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(final Callback<C> callback) {
|
||||||
|
OrbotHelper.get(ctxt).addStatusCallback(
|
||||||
|
new OrbotHelper.SimpleStatusCallback() {
|
||||||
|
@Override
|
||||||
|
public void onEnabled(Intent statusIntent) {
|
||||||
|
OrbotHelper.get(ctxt).removeStatusCallback(this);
|
||||||
|
|
||||||
|
try {
|
||||||
|
C connection=build(statusIntent);
|
||||||
|
|
||||||
|
if (validateTor) {
|
||||||
|
validateTor=false;
|
||||||
|
checkTor(callback, statusIntent, connection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback.onConnected(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
callback.onConnectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotYetInstalled() {
|
||||||
|
OrbotHelper.get(ctxt).removeStatusCallback(this);
|
||||||
|
callback.onTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusTimeout() {
|
||||||
|
OrbotHelper.get(ctxt).removeStatusCallback(this);
|
||||||
|
callback.onTimeout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkTor(final Callback<C> callback, final Intent status,
|
||||||
|
final C connection) {
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
String result=get(status, connection, TOR_CHECK_URL);
|
||||||
|
JSONObject json=new JSONObject(result);
|
||||||
|
|
||||||
|
if (json.optBoolean("IsTor", false)) {
|
||||||
|
callback.onConnected(connection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback.onInvalid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
callback.onConnectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an HttpUrlConnection that connects via Tor through
|
||||||
|
* Orbot.
|
||||||
|
*/
|
||||||
|
public class StrongConnectionBuilder
|
||||||
|
extends StrongBuilderBase<StrongConnectionBuilder, HttpURLConnection> {
|
||||||
|
private URL url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a StrongConnectionBuilder using the strongest set
|
||||||
|
* of options for security. Use this if the strongest set of
|
||||||
|
* options is what you want; otherwise, create a
|
||||||
|
* builder via the constructor and configure it as you see fit.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do
|
||||||
|
* @return a configured StrongConnectionBuilder
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
static public StrongConnectionBuilder forMaxSecurity(Context ctxt)
|
||||||
|
throws Exception {
|
||||||
|
return(new StrongConnectionBuilder(ctxt)
|
||||||
|
.withBestProxy());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a builder instance.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; builder will hold onto
|
||||||
|
* Application context
|
||||||
|
*/
|
||||||
|
public StrongConnectionBuilder(Context ctxt) {
|
||||||
|
super(ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor.
|
||||||
|
*
|
||||||
|
* @param original builder to clone
|
||||||
|
*/
|
||||||
|
public StrongConnectionBuilder(StrongConnectionBuilder original) {
|
||||||
|
super(original);
|
||||||
|
this.url=original.url;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
public boolean supportsSocksProxy() {
|
||||||
|
return(false);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the URL to build a connection for.
|
||||||
|
*
|
||||||
|
* @param url the URL
|
||||||
|
* @return the builder
|
||||||
|
* @throws MalformedURLException
|
||||||
|
*/
|
||||||
|
public StrongConnectionBuilder connectTo(String url)
|
||||||
|
throws MalformedURLException {
|
||||||
|
connectTo(new URL(url));
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the URL to build a connection for.
|
||||||
|
*
|
||||||
|
* @param url the URL
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
public StrongConnectionBuilder connectTo(URL url) {
|
||||||
|
this.url=url;
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public HttpURLConnection build(Intent status) throws IOException {
|
||||||
|
return(buildForUrl(status, url));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String get(Intent status, HttpURLConnection connection,
|
||||||
|
String url) throws Exception {
|
||||||
|
HttpURLConnection realConnection=buildForUrl(status, new URL(url));
|
||||||
|
|
||||||
|
return(slurp(realConnection.getInputStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpURLConnection buildForUrl(Intent status, URL urlToUse)
|
||||||
|
throws IOException {
|
||||||
|
URLConnection result;
|
||||||
|
Proxy proxy=buildProxy(status);
|
||||||
|
|
||||||
|
if (proxy==null) {
|
||||||
|
result=urlToUse.openConnection();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result=urlToUse.openConnection(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result instanceof HttpsURLConnection && sslContext!=null) {
|
||||||
|
SSLSocketFactory tlsOnly=buildSocketFactory();
|
||||||
|
HttpsURLConnection https=(HttpsURLConnection)result;
|
||||||
|
|
||||||
|
https.setSSLSocketFactory(tlsOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
return((HttpURLConnection)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on http://stackoverflow.com/a/309718/115145
|
||||||
|
|
||||||
|
public static String slurp(final InputStream is)
|
||||||
|
throws IOException {
|
||||||
|
final char[] buffer = new char[128];
|
||||||
|
final StringBuilder out = new StringBuilder();
|
||||||
|
final Reader in = new InputStreamReader(is, "UTF-8");
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int rsz = in.read(buffer, 0, buffer.length);
|
||||||
|
if (rsz < 0)
|
||||||
|
break;
|
||||||
|
out.append(buffer, 0, rsz);
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.client;
|
||||||
|
|
||||||
|
public class StrongConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ordered to prefer the stronger cipher suites as noted
|
||||||
|
* http://op-co.de/blog/posts/android_ssl_downgrade/
|
||||||
|
*/
|
||||||
|
public static final String ENABLED_CIPHERS[] = {
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
|
||||||
|
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
|
||||||
|
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_RSA_WITH_AES_256_CBC_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||||
|
"SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_RC4_128_MD5" };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ordered to prefer the stronger/newer TLS versions as noted
|
||||||
|
* http://op-co.de/blog/posts/android_ssl_downgrade/
|
||||||
|
*/
|
||||||
|
public static final String ENABLED_PROTOCOLS[] = { "TLSv1.2", "TLSv1.1",
|
||||||
|
"TLSv1" };
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
* Copyright 2015 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.example.myapplication.R;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.HttpHost;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.PlainSocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry;
|
||||||
|
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
|
||||||
|
import ch.boye.httpclientandroidlib.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
|
|
||||||
|
public class StrongHttpsClient extends DefaultHttpClient {
|
||||||
|
|
||||||
|
final Context context;
|
||||||
|
private HttpHost proxyHost;
|
||||||
|
private String proxyType;
|
||||||
|
private SocksAwareProxyRoutePlanner routePlanner;
|
||||||
|
|
||||||
|
private StrongSSLSocketFactory sFactory;
|
||||||
|
private SchemeRegistry mRegistry;
|
||||||
|
|
||||||
|
private final static String TRUSTSTORE_TYPE = "BKS";
|
||||||
|
private final static String TRUSTSTORE_PASSWORD = "changeit";
|
||||||
|
|
||||||
|
public StrongHttpsClient(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
mRegistry = new SchemeRegistry();
|
||||||
|
mRegistry.register(
|
||||||
|
new Scheme(TYPE_HTTP, 80, PlainSocketFactory.getSocketFactory()));
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeyStore keyStore = loadKeyStore();
|
||||||
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
trustManagerFactory.init(keyStore);
|
||||||
|
sFactory = new StrongSSLSocketFactory(context, trustManagerFactory.getTrustManagers(), keyStore, TRUSTSTORE_PASSWORD);
|
||||||
|
mRegistry.register(new Scheme("https", 443, sFactory));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore loadKeyStore () throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException
|
||||||
|
{
|
||||||
|
|
||||||
|
KeyStore trustStore = KeyStore.getInstance(TRUSTSTORE_TYPE);
|
||||||
|
// load our bundled cacerts from raw assets
|
||||||
|
InputStream in = context.getResources().openRawResource(R.raw.debiancacerts);
|
||||||
|
trustStore.load(in, TRUSTSTORE_PASSWORD.toCharArray());
|
||||||
|
|
||||||
|
return trustStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StrongHttpsClient(Context context, KeyStore keystore) {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
mRegistry = new SchemeRegistry();
|
||||||
|
mRegistry.register(
|
||||||
|
new Scheme(TYPE_HTTP, 80, PlainSocketFactory.getSocketFactory()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
sFactory = new StrongSSLSocketFactory(context, trustManagerFactory.getTrustManagers(), keystore, TRUSTSTORE_PASSWORD);
|
||||||
|
mRegistry.register(new Scheme("https", 443, sFactory));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ThreadSafeClientConnManager createClientConnectionManager() {
|
||||||
|
|
||||||
|
return new ThreadSafeClientConnManager(getParams(), mRegistry)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected ClientConnectionOperator createConnectionOperator(
|
||||||
|
SchemeRegistry schreg) {
|
||||||
|
|
||||||
|
return new SocksAwareClientConnOperator(schreg, proxyHost, proxyType,
|
||||||
|
routePlanner);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useProxy(boolean enableTor, String type, String host, int port)
|
||||||
|
{
|
||||||
|
if (enableTor)
|
||||||
|
{
|
||||||
|
this.proxyType = type;
|
||||||
|
|
||||||
|
if (type.equalsIgnoreCase(TYPE_SOCKS))
|
||||||
|
{
|
||||||
|
proxyHost = new HttpHost(host, port);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
proxyHost = new HttpHost(host, port, type);
|
||||||
|
getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getParams().removeParameter(ConnRoutePNames.DEFAULT_PROXY);
|
||||||
|
proxyHost = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableProxy ()
|
||||||
|
{
|
||||||
|
getParams().removeParameter(ConnRoutePNames.DEFAULT_PROXY);
|
||||||
|
proxyHost = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useProxyRoutePlanner(SocksAwareProxyRoutePlanner proxyRoutePlanner)
|
||||||
|
{
|
||||||
|
routePlanner = proxyRoutePlanner;
|
||||||
|
setRoutePlanner(proxyRoutePlanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT ADVISED, but some sites don't yet have latest protocols and ciphers available, and some
|
||||||
|
* apps still need to support them
|
||||||
|
* https://dev.guardianproject.info/issues/5644
|
||||||
|
*/
|
||||||
|
public void enableSSLCompatibilityMode() {
|
||||||
|
sFactory.setEnableStongerDefaultProtocalVersion(false);
|
||||||
|
sFactory.setEnableStongerDefaultSSLCipherSuite(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static String TYPE_SOCKS = "socks";
|
||||||
|
public final static String TYPE_HTTP = "http";
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.LayeredSchemeSocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.params.HttpParams;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
|
||||||
|
public class StrongSSLSocketFactory extends
|
||||||
|
ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory implements
|
||||||
|
LayeredSchemeSocketFactory {
|
||||||
|
|
||||||
|
private SSLSocketFactory mFactory = null;
|
||||||
|
|
||||||
|
private Proxy mProxy = null;
|
||||||
|
|
||||||
|
public static final String TLS = "TLS";
|
||||||
|
public static final String SSL = "SSL";
|
||||||
|
public static final String SSLV2 = "SSLv2";
|
||||||
|
|
||||||
|
// private X509HostnameVerifier mHostnameVerifier = new
|
||||||
|
// StrictHostnameVerifier();
|
||||||
|
// private final HostNameResolver mNameResolver = new
|
||||||
|
// StrongHostNameResolver();
|
||||||
|
|
||||||
|
private boolean mEnableStongerDefaultSSLCipherSuite = true;
|
||||||
|
private boolean mEnableStongerDefaultProtocalVersion = true;
|
||||||
|
|
||||||
|
private String[] mProtocols;
|
||||||
|
private String[] mCipherSuites;
|
||||||
|
|
||||||
|
public StrongSSLSocketFactory(Context context,
|
||||||
|
TrustManager[] trustManagers, KeyStore keyStore, String keyStorePassword)
|
||||||
|
throws KeyManagementException, UnrecoverableKeyException,
|
||||||
|
NoSuchAlgorithmException, KeyStoreException, CertificateException,
|
||||||
|
IOException {
|
||||||
|
super(keyStore);
|
||||||
|
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
KeyManager[] km = createKeyManagers(
|
||||||
|
keyStore,
|
||||||
|
keyStorePassword);
|
||||||
|
sslContext.init(km, trustManagers, new SecureRandom());
|
||||||
|
|
||||||
|
mFactory = sslContext.getSocketFactory();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readSSLParameters(SSLSocket sslSocket) {
|
||||||
|
List<String> protocolsToEnable = new ArrayList<String>();
|
||||||
|
List<String> supportedProtocols = Arrays.asList(sslSocket.getSupportedProtocols());
|
||||||
|
for(String enabledProtocol : StrongConstants.ENABLED_PROTOCOLS) {
|
||||||
|
if(supportedProtocols.contains(enabledProtocol)) {
|
||||||
|
protocolsToEnable.add(enabledProtocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mProtocols = protocolsToEnable.toArray(new String[protocolsToEnable.size()]);
|
||||||
|
|
||||||
|
List<String> cipherSuitesToEnable = new ArrayList<String>();
|
||||||
|
List<String> supportedCipherSuites = Arrays.asList(sslSocket.getSupportedCipherSuites());
|
||||||
|
for(String enabledCipherSuite : StrongConstants.ENABLED_CIPHERS) {
|
||||||
|
if(supportedCipherSuites.contains(enabledCipherSuite)) {
|
||||||
|
cipherSuitesToEnable.add(enabledCipherSuite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mCipherSuites = cipherSuitesToEnable.toArray(new String[cipherSuitesToEnable.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyManager[] createKeyManagers(final KeyStore keystore,
|
||||||
|
final String password) throws KeyStoreException,
|
||||||
|
NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||||
|
if (keystore == null) {
|
||||||
|
throw new IllegalArgumentException("Keystore may not be null");
|
||||||
|
}
|
||||||
|
KeyManagerFactory kmfactory = KeyManagerFactory
|
||||||
|
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
kmfactory.init(keystore, password != null ? password.toCharArray()
|
||||||
|
: null);
|
||||||
|
return kmfactory.getKeyManagers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket() throws IOException {
|
||||||
|
Socket newSocket = mFactory.createSocket();
|
||||||
|
enableStrongerDefaults(newSocket);
|
||||||
|
return newSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(Socket socket, String host, int port,
|
||||||
|
boolean autoClose) throws IOException, UnknownHostException {
|
||||||
|
|
||||||
|
Socket newSocket = mFactory.createSocket(socket, host, port, autoClose);
|
||||||
|
|
||||||
|
enableStrongerDefaults(newSocket);
|
||||||
|
|
||||||
|
return newSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults the SSL connection to use a strong cipher suite and TLS version
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
*/
|
||||||
|
private void enableStrongerDefaults(Socket socket) {
|
||||||
|
if (isSecure(socket)) {
|
||||||
|
SSLSocket sslSocket = (SSLSocket) socket;
|
||||||
|
readSSLParameters(sslSocket);
|
||||||
|
|
||||||
|
if (mEnableStongerDefaultProtocalVersion && mProtocols != null) {
|
||||||
|
sslSocket.setEnabledProtocols(mProtocols);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mEnableStongerDefaultSSLCipherSuite && mCipherSuites != null) {
|
||||||
|
sslSocket.setEnabledCipherSuites(mCipherSuites);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSecure(Socket sock) throws IllegalArgumentException {
|
||||||
|
return (sock instanceof SSLSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProxy(Proxy proxy) {
|
||||||
|
mProxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Proxy getProxy() {
|
||||||
|
return mProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableStongerDefaultSSLCipherSuite() {
|
||||||
|
return mEnableStongerDefaultSSLCipherSuite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableStongerDefaultSSLCipherSuite(boolean enable) {
|
||||||
|
this.mEnableStongerDefaultSSLCipherSuite = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableStongerDefaultProtocalVersion() {
|
||||||
|
return mEnableStongerDefaultProtocalVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableStongerDefaultProtocalVersion(boolean enable) {
|
||||||
|
this.mEnableStongerDefaultProtocalVersion = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(HttpParams httpParams) throws IOException {
|
||||||
|
Socket newSocket = mFactory.createSocket();
|
||||||
|
|
||||||
|
enableStrongerDefaults(newSocket);
|
||||||
|
|
||||||
|
return newSocket;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createLayeredSocket(Socket arg0, String arg1, int arg2,
|
||||||
|
boolean arg3) throws IOException, UnknownHostException {
|
||||||
|
return ((LayeredSchemeSocketFactory) mFactory).createLayeredSocket(
|
||||||
|
arg0, arg1, arg2, arg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,544 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Bhavit Singh Sengar
|
||||||
|
* Copyright 2015-2016 Hans-Christoph Steiner
|
||||||
|
* Copyright 2015-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* From https://stackoverflow.com/a/29946540
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.client;
|
||||||
|
|
||||||
|
import android.net.SSLCertificateSocketFactory;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.net.ssl.HandshakeCompletedListener;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* While making a secure connection, Android's {@link HttpsURLConnection} falls
|
||||||
|
* back to SSLv3 from TLSv1. This is a bug in android versions < 4.4. It can be
|
||||||
|
* fixed by removing the SSLv3 protocol from Enabled Protocols list. Use this as
|
||||||
|
* the {@link SSLSocketFactory} for
|
||||||
|
* {@link HttpsURLConnection#setDefaultSSLSocketFactory(SSLSocketFactory)}
|
||||||
|
*
|
||||||
|
* @author Bhavit S. Sengar
|
||||||
|
* @author Hans-Christoph Steiner
|
||||||
|
*/
|
||||||
|
public class TlsOnlySocketFactory extends SSLSocketFactory {
|
||||||
|
private static final int HANDSHAKE_TIMEOUT=0;
|
||||||
|
private static final String TAG = "TlsOnlySocketFactory";
|
||||||
|
private final SSLSocketFactory delegate;
|
||||||
|
private final boolean compatible;
|
||||||
|
|
||||||
|
public TlsOnlySocketFactory() {
|
||||||
|
this.delegate =SSLCertificateSocketFactory.getDefault(HANDSHAKE_TIMEOUT, null);
|
||||||
|
this.compatible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TlsOnlySocketFactory(SSLSocketFactory delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.compatible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make {@link SSLSocket}s that are compatible with outdated servers.
|
||||||
|
*
|
||||||
|
* @param delegate
|
||||||
|
* @param compatible
|
||||||
|
*/
|
||||||
|
public TlsOnlySocketFactory(SSLSocketFactory delegate, boolean compatible) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.compatible = compatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getDefaultCipherSuites() {
|
||||||
|
return delegate.getDefaultCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportedCipherSuites() {
|
||||||
|
return delegate.getSupportedCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Socket makeSocketSafe(Socket socket, String host) {
|
||||||
|
if (socket instanceof SSLSocket) {
|
||||||
|
TlsOnlySSLSocket tempSocket=
|
||||||
|
new TlsOnlySSLSocket((SSLSocket) socket, compatible);
|
||||||
|
|
||||||
|
if (delegate instanceof SSLCertificateSocketFactory &&
|
||||||
|
Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
((android.net.SSLCertificateSocketFactory)delegate)
|
||||||
|
.setHostname(socket, host);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tempSocket.setHostname(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket = tempSocket;
|
||||||
|
}
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
|
||||||
|
throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(s, host, port, autoClose), host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(String host, int port) throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(host, port), host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
|
||||||
|
throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort), host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(host, port), host.getHostName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
|
||||||
|
int localPort) throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort),
|
||||||
|
address.getHostName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TlsOnlySSLSocket extends DelegateSSLSocket {
|
||||||
|
|
||||||
|
final boolean compatible;
|
||||||
|
|
||||||
|
private TlsOnlySSLSocket(SSLSocket delegate, boolean compatible) {
|
||||||
|
super(delegate);
|
||||||
|
this.compatible = compatible;
|
||||||
|
|
||||||
|
// badly configured servers can't handle a good config
|
||||||
|
if (compatible) {
|
||||||
|
ArrayList<String> protocols = new ArrayList<String>(Arrays.asList(delegate
|
||||||
|
.getEnabledProtocols()));
|
||||||
|
protocols.remove("SSLv2");
|
||||||
|
protocols.remove("SSLv3");
|
||||||
|
super.setEnabledProtocols(protocols.toArray(new String[protocols.size()]));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Exclude extremely weak EXPORT ciphers. NULL ciphers should
|
||||||
|
* never even have been an option in TLS.
|
||||||
|
*/
|
||||||
|
ArrayList<String> enabled = new ArrayList<String>(10);
|
||||||
|
Pattern exclude = Pattern.compile(".*(EXPORT|NULL).*");
|
||||||
|
for (String cipher : delegate.getEnabledCipherSuites()) {
|
||||||
|
if (!exclude.matcher(cipher).matches()) {
|
||||||
|
enabled.add(cipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.setEnabledCipherSuites(enabled.toArray(new String[enabled.size()]));
|
||||||
|
return;
|
||||||
|
} // else
|
||||||
|
|
||||||
|
// 16-19 support v1.1 and v1.2 but only by default starting in 20+
|
||||||
|
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
|
||||||
|
ArrayList<String> protocols = new ArrayList<String>(Arrays.asList(delegate
|
||||||
|
.getSupportedProtocols()));
|
||||||
|
protocols.remove("SSLv2");
|
||||||
|
protocols.remove("SSLv3");
|
||||||
|
super.setEnabledProtocols(protocols.toArray(new String[protocols.size()]));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Exclude weak ciphers, like EXPORT, MD5, DES, and DH. NULL ciphers
|
||||||
|
* should never even have been an option in TLS.
|
||||||
|
*/
|
||||||
|
ArrayList<String> enabledCiphers = new ArrayList<String>(10);
|
||||||
|
Pattern exclude = Pattern.compile(".*(_DES|DH_|DSS|EXPORT|MD5|NULL|RC4).*");
|
||||||
|
for (String cipher : delegate.getSupportedCipherSuites()) {
|
||||||
|
if (!exclude.matcher(cipher).matches()) {
|
||||||
|
enabledCiphers.add(cipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.setEnabledCipherSuites(enabledCiphers.toArray(new String[enabledCiphers.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This works around a bug in Android < 19 where SSLv3 is forced
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setEnabledProtocols(String[] protocols) {
|
||||||
|
if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
|
||||||
|
List<String> systemProtocols;
|
||||||
|
if (this.compatible) {
|
||||||
|
systemProtocols = Arrays.asList(delegate.getEnabledProtocols());
|
||||||
|
} else {
|
||||||
|
systemProtocols = Arrays.asList(delegate.getSupportedProtocols());
|
||||||
|
}
|
||||||
|
List<String> enabledProtocols = new ArrayList<String>(systemProtocols);
|
||||||
|
if (enabledProtocols.size() > 1) {
|
||||||
|
enabledProtocols.remove("SSLv2");
|
||||||
|
enabledProtocols.remove("SSLv3");
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "SSL stuck with protocol available for "
|
||||||
|
+ String.valueOf(enabledProtocols));
|
||||||
|
}
|
||||||
|
protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
|
||||||
|
}
|
||||||
|
super.setEnabledProtocols(protocols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DelegateSSLSocket extends SSLSocket {
|
||||||
|
|
||||||
|
protected final SSLSocket delegate;
|
||||||
|
|
||||||
|
DelegateSSLSocket(SSLSocket delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportedCipherSuites() {
|
||||||
|
return delegate.getSupportedCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getEnabledCipherSuites() {
|
||||||
|
return delegate.getEnabledCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabledCipherSuites(String[] suites) {
|
||||||
|
delegate.setEnabledCipherSuites(suites);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportedProtocols() {
|
||||||
|
return delegate.getSupportedProtocols();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getEnabledProtocols() {
|
||||||
|
return delegate.getEnabledProtocols();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabledProtocols(String[] protocols) {
|
||||||
|
delegate.setEnabledProtocols(protocols);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSession getSession() {
|
||||||
|
return delegate.getSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
||||||
|
delegate.addHandshakeCompletedListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
||||||
|
delegate.removeHandshakeCompletedListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startHandshake() throws IOException {
|
||||||
|
delegate.startHandshake();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseClientMode(boolean mode) {
|
||||||
|
delegate.setUseClientMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getUseClientMode() {
|
||||||
|
return delegate.getUseClientMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNeedClientAuth(boolean need) {
|
||||||
|
delegate.setNeedClientAuth(need);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWantClientAuth(boolean want) {
|
||||||
|
delegate.setWantClientAuth(want);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getNeedClientAuth() {
|
||||||
|
return delegate.getNeedClientAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getWantClientAuth() {
|
||||||
|
return delegate.getWantClientAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnableSessionCreation(boolean flag) {
|
||||||
|
delegate.setEnableSessionCreation(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getEnableSessionCreation() {
|
||||||
|
return delegate.getEnableSessionCreation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bind(SocketAddress localAddr) throws IOException {
|
||||||
|
delegate.bind(localAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close() throws IOException {
|
||||||
|
delegate.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(SocketAddress remoteAddr) throws IOException {
|
||||||
|
delegate.connect(remoteAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
|
||||||
|
delegate.connect(remoteAddr, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketChannel getChannel() {
|
||||||
|
return delegate.getChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress getInetAddress() {
|
||||||
|
return delegate.getInetAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
return delegate.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getKeepAlive() throws SocketException {
|
||||||
|
return delegate.getKeepAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress getLocalAddress() {
|
||||||
|
return delegate.getLocalAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLocalPort() {
|
||||||
|
return delegate.getLocalPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress getLocalSocketAddress() {
|
||||||
|
return delegate.getLocalSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getOOBInline() throws SocketException {
|
||||||
|
return delegate.getOOBInline();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() throws IOException {
|
||||||
|
return delegate.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPort() {
|
||||||
|
return delegate.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int getReceiveBufferSize() throws SocketException {
|
||||||
|
return delegate.getReceiveBufferSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress getRemoteSocketAddress() {
|
||||||
|
return delegate.getRemoteSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getReuseAddress() throws SocketException {
|
||||||
|
return delegate.getReuseAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int getSendBufferSize() throws SocketException {
|
||||||
|
return delegate.getSendBufferSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSoLinger() throws SocketException {
|
||||||
|
return delegate.getSoLinger();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int getSoTimeout() throws SocketException {
|
||||||
|
return delegate.getSoTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getTcpNoDelay() throws SocketException {
|
||||||
|
return delegate.getTcpNoDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTrafficClass() throws SocketException {
|
||||||
|
return delegate.getTrafficClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBound() {
|
||||||
|
return delegate.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return delegate.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return delegate.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInputShutdown() {
|
||||||
|
return delegate.isInputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOutputShutdown() {
|
||||||
|
return delegate.isOutputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendUrgentData(int value) throws IOException {
|
||||||
|
delegate.sendUrgentData(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setKeepAlive(boolean keepAlive) throws SocketException {
|
||||||
|
delegate.setKeepAlive(keepAlive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOOBInline(boolean oobinline) throws SocketException {
|
||||||
|
delegate.setOOBInline(oobinline);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
|
||||||
|
delegate.setPerformancePreferences(connectionTime,
|
||||||
|
latency, bandwidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setReceiveBufferSize(int size) throws SocketException {
|
||||||
|
delegate.setReceiveBufferSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReuseAddress(boolean reuse) throws SocketException {
|
||||||
|
delegate.setReuseAddress(reuse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setSendBufferSize(int size) throws SocketException {
|
||||||
|
delegate.setSendBufferSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSoLinger(boolean on, int timeout) throws SocketException {
|
||||||
|
delegate.setSoLinger(on, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setSoTimeout(int timeout) throws SocketException {
|
||||||
|
delegate.setSoTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTcpNoDelay(boolean on) throws SocketException {
|
||||||
|
delegate.setTcpNoDelay(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTrafficClass(int value) throws SocketException {
|
||||||
|
delegate.setTrafficClass(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdownInput() throws IOException {
|
||||||
|
delegate.shutdownInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdownOutput() throws IOException {
|
||||||
|
delegate.shutdownOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspired by https://github.com/k9mail/k-9/commit/54f9fd36a77423a55f63fbf9b1bcea055a239768
|
||||||
|
|
||||||
|
public DelegateSSLSocket setHostname(String host) {
|
||||||
|
try {
|
||||||
|
delegate
|
||||||
|
.getClass()
|
||||||
|
.getMethod("setHostname", String.class)
|
||||||
|
.invoke(delegate, host);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Could not enable SNI", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return delegate.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return delegate.equals(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,701 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2014-2016 Hans-Christoph Steiner
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
* Portions Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to simplify setting up a proxy connection
|
||||||
|
* to Orbot.
|
||||||
|
*
|
||||||
|
* If you are using classes in the info.guardianproject.netcipher.client
|
||||||
|
* package, call OrbotHelper.get(this).init(); from onCreate()
|
||||||
|
* of a custom Application subclass, or from some other guaranteed
|
||||||
|
* entry point to your app. At that point, the
|
||||||
|
* info.guardianproject.netcipher.client classes will be ready
|
||||||
|
* for use.
|
||||||
|
*/
|
||||||
|
public class OrbotHelper implements ProxyHelper {
|
||||||
|
|
||||||
|
private final static int REQUEST_CODE_STATUS = 100;
|
||||||
|
|
||||||
|
public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
|
||||||
|
public final static String ORBOT_MARKET_URI = "market://details?id=" + ORBOT_PACKAGE_NAME;
|
||||||
|
public final static String ORBOT_FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
|
||||||
|
+ ORBOT_PACKAGE_NAME;
|
||||||
|
public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
|
||||||
|
+ ORBOT_PACKAGE_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to Orbot to transparently start Tor services
|
||||||
|
*/
|
||||||
|
public final static String ACTION_START = "org.torproject.android.intent.action.START";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status
|
||||||
|
* included as an {@link #EXTRA_STATUS} {@code String}. Your app should
|
||||||
|
* always receive {@code ACTION_STATUS Intent}s since any other app could
|
||||||
|
* start Orbot. Also, user-triggered starts and stops will also cause
|
||||||
|
* {@code ACTION_STATUS Intent}s to be broadcast.
|
||||||
|
*/
|
||||||
|
public final static String ACTION_STATUS = "org.torproject.android.intent.action.STATUS";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code String} that contains a status constant: {@link #STATUS_ON},
|
||||||
|
* {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
|
||||||
|
* {@link #STATUS_STOPPING}
|
||||||
|
*/
|
||||||
|
public final static String EXTRA_STATUS = "org.torproject.android.intent.extra.STATUS";
|
||||||
|
/**
|
||||||
|
* A {@link String} {@code packageName} for Orbot to direct its status reply
|
||||||
|
* to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
|
||||||
|
*/
|
||||||
|
public final static String EXTRA_PACKAGE_NAME = "org.torproject.android.intent.extra.PACKAGE_NAME";
|
||||||
|
|
||||||
|
public final static String EXTRA_PROXY_PORT_HTTP = "org.torproject.android.intent.extra.HTTP_PROXY_PORT";
|
||||||
|
public final static String EXTRA_PROXY_PORT_SOCKS = "org.torproject.android.intent.extra.SOCKS_PROXY_PORT";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All tor-related services and daemons are stopped
|
||||||
|
*/
|
||||||
|
public final static String STATUS_OFF = "OFF";
|
||||||
|
/**
|
||||||
|
* All tor-related services and daemons have completed starting
|
||||||
|
*/
|
||||||
|
public final static String STATUS_ON = "ON";
|
||||||
|
public final static String STATUS_STARTING = "STARTING";
|
||||||
|
public final static String STATUS_STOPPING = "STOPPING";
|
||||||
|
/**
|
||||||
|
* The user has disabled the ability for background starts triggered by
|
||||||
|
* apps. Fallback to the old Intent that brings up Orbot.
|
||||||
|
*/
|
||||||
|
public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";
|
||||||
|
|
||||||
|
public final static String ACTION_START_TOR = "org.torproject.android.START_TOR";
|
||||||
|
public final static String ACTION_REQUEST_HS = "org.torproject.android.REQUEST_HS_PORT";
|
||||||
|
public final static int START_TOR_RESULT = 0x9234;
|
||||||
|
public final static int HS_REQUEST_CODE = 9999;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
private OrbotHelper() {
|
||||||
|
// only static utility methods, do not instantiate
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a {@link URL} is a Tor Hidden Service host name, also known
|
||||||
|
* as an ".onion address".
|
||||||
|
*
|
||||||
|
* @return whether the host name is a Tor .onion address
|
||||||
|
*/
|
||||||
|
public static boolean isOnionAddress(URL url) {
|
||||||
|
return url.getHost().endsWith(".onion");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a URL {@link String} is a Tor Hidden Service host name, also known
|
||||||
|
* as an ".onion address".
|
||||||
|
*
|
||||||
|
* @return whether the host name is a Tor .onion address
|
||||||
|
*/
|
||||||
|
public static boolean isOnionAddress(String urlString) {
|
||||||
|
try {
|
||||||
|
return isOnionAddress(new URL(urlString));
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a {@link Uri} is a Tor Hidden Service host name, also known
|
||||||
|
* as an ".onion address".
|
||||||
|
*
|
||||||
|
* @return whether the host name is a Tor .onion address
|
||||||
|
*/
|
||||||
|
public static boolean isOnionAddress(Uri uri) {
|
||||||
|
return uri.getHost().endsWith(".onion");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the tor process is running. This method is very
|
||||||
|
* brittle, and is therefore deprecated in favor of using the
|
||||||
|
* {@link #ACTION_STATUS} {@code Intent} along with the
|
||||||
|
* {@link #requestStartTor(Context)} method.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static boolean isOrbotRunning(Context context) {
|
||||||
|
int procId = TorServiceUtils.findProcessId(context);
|
||||||
|
|
||||||
|
return (procId != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isOrbotInstalled(Context context) {
|
||||||
|
return isAppInstalled(context, ORBOT_PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAppInstalled(Context context, String uri) {
|
||||||
|
try {
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
|
||||||
|
return true;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void requestHiddenServiceOnPort(Activity activity, int port) {
|
||||||
|
Intent intent = new Intent(ACTION_REQUEST_HS);
|
||||||
|
intent.setPackage(ORBOT_PACKAGE_NAME);
|
||||||
|
intent.putExtra("hs_port", port);
|
||||||
|
|
||||||
|
activity.startActivityForResult(intent, HS_REQUEST_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First, checks whether Orbot is installed. If Orbot is installed, then a
|
||||||
|
* broadcast {@link Intent} is sent to request Orbot to start
|
||||||
|
* transparently in the background. When Orbot receives this {@code
|
||||||
|
* Intent}, it will immediately reply to the app that called this method
|
||||||
|
* with an {@link #ACTION_STATUS} {@code Intent} that is broadcast to the
|
||||||
|
* {@code packageName} of the provided {@link Context} (i.e. {@link
|
||||||
|
* Context#getPackageName()}.
|
||||||
|
* <p>
|
||||||
|
* That reply {@link #ACTION_STATUS} {@code Intent} could say that the user
|
||||||
|
* has disabled background starts with the status
|
||||||
|
* {@link #STATUS_STARTS_DISABLED}. That means that Orbot ignored this
|
||||||
|
* request. To directly prompt the user to start Tor, use
|
||||||
|
* {@link #requestShowOrbotStart(Activity)}, which will bring up
|
||||||
|
* Orbot itself for the user to manually start Tor. Orbot always broadcasts
|
||||||
|
* it's status, so your app will receive those no matter how Tor gets
|
||||||
|
* started.
|
||||||
|
*
|
||||||
|
* @param context the app {@link Context} will receive the reply
|
||||||
|
* @return whether the start request was sent to Orbot
|
||||||
|
* @see #requestShowOrbotStart(Activity activity)
|
||||||
|
*/
|
||||||
|
public static boolean requestStartTor(Context context) {
|
||||||
|
if (OrbotHelper.isOrbotInstalled(context)) {
|
||||||
|
Log.i("OrbotHelper", "requestStartTor " + context.getPackageName());
|
||||||
|
Intent intent = getOrbotStartIntent(context);
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an {@link Intent} for starting Orbot. Orbot will reply with the
|
||||||
|
* current status to the {@code packageName} of the app in the provided
|
||||||
|
* {@link Context} (i.e. {@link Context#getPackageName()}.
|
||||||
|
*/
|
||||||
|
public static Intent getOrbotStartIntent(Context context) {
|
||||||
|
Intent intent = new Intent(ACTION_START);
|
||||||
|
intent.setPackage(ORBOT_PACKAGE_NAME);
|
||||||
|
intent.putExtra(EXTRA_PACKAGE_NAME, context.getPackageName());
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a barebones {@link Intent} for starting Orbot. This is deprecated
|
||||||
|
* in favor of {@link #getOrbotStartIntent(Context)}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static Intent getOrbotStartIntent() {
|
||||||
|
Intent intent = new Intent(ACTION_START);
|
||||||
|
intent.setPackage(ORBOT_PACKAGE_NAME);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First, checks whether Orbot is installed, then checks whether Orbot is
|
||||||
|
* running. If Orbot is installed and not running, then an {@link Intent} is
|
||||||
|
* sent to request the user to start Orbot, which will show the main Orbot screen.
|
||||||
|
* The result will be returned in
|
||||||
|
* {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
|
||||||
|
* with a {@code requestCode} of {@code START_TOR_RESULT}
|
||||||
|
* <p>
|
||||||
|
* Orbot will also always broadcast the status of starting Tor via the
|
||||||
|
* {@link #ACTION_STATUS} Intent, no matter how it is started.
|
||||||
|
*
|
||||||
|
* @param activity the {@code Activity} that gets the result of the
|
||||||
|
* {@link #START_TOR_RESULT} request
|
||||||
|
* @return whether the start request was sent to Orbot
|
||||||
|
* @see #requestStartTor(Context context)
|
||||||
|
*/
|
||||||
|
public static boolean requestShowOrbotStart(Activity activity) {
|
||||||
|
if (OrbotHelper.isOrbotInstalled(activity)) {
|
||||||
|
if (!OrbotHelper.isOrbotRunning(activity)) {
|
||||||
|
Intent intent = getShowOrbotStartIntent();
|
||||||
|
activity.startActivityForResult(intent, START_TOR_RESULT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent getShowOrbotStartIntent() {
|
||||||
|
Intent intent = new Intent(ACTION_START_TOR);
|
||||||
|
intent.setPackage(ORBOT_PACKAGE_NAME);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent getOrbotInstallIntent(Context context) {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(ORBOT_MARKET_URI));
|
||||||
|
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
|
||||||
|
|
||||||
|
String foundPackageName = null;
|
||||||
|
for (ResolveInfo r : resInfos) {
|
||||||
|
Log.i("OrbotHelper", "market: " + r.activityInfo.packageName);
|
||||||
|
if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
|
||||||
|
|| TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
|
||||||
|
foundPackageName = r.activityInfo.packageName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundPackageName == null) {
|
||||||
|
intent.setData(Uri.parse(ORBOT_FDROID_URI));
|
||||||
|
} else {
|
||||||
|
intent.setPackage(foundPackageName);
|
||||||
|
}
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInstalled(Context context) {
|
||||||
|
return isOrbotInstalled(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestStatus(Context context) {
|
||||||
|
isOrbotRunning(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requestStart(Context context) {
|
||||||
|
return requestStartTor(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getInstallIntent(Context context) {
|
||||||
|
return getOrbotInstallIntent(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getStartIntent(Context context) {
|
||||||
|
return getOrbotStartIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Orbot";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MLM additions */
|
||||||
|
|
||||||
|
private final Context ctxt;
|
||||||
|
private final Handler handler;
|
||||||
|
private boolean isInstalled=false;
|
||||||
|
private Intent lastStatusIntent=null;
|
||||||
|
private Set<StatusCallback> statusCallbacks=
|
||||||
|
newSetFromMap(new WeakHashMap<StatusCallback, Boolean>());
|
||||||
|
private Set<InstallCallback> installCallbacks=
|
||||||
|
newSetFromMap(new WeakHashMap<InstallCallback, Boolean>());
|
||||||
|
private long statusTimeoutMs=30000L;
|
||||||
|
private long installTimeoutMs=60000L;
|
||||||
|
private boolean validateOrbot=true;
|
||||||
|
|
||||||
|
abstract public static class SimpleStatusCallback
|
||||||
|
implements StatusCallback {
|
||||||
|
@Override
|
||||||
|
public void onEnabled(Intent statusIntent) {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStarting() {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopping() {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisabled() {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotYetInstalled() {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface used for reporting the results of an
|
||||||
|
* attempt to install Orbot
|
||||||
|
*/
|
||||||
|
public interface InstallCallback {
|
||||||
|
void onInstalled();
|
||||||
|
void onInstallTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static volatile OrbotHelper INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the singleton, initializing if if needed
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do, as we will hold onto
|
||||||
|
* the Application
|
||||||
|
* @return the singleton
|
||||||
|
*/
|
||||||
|
synchronized public static OrbotHelper get(Context ctxt) {
|
||||||
|
if (INSTANCE==null) {
|
||||||
|
INSTANCE=new OrbotHelper(ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard constructor
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; OrbotInitializer will hold
|
||||||
|
* onto the Application context
|
||||||
|
*/
|
||||||
|
private OrbotHelper(Context ctxt) {
|
||||||
|
this.ctxt=ctxt.getApplicationContext();
|
||||||
|
this.handler=new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a StatusCallback to be called when we find out that
|
||||||
|
* Orbot is ready. If Orbot is ready for use, your callback
|
||||||
|
* will be called with onEnabled() immediately, before this
|
||||||
|
* method returns.
|
||||||
|
*
|
||||||
|
* @param cb a callback
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper addStatusCallback(StatusCallback cb) {
|
||||||
|
statusCallbacks.add(cb);
|
||||||
|
|
||||||
|
if (lastStatusIntent!=null) {
|
||||||
|
String status=
|
||||||
|
lastStatusIntent.getStringExtra(OrbotHelper.EXTRA_STATUS);
|
||||||
|
|
||||||
|
if (status.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
cb.onEnabled(lastStatusIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an existing registered StatusCallback.
|
||||||
|
*
|
||||||
|
* @param cb the callback to remove
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper removeStatusCallback(StatusCallback cb) {
|
||||||
|
statusCallbacks.remove(cb);
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an InstallCallback to be called when we find out that
|
||||||
|
* Orbot is installed
|
||||||
|
*
|
||||||
|
* @param cb a callback
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper addInstallCallback(InstallCallback cb) {
|
||||||
|
installCallbacks.add(cb);
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an existing registered InstallCallback.
|
||||||
|
*
|
||||||
|
* @param cb the callback to remove
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper removeInstallCallback(InstallCallback cb) {
|
||||||
|
installCallbacks.remove(cb);
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets how long of a delay, in milliseconds, after trying
|
||||||
|
* to get a status from Orbot before we give up.
|
||||||
|
* Defaults to 30000ms = 30 seconds = 0.000347222 days
|
||||||
|
*
|
||||||
|
* @param timeoutMs delay period in milliseconds
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper statusTimeout(long timeoutMs) {
|
||||||
|
statusTimeoutMs=timeoutMs;
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets how long of a delay, in milliseconds, after trying
|
||||||
|
* to install Orbot do we assume that it's not happening.
|
||||||
|
* Defaults to 60000ms = 60 seconds = 1 minute = 1.90259e-6 years
|
||||||
|
*
|
||||||
|
* @param timeoutMs delay period in milliseconds
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper installTimeout(long timeoutMs) {
|
||||||
|
installTimeoutMs=timeoutMs;
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, NetCipher ensures that the Orbot on the
|
||||||
|
* device is one of the official builds. Call this method
|
||||||
|
* to skip that validation. Mostly, this is for developers
|
||||||
|
* who have their own custom Orbot builds (e.g., for
|
||||||
|
* dedicated hardware).
|
||||||
|
*
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper skipOrbotValidation() {
|
||||||
|
validateOrbot=false;
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if Orbot is installed (the last time we checked),
|
||||||
|
* false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isInstalled() {
|
||||||
|
return(isInstalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the connection to Orbot, revalidating that it
|
||||||
|
* is installed and requesting fresh status broadcasts.
|
||||||
|
*
|
||||||
|
* @return true if initialization is proceeding, false if
|
||||||
|
* Orbot is not installed
|
||||||
|
*/
|
||||||
|
public boolean init() {
|
||||||
|
Intent orbot=OrbotHelper.getOrbotStartIntent(ctxt);
|
||||||
|
|
||||||
|
if (validateOrbot) {
|
||||||
|
ArrayList<String> hashes=new ArrayList<String>();
|
||||||
|
|
||||||
|
hashes.add("A4:54:B8:7A:18:47:A8:9E:D7:F5:E7:0F:BA:6B:BA:96:F3:EF:29:C2:6E:09:81:20:4F:E3:47:BF:23:1D:FD:5B");
|
||||||
|
hashes.add("A7:02:07:92:4F:61:FF:09:37:1D:54:84:14:5C:4B:EE:77:2C:55:C1:9E:EE:23:2F:57:70:E1:82:71:F7:CB:AE");
|
||||||
|
|
||||||
|
orbot=
|
||||||
|
SignatureUtils.validateBroadcastIntent(ctxt, orbot,
|
||||||
|
hashes, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orbot!=null) {
|
||||||
|
isInstalled=true;
|
||||||
|
handler.postDelayed(onStatusTimeout, statusTimeoutMs);
|
||||||
|
ctxt.registerReceiver(orbotStatusReceiver,
|
||||||
|
new IntentFilter(OrbotHelper.ACTION_STATUS));
|
||||||
|
ctxt.sendBroadcast(orbot);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isInstalled=false;
|
||||||
|
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onNotYetInstalled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(isInstalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given that init() returned false, calling installOrbot()
|
||||||
|
* will trigger an attempt to install Orbot from an available
|
||||||
|
* distribution channel (e.g., the Play Store). Only call this
|
||||||
|
* if the user is expecting it, such as in response to tapping
|
||||||
|
* a dialog button or an action bar item.
|
||||||
|
*
|
||||||
|
* Note that installation may take a long time, even if
|
||||||
|
* the user is proceeding with the installation, due to network
|
||||||
|
* speeds, waiting for user input, and so on. Either specify
|
||||||
|
* a long timeout, or consider the timeout to be merely advisory
|
||||||
|
* and use some other user input to cause you to try
|
||||||
|
* init() again after, presumably, Orbot has been installed
|
||||||
|
* and configured by the user.
|
||||||
|
*
|
||||||
|
* If the user does install Orbot, we will attempt init()
|
||||||
|
* again automatically. Hence, you will probably need user input
|
||||||
|
* to tell you when the user has gotten Orbot up and going.
|
||||||
|
*
|
||||||
|
* @param host the Activity that is triggering this work
|
||||||
|
*/
|
||||||
|
public void installOrbot(Activity host) {
|
||||||
|
handler.postDelayed(onInstallTimeout, installTimeoutMs);
|
||||||
|
|
||||||
|
IntentFilter filter=
|
||||||
|
new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||||
|
|
||||||
|
filter.addDataScheme("package");
|
||||||
|
|
||||||
|
ctxt.registerReceiver(orbotInstallReceiver, filter);
|
||||||
|
host.startActivity(OrbotHelper.getOrbotInstallIntent(ctxt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BroadcastReceiver orbotStatusReceiver=new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctxt, Intent intent) {
|
||||||
|
if (TextUtils.equals(intent.getAction(),
|
||||||
|
OrbotHelper.ACTION_STATUS)) {
|
||||||
|
String status=intent.getStringExtra(OrbotHelper.EXTRA_STATUS);
|
||||||
|
|
||||||
|
if (status.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
lastStatusIntent=intent;
|
||||||
|
handler.removeCallbacks(onStatusTimeout);
|
||||||
|
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onEnabled(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (status.equals(OrbotHelper.STATUS_OFF)) {
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onDisabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (status.equals(OrbotHelper.STATUS_STARTING)) {
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onStarting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (status.equals(OrbotHelper.STATUS_STOPPING)) {
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onStopping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private Runnable onStatusTimeout=new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ctxt.unregisterReceiver(orbotStatusReceiver);
|
||||||
|
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onStatusTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private BroadcastReceiver orbotInstallReceiver=new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctxt, Intent intent) {
|
||||||
|
if (TextUtils.equals(intent.getAction(),
|
||||||
|
Intent.ACTION_PACKAGE_ADDED)) {
|
||||||
|
String pkgName=intent.getData().getEncodedSchemeSpecificPart();
|
||||||
|
|
||||||
|
if (OrbotHelper.ORBOT_PACKAGE_NAME.equals(pkgName)) {
|
||||||
|
isInstalled=true;
|
||||||
|
handler.removeCallbacks(onInstallTimeout);
|
||||||
|
ctxt.unregisterReceiver(orbotInstallReceiver);
|
||||||
|
|
||||||
|
for (InstallCallback cb : installCallbacks) {
|
||||||
|
cb.onInstalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private Runnable onInstallTimeout=new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ctxt.unregisterReceiver(orbotInstallReceiver);
|
||||||
|
|
||||||
|
for (InstallCallback cb : installCallbacks) {
|
||||||
|
cb.onInstallTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
|
||||||
|
if (map.isEmpty()) {
|
||||||
|
return new SetFromMap<E>(map);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("map not empty");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
public interface ProxyHelper {
|
||||||
|
|
||||||
|
public boolean isInstalled (Context context);
|
||||||
|
public void requestStatus (Context context);
|
||||||
|
public boolean requestStart (Context context);
|
||||||
|
public Intent getInstallIntent (Context context);
|
||||||
|
public Intent getStartIntent (Context context);
|
||||||
|
public String getName ();
|
||||||
|
|
||||||
|
public final static String FDROID_PACKAGE_NAME = "org.fdroid.fdroid";
|
||||||
|
public final static String PLAY_PACKAGE_NAME = "com.android.vending";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to Orbot to transparently start Tor services
|
||||||
|
*/
|
||||||
|
public final static String ACTION_START = "android.intent.action.PROXY_START";
|
||||||
|
/**
|
||||||
|
* {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status
|
||||||
|
*/
|
||||||
|
public final static String ACTION_STATUS = "android.intent.action.PROXY_STATUS";
|
||||||
|
/**
|
||||||
|
* {@code String} that contains a status constant: {@link #STATUS_ON},
|
||||||
|
* {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
|
||||||
|
* {@link #STATUS_STOPPING}
|
||||||
|
*/
|
||||||
|
public final static String EXTRA_STATUS = "android.intent.extra.PROXY_STATUS";
|
||||||
|
|
||||||
|
public final static String EXTRA_PROXY_PORT_HTTP = "android.intent.extra.PROXY_PORT_HTTP";
|
||||||
|
public final static String EXTRA_PROXY_PORT_SOCKS = "android.intent.extra.PROXY_PORT_SOCKS";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link String} {@code packageName} for Orbot to direct its status reply
|
||||||
|
* to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
|
||||||
|
*/
|
||||||
|
public final static String EXTRA_PACKAGE_NAME = "android.intent.extra.PROXY_PACKAGE_NAME";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All tor-related services and daemons are stopped
|
||||||
|
*/
|
||||||
|
public final static String STATUS_OFF = "OFF";
|
||||||
|
/**
|
||||||
|
* All tor-related services and daemons have completed starting
|
||||||
|
*/
|
||||||
|
public final static String STATUS_ON = "ON";
|
||||||
|
public final static String STATUS_STARTING = "STARTING";
|
||||||
|
public final static String STATUS_STOPPING = "STOPPING";
|
||||||
|
/**
|
||||||
|
* The user has disabled the ability for background starts triggered by
|
||||||
|
* apps. Fallback to the old Intent that brings up Orbot.
|
||||||
|
*/
|
||||||
|
public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.proxy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class ProxySelector extends java.net.ProxySelector {
|
||||||
|
|
||||||
|
private ArrayList<Proxy> listProxies;
|
||||||
|
|
||||||
|
public ProxySelector ()
|
||||||
|
{
|
||||||
|
super ();
|
||||||
|
|
||||||
|
listProxies = new ArrayList<Proxy>();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addProxy (Proxy.Type type,String host, int port)
|
||||||
|
{
|
||||||
|
Proxy proxy = new Proxy(type,new InetSocketAddress(host, port));
|
||||||
|
listProxies.add(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectFailed(URI uri, SocketAddress address,
|
||||||
|
IOException failure) {
|
||||||
|
Log.w("ProxySelector","could not connect to " + address.toString() + ": " + failure.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Proxy> select(URI uri) {
|
||||||
|
|
||||||
|
return listProxies;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.proxy;
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
public class PsiphonHelper implements ProxyHelper {
|
||||||
|
|
||||||
|
public final static String PACKAGE_NAME = "com.psiphon3";
|
||||||
|
public final static String COMPONENT_NAME = "com.psiphon3.StatusActivity";
|
||||||
|
|
||||||
|
|
||||||
|
public final static String MARKET_URI = "market://details?id=" + PACKAGE_NAME;
|
||||||
|
public final static String FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
|
||||||
|
+ PACKAGE_NAME;
|
||||||
|
public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
|
||||||
|
+ PACKAGE_NAME;
|
||||||
|
|
||||||
|
public final static int DEFAULT_SOCKS_PORT = 1080;
|
||||||
|
public final static int DEFAULT_HTTP_PORT = 8080;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInstalled(Context context) {
|
||||||
|
return isAppInstalled(context, PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean isAppInstalled(Context context, String uri) {
|
||||||
|
try {
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
|
||||||
|
return true;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestStatus(final Context context) {
|
||||||
|
|
||||||
|
Thread thread = new Thread ()
|
||||||
|
{
|
||||||
|
public void run ()
|
||||||
|
{
|
||||||
|
//can connect to default HTTP proxy port?
|
||||||
|
boolean isSocksOpen = false;
|
||||||
|
boolean isHttpOpen = false;
|
||||||
|
|
||||||
|
int socksPort = DEFAULT_SOCKS_PORT;
|
||||||
|
int httpPort = DEFAULT_HTTP_PORT;
|
||||||
|
|
||||||
|
for (int i = 0; i < 10 && (!isSocksOpen); i++)
|
||||||
|
isSocksOpen = isPortOpen("127.0.0.1",socksPort++,100);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10 && (!isHttpOpen); i++)
|
||||||
|
isHttpOpen = isPortOpen("127.0.0.1",httpPort++,100);
|
||||||
|
|
||||||
|
//any other check?
|
||||||
|
|
||||||
|
Intent intent = new Intent(ProxyHelper.ACTION_STATUS);
|
||||||
|
intent.putExtra(EXTRA_PACKAGE_NAME, PACKAGE_NAME);
|
||||||
|
|
||||||
|
if (isSocksOpen && isHttpOpen)
|
||||||
|
{
|
||||||
|
intent.putExtra(EXTRA_STATUS, STATUS_ON);
|
||||||
|
|
||||||
|
intent.putExtra(EXTRA_PROXY_PORT_HTTP, httpPort-1);
|
||||||
|
intent.putExtra(EXTRA_PROXY_PORT_SOCKS, socksPort-1);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intent.putExtra(EXTRA_STATUS, STATUS_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requestStart(Context context) {
|
||||||
|
|
||||||
|
Intent intent = getStartIntent(context);
|
||||||
|
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getInstallIntent(Context context) {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(MARKET_URI));
|
||||||
|
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
|
||||||
|
|
||||||
|
String foundPackageName = null;
|
||||||
|
for (ResolveInfo r : resInfos) {
|
||||||
|
if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
|
||||||
|
|| TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
|
||||||
|
foundPackageName = r.activityInfo.packageName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundPackageName == null) {
|
||||||
|
intent.setData(Uri.parse(FDROID_URI));
|
||||||
|
} else {
|
||||||
|
intent.setPackage(foundPackageName);
|
||||||
|
}
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getStartIntent(Context context) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setComponent(new ComponentName(PACKAGE_NAME, COMPONENT_NAME));
|
||||||
|
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPortOpen(final String ip, final int port, final int timeout) {
|
||||||
|
try {
|
||||||
|
Socket socket = new Socket();
|
||||||
|
socket.connect(new InetSocketAddress(ip, port), timeout);
|
||||||
|
socket.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch(ConnectException ce){
|
||||||
|
ce.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return PACKAGE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.proxy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.AbstractSet;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
class SetFromMap<E> extends AbstractSet<E>
|
||||||
|
implements Serializable {
|
||||||
|
private static final long serialVersionUID = 2454657854757543876L;
|
||||||
|
// Must be named as is, to pass serialization compatibility test.
|
||||||
|
private final Map<E, Boolean> m;
|
||||||
|
private transient Set<E> backingSet;
|
||||||
|
SetFromMap(final Map<E, Boolean> map) {
|
||||||
|
m = map;
|
||||||
|
backingSet = map.keySet();
|
||||||
|
}
|
||||||
|
@Override public boolean equals(Object object) {
|
||||||
|
return backingSet.equals(object);
|
||||||
|
}
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return backingSet.hashCode();
|
||||||
|
}
|
||||||
|
@Override public boolean add(E object) {
|
||||||
|
return m.put(object, Boolean.TRUE) == null;
|
||||||
|
}
|
||||||
|
@Override public void clear() {
|
||||||
|
m.clear();
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return backingSet.toString();
|
||||||
|
}
|
||||||
|
@Override public boolean contains(Object object) {
|
||||||
|
return backingSet.contains(object);
|
||||||
|
}
|
||||||
|
@Override public boolean containsAll(Collection<?> collection) {
|
||||||
|
return backingSet.containsAll(collection);
|
||||||
|
}
|
||||||
|
@Override public boolean isEmpty() {
|
||||||
|
return m.isEmpty();
|
||||||
|
}
|
||||||
|
@Override public boolean remove(Object object) {
|
||||||
|
return m.remove(object) != null;
|
||||||
|
}
|
||||||
|
@Override public boolean retainAll(Collection<?> collection) {
|
||||||
|
return backingSet.retainAll(collection);
|
||||||
|
}
|
||||||
|
@Override public Object[] toArray() {
|
||||||
|
return backingSet.toArray();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public <T> T[] toArray(T[] contents) {
|
||||||
|
return backingSet.toArray(contents);
|
||||||
|
}
|
||||||
|
@Override public Iterator<E> iterator() {
|
||||||
|
return backingSet.iterator();
|
||||||
|
}
|
||||||
|
@Override public int size() {
|
||||||
|
return m.size();
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void readObject(ObjectInputStream stream)
|
||||||
|
throws IOException, ClassNotFoundException {
|
||||||
|
stream.defaultReadObject();
|
||||||
|
backingSet = m.keySet();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,476 @@
|
||||||
|
/***
|
||||||
|
Copyright (c) 2014 CommonsWare, LLC
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
not use this file except in compliance with the License. You may obtain
|
||||||
|
a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.Signature;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SignatureUtils {
|
||||||
|
public static String getOwnSignatureHash(Context ctxt)
|
||||||
|
throws
|
||||||
|
NameNotFoundException,
|
||||||
|
NoSuchAlgorithmException {
|
||||||
|
return(getSignatureHash(ctxt, ctxt.getPackageName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSignatureHash(Context ctxt, String packageName)
|
||||||
|
throws
|
||||||
|
NameNotFoundException,
|
||||||
|
NoSuchAlgorithmException {
|
||||||
|
MessageDigest md=MessageDigest.getInstance("SHA-256");
|
||||||
|
Signature sig=
|
||||||
|
ctxt.getPackageManager()
|
||||||
|
.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures[0];
|
||||||
|
|
||||||
|
return(toHexStringWithColons(md.digest(sig.toByteArray())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on https://stackoverflow.com/a/2197650/115145
|
||||||
|
|
||||||
|
public static String toHexStringWithColons(byte[] bytes) {
|
||||||
|
char[] hexArray=
|
||||||
|
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
|
||||||
|
'C', 'D', 'E', 'F' };
|
||||||
|
char[] hexChars=new char[(bytes.length * 3) - 1];
|
||||||
|
int v;
|
||||||
|
|
||||||
|
for (int j=0; j < bytes.length; j++) {
|
||||||
|
v=bytes[j] & 0xFF;
|
||||||
|
hexChars[j * 3]=hexArray[v / 16];
|
||||||
|
hexChars[j * 3 + 1]=hexArray[v % 16];
|
||||||
|
|
||||||
|
if (j < bytes.length - 1) {
|
||||||
|
hexChars[j * 3 + 2]=':';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(hexChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the broadcast receiver for a given Intent
|
||||||
|
* has the desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the receiver, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some receiver whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* receiver was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no receiver was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching receiver
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching receiver, so the "broadcast" will only go to this
|
||||||
|
* one component.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to broadcast
|
||||||
|
* @param sigHash the signature hash of the app that you expect
|
||||||
|
* to handle this broadcast
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching receiver is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching receiver with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target receiver added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateBroadcastIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
String sigHash,
|
||||||
|
boolean failIfHack) {
|
||||||
|
ArrayList<String> sigHashes=new ArrayList<String>();
|
||||||
|
|
||||||
|
sigHashes.add(sigHash);
|
||||||
|
|
||||||
|
return(validateBroadcastIntent(ctxt, toValidate, sigHashes,
|
||||||
|
failIfHack));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the broadcast receiver for a given Intent
|
||||||
|
* has a desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the receiver, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has a proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some receiver whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* receiver was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no receiver was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching receiver
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching receiver, so the "broadcast" will only go to this
|
||||||
|
* one component.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to broadcast
|
||||||
|
* @param sigHashes the possible signature hashes of the app
|
||||||
|
* that you expect to handle this broadcast
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching receiver is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching receiver with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target receiver added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateBroadcastIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
List<String> sigHashes,
|
||||||
|
boolean failIfHack) {
|
||||||
|
PackageManager pm=ctxt.getPackageManager();
|
||||||
|
Intent result=null;
|
||||||
|
List<ResolveInfo> receivers=
|
||||||
|
pm.queryBroadcastReceivers(toValidate, 0);
|
||||||
|
|
||||||
|
if (receivers!=null) {
|
||||||
|
for (ResolveInfo info : receivers) {
|
||||||
|
try {
|
||||||
|
if (sigHashes.contains(getSignatureHash(ctxt,
|
||||||
|
info.activityInfo.packageName))) {
|
||||||
|
ComponentName cn=
|
||||||
|
new ComponentName(info.activityInfo.packageName,
|
||||||
|
info.activityInfo.name);
|
||||||
|
|
||||||
|
result=new Intent(toValidate).setComponent(cn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (failIfHack) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Package has signature hash mismatch: "+
|
||||||
|
info.activityInfo.packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
catch (NameNotFoundException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the activity for a given Intent has the
|
||||||
|
* desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the activity, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some activity whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* activity was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no activity was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching activity
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching activity, so a call to startActivity() for this
|
||||||
|
* Intent is guaranteed to go to this specific activity.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to use with
|
||||||
|
* startActivity()
|
||||||
|
* @param sigHash the signature hash of the app that you expect
|
||||||
|
* to handle this activity
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching activity is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching activity with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target activity added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateActivityIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
String sigHash,
|
||||||
|
boolean failIfHack) {
|
||||||
|
ArrayList<String> sigHashes=new ArrayList<String>();
|
||||||
|
|
||||||
|
sigHashes.add(sigHash);
|
||||||
|
|
||||||
|
return(validateActivityIntent(ctxt, toValidate, sigHashes,
|
||||||
|
failIfHack));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the activity for a given Intent has the
|
||||||
|
* desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the activity, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some activity whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* activity was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no activity was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching activity
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching activity, so a call to startActivity() for this
|
||||||
|
* Intent is guaranteed to go to this specific activity.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to use with
|
||||||
|
* startActivity()
|
||||||
|
* @param sigHashes the signature hashes of the app that you expect
|
||||||
|
* to handle this activity
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching activity is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching activity with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target activity added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateActivityIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
List<String> sigHashes,
|
||||||
|
boolean failIfHack) {
|
||||||
|
PackageManager pm=ctxt.getPackageManager();
|
||||||
|
Intent result=null;
|
||||||
|
List<ResolveInfo> activities=
|
||||||
|
pm.queryIntentActivities(toValidate, 0);
|
||||||
|
|
||||||
|
if (activities!=null) {
|
||||||
|
for (ResolveInfo info : activities) {
|
||||||
|
try {
|
||||||
|
if (sigHashes.contains(getSignatureHash(ctxt,
|
||||||
|
info.activityInfo.packageName))) {
|
||||||
|
ComponentName cn=
|
||||||
|
new ComponentName(info.activityInfo.packageName,
|
||||||
|
info.activityInfo.name);
|
||||||
|
|
||||||
|
result=new Intent(toValidate).setComponent(cn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (failIfHack) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Package has signature hash mismatch: "+
|
||||||
|
info.activityInfo.packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
catch (NameNotFoundException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the service for a given Intent has the
|
||||||
|
* desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the service, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some service whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* service was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no service was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching service
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching service, so a call to startService() or
|
||||||
|
* bindService() for this Intent is guaranteed to go to this
|
||||||
|
* specific service.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to use with
|
||||||
|
* startService() or bindService()
|
||||||
|
* @param sigHash the signature hash of the app that you expect
|
||||||
|
* to handle this service
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching service is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching service with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target service added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateServiceIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
String sigHash,
|
||||||
|
boolean failIfHack) {
|
||||||
|
ArrayList<String> sigHashes=new ArrayList<String>();
|
||||||
|
|
||||||
|
sigHashes.add(sigHash);
|
||||||
|
|
||||||
|
return(validateServiceIntent(ctxt, toValidate, sigHashes,
|
||||||
|
failIfHack));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the service for a given Intent has the
|
||||||
|
* desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the service, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some service whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* service was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no service was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching service
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching service, so a call to startService() or
|
||||||
|
* bindService() for this Intent is guaranteed to go to this
|
||||||
|
* specific service.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to use with
|
||||||
|
* startService() or bindService()
|
||||||
|
* @param sigHashes the signature hash of the app that you expect
|
||||||
|
* to handle this service
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching service is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching service with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target service added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateServiceIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
List<String> sigHashes,
|
||||||
|
boolean failIfHack) {
|
||||||
|
PackageManager pm=ctxt.getPackageManager();
|
||||||
|
Intent result=null;
|
||||||
|
List<ResolveInfo> services=
|
||||||
|
pm.queryIntentServices(toValidate, 0);
|
||||||
|
|
||||||
|
if (services!=null) {
|
||||||
|
for (ResolveInfo info : services) {
|
||||||
|
try {
|
||||||
|
if (sigHashes.contains(getSignatureHash(ctxt,
|
||||||
|
info.serviceInfo.packageName))) {
|
||||||
|
ComponentName cn=
|
||||||
|
new ComponentName(info.serviceInfo.packageName,
|
||||||
|
info.serviceInfo.name);
|
||||||
|
|
||||||
|
result=new Intent(toValidate).setComponent(cn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (failIfHack) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Package has signature hash mismatch: "+
|
||||||
|
info.activityInfo.packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
catch (NameNotFoundException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface used for reporting Orbot status
|
||||||
|
*/
|
||||||
|
public interface StatusCallback {
|
||||||
|
/**
|
||||||
|
* Called when Orbot is operational
|
||||||
|
*
|
||||||
|
* @param statusIntent an Intent containing information about
|
||||||
|
* Orbot, including proxy ports
|
||||||
|
*/
|
||||||
|
void onEnabled(Intent statusIntent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when Orbot reports that it is starting up
|
||||||
|
*/
|
||||||
|
void onStarting();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when Orbot reports that it is shutting down
|
||||||
|
*/
|
||||||
|
void onStopping();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when Orbot reports that it is no longer running
|
||||||
|
*/
|
||||||
|
void onDisabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if our attempt to get a status from Orbot failed
|
||||||
|
* after a defined period of time. See statusTimeout() on
|
||||||
|
* OrbotInitializer.
|
||||||
|
*/
|
||||||
|
void onStatusTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if Orbot is not yet installed. Usually, you handle
|
||||||
|
* this by checking the return value from init() on OrbotInitializer
|
||||||
|
* or calling isInstalled() on OrbotInitializer. However, if
|
||||||
|
* you have need for it, if a callback is registered before
|
||||||
|
* an init() call determines that Orbot is not installed, your
|
||||||
|
* callback will be called with onNotYetInstalled().
|
||||||
|
*/
|
||||||
|
void onNotYetInstalled();
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
public class TorServiceUtils {
|
||||||
|
|
||||||
|
private final static String TAG = "TorUtils";
|
||||||
|
// various console cmds
|
||||||
|
public final static String SHELL_CMD_CHMOD = "chmod";
|
||||||
|
public final static String SHELL_CMD_KILL = "kill -9";
|
||||||
|
public final static String SHELL_CMD_RM = "rm";
|
||||||
|
public final static String SHELL_CMD_PS = "ps";
|
||||||
|
public final static String SHELL_CMD_PIDOF = "pidof";
|
||||||
|
|
||||||
|
public final static String CHMOD_EXE_VALUE = "700";
|
||||||
|
|
||||||
|
public static boolean isRootPossible()
|
||||||
|
{
|
||||||
|
|
||||||
|
StringBuilder log = new StringBuilder();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Check if Superuser.apk exists
|
||||||
|
File fileSU = new File("/system/app/Superuser.apk");
|
||||||
|
if (fileSU.exists())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fileSU = new File("/system/app/superuser.apk");
|
||||||
|
if (fileSU.exists())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fileSU = new File("/system/bin/su");
|
||||||
|
if (fileSU.exists())
|
||||||
|
{
|
||||||
|
String[] cmd = {
|
||||||
|
"su"
|
||||||
|
};
|
||||||
|
int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true);
|
||||||
|
if (exitCode != 0)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for 'su' binary
|
||||||
|
String[] cmd = {
|
||||||
|
"which su"
|
||||||
|
};
|
||||||
|
int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true);
|
||||||
|
|
||||||
|
if (exitCode == 0) {
|
||||||
|
Log.d(TAG, "root exists, but not sure about permissions");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
// this means that there is no root to be had (normally) so we won't
|
||||||
|
// log anything
|
||||||
|
Log.e(TAG, "Error checking for root access", e);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error checking for root access", e);
|
||||||
|
// this means that there is no root to be had (normally)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e(TAG, "Could not acquire root permissions");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int findProcessId(Context context) {
|
||||||
|
String dataPath = context.getFilesDir().getParentFile().getParentFile().getAbsolutePath();
|
||||||
|
String command = dataPath + "/" + OrbotHelper.ORBOT_PACKAGE_NAME + "/app_bin/tor";
|
||||||
|
int procId = -1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
procId = findProcessIdWithPidOf(command);
|
||||||
|
|
||||||
|
if (procId == -1)
|
||||||
|
procId = findProcessIdWithPS(command);
|
||||||
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
|
procId = findProcessIdWithPS(command);
|
||||||
|
} catch (Exception e2) {
|
||||||
|
Log.e(TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return procId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use 'pidof' command
|
||||||
|
public static int findProcessIdWithPidOf(String command) throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
int procId = -1;
|
||||||
|
|
||||||
|
Runtime r = Runtime.getRuntime();
|
||||||
|
|
||||||
|
Process procPs = null;
|
||||||
|
|
||||||
|
String baseName = new File(command).getName();
|
||||||
|
// fix contributed my mikos on 2010.12.10
|
||||||
|
procPs = r.exec(new String[] {
|
||||||
|
SHELL_CMD_PIDOF, baseName
|
||||||
|
});
|
||||||
|
// procPs = r.exec(SHELL_CMD_PIDOF);
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
|
||||||
|
String line = null;
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// this line should just be the process id
|
||||||
|
procId = Integer.parseInt(line.trim());
|
||||||
|
break;
|
||||||
|
} catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
Log.e("TorServiceUtils", "unable to parse process pid: " + line, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return procId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// use 'ps' command
|
||||||
|
public static int findProcessIdWithPS(String command) throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
int procId = -1;
|
||||||
|
|
||||||
|
Runtime r = Runtime.getRuntime();
|
||||||
|
|
||||||
|
Process procPs = null;
|
||||||
|
|
||||||
|
procPs = r.exec(SHELL_CMD_PS);
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
|
||||||
|
String line = null;
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) != null)
|
||||||
|
{
|
||||||
|
if (line.indexOf(' ' + command) != -1)
|
||||||
|
{
|
||||||
|
|
||||||
|
StringTokenizer st = new StringTokenizer(line, " ");
|
||||||
|
st.nextToken(); // proc owner
|
||||||
|
|
||||||
|
procId = Integer.parseInt(st.nextToken().trim());
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return procId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int doShellCommand(String[] cmds, StringBuilder log, boolean runAsRoot,
|
||||||
|
boolean waitFor) throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
Process proc = null;
|
||||||
|
int exitCode = -1;
|
||||||
|
|
||||||
|
if (runAsRoot)
|
||||||
|
proc = Runtime.getRuntime().exec("su");
|
||||||
|
else
|
||||||
|
proc = Runtime.getRuntime().exec("sh");
|
||||||
|
|
||||||
|
OutputStreamWriter out = new OutputStreamWriter(proc.getOutputStream());
|
||||||
|
|
||||||
|
for (int i = 0; i < cmds.length; i++)
|
||||||
|
{
|
||||||
|
// TorService.logMessage("executing shell cmd: " + cmds[i] +
|
||||||
|
// "; runAsRoot=" + runAsRoot + ";waitFor=" + waitFor);
|
||||||
|
|
||||||
|
out.write(cmds[i]);
|
||||||
|
out.write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
out.write("exit\n");
|
||||||
|
out.flush();
|
||||||
|
|
||||||
|
if (waitFor)
|
||||||
|
{
|
||||||
|
|
||||||
|
final char buf[] = new char[10];
|
||||||
|
|
||||||
|
// Consume the "stdout"
|
||||||
|
InputStreamReader reader = new InputStreamReader(proc.getInputStream());
|
||||||
|
int read = 0;
|
||||||
|
while ((read = reader.read(buf)) != -1) {
|
||||||
|
if (log != null)
|
||||||
|
log.append(buf, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the "stderr"
|
||||||
|
reader = new InputStreamReader(proc.getErrorStream());
|
||||||
|
read = 0;
|
||||||
|
while ((read = reader.read(buf)) != -1) {
|
||||||
|
if (log != null)
|
||||||
|
log.append(buf, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = proc.waitFor();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,832 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Anthony Restaino
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.darkweb.genesissearchengine.netcipher.web;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Proxy;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.HttpHost;
|
||||||
|
|
||||||
|
public class WebkitProxy {
|
||||||
|
|
||||||
|
private final static String DEFAULT_HOST = "localhost";//"127.0.0.1";
|
||||||
|
private final static int DEFAULT_PORT = 8118;
|
||||||
|
private final static int DEFAULT_SOCKS_PORT = 9050;
|
||||||
|
|
||||||
|
private final static int REQUEST_CODE = 0;
|
||||||
|
|
||||||
|
private final static String TAG = "OrbotHelpher";
|
||||||
|
|
||||||
|
public static boolean setProxy(String appClass, Context ctx, WebView wView, String host, int port) throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
setSystemProperties(host, port);
|
||||||
|
|
||||||
|
boolean worked = false;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < 13)
|
||||||
|
{
|
||||||
|
// worked = setWebkitProxyGingerbread(ctx, host, port);
|
||||||
|
setProxyUpToHC(wView, host, port);
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT < 19)
|
||||||
|
{
|
||||||
|
worked = setWebkitProxyICS(ctx, host, port);
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT < 20)
|
||||||
|
{
|
||||||
|
worked = setKitKatProxy(appClass, ctx, host, port);
|
||||||
|
|
||||||
|
if (!worked) //some kitkat's still use ICS browser component (like Cyanogen 11)
|
||||||
|
worked = setWebkitProxyICS(ctx, host, port);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT >= 21)
|
||||||
|
{
|
||||||
|
worked = setWebkitProxyLollipop(ctx, host, port);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return worked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setSystemProperties(String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
System.setProperty("proxyHost", host);
|
||||||
|
System.setProperty("proxyPort", Integer.toString(port));
|
||||||
|
|
||||||
|
System.setProperty("http.proxyHost", host);
|
||||||
|
System.setProperty("http.proxyPort", Integer.toString(port));
|
||||||
|
|
||||||
|
System.setProperty("https.proxyHost", host);
|
||||||
|
System.setProperty("https.proxyPort", Integer.toString(port));
|
||||||
|
|
||||||
|
|
||||||
|
System.setProperty("socks.proxyHost", host);
|
||||||
|
System.setProperty("socks.proxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
||||||
|
|
||||||
|
System.setProperty("socksProxyHost", host);
|
||||||
|
System.setProperty("socksProxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
ProxySelector pSelect = new ProxySelector();
|
||||||
|
pSelect.addProxy(Proxy.Type.HTTP, host, port);
|
||||||
|
ProxySelector.setDefault(pSelect);
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
System.setProperty("http_proxy", "http://" + host + ":" + port);
|
||||||
|
System.setProperty("proxy-server", "http://" + host + ":" + port);
|
||||||
|
System.setProperty("host-resolver-rules","MAP * 0.0.0.0 , EXCLUDE myproxy");
|
||||||
|
|
||||||
|
System.getProperty("networkaddress.cache.ttl", "-1");
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetSystemProperties()
|
||||||
|
{
|
||||||
|
|
||||||
|
System.setProperty("proxyHost", "");
|
||||||
|
System.setProperty("proxyPort", "");
|
||||||
|
|
||||||
|
System.setProperty("http.proxyHost", "");
|
||||||
|
System.setProperty("http.proxyPort", "");
|
||||||
|
|
||||||
|
System.setProperty("https.proxyHost", "");
|
||||||
|
System.setProperty("https.proxyPort", "");
|
||||||
|
|
||||||
|
|
||||||
|
System.setProperty("socks.proxyHost", "");
|
||||||
|
System.setProperty("socks.proxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
||||||
|
|
||||||
|
System.setProperty("socksProxyHost", "");
|
||||||
|
System.setProperty("socksProxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override WebKit Proxy settings
|
||||||
|
*
|
||||||
|
* @param ctx Android ApplicationContext
|
||||||
|
* @param host
|
||||||
|
* @param port
|
||||||
|
* @return true if Proxy was successfully set
|
||||||
|
*/
|
||||||
|
private static boolean setWebkitProxyGingerbread(Context ctx, String host, int port)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
boolean ret = false;
|
||||||
|
|
||||||
|
Object requestQueueObject = getRequestQueue(ctx);
|
||||||
|
if (requestQueueObject != null) {
|
||||||
|
// Create Proxy config object and set it into request Q
|
||||||
|
HttpHost httpHost = new HttpHost(host, port, "http");
|
||||||
|
setDeclaredField(requestQueueObject, "mProxyHost", httpHost);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Proxy for Android 3.2 and below.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private static boolean setProxyUpToHC(WebView webview, String host, int port) {
|
||||||
|
Log.d(TAG, "Setting proxy with <= 3.2 API.");
|
||||||
|
|
||||||
|
HttpHost proxyServer = new HttpHost(host, port);
|
||||||
|
// Getting network
|
||||||
|
Class networkClass = null;
|
||||||
|
Object network = null;
|
||||||
|
try {
|
||||||
|
networkClass = Class.forName("android.webkit.Network");
|
||||||
|
if (networkClass == null) {
|
||||||
|
Log.e(TAG, "failed to get class for android.webkit.Network");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Method getInstanceMethod = networkClass.getMethod("getInstance", Context.class);
|
||||||
|
if (getInstanceMethod == null) {
|
||||||
|
Log.e(TAG, "failed to get getInstance method");
|
||||||
|
}
|
||||||
|
network = getInstanceMethod.invoke(networkClass, new Object[]{webview.getContext()});
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "error getting network: " + ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (network == null) {
|
||||||
|
Log.e(TAG, "error getting network: network is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Object requestQueue = null;
|
||||||
|
try {
|
||||||
|
Field requestQueueField = networkClass
|
||||||
|
.getDeclaredField("mRequestQueue");
|
||||||
|
requestQueue = getFieldValueSafely(requestQueueField, network);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "error getting field value");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (requestQueue == null) {
|
||||||
|
Log.e(TAG, "Request queue is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Field proxyHostField = null;
|
||||||
|
try {
|
||||||
|
Class requestQueueClass = Class.forName("android.net.http.RequestQueue");
|
||||||
|
proxyHostField = requestQueueClass
|
||||||
|
.getDeclaredField("mProxyHost");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "error getting proxy host field");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean temp = proxyHostField.isAccessible();
|
||||||
|
try {
|
||||||
|
proxyHostField.setAccessible(true);
|
||||||
|
proxyHostField.set(requestQueue, proxyServer);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "error setting proxy host");
|
||||||
|
} finally {
|
||||||
|
proxyHostField.setAccessible(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Setting proxy with <= 3.2 API successful!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Object getFieldValueSafely(Field field, Object classInstance) throws IllegalArgumentException, IllegalAccessException {
|
||||||
|
boolean oldAccessibleValue = field.isAccessible();
|
||||||
|
field.setAccessible(true);
|
||||||
|
Object result = field.get(classInstance);
|
||||||
|
field.setAccessible(oldAccessibleValue);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean setWebkitProxyICS(Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
// PSIPHON: added support for Android 4.x WebView proxy
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("android.webkit.WebViewCore");
|
||||||
|
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (webViewCoreClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("sendStaticMessage", Integer.TYPE,
|
||||||
|
Object.class);
|
||||||
|
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (m != null && c != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
c.setAccessible(true);
|
||||||
|
Object properties = c.newInstance(host, port, null);
|
||||||
|
|
||||||
|
// android.webkit.WebViewCore.EventHub.PROXY_CHANGED = 193;
|
||||||
|
m.invoke(null, 193, properties);
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(19)
|
||||||
|
public static boolean resetKitKatProxy(String appClass, Context appContext) {
|
||||||
|
|
||||||
|
return setKitKatProxy(appClass, appContext,null,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(19)
|
||||||
|
private static boolean setKitKatProxy(String appClass, Context appContext, String host, int port) {
|
||||||
|
//Context appContext = webView.getContext().getApplicationContext();
|
||||||
|
|
||||||
|
if (host != null)
|
||||||
|
{
|
||||||
|
System.setProperty("http.proxyHost", host);
|
||||||
|
System.setProperty("http.proxyPort", Integer.toString(port));
|
||||||
|
System.setProperty("https.proxyHost", host);
|
||||||
|
System.setProperty("https.proxyPort", Integer.toString(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class applictionCls = Class.forName(appClass);
|
||||||
|
Field loadedApkField = applictionCls.getField("mLoadedApk");
|
||||||
|
loadedApkField.setAccessible(true);
|
||||||
|
Object loadedApk = loadedApkField.get(appContext);
|
||||||
|
Class loadedApkCls = Class.forName("android.app.LoadedApk");
|
||||||
|
Field receiversField = loadedApkCls.getDeclaredField("mReceivers");
|
||||||
|
receiversField.setAccessible(true);
|
||||||
|
ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
|
||||||
|
for (Object receiverMap : receivers.values()) {
|
||||||
|
for (Object rec : ((ArrayMap) receiverMap).keySet()) {
|
||||||
|
Class clazz = rec.getClass();
|
||||||
|
if (clazz.getName().contains("ProxyChangeListener")) {
|
||||||
|
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
|
||||||
|
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
|
||||||
|
|
||||||
|
if (host != null)
|
||||||
|
{
|
||||||
|
/*********** optional, may be need in future *************/
|
||||||
|
final String CLASS_NAME = "android.net.ProxyProperties";
|
||||||
|
Class cls = Class.forName(CLASS_NAME);
|
||||||
|
Constructor constructor = cls.getConstructor(String.class, Integer.TYPE, String.class);
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
Object proxyProperties = constructor.newInstance(host, port, null);
|
||||||
|
intent.putExtra("proxy", (Parcelable) proxyProperties);
|
||||||
|
/*********** optional, may be need in future *************/
|
||||||
|
}
|
||||||
|
|
||||||
|
onReceiveMethod.invoke(rec, appContext, intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
}
|
||||||
|
return false; }
|
||||||
|
|
||||||
|
@TargetApi(21)
|
||||||
|
public static boolean resetLollipopProxy(String appClass, Context appContext) {
|
||||||
|
|
||||||
|
return setWebkitProxyLollipop(appContext,null,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://stackanswers.com/questions/25272393/android-webview-set-proxy-programmatically-on-android-l
|
||||||
|
@TargetApi(21) // for android.util.ArrayMap methods
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private static boolean setWebkitProxyLollipop(Context appContext, String host, int port)
|
||||||
|
{
|
||||||
|
System.setProperty("http.proxyHost", host);
|
||||||
|
System.setProperty("http.proxyPort", Integer.toString(port));
|
||||||
|
System.setProperty("https.proxyHost", host);
|
||||||
|
System.setProperty("https.proxyPort", Integer.toString(port));
|
||||||
|
try {
|
||||||
|
Class applictionClass = Class.forName("android.app.Application");
|
||||||
|
Field mLoadedApkField = applictionClass.getDeclaredField("mLoadedApk");
|
||||||
|
mLoadedApkField.setAccessible(true);
|
||||||
|
Object mloadedApk = mLoadedApkField.get(appContext);
|
||||||
|
Class loadedApkClass = Class.forName("android.app.LoadedApk");
|
||||||
|
Field mReceiversField = loadedApkClass.getDeclaredField("mReceivers");
|
||||||
|
mReceiversField.setAccessible(true);
|
||||||
|
ArrayMap receivers = (ArrayMap) mReceiversField.get(mloadedApk);
|
||||||
|
for (Object receiverMap : receivers.values())
|
||||||
|
{
|
||||||
|
for (Object receiver : ((ArrayMap) receiverMap).keySet())
|
||||||
|
{
|
||||||
|
Class clazz = receiver.getClass();
|
||||||
|
if (clazz.getName().contains("ProxyChangeListener"))
|
||||||
|
{
|
||||||
|
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
|
||||||
|
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
|
||||||
|
onReceiveMethod.invoke(receiver, appContext, intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
catch (NoSuchFieldException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
catch (IllegalAccessException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean sendProxyChangedIntent(Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (c != null)
|
||||||
|
{
|
||||||
|
c.setAccessible(true);
|
||||||
|
Object properties = c.newInstance(host, port, null);
|
||||||
|
|
||||||
|
Intent intent = new Intent(android.net.Proxy.PROXY_CHANGE_ACTION);
|
||||||
|
intent.putExtra("proxy",(Parcelable)properties);
|
||||||
|
ctx.sendBroadcast(intent);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception sending Intent ",e);
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception sending Intent ",e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
private static boolean setKitKatProxy0(Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class cmClass = Class.forName("android.net.ConnectivityManager");
|
||||||
|
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (cmClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (c != null)
|
||||||
|
{
|
||||||
|
c.setAccessible(true);
|
||||||
|
|
||||||
|
Object proxyProps = c.newInstance(host, port, null);
|
||||||
|
ConnectivityManager cm =
|
||||||
|
(ConnectivityManager)ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
|
||||||
|
Method mSetGlobalProxy = cmClass.getDeclaredMethod("setGlobalProxy", proxyPropertiesClass);
|
||||||
|
|
||||||
|
mSetGlobalProxy.invoke(cm, proxyProps);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"ConnectivityManager.setGlobalProxy ",e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
//CommandLine.initFromFile(COMMAND_LINE_FILE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
private static boolean setKitKatProxy2 (Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
String commandLinePath = "/data/local/tmp/orweb.conf";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("org.chromium.content.common.CommandLine");
|
||||||
|
|
||||||
|
if (webViewCoreClass != null)
|
||||||
|
{
|
||||||
|
for (Method method : webViewCoreClass.getDeclaredMethods())
|
||||||
|
{
|
||||||
|
Log.d("Orweb","Proxy methods: " + method.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("initFromFile",
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (m != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
m.invoke(null, commandLinePath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
private static boolean setKitKatProxy (Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("android.net.Proxy");
|
||||||
|
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (webViewCoreClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
for (Method method : webViewCoreClass.getDeclaredMethods())
|
||||||
|
{
|
||||||
|
Log.d("Orweb","Proxy methods: " + method.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("setHttpProxySystemProperty",
|
||||||
|
proxyPropertiesClass);
|
||||||
|
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (m != null && c != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
c.setAccessible(true);
|
||||||
|
Object properties = c.newInstance(host, port, null);
|
||||||
|
|
||||||
|
m.invoke(null, properties);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean resetProxyForKitKat ()
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("android.net.Proxy");
|
||||||
|
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (webViewCoreClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
for (Method method : webViewCoreClass.getDeclaredMethods())
|
||||||
|
{
|
||||||
|
Log.d("Orweb","Proxy methods: " + method.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("setHttpProxySystemProperty",
|
||||||
|
proxyPropertiesClass);
|
||||||
|
|
||||||
|
if (m != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
|
||||||
|
m.invoke(null, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}**/
|
||||||
|
|
||||||
|
public static void resetProxy(String appClass, Context ctx) throws Exception {
|
||||||
|
|
||||||
|
resetSystemProperties();
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < 14)
|
||||||
|
{
|
||||||
|
resetProxyForGingerBread(ctx);
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT < 19)
|
||||||
|
{
|
||||||
|
resetProxyForICS();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resetKitKatProxy(appClass, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetProxyForICS() throws Exception{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("android.webkit.WebViewCore");
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (webViewCoreClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("sendStaticMessage", Integer.TYPE,
|
||||||
|
Object.class);
|
||||||
|
|
||||||
|
if (m != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
|
||||||
|
// android.webkit.WebViewCore.EventHub.PROXY_CHANGED = 193;
|
||||||
|
m.invoke(null, 193, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
throw e;
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetProxyForGingerBread(Context ctx) throws Exception {
|
||||||
|
Object requestQueueObject = getRequestQueue(ctx);
|
||||||
|
if (requestQueueObject != null) {
|
||||||
|
setDeclaredField(requestQueueObject, "mProxyHost", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getRequestQueue(Context ctx) throws Exception {
|
||||||
|
Object ret = null;
|
||||||
|
Class networkClass = Class.forName("android.webkit.Network");
|
||||||
|
if (networkClass != null) {
|
||||||
|
Object networkObj = invokeMethod(networkClass, "getInstance", new Object[] {
|
||||||
|
ctx
|
||||||
|
}, Context.class);
|
||||||
|
if (networkObj != null) {
|
||||||
|
ret = getDeclaredField(networkObj, "mRequestQueue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getDeclaredField(Object obj, String name)
|
||||||
|
throws SecurityException, NoSuchFieldException,
|
||||||
|
IllegalArgumentException, IllegalAccessException {
|
||||||
|
Field f = obj.getClass().getDeclaredField(name);
|
||||||
|
f.setAccessible(true);
|
||||||
|
Object out = f.get(obj);
|
||||||
|
// System.out.println(obj.getClass().getName() + "." + name + " = "+
|
||||||
|
// out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setDeclaredField(Object obj, String name, Object value)
|
||||||
|
throws SecurityException, NoSuchFieldException,
|
||||||
|
IllegalArgumentException, IllegalAccessException {
|
||||||
|
Field f = obj.getClass().getDeclaredField(name);
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(obj, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object invokeMethod(Object object, String methodName, Object[] params,
|
||||||
|
Class... types) throws Exception {
|
||||||
|
Object out = null;
|
||||||
|
Class c = object instanceof Class ? (Class) object : object.getClass();
|
||||||
|
if (types != null) {
|
||||||
|
Method method = c.getMethod(methodName, types);
|
||||||
|
out = method.invoke(object, params);
|
||||||
|
} else {
|
||||||
|
Method method = c.getMethod(methodName);
|
||||||
|
out = method.invoke(object);
|
||||||
|
}
|
||||||
|
// System.out.println(object.getClass().getName() + "." + methodName +
|
||||||
|
// "() = "+ out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Socket getSocket(Context context, String proxyHost, int proxyPort)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
Socket sock = new Socket();
|
||||||
|
|
||||||
|
sock.connect(new InetSocketAddress(proxyHost, proxyPort), 10000);
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Socket getSocket(Context context) throws IOException
|
||||||
|
{
|
||||||
|
return getSocket(context, DEFAULT_HOST, DEFAULT_SOCKS_PORT);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertDialog initOrbot(Activity activity,
|
||||||
|
CharSequence stringTitle,
|
||||||
|
CharSequence stringMessage,
|
||||||
|
CharSequence stringButtonYes,
|
||||||
|
CharSequence stringButtonNo,
|
||||||
|
CharSequence stringDesiredBarcodeFormats) {
|
||||||
|
Intent intentScan = new Intent("org.torproject.android.START_TOR");
|
||||||
|
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
|
||||||
|
|
||||||
|
try {
|
||||||
|
activity.startActivityForResult(intentScan, REQUEST_CODE);
|
||||||
|
return null;
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
return showDownloadDialog(activity, stringTitle, stringMessage, stringButtonYes,
|
||||||
|
stringButtonNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AlertDialog showDownloadDialog(final Activity activity,
|
||||||
|
CharSequence stringTitle,
|
||||||
|
CharSequence stringMessage,
|
||||||
|
CharSequence stringButtonYes,
|
||||||
|
CharSequence stringButtonNo) {
|
||||||
|
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
|
||||||
|
downloadDialog.setTitle(stringTitle);
|
||||||
|
downloadDialog.setMessage(stringMessage);
|
||||||
|
downloadDialog.setPositiveButton(stringButtonYes, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
Uri uri = Uri.parse("market://search?q=pname:org.torproject.android");
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
downloadDialog.setNegativeButton(stringButtonNo, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return downloadDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -12,7 +12,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">digital freedom</string>
|
<string name="GENERAL_LOADING" translatable="true">digital freedom</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">reload</string>
|
<string name="GENERAL_RELOAD" translatable="true">reload</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">you are facing the one of the following problem. webpage or website might not be working. your internet connection might be poor. you might be using a proxy. website might be blocked by firewall</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">you are facing the one of the following problem. webpage or website might not be working. your internet connection might be poor. you might be using a proxy. website might be blocked by firewall</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel Strikes Again</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel Strikes Again</string>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
|
|
Binary file not shown.
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">llibertat digital</string>
|
<string name="GENERAL_LOADING" translatable="true">llibertat digital</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">recarregar</string>
|
<string name="GENERAL_RELOAD" translatable="true">recarregar</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">us trobeu amb el problema següent. pot ser que la pàgina web o el lloc web no funcionin. és possible que la vostra connexió a Internet sigui deficient. és possible que utilitzeu un servidor intermediari. el tallafoc podria bloquejar el lloc web</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">us trobeu amb el problema següent. pot ser que la pàgina web o el lloc web no funcionin. és possible que la vostra connexió a Internet sigui deficient. és possible que utilitzeu un servidor intermediari. el tallafoc podria bloquejar el lloc web</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC, Israel torna a atacar</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC, Israel torna a atacar</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">digitální svoboda</string>
|
<string name="GENERAL_LOADING" translatable="true">digitální svoboda</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">Znovu načíst</string>
|
<string name="GENERAL_RELOAD" translatable="true">Znovu načíst</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">čelíte jednomu z následujících problémů. webová stránka nebo web nemusí fungovat. vaše připojení k internetu může být špatné. možná používáte proxy. web může být blokován bránou firewall</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">čelíte jednomu z následujících problémů. webová stránka nebo web nemusí fungovat. vaše připojení k internetu může být špatné. možná používáte proxy. web může být blokován bránou firewall</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Izrael znovu udeří</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Izrael znovu udeří</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">digitale Freiheit</string>
|
<string name="GENERAL_LOADING" translatable="true">digitale Freiheit</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">neu laden</string>
|
<string name="GENERAL_RELOAD" translatable="true">neu laden</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">Sie stehen vor einem der folgenden Probleme. Webseite oder Website funktioniert möglicherweise nicht. Ihre Internetverbindung ist möglicherweise schlecht. Möglicherweise verwenden Sie einen Proxy. Die Website wird möglicherweise von der Firewall blockiert</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">Sie stehen vor einem der folgenden Probleme. Webseite oder Website funktioniert möglicherweise nicht. Ihre Internetverbindung ist möglicherweise schlecht. Möglicherweise verwenden Sie einen Proxy. Die Website wird möglicherweise von der Firewall blockiert</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel schlägt erneut zu</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel schlägt erneut zu</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">ψηφιακή ελευθερία</string>
|
<string name="GENERAL_LOADING" translatable="true">ψηφιακή ελευθερία</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">φορτώνω πάλι</string>
|
<string name="GENERAL_RELOAD" translatable="true">φορτώνω πάλι</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">αντιμετωπίζετε ένα από τα ακόλουθα προβλήματα. η ιστοσελίδα ή ο ιστότοπος ενδέχεται να μην λειτουργούν. η σύνδεσή σας στο Διαδίκτυο μπορεί να είναι κακή. μπορεί να χρησιμοποιείτε διακομιστή μεσολάβησης. Ο ιστότοπος ενδέχεται να αποκλειστεί από το τείχος προστασίας</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">αντιμετωπίζετε ένα από τα ακόλουθα προβλήματα. η ιστοσελίδα ή ο ιστότοπος ενδέχεται να μην λειτουργούν. η σύνδεσή σας στο Διαδίκτυο μπορεί να είναι κακή. μπορεί να χρησιμοποιείτε διακομιστή μεσολάβησης. Ο ιστότοπος ενδέχεται να αποκλειστεί από το τείχος προστασίας</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Το Ισραήλ χτυπά ξανά</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Το Ισραήλ χτυπά ξανά</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">liberté numérique</string>
|
<string name="GENERAL_LOADING" translatable="true">liberté numérique</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">recharger</string>
|
<string name="GENERAL_RELOAD" translatable="true">recharger</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">vous êtes confronté à l\'un des problèmes suivants. la page Web ou le site Web peut ne pas fonctionner. votre connexion Internet est peut-être mauvaise. vous utilisez peut-être un proxy. le site Web peut être bloqué par le pare-feu</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">vous êtes confronté à l\'un des problèmes suivants. la page Web ou le site Web peut ne pas fonctionner. votre connexion Internet est peut-être mauvaise. vous utilisez peut-être un proxy. le site Web peut être bloqué par le pare-feu</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israël frappe à nouveau</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israël frappe à nouveau</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">digitális szabadság</string>
|
<string name="GENERAL_LOADING" translatable="true">digitális szabadság</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">újratöltés</string>
|
<string name="GENERAL_RELOAD" translatable="true">újratöltés</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">a következő probléma egyikével áll szemben. előfordulhat, hogy egy weboldal vagy webhely nem működik. gyenge lehet az internetkapcsolat. lehet, hogy proxyt használ. előfordulhat, hogy a webhelyet tűzfal blokkolja</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">a következő probléma egyikével áll szemben. előfordulhat, hogy egy weboldal vagy webhely nem működik. gyenge lehet az internetkapcsolat. lehet, hogy proxyt használ. előfordulhat, hogy a webhelyet tűzfal blokkolja</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Izrael újra sztrájkol</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Izrael újra sztrájkol</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">libertà digitale</string>
|
<string name="GENERAL_LOADING" translatable="true">libertà digitale</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">ricaricare</string>
|
<string name="GENERAL_RELOAD" translatable="true">ricaricare</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">stai affrontando uno dei seguenti problemi. la pagina web o il sito web potrebbero non funzionare. la tua connessione Internet potrebbe essere scarsa. potresti utilizzare un proxy. il sito Web potrebbe essere bloccato dal firewall</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">stai affrontando uno dei seguenti problemi. la pagina web o il sito web potrebbero non funzionare. la tua connessione Internet potrebbe essere scarsa. potresti utilizzare un proxy. il sito Web potrebbe essere bloccato dal firewall</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israele colpisce ancora</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israele colpisce ancora</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">デジタルの自由</string>
|
<string name="GENERAL_LOADING" translatable="true">デジタルの自由</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">リロード</string>
|
<string name="GENERAL_RELOAD" translatable="true">リロード</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">次のいずれかの問題が発生しています。ウェブページまたはウェブサイトが機能していない可能性があります。インターネット接続が悪い可能性があります。プロキシを使用している可能性があります。ウェブサイトがファイアウォールによってブロックされている可能性があります</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">次のいずれかの問題が発生しています。ウェブページまたはウェブサイトが機能していない可能性があります。インターネット接続が悪い可能性があります。プロキシを使用している可能性があります。ウェブサイトがファイアウォールによってブロックされている可能性があります</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC |イスラエルが再びストライキ</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC |イスラエルが再びストライキ</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">디지털 자유</string>
|
<string name="GENERAL_LOADING" translatable="true">디지털 자유</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">재 장전</string>
|
<string name="GENERAL_RELOAD" translatable="true">재 장전</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">다음 문제 중 하나에 직면하고 있습니다. 웹 페이지 또는 웹 사이트가 작동하지 않을 수 있습니다. 인터넷 연결 상태가 좋지 않을 수 있습니다. 프록시를 사용하고있을 수 있습니다. 웹 사이트가 방화벽에 의해 차단 될 수 있음</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">다음 문제 중 하나에 직면하고 있습니다. 웹 페이지 또는 웹 사이트가 작동하지 않을 수 있습니다. 인터넷 연결 상태가 좋지 않을 수 있습니다. 프록시를 사용하고있을 수 있습니다. 웹 사이트가 방화벽에 의해 차단 될 수 있음</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | 이스라엘이 다시 공격하다</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | 이스라엘이 다시 공격하다</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">digital freedom</string>
|
<string name="GENERAL_LOADING" translatable="true">digital freedom</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">recarregar</string>
|
<string name="GENERAL_RELOAD" translatable="true">recarregar</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">você está enfrentando um dos seguintes problemas. página da Web ou site pode não estar funcionando. sua conexão com a internet pode estar ruim. você pode estar usando um proxy. site pode estar bloqueado por firewall</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">você está enfrentando um dos seguintes problemas. página da Web ou site pode não estar funcionando. sua conexão com a internet pode estar ruim. você pode estar usando um proxy. site pode estar bloqueado por firewall</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel ataca novamente</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel ataca novamente</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">libertatea digitală</string>
|
<string name="GENERAL_LOADING" translatable="true">libertatea digitală</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">reîncărcați</string>
|
<string name="GENERAL_RELOAD" translatable="true">reîncărcați</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">vă confruntați cu una dintre următoarele probleme. este posibil ca pagina web sau site-ul web să nu funcționeze. conexiunea la internet ar putea fi slabă. s-ar putea să utilizați un proxy. site-ul web ar putea fi blocat de firewall</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">vă confruntați cu una dintre următoarele probleme. este posibil ca pagina web sau site-ul web să nu funcționeze. conexiunea la internet ar putea fi slabă. s-ar putea să utilizați un proxy. site-ul web ar putea fi blocat de firewall</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israelul lovește din nou</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israelul lovește din nou</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">цифровая свобода</string>
|
<string name="GENERAL_LOADING" translatable="true">цифровая свобода</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">перезагрузить</string>
|
<string name="GENERAL_RELOAD" translatable="true">перезагрузить</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">вы столкнулись с одной из следующих проблем. веб-страница или веб-сайт могут не работать. ваше интернет-соединение может быть плохим. вы можете использовать прокси. веб-сайт может быть заблокирован брандмауэром</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">вы столкнулись с одной из следующих проблем. веб-страница или веб-сайт могут не работать. ваше интернет-соединение может быть плохим. вы можете использовать прокси. веб-сайт может быть заблокирован брандмауэром</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Израиль снова наносит удар</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Израиль снова наносит удар</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">เสรีภาพดิจิทัล</string>
|
<string name="GENERAL_LOADING" translatable="true">เสรีภาพดิจิทัล</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">โหลดใหม่</string>
|
<string name="GENERAL_RELOAD" translatable="true">โหลดใหม่</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">คุณกำลังประสบปัญหาอย่างใดอย่างหนึ่งต่อไปนี้ หน้าเว็บหรือเว็บไซต์อาจไม่ทำงาน การเชื่อมต่ออินเทอร์เน็ตของคุณอาจไม่ดี คุณอาจใช้พร็อกซี เว็บไซต์อาจถูกปิดกั้นโดยไฟร์วอลล์</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">คุณกำลังประสบปัญหาอย่างใดอย่างหนึ่งต่อไปนี้ หน้าเว็บหรือเว็บไซต์อาจไม่ทำงาน การเชื่อมต่ออินเทอร์เน็ตของคุณอาจไม่ดี คุณอาจใช้พร็อกซี เว็บไซต์อาจถูกปิดกั้นโดยไฟร์วอลล์</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | อิสราเอลนัดหยุดงานอีกครั้ง</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | อิสราเอลนัดหยุดงานอีกครั้ง</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">dijital özgürlük</string>
|
<string name="GENERAL_LOADING" translatable="true">dijital özgürlük</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">Tekrar yükle</string>
|
<string name="GENERAL_RELOAD" translatable="true">Tekrar yükle</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">aşağıdaki problemlerden biriyle karşı karşıyasınız. web sayfası veya web sitesi çalışmıyor olabilir. İnternet bağlantınız zayıf olabilir. bir proxy kullanıyor olabilirsiniz. web sitesi güvenlik duvarı tarafından engelleniyor olabilir</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">aşağıdaki problemlerden biriyle karşı karşıyasınız. web sayfası veya web sitesi çalışmıyor olabilir. İnternet bağlantınız zayıf olabilir. bir proxy kullanıyor olabilirsiniz. web sitesi güvenlik duvarı tarafından engelleniyor olabilir</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | İsrail Yeniden Grevde</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | İsrail Yeniden Grevde</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">ڈیجیٹل آزادی</string>
|
<string name="GENERAL_LOADING" translatable="true">ڈیجیٹل آزادی</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">دوبارہ لوڈ کریں</string>
|
<string name="GENERAL_RELOAD" translatable="true">دوبارہ لوڈ کریں</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">آپ کو مندرجہ ذیل میں سے ایک مسئلہ درپیش ہے۔ ہوسکتا ہے کہ ویب صفحہ یا ویب سائٹ کام نہیں کررہی ہے۔ ہوسکتا ہے کہ آپ کا انٹرنیٹ کنیکشن خراب ہو۔ آپ شاید ایک پراکسی استعمال کر رہے ہوں گے۔ ہوسکتا ہے کہ ویب سائٹ فائر وال کے ذریعے مسدود کردی جائے</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">آپ کو مندرجہ ذیل میں سے ایک مسئلہ درپیش ہے۔ ہوسکتا ہے کہ ویب صفحہ یا ویب سائٹ کام نہیں کررہی ہے۔ ہوسکتا ہے کہ آپ کا انٹرنیٹ کنیکشن خراب ہو۔ آپ شاید ایک پراکسی استعمال کر رہے ہوں گے۔ ہوسکتا ہے کہ ویب سائٹ فائر وال کے ذریعے مسدود کردی جائے</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">بی بی سی | اسرائیل نے ایک بار پھر حملہ کیا</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">بی بی سی | اسرائیل نے ایک بار پھر حملہ کیا</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">tự do kỹ thuật số</string>
|
<string name="GENERAL_LOADING" translatable="true">tự do kỹ thuật số</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">tải lại</string>
|
<string name="GENERAL_RELOAD" translatable="true">tải lại</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">bạn đang phải đối mặt với một trong những vấn đề sau. trang web hoặc trang web có thể không hoạt động. kết nối internet của bạn có thể kém. bạn có thể đang sử dụng proxy. trang web có thể bị tường lửa chặn</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">bạn đang phải đối mặt với một trong những vấn đề sau. trang web hoặc trang web có thể không hoạt động. kết nối internet của bạn có thể kém. bạn có thể đang sử dụng proxy. trang web có thể bị tường lửa chặn</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel lại tấn công</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel lại tấn công</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">数字自由</string>
|
<string name="GENERAL_LOADING" translatable="true">数字自由</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">重装</string>
|
<string name="GENERAL_RELOAD" translatable="true">重装</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">您正面临以下问题之一。网页或网站可能无法正常工作。您的互联网连接可能不佳。您可能正在使用代理。网站可能被防火墙阻止</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">您正面临以下问题之一。网页或网站可能无法正常工作。您的互联网连接可能不佳。您可能正在使用代理。网站可能被防火墙阻止</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">英国广播公司|以色列再次罢工</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">英国广播公司|以色列再次罢工</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="GENERAL_LOADING" translatable="true">Online Freedom</string>
|
<string name="GENERAL_LOADING" translatable="true">Online Freedom</string>
|
||||||
<string name="GENERAL_RELOAD" translatable="true">Reload</string>
|
<string name="GENERAL_RELOAD" translatable="true">Reload</string>
|
||||||
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">These might be the problems you are facing \n\n• Webpage or Website might be down \n• Your Internet connection might be poor \n• You might be using a proxy \n• Website might be blocked by firewall</string>
|
<string name="GENERAL_INTERNET_ERROR_MESSAGE" translatable="true">These might be the problems you are facing \n\n• Webpage or Website might be down \n• Your Internet connection might be poor \n• You might be using a proxy \n• Website might be blocked by firewall</string>
|
||||||
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.fileprovider</string>
|
<string name="GENERAL_FILE_PROVIDER_AUTHORITY" translatable="false">com.darkweb.genesissearchengine.provider</string>
|
||||||
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel Strikes Again</string>
|
<string name="GENERAL_DEFAULT_HINT_SUGGESTION" translatable="true">BBC | Israel Strikes Again</string>
|
||||||
<string name="GENERAL_HIDDEN_WEB" translatable="false">Search the web ...</string>
|
<string name="GENERAL_HIDDEN_WEB" translatable="false">Search the web ...</string>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="src" path="gen"/>
|
||||||
|
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||||
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||||
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||||
|
<classpathentry kind="output" path="bin/classes"/>
|
||||||
|
</classpath>
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>libnetcipher</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="info.guardianproject.netcipher"
|
||||||
|
android:versionCode="12"
|
||||||
|
android:versionName="1.2">
|
||||||
|
|
||||||
|
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="22" />
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -0,0 +1,62 @@
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// If you want to fetch these from Maven, uncomment these lines and change
|
||||||
|
// the *.jar depend to exclude these libs:
|
||||||
|
compile fileTree(dir: 'libs', include: '*.jar')
|
||||||
|
androidTestCompile 'com.android.support.test:runner:0.4.1'
|
||||||
|
androidTestCompile 'com.android.support.test:rules:0.4.1'
|
||||||
|
androidTestCompile 'junit:junit:4.12'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 22
|
||||||
|
buildToolsVersion '26.0.2'
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
manifest.srcFile 'AndroidManifest.xml'
|
||||||
|
java.srcDirs = ['src']
|
||||||
|
resources.srcDirs = ['src']
|
||||||
|
aidl.srcDirs = ['src']
|
||||||
|
renderscript.srcDirs = ['src']
|
||||||
|
res.srcDirs = ['res']
|
||||||
|
assets.srcDirs = ['assets']
|
||||||
|
}
|
||||||
|
|
||||||
|
androidTest {
|
||||||
|
manifest.srcFile '../netciphertest/AndroidManifest.xml'
|
||||||
|
java.srcDirs = ['../netciphertest/src']
|
||||||
|
resources.srcDirs = ['../netciphertest/src']
|
||||||
|
aidl.srcDirs = ['../netciphertest/src']
|
||||||
|
renderscript.srcDirs = ['../netciphertest/src']
|
||||||
|
res.srcDirs = ['../netciphertest/res']
|
||||||
|
assets.srcDirs = ['../netciphertest/assets']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
|
||||||
|
htmlReport true
|
||||||
|
xmlReport false
|
||||||
|
textReport false
|
||||||
|
}
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
// prevent tests on JVM from dying on android.util.Log calls
|
||||||
|
returnDefaultValues = true
|
||||||
|
all {
|
||||||
|
testLogging {
|
||||||
|
events "skipped", "failed", "standardOut", "standardError"
|
||||||
|
showStandardStreams = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project name="custom_rules">
|
||||||
|
|
||||||
|
<property name="name" value="netcipher" />
|
||||||
|
<target name="-getgitdetails" >
|
||||||
|
<exec executable="git" outputproperty="git.describe">
|
||||||
|
<arg value="describe"/>
|
||||||
|
</exec>
|
||||||
|
<exec executable="git" outputproperty="git.revision">
|
||||||
|
<arg value="rev-parse"/>
|
||||||
|
<arg value="HEAD"/>
|
||||||
|
</exec>
|
||||||
|
<property name="jar.name" value="${name}-${git.describe}" />
|
||||||
|
<property name="javadoc.jar" value="${jar.name}-javadoc.jar" />
|
||||||
|
<property name="source.jar" value="${jar.name}-source.jar" />
|
||||||
|
<property name="pom" value="${jar.name}-source.jar" />
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="-pre-clean" depends="-getgitdetails">
|
||||||
|
<property name="delete.pattern" value="${jar.name}*.jar*" />
|
||||||
|
<echo message="deleting ${delete.pattern}" />
|
||||||
|
<delete failonerror="false">
|
||||||
|
<fileset dir="." includes="${delete.pattern}" />
|
||||||
|
</delete>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="-create-manifest">
|
||||||
|
<echo message="Creating custom MANIFEST.MF" />
|
||||||
|
<manifest file="MANIFEST.MF">
|
||||||
|
<attribute name="Extension-Name" value="info.guardianproject.${name}"/>
|
||||||
|
<attribute name="Implementation-Vendor" value="Guardian Project"/>
|
||||||
|
<attribute name="Implementation-Title" value="NetCipher"/>
|
||||||
|
<attribute name="Implementation-URL" value="https://dev.guardianproject.info/projects/${name}"/>
|
||||||
|
<attribute name="Implementation-Git-URL" value="https://github.com/guardianproject/${name}"/>
|
||||||
|
</manifest>
|
||||||
|
<replaceregexp file="MANIFEST.MF"
|
||||||
|
match="\nCreated-By:.*?\n"
|
||||||
|
replace=""
|
||||||
|
flags="m"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="-pre-build" depends="-getgitdetails,-create-manifest">
|
||||||
|
<echo message="running" />
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="javadoc" description="Generate Javadocs" depends="-pre-build">
|
||||||
|
<property name="javadoc.dir" value="${out.dir}/javadoc" />
|
||||||
|
<javadoc sourcepath="${source.dir}"
|
||||||
|
classpath="${toString:project.all.jars.path}:${toString:project.target.class.path}"
|
||||||
|
destdir="${javadoc.dir}"
|
||||||
|
packagenames="info.guardianproject.*"
|
||||||
|
windowtitle="${ant.project.name}"
|
||||||
|
linkoffline="https://developer.android.com/reference ${sdk.dir}/docs/reference"
|
||||||
|
linksource="true"
|
||||||
|
additionalparam="-notimestamp"
|
||||||
|
doctitle="${ant.project.name}" />
|
||||||
|
<jar destfile="${javadoc.jar}"
|
||||||
|
manifest="MANIFEST.MF"
|
||||||
|
basedir="${javadoc.dir}">
|
||||||
|
</jar>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="source" description="Generate Javadocs" depends="-build-setup,-getgitdetails">
|
||||||
|
<jar destfile="${source.jar}"
|
||||||
|
manifest="MANIFEST.MF"
|
||||||
|
includes="info/**"
|
||||||
|
basedir="${source.dir}">
|
||||||
|
</jar>
|
||||||
|
<delete file="${source.jar}.asc"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="pom" description="Generate maven .pom" depends="-build-setup,-getgitdetails">
|
||||||
|
<property name="pom.file" value="${jar.name}.pom" />
|
||||||
|
<copy file="${name}.pom" tofile="${pom.file}" />
|
||||||
|
<replaceregexp file="${pom.file}"
|
||||||
|
match="<version>.*</version>"
|
||||||
|
replace="<version>${git.describe}</version>"
|
||||||
|
byline="true" />
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="-post-build" depends="-getgitdetails">
|
||||||
|
<condition property="build.is.debug" value="true" else="false">
|
||||||
|
<equals arg1="${build.target}" arg2="debug" />
|
||||||
|
</condition>
|
||||||
|
<if condition="${build.is.debug}">
|
||||||
|
<then>
|
||||||
|
<property name="release.jar" value="${jar.name}-debug.jar" />
|
||||||
|
</then>
|
||||||
|
<else>
|
||||||
|
<property name="release.jar" value="${jar.name}.jar" />
|
||||||
|
</else>
|
||||||
|
</if>
|
||||||
|
<property file="${sdk.dir}/tools/source.properties" />
|
||||||
|
<delete file="${release.jar}"/>
|
||||||
|
<jar destfile="${release.jar}"
|
||||||
|
manifest="MANIFEST.MF"
|
||||||
|
includes="info/**"
|
||||||
|
basedir="${out.classes.absolute.dir}">
|
||||||
|
</jar>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="release-all" depends="clean,release,javadoc,source,pom" />
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>info.guardianproject.netcipher</groupId>
|
||||||
|
<artifactId>netcipher</artifactId>
|
||||||
|
<version>1.2</version>
|
||||||
|
<name>NetCipher</name>
|
||||||
|
<url>https://guardianproject.info/code/netcipher</url>
|
||||||
|
<description>NetCipher is a library for Android that provides multiple means to improve network security in mobile applications. It provides best practices TLS settings using the standard Android HTTP methods, HttpURLConnection and Apache HTTP Client, provides simple Tor integration, makes it easy to configure proxies for HTTP connections and `WebView` instances.</description>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>Apache-2.0</name>
|
||||||
|
<url>https://github.com/guardianproject/NetCipher/blob/master/LICENSE.txt</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>guardianproject</id>
|
||||||
|
<name>Guardian Project</name>
|
||||||
|
<email>support@guardianproject.info</email>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
<issueManagement>
|
||||||
|
<url>https://dev.guardianproject.info/projects/netcipher/issues</url>
|
||||||
|
<system>Redmine</system>
|
||||||
|
</issueManagement>
|
||||||
|
<scm>
|
||||||
|
<connection>scm:https://github.com/guardianproject/NetCipher.git</connection>
|
||||||
|
<developerConnection>scm:git@github.com:guardianproject/NetCipher.git</developerConnection>
|
||||||
|
<url>scm:https://github.com/guardianproject/NetCipher</url>
|
||||||
|
</scm>
|
||||||
|
</project>
|
|
@ -0,0 +1,20 @@
|
||||||
|
# To enable ProGuard in your project, edit project.properties
|
||||||
|
# to define the proguard.config property as described in that file.
|
||||||
|
#
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the ProGuard
|
||||||
|
# include property in project.properties.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
|
@ -0,0 +1,15 @@
|
||||||
|
# This file is automatically generated by Android Tools.
|
||||||
|
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems.
|
||||||
|
#
|
||||||
|
# To customize properties used by the Ant build system edit
|
||||||
|
# "ant.properties", and override values to adapt the script to your
|
||||||
|
# project structure.
|
||||||
|
#
|
||||||
|
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||||
|
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||||
|
|
||||||
|
# Project target.
|
||||||
|
target=android-22
|
||||||
|
android.library=true
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,357 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2014-2016 Hans-Christoph Steiner
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
import info.guardianproject.netcipher.client.TlsOnlySocketFactory;
|
||||||
|
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||||
|
|
||||||
|
public class NetCipher {
|
||||||
|
private static final String TAG = "NetCipher";
|
||||||
|
|
||||||
|
private NetCipher() {
|
||||||
|
// this is a utility class with only static methods
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static Proxy ORBOT_HTTP_PROXY = new Proxy(Proxy.Type.HTTP,
|
||||||
|
new InetSocketAddress("127.0.0.1", 8118));
|
||||||
|
|
||||||
|
private static Proxy proxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the global HTTP proxy for all new {@link HttpURLConnection}s and
|
||||||
|
* {@link HttpsURLConnection}s that are created after this is called.
|
||||||
|
* <p/>
|
||||||
|
* {@link #useTor()} will override this setting. Traffic must be directed
|
||||||
|
* to Tor using the proxy settings, and Orbot has its own proxy settings
|
||||||
|
* for connections that need proxies to work. So if "use Tor" is enabled,
|
||||||
|
* as tested by looking for the static instance of Proxy, then no other
|
||||||
|
* proxy settings are allowed to override the current Tor proxy.
|
||||||
|
*
|
||||||
|
* @param host the IP address for the HTTP proxy to use globally
|
||||||
|
* @param port the port number for the HTTP proxy to use globally
|
||||||
|
*/
|
||||||
|
public static void setProxy(String host, int port) {
|
||||||
|
if (!TextUtils.isEmpty(host) && port > 0) {
|
||||||
|
InetSocketAddress isa = new InetSocketAddress(host, port);
|
||||||
|
setProxy(new Proxy(Proxy.Type.HTTP, isa));
|
||||||
|
} else if (NetCipher.proxy != ORBOT_HTTP_PROXY) {
|
||||||
|
setProxy(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the global HTTP proxy for all new {@link HttpURLConnection}s and
|
||||||
|
* {@link HttpsURLConnection}s that are created after this is called.
|
||||||
|
* <p/>
|
||||||
|
* {@link #useTor()} will override this setting. Traffic must be directed
|
||||||
|
* to Tor using the proxy settings, and Orbot has its own proxy settings
|
||||||
|
* for connections that need proxies to work. So if "use Tor" is enabled,
|
||||||
|
* as tested by looking for the static instance of Proxy, then no other
|
||||||
|
* proxy settings are allowed to override the current Tor proxy.
|
||||||
|
*
|
||||||
|
* @param proxy the HTTP proxy to use globally
|
||||||
|
*/
|
||||||
|
public static void setProxy(Proxy proxy) {
|
||||||
|
if (proxy != null && NetCipher.proxy == ORBOT_HTTP_PROXY) {
|
||||||
|
Log.w(TAG, "useTor is enabled, ignoring new proxy settings!");
|
||||||
|
} else {
|
||||||
|
NetCipher.proxy = proxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently active global HTTP {@link Proxy}.
|
||||||
|
*
|
||||||
|
* @return the active HTTP {@link Proxy}
|
||||||
|
*/
|
||||||
|
public static Proxy getProxy() {
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the global HTTP proxy for all new {@link HttpURLConnection}s and
|
||||||
|
* {@link HttpsURLConnection}s that are created after this is called. This
|
||||||
|
* returns things to the default, proxy-less state.
|
||||||
|
*/
|
||||||
|
public static void clearProxy() {
|
||||||
|
setProxy(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Orbot as the global HTTP proxy for all new {@link HttpURLConnection}
|
||||||
|
* s and {@link HttpsURLConnection}s that are created after this is called.
|
||||||
|
* This overrides all future calls to {@link #setProxy(Proxy)}, except to
|
||||||
|
* clear the proxy, e.g. {@code #setProxy(null)} or {@link #clearProxy()}.
|
||||||
|
* <p/>
|
||||||
|
* Traffic must be directed to Tor using the proxy settings, and Orbot has its
|
||||||
|
* own proxy settings for connections that need proxies to work. So if "use
|
||||||
|
* Tor" is enabled, as tested by looking for the static instance of Proxy,
|
||||||
|
* then no other proxy settings are allowed to override the current Tor proxy.
|
||||||
|
*/
|
||||||
|
public static void useTor() {
|
||||||
|
setProxy(ORBOT_HTTP_PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link TlsOnlySocketFactory} from NetCipher.
|
||||||
|
*
|
||||||
|
* @see HttpsURLConnection#setDefaultSSLSocketFactory(SSLSocketFactory)
|
||||||
|
*/
|
||||||
|
public static TlsOnlySocketFactory getTlsOnlySocketFactory() {
|
||||||
|
return getTlsOnlySocketFactory(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link TlsOnlySocketFactory} from NetCipher, and specify whether
|
||||||
|
* it should use a more compatible, but less strong, suite of ciphers.
|
||||||
|
*
|
||||||
|
* @see HttpsURLConnection#setDefaultSSLSocketFactory(SSLSocketFactory)
|
||||||
|
*/
|
||||||
|
public static TlsOnlySocketFactory getTlsOnlySocketFactory(boolean compatible) {
|
||||||
|
SSLContext sslcontext;
|
||||||
|
try {
|
||||||
|
sslcontext = SSLContext.getInstance("TLSv1");
|
||||||
|
sslcontext.init(null, null, null);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
} catch (KeyManagementException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
return new TlsOnlySocketFactory(sslcontext.getSocketFactory(), compatible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link URL}, and specify whether
|
||||||
|
* it should use a more compatible, but less strong, suite of ciphers.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @param compatible
|
||||||
|
* @return the {@code url} in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(URL url, boolean compatible)
|
||||||
|
throws IOException {
|
||||||
|
// .onion addresses only work via Tor, so force Tor for all of them
|
||||||
|
Proxy proxy = NetCipher.proxy;
|
||||||
|
if (OrbotHelper.isOnionAddress(url))
|
||||||
|
proxy = ORBOT_HTTP_PROXY;
|
||||||
|
|
||||||
|
HttpURLConnection connection;
|
||||||
|
if (proxy != null) {
|
||||||
|
connection = (HttpURLConnection) url.openConnection(proxy);
|
||||||
|
} else {
|
||||||
|
connection = (HttpURLConnection) url.openConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection instanceof HttpsURLConnection) {
|
||||||
|
HttpsURLConnection httpsConnection = ((HttpsURLConnection) connection);
|
||||||
|
SSLSocketFactory tlsOnly = getTlsOnlySocketFactory(compatible);
|
||||||
|
httpsConnection.setSSLSocketFactory(tlsOnly);
|
||||||
|
if (Build.VERSION.SDK_INT < 16) {
|
||||||
|
httpsConnection.setHostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a URL {@link String} using the best
|
||||||
|
* TLS configuration available on the device.
|
||||||
|
*
|
||||||
|
* @param urlString
|
||||||
|
* @return the URL in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(String urlString) throws IOException {
|
||||||
|
URL url = new URL(urlString.replaceFirst("^[Hh][Tt][Tt][Pp]:", "https:"));
|
||||||
|
return getHttpsURLConnection(url, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link Uri} using the best TLS
|
||||||
|
* configuration available on the device.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @return the {@code uri} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(Uri uri) throws IOException {
|
||||||
|
return getHttpsURLConnection(uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link URI} using the best TLS
|
||||||
|
* configuration available on the device.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @return the {@code uri} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(URI uri) throws IOException {
|
||||||
|
if (TextUtils.equals(uri.getScheme(), "https"))
|
||||||
|
return getHttpsURLConnection(uri.toURL(), false);
|
||||||
|
else
|
||||||
|
// otherwise force scheme to https
|
||||||
|
return getHttpsURLConnection(uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link URL} using the best TLS
|
||||||
|
* configuration available on the device.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(URL url) throws IOException {
|
||||||
|
return getHttpsURLConnection(url, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link URL} using a more
|
||||||
|
* compatible, but less strong, suite of ciphers.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getCompatibleHttpsURLConnection(URL url) throws IOException {
|
||||||
|
return getHttpsURLConnection(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpsURLConnection} from a {@link URL}, and specify whether
|
||||||
|
* it should use a more compatible, but less strong, suite of ciphers.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @param compatible
|
||||||
|
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
||||||
|
* or if an HTTP URL is given that does not support HTTPS
|
||||||
|
*/
|
||||||
|
public static HttpsURLConnection getHttpsURLConnection(URL url, boolean compatible)
|
||||||
|
throws IOException {
|
||||||
|
// use default method, but enforce a HttpsURLConnection
|
||||||
|
HttpURLConnection connection = getHttpURLConnection(url, compatible);
|
||||||
|
if (connection instanceof HttpsURLConnection) {
|
||||||
|
return (HttpsURLConnection) connection;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("not an HTTPS connection!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link URL}. If the connection is
|
||||||
|
* {@code https://}, it will use a more compatible, but less strong, TLS
|
||||||
|
* configuration.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getCompatibleHttpURLConnection(URL url) throws IOException {
|
||||||
|
return getHttpURLConnection(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a URL {@link String}. If it is an
|
||||||
|
* {@code https://} link, then this will use the best TLS configuration
|
||||||
|
* available on the device.
|
||||||
|
*
|
||||||
|
* @param urlString
|
||||||
|
* @return the URL in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(String urlString) throws IOException {
|
||||||
|
return getHttpURLConnection(new URL(urlString));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link Uri}. If it is an
|
||||||
|
* {@code https://} link, then this will use the best TLS configuration
|
||||||
|
* available on the device.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @return the {@code uri} in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(Uri uri) throws IOException {
|
||||||
|
return getHttpURLConnection(uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link URI}. If it is an
|
||||||
|
* {@code https://} link, then this will use the best TLS configuration
|
||||||
|
* available on the device.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @return the {@code uri} in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(URI uri) throws IOException {
|
||||||
|
return getHttpURLConnection(uri.toURL());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link HttpURLConnection} from a {@link URL}. If it is an
|
||||||
|
* {@code https://} link, then this will use the best TLS configuration
|
||||||
|
* available on the device.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @return the {@code url} in an instance of {@link HttpURLConnection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection getHttpURLConnection(URL url) throws IOException {
|
||||||
|
return (HttpURLConnection) getHttpURLConnection(url, false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,255 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.client;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.HttpHost;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.HttpHostConnectException;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.OperatedClientConnection;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.SchemeSocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.SocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.impl.conn.DefaultClientConnectionOperator;
|
||||||
|
import ch.boye.httpclientandroidlib.params.HttpParams;
|
||||||
|
import ch.boye.httpclientandroidlib.protocol.HttpContext;
|
||||||
|
|
||||||
|
public class SocksAwareClientConnOperator extends DefaultClientConnectionOperator {
|
||||||
|
|
||||||
|
private static final int CONNECT_TIMEOUT_MILLISECONDS = 60000;
|
||||||
|
private static final int READ_TIMEOUT_MILLISECONDS = 60000;
|
||||||
|
|
||||||
|
private HttpHost mProxyHost;
|
||||||
|
private String mProxyType;
|
||||||
|
private SocksAwareProxyRoutePlanner mRoutePlanner;
|
||||||
|
|
||||||
|
public SocksAwareClientConnOperator(SchemeRegistry registry,
|
||||||
|
HttpHost proxyHost,
|
||||||
|
String proxyType,
|
||||||
|
SocksAwareProxyRoutePlanner proxyRoutePlanner) {
|
||||||
|
super(registry);
|
||||||
|
|
||||||
|
mProxyHost = proxyHost;
|
||||||
|
mProxyType = proxyType;
|
||||||
|
mRoutePlanner = proxyRoutePlanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void openConnection(
|
||||||
|
final OperatedClientConnection conn,
|
||||||
|
final HttpHost target,
|
||||||
|
final InetAddress local,
|
||||||
|
final HttpContext context,
|
||||||
|
final HttpParams params) throws IOException {
|
||||||
|
if (mProxyHost != null) {
|
||||||
|
if (mProxyType != null && mProxyType.equalsIgnoreCase("socks")) {
|
||||||
|
Log.d("StrongHTTPS", "proxying using SOCKS");
|
||||||
|
openSocksConnection(mProxyHost, conn, target, local, context, params);
|
||||||
|
} else {
|
||||||
|
Log.d("StrongHTTPS", "proxying with: " + mProxyType);
|
||||||
|
openNonSocksConnection(conn, target, local, context, params);
|
||||||
|
}
|
||||||
|
} else if (mRoutePlanner != null) {
|
||||||
|
if (mRoutePlanner.isProxy(target)) {
|
||||||
|
// HTTP proxy, already handled by the route planner system
|
||||||
|
Log.d("StrongHTTPS", "proxying using non-SOCKS");
|
||||||
|
openNonSocksConnection(conn, target, local, context, params);
|
||||||
|
} else {
|
||||||
|
// Either SOCKS or direct
|
||||||
|
HttpHost proxy = mRoutePlanner.determineRequiredProxy(target, null, context);
|
||||||
|
if (proxy == null) {
|
||||||
|
Log.d("StrongHTTPS", "not proxying");
|
||||||
|
openNonSocksConnection(conn, target, local, context, params);
|
||||||
|
} else if (mRoutePlanner.isSocksProxy(proxy)) {
|
||||||
|
Log.d("StrongHTTPS", "proxying using SOCKS");
|
||||||
|
openSocksConnection(proxy, conn, target, local, context, params);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Non-SOCKS proxy returned");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("StrongHTTPS", "not proxying");
|
||||||
|
openNonSocksConnection(conn, target, local, context, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openNonSocksConnection(
|
||||||
|
final OperatedClientConnection conn,
|
||||||
|
final HttpHost target,
|
||||||
|
final InetAddress local,
|
||||||
|
final HttpContext context,
|
||||||
|
final HttpParams params) throws IOException {
|
||||||
|
if (conn == null) {
|
||||||
|
throw new IllegalArgumentException("Connection must not be null.");
|
||||||
|
}
|
||||||
|
if (target == null) {
|
||||||
|
throw new IllegalArgumentException("Target host must not be null.");
|
||||||
|
}
|
||||||
|
// local address may be null
|
||||||
|
// @@@ is context allowed to be null?
|
||||||
|
if (params == null) {
|
||||||
|
throw new IllegalArgumentException("Parameters must not be null.");
|
||||||
|
}
|
||||||
|
if (conn.isOpen()) {
|
||||||
|
throw new IllegalArgumentException("Connection must not be open.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
|
||||||
|
final SocketFactory sf = schm.getSocketFactory();
|
||||||
|
|
||||||
|
Socket sock = sf.createSocket();
|
||||||
|
conn.opening(sock, target);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Socket connsock = sf.connectSocket(sock, target.getHostName(),
|
||||||
|
schm.resolvePort(target.getPort()),
|
||||||
|
local, 0, params);
|
||||||
|
|
||||||
|
if (sock != connsock) {
|
||||||
|
sock = connsock;
|
||||||
|
conn.opening(sock, target);
|
||||||
|
}
|
||||||
|
} catch (ConnectException ex) {
|
||||||
|
throw new HttpHostConnectException(target, ex);
|
||||||
|
}
|
||||||
|
prepareSocket(sock, context, params);
|
||||||
|
conn.openCompleted(sf.isSecure(sock), params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derived from the original DefaultClientConnectionOperator.java in Apache HttpClient 4.2
|
||||||
|
private void openSocksConnection(
|
||||||
|
final HttpHost proxy,
|
||||||
|
final OperatedClientConnection conn,
|
||||||
|
final HttpHost target,
|
||||||
|
final InetAddress local,
|
||||||
|
final HttpContext context,
|
||||||
|
final HttpParams params) throws IOException {
|
||||||
|
Socket socket = null;
|
||||||
|
Socket sslSocket = null;
|
||||||
|
try {
|
||||||
|
if (conn == null || target == null || params == null) {
|
||||||
|
throw new IllegalArgumentException("Required argument may not be null");
|
||||||
|
}
|
||||||
|
if (conn.isOpen()) {
|
||||||
|
throw new IllegalStateException("Connection must not be open");
|
||||||
|
}
|
||||||
|
|
||||||
|
Scheme scheme = schemeRegistry.getScheme(target.getSchemeName());
|
||||||
|
SchemeSocketFactory schemeSocketFactory = scheme.getSchemeSocketFactory();
|
||||||
|
|
||||||
|
int port = scheme.resolvePort(target.getPort());
|
||||||
|
String host = target.getHostName();
|
||||||
|
|
||||||
|
// Perform explicit SOCKS4a connection request. SOCKS4a supports remote host name resolution
|
||||||
|
// (i.e., Tor resolves the hostname, which may be an onion address).
|
||||||
|
// The Android (Apache Harmony) Socket class appears to support only SOCKS4 and throws an
|
||||||
|
// exception on an address created using INetAddress.createUnresolved() -- so the typical
|
||||||
|
// technique for using Java SOCKS4a/5 doesn't appear to work on Android:
|
||||||
|
// https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/java/net/PlainSocketImpl.java
|
||||||
|
// See also: http://www.mit.edu/~foley/TinFoil/src/tinfoil/TorLib.java, for a similar implementation
|
||||||
|
|
||||||
|
// From http://en.wikipedia.org/wiki/SOCKS#SOCKS4a:
|
||||||
|
//
|
||||||
|
// field 1: SOCKS version number, 1 byte, must be 0x04 for this version
|
||||||
|
// field 2: command code, 1 byte:
|
||||||
|
// 0x01 = establish a TCP/IP stream connection
|
||||||
|
// 0x02 = establish a TCP/IP port binding
|
||||||
|
// field 3: network byte order port number, 2 bytes
|
||||||
|
// field 4: deliberate invalid IP address, 4 bytes, first three must be 0x00 and the last one must not be 0x00
|
||||||
|
// field 5: the user ID string, variable length, terminated with a null (0x00)
|
||||||
|
// field 6: the domain name of the host we want to contact, variable length, terminated with a null (0x00)
|
||||||
|
|
||||||
|
|
||||||
|
socket = new Socket();
|
||||||
|
conn.opening(socket, target);
|
||||||
|
socket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);
|
||||||
|
socket.connect(new InetSocketAddress(proxy.getHostName(), proxy.getPort()), CONNECT_TIMEOUT_MILLISECONDS);
|
||||||
|
|
||||||
|
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
|
||||||
|
outputStream.write((byte) 0x04);
|
||||||
|
outputStream.write((byte) 0x01);
|
||||||
|
outputStream.writeShort((short) port);
|
||||||
|
outputStream.writeInt(0x01);
|
||||||
|
outputStream.write((byte) 0x00);
|
||||||
|
outputStream.write(host.getBytes());
|
||||||
|
outputStream.write((byte) 0x00);
|
||||||
|
|
||||||
|
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
|
||||||
|
if (inputStream.readByte() != (byte) 0x00 || inputStream.readByte() != (byte) 0x5a) {
|
||||||
|
throw new IOException("SOCKS4a connect failed");
|
||||||
|
}
|
||||||
|
inputStream.readShort();
|
||||||
|
inputStream.readInt();
|
||||||
|
|
||||||
|
if (schemeSocketFactory instanceof SSLSocketFactory) {
|
||||||
|
sslSocket = ((SSLSocketFactory) schemeSocketFactory).createLayeredSocket(socket, host, port, params);
|
||||||
|
conn.opening(sslSocket, target);
|
||||||
|
sslSocket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);
|
||||||
|
prepareSocket(sslSocket, context, params);
|
||||||
|
conn.openCompleted(schemeSocketFactory.isSecure(sslSocket), params);
|
||||||
|
} else {
|
||||||
|
conn.opening(socket, target);
|
||||||
|
socket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);
|
||||||
|
prepareSocket(socket, context, params);
|
||||||
|
conn.openCompleted(schemeSocketFactory.isSecure(socket), params);
|
||||||
|
}
|
||||||
|
// TODO: clarify which connection throws java.net.SocketTimeoutException?
|
||||||
|
} catch (IOException e) {
|
||||||
|
try {
|
||||||
|
if (sslSocket != null) {
|
||||||
|
sslSocket.close();
|
||||||
|
}
|
||||||
|
if (socket != null) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSecureConnection(
|
||||||
|
final OperatedClientConnection conn,
|
||||||
|
final HttpHost target,
|
||||||
|
final HttpContext context,
|
||||||
|
final HttpParams params) throws IOException {
|
||||||
|
if (mProxyHost != null && mProxyType.equalsIgnoreCase("socks"))
|
||||||
|
throw new RuntimeException("operation not supported");
|
||||||
|
else
|
||||||
|
super.updateSecureConnection(conn, target, context, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InetAddress[] resolveHostname(final String host) throws UnknownHostException {
|
||||||
|
if (mProxyHost != null && mProxyType.equalsIgnoreCase("socks"))
|
||||||
|
throw new RuntimeException("operation not supported");
|
||||||
|
else
|
||||||
|
return super.resolveHostname(host);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package info.guardianproject.netcipher.client;
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.HttpException;
|
||||||
|
import ch.boye.httpclientandroidlib.HttpHost;
|
||||||
|
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.SchemePortResolver;
|
||||||
|
import ch.boye.httpclientandroidlib.impl.conn.DefaultRoutePlanner;
|
||||||
|
import ch.boye.httpclientandroidlib.protocol.HttpContext;
|
||||||
|
|
||||||
|
public abstract class SocksAwareProxyRoutePlanner extends DefaultRoutePlanner {
|
||||||
|
public SocksAwareProxyRoutePlanner(SchemePortResolver schemePortResolver) {
|
||||||
|
super(schemePortResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpHost determineProxy(
|
||||||
|
HttpHost target,
|
||||||
|
HttpRequest request,
|
||||||
|
HttpContext context) throws HttpException {
|
||||||
|
HttpHost proxy = determineRequiredProxy(target, request, context);
|
||||||
|
if (isSocksProxy(proxy))
|
||||||
|
proxy = null;
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the proxy required for the provided target.
|
||||||
|
*
|
||||||
|
* @param target see {@link #determineProxy(HttpHost, HttpRequest, HttpContext) determineProxy()}
|
||||||
|
* @param request see {@link #determineProxy(HttpHost, HttpRequest, HttpContext) determineProxy()}.
|
||||||
|
* Will be null when called from {@link SocksAwareClientConnOperator} to
|
||||||
|
* determine if target requires a SOCKS proxy, so don't rely on it in this case.
|
||||||
|
* @param context see {@link #determineProxy(HttpHost, HttpRequest, HttpContext) determineProxy()}
|
||||||
|
* @return the proxy required for this target, or null if should connect directly.
|
||||||
|
*/
|
||||||
|
protected abstract HttpHost determineRequiredProxy(
|
||||||
|
HttpHost target,
|
||||||
|
HttpRequest request,
|
||||||
|
HttpContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the provided target is a proxy we define.
|
||||||
|
*
|
||||||
|
* @param target to check
|
||||||
|
* @return true if this is a proxy, false otherwise
|
||||||
|
*/
|
||||||
|
protected abstract boolean isProxy(HttpHost target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the provided target is a SOCKS proxy we define.
|
||||||
|
*
|
||||||
|
* @param target to check
|
||||||
|
* @return true if this target is a SOCKS proxy, false otherwise.
|
||||||
|
*/
|
||||||
|
protected abstract boolean isSocksProxy(HttpHost target);
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
|
||||||
|
public interface StrongBuilder<T extends StrongBuilder, C> {
|
||||||
|
/**
|
||||||
|
* Callback to get a connection handed to you for use,
|
||||||
|
* already set up for NetCipher.
|
||||||
|
*
|
||||||
|
* @param <C> the type of connection created by this builder
|
||||||
|
*/
|
||||||
|
interface Callback<C> {
|
||||||
|
/**
|
||||||
|
* Called when the NetCipher-enhanced connection is ready
|
||||||
|
* for use.
|
||||||
|
*
|
||||||
|
* @param connection the connection
|
||||||
|
*/
|
||||||
|
void onConnected(C connection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if we tried to connect through to Orbot but failed
|
||||||
|
* for some reason
|
||||||
|
*
|
||||||
|
* @param e the reason
|
||||||
|
*/
|
||||||
|
void onConnectionException(Exception e);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if our attempt to get a status from Orbot failed
|
||||||
|
* after a defined period of time. See statusTimeout() on
|
||||||
|
* OrbotInitializer.
|
||||||
|
*/
|
||||||
|
void onTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if you requested validation that we are connecting
|
||||||
|
* through Tor, and while we were able to connect to Orbot, that
|
||||||
|
* validation failed.
|
||||||
|
*/
|
||||||
|
void onInvalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this to configure the Tor proxy from the results
|
||||||
|
* returned by Orbot, using the best available proxy
|
||||||
|
* (SOCKS if possible, else HTTP)
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withBestProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if this builder supports HTTP proxies, false
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
boolean supportsHttpProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this to configure the Tor proxy from the results
|
||||||
|
* returned by Orbot, using the HTTP proxy.
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withHttpProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if this builder supports SOCKS proxies, false
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
boolean supportsSocksProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this to configure the Tor proxy from the results
|
||||||
|
* returned by Orbot, using the SOCKS proxy.
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withSocksProxy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies your own custom TrustManagers, such as for
|
||||||
|
* replacing the stock keystore support with a custom
|
||||||
|
* keystore.
|
||||||
|
*
|
||||||
|
* @param trustManagers the TrustManagers to use
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withTrustManagers(TrustManager[] trustManagers)
|
||||||
|
throws NoSuchAlgorithmException, KeyManagementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this if you want a weaker set of supported ciphers,
|
||||||
|
* because you are running into compatibility problems with
|
||||||
|
* some server due to a cipher mismatch. The better solution
|
||||||
|
* is to fix the server.
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withWeakCiphers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this if you want the builder to confirm that we are
|
||||||
|
* communicating over Tor, by reaching out to a Tor test
|
||||||
|
* server and confirming our connection status. By default,
|
||||||
|
* this is skipped. Adding this check adds security, but it
|
||||||
|
* has the chance of false negatives (e.g., we cannot reach
|
||||||
|
* that Tor server for some reason).
|
||||||
|
*
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
T withTorValidation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a connection, applying the configuration already
|
||||||
|
* specified in the builder.
|
||||||
|
*
|
||||||
|
* @param status status Intent from OrbotInitializer
|
||||||
|
* @return the connection
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
C build(Intent status) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous version of build(), one that uses OrbotInitializer
|
||||||
|
* internally to get the status and checks the validity of the Tor
|
||||||
|
* connection (if requested). Note that your callback methods may
|
||||||
|
* be invoked on any thread; do not assume that they will be called
|
||||||
|
* on any particular thread.
|
||||||
|
*
|
||||||
|
* @param callback Callback to get a connection handed to you
|
||||||
|
* for use, already set up for NetCipher
|
||||||
|
*/
|
||||||
|
void build(Callback<C> callback);
|
||||||
|
}
|
|
@ -0,0 +1,287 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
* Copyright 2015 str4d
|
||||||
|
* Portions Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an HttpUrlConnection that connects via Tor through
|
||||||
|
* Orbot.
|
||||||
|
*/
|
||||||
|
abstract public class
|
||||||
|
StrongBuilderBase<T extends StrongBuilderBase, C>
|
||||||
|
implements StrongBuilder<T, C> {
|
||||||
|
/**
|
||||||
|
* Performs an HTTP GET request using the supplied connection
|
||||||
|
* to a supplied URL, returning the String response or
|
||||||
|
* throws an Exception (e.g., cannot reach the server).
|
||||||
|
* This is used as part of validating the Tor connection.
|
||||||
|
*
|
||||||
|
* @param status the status Intent we got back from Orbot
|
||||||
|
* @param connection a connection of the type for the builder
|
||||||
|
* @param url an public Web page
|
||||||
|
* @return the String response from the GET request
|
||||||
|
*/
|
||||||
|
abstract protected String get(Intent status, C connection, String url)
|
||||||
|
throws Exception;
|
||||||
|
|
||||||
|
final static String TOR_CHECK_URL="https://check.torproject.org/api/ip";
|
||||||
|
private final static String PROXY_HOST="127.0.0.1";
|
||||||
|
protected final Context ctxt;
|
||||||
|
protected Proxy.Type proxyType;
|
||||||
|
protected SSLContext sslContext=null;
|
||||||
|
protected boolean useWeakCiphers=false;
|
||||||
|
protected boolean validateTor=false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard constructor.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the StrongBuilderBase
|
||||||
|
* will hold onto the Application singleton
|
||||||
|
*/
|
||||||
|
public StrongBuilderBase(Context ctxt) {
|
||||||
|
this.ctxt=ctxt.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor.
|
||||||
|
*
|
||||||
|
* @param original builder to clone
|
||||||
|
*/
|
||||||
|
public StrongBuilderBase(StrongBuilderBase original) {
|
||||||
|
this.ctxt=original.ctxt;
|
||||||
|
this.proxyType=original.proxyType;
|
||||||
|
this.sslContext=original.sslContext;
|
||||||
|
this.useWeakCiphers=original.useWeakCiphers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withBestProxy() {
|
||||||
|
if (supportsSocksProxy()) {
|
||||||
|
return(withSocksProxy());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return(withHttpProxy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean supportsHttpProxy() {
|
||||||
|
return(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withHttpProxy() {
|
||||||
|
proxyType=Proxy.Type.HTTP;
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean supportsSocksProxy() {
|
||||||
|
return(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withSocksProxy() {
|
||||||
|
proxyType=Proxy.Type.SOCKS;
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withTrustManagers(TrustManager[] trustManagers)
|
||||||
|
throws NoSuchAlgorithmException, KeyManagementException {
|
||||||
|
|
||||||
|
sslContext=SSLContext.getInstance("TLSv1");
|
||||||
|
sslContext.init(null, trustManagers, null);
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withWeakCiphers() {
|
||||||
|
useWeakCiphers=true;
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T withTorValidation() {
|
||||||
|
validateTor=true;
|
||||||
|
|
||||||
|
return((T)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SSLContext getSSLContext() {
|
||||||
|
return(sslContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSocksPort(Intent status) {
|
||||||
|
if (status.getStringExtra(OrbotHelper.EXTRA_STATUS)
|
||||||
|
.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
return(status.getIntExtra(OrbotHelper.EXTRA_PROXY_PORT_SOCKS,
|
||||||
|
9050));
|
||||||
|
}
|
||||||
|
|
||||||
|
return(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHttpPort(Intent status) {
|
||||||
|
if (status.getStringExtra(OrbotHelper.EXTRA_STATUS)
|
||||||
|
.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
return(status.getIntExtra(OrbotHelper.EXTRA_PROXY_PORT_HTTP,
|
||||||
|
8118));
|
||||||
|
}
|
||||||
|
|
||||||
|
return(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SSLSocketFactory buildSocketFactory() {
|
||||||
|
if (sslContext==null) {
|
||||||
|
return(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
SSLSocketFactory result=
|
||||||
|
new TlsOnlySocketFactory(sslContext.getSocketFactory(),
|
||||||
|
useWeakCiphers);
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Proxy buildProxy(Intent status) {
|
||||||
|
Proxy result=null;
|
||||||
|
|
||||||
|
if (status.getStringExtra(OrbotHelper.EXTRA_STATUS)
|
||||||
|
.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
if (proxyType==Proxy.Type.SOCKS) {
|
||||||
|
result=new Proxy(Proxy.Type.SOCKS,
|
||||||
|
new InetSocketAddress(PROXY_HOST, getSocksPort(status)));
|
||||||
|
}
|
||||||
|
else if (proxyType==Proxy.Type.HTTP) {
|
||||||
|
result=new Proxy(Proxy.Type.HTTP,
|
||||||
|
new InetSocketAddress(PROXY_HOST, getHttpPort(status)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(final Callback<C> callback) {
|
||||||
|
OrbotHelper.get(ctxt).addStatusCallback(
|
||||||
|
new OrbotHelper.SimpleStatusCallback() {
|
||||||
|
@Override
|
||||||
|
public void onEnabled(Intent statusIntent) {
|
||||||
|
OrbotHelper.get(ctxt).removeStatusCallback(this);
|
||||||
|
|
||||||
|
try {
|
||||||
|
C connection=build(statusIntent);
|
||||||
|
|
||||||
|
if (validateTor) {
|
||||||
|
validateTor=false;
|
||||||
|
checkTor(callback, statusIntent, connection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback.onConnected(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
callback.onConnectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotYetInstalled() {
|
||||||
|
OrbotHelper.get(ctxt).removeStatusCallback(this);
|
||||||
|
callback.onTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusTimeout() {
|
||||||
|
OrbotHelper.get(ctxt).removeStatusCallback(this);
|
||||||
|
callback.onTimeout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkTor(final Callback<C> callback, final Intent status,
|
||||||
|
final C connection) {
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
String result=get(status, connection, TOR_CHECK_URL);
|
||||||
|
JSONObject json=new JSONObject(result);
|
||||||
|
|
||||||
|
if (json.optBoolean("IsTor", false)) {
|
||||||
|
callback.onConnected(connection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback.onInvalid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
callback.onConnectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an HttpUrlConnection that connects via Tor through
|
||||||
|
* Orbot.
|
||||||
|
*/
|
||||||
|
public class StrongConnectionBuilder
|
||||||
|
extends StrongBuilderBase<StrongConnectionBuilder, HttpURLConnection> {
|
||||||
|
private URL url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a StrongConnectionBuilder using the strongest set
|
||||||
|
* of options for security. Use this if the strongest set of
|
||||||
|
* options is what you want; otherwise, create a
|
||||||
|
* builder via the constructor and configure it as you see fit.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do
|
||||||
|
* @return a configured StrongConnectionBuilder
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
static public StrongConnectionBuilder forMaxSecurity(Context ctxt)
|
||||||
|
throws Exception {
|
||||||
|
return(new StrongConnectionBuilder(ctxt)
|
||||||
|
.withBestProxy());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a builder instance.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; builder will hold onto
|
||||||
|
* Application context
|
||||||
|
*/
|
||||||
|
public StrongConnectionBuilder(Context ctxt) {
|
||||||
|
super(ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor.
|
||||||
|
*
|
||||||
|
* @param original builder to clone
|
||||||
|
*/
|
||||||
|
public StrongConnectionBuilder(StrongConnectionBuilder original) {
|
||||||
|
super(original);
|
||||||
|
this.url=original.url;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
public boolean supportsSocksProxy() {
|
||||||
|
return(false);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the URL to build a connection for.
|
||||||
|
*
|
||||||
|
* @param url the URL
|
||||||
|
* @return the builder
|
||||||
|
* @throws MalformedURLException
|
||||||
|
*/
|
||||||
|
public StrongConnectionBuilder connectTo(String url)
|
||||||
|
throws MalformedURLException {
|
||||||
|
connectTo(new URL(url));
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the URL to build a connection for.
|
||||||
|
*
|
||||||
|
* @param url the URL
|
||||||
|
* @return the builder
|
||||||
|
*/
|
||||||
|
public StrongConnectionBuilder connectTo(URL url) {
|
||||||
|
this.url=url;
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public HttpURLConnection build(Intent status) throws IOException {
|
||||||
|
return(buildForUrl(status, url));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String get(Intent status, HttpURLConnection connection,
|
||||||
|
String url) throws Exception {
|
||||||
|
HttpURLConnection realConnection=buildForUrl(status, new URL(url));
|
||||||
|
|
||||||
|
return(slurp(realConnection.getInputStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpURLConnection buildForUrl(Intent status, URL urlToUse)
|
||||||
|
throws IOException {
|
||||||
|
URLConnection result;
|
||||||
|
Proxy proxy=buildProxy(status);
|
||||||
|
|
||||||
|
if (proxy==null) {
|
||||||
|
result=urlToUse.openConnection();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result=urlToUse.openConnection(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result instanceof HttpsURLConnection && sslContext!=null) {
|
||||||
|
SSLSocketFactory tlsOnly=buildSocketFactory();
|
||||||
|
HttpsURLConnection https=(HttpsURLConnection)result;
|
||||||
|
|
||||||
|
https.setSSLSocketFactory(tlsOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
return((HttpURLConnection)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on http://stackoverflow.com/a/309718/115145
|
||||||
|
|
||||||
|
public static String slurp(final InputStream is)
|
||||||
|
throws IOException {
|
||||||
|
final char[] buffer = new char[128];
|
||||||
|
final StringBuilder out = new StringBuilder();
|
||||||
|
final Reader in = new InputStreamReader(is, "UTF-8");
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int rsz = in.read(buffer, 0, buffer.length);
|
||||||
|
if (rsz < 0)
|
||||||
|
break;
|
||||||
|
out.append(buffer, 0, rsz);
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package info.guardianproject.netcipher.client;
|
||||||
|
|
||||||
|
public class StrongConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ordered to prefer the stronger cipher suites as noted
|
||||||
|
* http://op-co.de/blog/posts/android_ssl_downgrade/
|
||||||
|
*/
|
||||||
|
public static final String ENABLED_CIPHERS[] = {
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
|
||||||
|
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
|
||||||
|
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||||
|
"TLS_RSA_WITH_AES_256_CBC_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||||
|
"SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_RC4_128_MD5" };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ordered to prefer the stronger/newer TLS versions as noted
|
||||||
|
* http://op-co.de/blog/posts/android_ssl_downgrade/
|
||||||
|
*/
|
||||||
|
public static final String ENABLED_PROTOCOLS[] = { "TLSv1.2", "TLSv1.1",
|
||||||
|
"TLSv1" };
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
* Copyright 2015 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.HttpHost;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.PlainSocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry;
|
||||||
|
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
|
||||||
|
import ch.boye.httpclientandroidlib.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
|
import info.guardianproject.netcipher.R;
|
||||||
|
|
||||||
|
public class StrongHttpsClient extends DefaultHttpClient {
|
||||||
|
|
||||||
|
final Context context;
|
||||||
|
private HttpHost proxyHost;
|
||||||
|
private String proxyType;
|
||||||
|
private SocksAwareProxyRoutePlanner routePlanner;
|
||||||
|
|
||||||
|
private StrongSSLSocketFactory sFactory;
|
||||||
|
private SchemeRegistry mRegistry;
|
||||||
|
|
||||||
|
private final static String TRUSTSTORE_TYPE = "BKS";
|
||||||
|
private final static String TRUSTSTORE_PASSWORD = "changeit";
|
||||||
|
|
||||||
|
public StrongHttpsClient(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
mRegistry = new SchemeRegistry();
|
||||||
|
mRegistry.register(
|
||||||
|
new Scheme(TYPE_HTTP, 80, PlainSocketFactory.getSocketFactory()));
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeyStore keyStore = loadKeyStore();
|
||||||
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
trustManagerFactory.init(keyStore);
|
||||||
|
sFactory = new StrongSSLSocketFactory(context, trustManagerFactory.getTrustManagers(), keyStore, TRUSTSTORE_PASSWORD);
|
||||||
|
mRegistry.register(new Scheme("https", 443, sFactory));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore loadKeyStore () throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException
|
||||||
|
{
|
||||||
|
|
||||||
|
KeyStore trustStore = KeyStore.getInstance(TRUSTSTORE_TYPE);
|
||||||
|
// load our bundled cacerts from raw assets
|
||||||
|
InputStream in = context.getResources().openRawResource(R.raw.debiancacerts);
|
||||||
|
trustStore.load(in, TRUSTSTORE_PASSWORD.toCharArray());
|
||||||
|
|
||||||
|
return trustStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StrongHttpsClient(Context context, KeyStore keystore) {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
mRegistry = new SchemeRegistry();
|
||||||
|
mRegistry.register(
|
||||||
|
new Scheme(TYPE_HTTP, 80, PlainSocketFactory.getSocketFactory()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
sFactory = new StrongSSLSocketFactory(context, trustManagerFactory.getTrustManagers(), keystore, TRUSTSTORE_PASSWORD);
|
||||||
|
mRegistry.register(new Scheme("https", 443, sFactory));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ThreadSafeClientConnManager createClientConnectionManager() {
|
||||||
|
|
||||||
|
return new ThreadSafeClientConnManager(getParams(), mRegistry)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected ClientConnectionOperator createConnectionOperator(
|
||||||
|
SchemeRegistry schreg) {
|
||||||
|
|
||||||
|
return new SocksAwareClientConnOperator(schreg, proxyHost, proxyType,
|
||||||
|
routePlanner);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useProxy(boolean enableTor, String type, String host, int port)
|
||||||
|
{
|
||||||
|
if (enableTor)
|
||||||
|
{
|
||||||
|
this.proxyType = type;
|
||||||
|
|
||||||
|
if (type.equalsIgnoreCase(TYPE_SOCKS))
|
||||||
|
{
|
||||||
|
proxyHost = new HttpHost(host, port);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
proxyHost = new HttpHost(host, port, type);
|
||||||
|
getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getParams().removeParameter(ConnRoutePNames.DEFAULT_PROXY);
|
||||||
|
proxyHost = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableProxy ()
|
||||||
|
{
|
||||||
|
getParams().removeParameter(ConnRoutePNames.DEFAULT_PROXY);
|
||||||
|
proxyHost = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useProxyRoutePlanner(SocksAwareProxyRoutePlanner proxyRoutePlanner)
|
||||||
|
{
|
||||||
|
routePlanner = proxyRoutePlanner;
|
||||||
|
setRoutePlanner(proxyRoutePlanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT ADVISED, but some sites don't yet have latest protocols and ciphers available, and some
|
||||||
|
* apps still need to support them
|
||||||
|
* https://dev.guardianproject.info/issues/5644
|
||||||
|
*/
|
||||||
|
public void enableSSLCompatibilityMode() {
|
||||||
|
sFactory.setEnableStongerDefaultProtocalVersion(false);
|
||||||
|
sFactory.setEnableStongerDefaultSSLCipherSuite(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static String TYPE_SOCKS = "socks";
|
||||||
|
public final static String TYPE_HTTP = "http";
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package info.guardianproject.netcipher.client;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import ch.boye.httpclientandroidlib.conn.scheme.LayeredSchemeSocketFactory;
|
||||||
|
import ch.boye.httpclientandroidlib.params.HttpParams;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
|
||||||
|
public class StrongSSLSocketFactory extends
|
||||||
|
ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory implements
|
||||||
|
LayeredSchemeSocketFactory {
|
||||||
|
|
||||||
|
private SSLSocketFactory mFactory = null;
|
||||||
|
|
||||||
|
private Proxy mProxy = null;
|
||||||
|
|
||||||
|
public static final String TLS = "TLS";
|
||||||
|
public static final String SSL = "SSL";
|
||||||
|
public static final String SSLV2 = "SSLv2";
|
||||||
|
|
||||||
|
// private X509HostnameVerifier mHostnameVerifier = new
|
||||||
|
// StrictHostnameVerifier();
|
||||||
|
// private final HostNameResolver mNameResolver = new
|
||||||
|
// StrongHostNameResolver();
|
||||||
|
|
||||||
|
private boolean mEnableStongerDefaultSSLCipherSuite = true;
|
||||||
|
private boolean mEnableStongerDefaultProtocalVersion = true;
|
||||||
|
|
||||||
|
private String[] mProtocols;
|
||||||
|
private String[] mCipherSuites;
|
||||||
|
|
||||||
|
public StrongSSLSocketFactory(Context context,
|
||||||
|
TrustManager[] trustManagers, KeyStore keyStore, String keyStorePassword)
|
||||||
|
throws KeyManagementException, UnrecoverableKeyException,
|
||||||
|
NoSuchAlgorithmException, KeyStoreException, CertificateException,
|
||||||
|
IOException {
|
||||||
|
super(keyStore);
|
||||||
|
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
KeyManager[] km = createKeyManagers(
|
||||||
|
keyStore,
|
||||||
|
keyStorePassword);
|
||||||
|
sslContext.init(km, trustManagers, new SecureRandom());
|
||||||
|
|
||||||
|
mFactory = sslContext.getSocketFactory();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readSSLParameters(SSLSocket sslSocket) {
|
||||||
|
List<String> protocolsToEnable = new ArrayList<String>();
|
||||||
|
List<String> supportedProtocols = Arrays.asList(sslSocket.getSupportedProtocols());
|
||||||
|
for(String enabledProtocol : StrongConstants.ENABLED_PROTOCOLS) {
|
||||||
|
if(supportedProtocols.contains(enabledProtocol)) {
|
||||||
|
protocolsToEnable.add(enabledProtocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mProtocols = protocolsToEnable.toArray(new String[protocolsToEnable.size()]);
|
||||||
|
|
||||||
|
List<String> cipherSuitesToEnable = new ArrayList<String>();
|
||||||
|
List<String> supportedCipherSuites = Arrays.asList(sslSocket.getSupportedCipherSuites());
|
||||||
|
for(String enabledCipherSuite : StrongConstants.ENABLED_CIPHERS) {
|
||||||
|
if(supportedCipherSuites.contains(enabledCipherSuite)) {
|
||||||
|
cipherSuitesToEnable.add(enabledCipherSuite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mCipherSuites = cipherSuitesToEnable.toArray(new String[cipherSuitesToEnable.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyManager[] createKeyManagers(final KeyStore keystore,
|
||||||
|
final String password) throws KeyStoreException,
|
||||||
|
NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||||
|
if (keystore == null) {
|
||||||
|
throw new IllegalArgumentException("Keystore may not be null");
|
||||||
|
}
|
||||||
|
KeyManagerFactory kmfactory = KeyManagerFactory
|
||||||
|
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
kmfactory.init(keystore, password != null ? password.toCharArray()
|
||||||
|
: null);
|
||||||
|
return kmfactory.getKeyManagers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket() throws IOException {
|
||||||
|
Socket newSocket = mFactory.createSocket();
|
||||||
|
enableStrongerDefaults(newSocket);
|
||||||
|
return newSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(Socket socket, String host, int port,
|
||||||
|
boolean autoClose) throws IOException, UnknownHostException {
|
||||||
|
|
||||||
|
Socket newSocket = mFactory.createSocket(socket, host, port, autoClose);
|
||||||
|
|
||||||
|
enableStrongerDefaults(newSocket);
|
||||||
|
|
||||||
|
return newSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults the SSL connection to use a strong cipher suite and TLS version
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
*/
|
||||||
|
private void enableStrongerDefaults(Socket socket) {
|
||||||
|
if (isSecure(socket)) {
|
||||||
|
SSLSocket sslSocket = (SSLSocket) socket;
|
||||||
|
readSSLParameters(sslSocket);
|
||||||
|
|
||||||
|
if (mEnableStongerDefaultProtocalVersion && mProtocols != null) {
|
||||||
|
sslSocket.setEnabledProtocols(mProtocols);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mEnableStongerDefaultSSLCipherSuite && mCipherSuites != null) {
|
||||||
|
sslSocket.setEnabledCipherSuites(mCipherSuites);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSecure(Socket sock) throws IllegalArgumentException {
|
||||||
|
return (sock instanceof SSLSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProxy(Proxy proxy) {
|
||||||
|
mProxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Proxy getProxy() {
|
||||||
|
return mProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableStongerDefaultSSLCipherSuite() {
|
||||||
|
return mEnableStongerDefaultSSLCipherSuite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableStongerDefaultSSLCipherSuite(boolean enable) {
|
||||||
|
this.mEnableStongerDefaultSSLCipherSuite = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableStongerDefaultProtocalVersion() {
|
||||||
|
return mEnableStongerDefaultProtocalVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableStongerDefaultProtocalVersion(boolean enable) {
|
||||||
|
this.mEnableStongerDefaultProtocalVersion = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(HttpParams httpParams) throws IOException {
|
||||||
|
Socket newSocket = mFactory.createSocket();
|
||||||
|
|
||||||
|
enableStrongerDefaults(newSocket);
|
||||||
|
|
||||||
|
return newSocket;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createLayeredSocket(Socket arg0, String arg1, int arg2,
|
||||||
|
boolean arg3) throws IOException, UnknownHostException {
|
||||||
|
return ((LayeredSchemeSocketFactory) mFactory).createLayeredSocket(
|
||||||
|
arg0, arg1, arg2, arg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,544 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Bhavit Singh Sengar
|
||||||
|
* Copyright 2015-2016 Hans-Christoph Steiner
|
||||||
|
* Copyright 2015-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* From https://stackoverflow.com/a/29946540
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.client;
|
||||||
|
|
||||||
|
import android.net.SSLCertificateSocketFactory;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.net.ssl.HandshakeCompletedListener;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* While making a secure connection, Android's {@link HttpsURLConnection} falls
|
||||||
|
* back to SSLv3 from TLSv1. This is a bug in android versions < 4.4. It can be
|
||||||
|
* fixed by removing the SSLv3 protocol from Enabled Protocols list. Use this as
|
||||||
|
* the {@link SSLSocketFactory} for
|
||||||
|
* {@link HttpsURLConnection#setDefaultSSLSocketFactory(SSLSocketFactory)}
|
||||||
|
*
|
||||||
|
* @author Bhavit S. Sengar
|
||||||
|
* @author Hans-Christoph Steiner
|
||||||
|
*/
|
||||||
|
public class TlsOnlySocketFactory extends SSLSocketFactory {
|
||||||
|
private static final int HANDSHAKE_TIMEOUT=0;
|
||||||
|
private static final String TAG = "TlsOnlySocketFactory";
|
||||||
|
private final SSLSocketFactory delegate;
|
||||||
|
private final boolean compatible;
|
||||||
|
|
||||||
|
public TlsOnlySocketFactory() {
|
||||||
|
this.delegate =SSLCertificateSocketFactory.getDefault(HANDSHAKE_TIMEOUT, null);
|
||||||
|
this.compatible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TlsOnlySocketFactory(SSLSocketFactory delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.compatible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make {@link SSLSocket}s that are compatible with outdated servers.
|
||||||
|
*
|
||||||
|
* @param delegate
|
||||||
|
* @param compatible
|
||||||
|
*/
|
||||||
|
public TlsOnlySocketFactory(SSLSocketFactory delegate, boolean compatible) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.compatible = compatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getDefaultCipherSuites() {
|
||||||
|
return delegate.getDefaultCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportedCipherSuites() {
|
||||||
|
return delegate.getSupportedCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Socket makeSocketSafe(Socket socket, String host) {
|
||||||
|
if (socket instanceof SSLSocket) {
|
||||||
|
TlsOnlySSLSocket tempSocket=
|
||||||
|
new TlsOnlySSLSocket((SSLSocket) socket, compatible);
|
||||||
|
|
||||||
|
if (delegate instanceof SSLCertificateSocketFactory &&
|
||||||
|
Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
((android.net.SSLCertificateSocketFactory)delegate)
|
||||||
|
.setHostname(socket, host);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tempSocket.setHostname(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket = tempSocket;
|
||||||
|
}
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
|
||||||
|
throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(s, host, port, autoClose), host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(String host, int port) throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(host, port), host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
|
||||||
|
throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort), host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(host, port), host.getHostName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
|
||||||
|
int localPort) throws IOException {
|
||||||
|
return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort),
|
||||||
|
address.getHostName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TlsOnlySSLSocket extends DelegateSSLSocket {
|
||||||
|
|
||||||
|
final boolean compatible;
|
||||||
|
|
||||||
|
private TlsOnlySSLSocket(SSLSocket delegate, boolean compatible) {
|
||||||
|
super(delegate);
|
||||||
|
this.compatible = compatible;
|
||||||
|
|
||||||
|
// badly configured servers can't handle a good config
|
||||||
|
if (compatible) {
|
||||||
|
ArrayList<String> protocols = new ArrayList<String>(Arrays.asList(delegate
|
||||||
|
.getEnabledProtocols()));
|
||||||
|
protocols.remove("SSLv2");
|
||||||
|
protocols.remove("SSLv3");
|
||||||
|
super.setEnabledProtocols(protocols.toArray(new String[protocols.size()]));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Exclude extremely weak EXPORT ciphers. NULL ciphers should
|
||||||
|
* never even have been an option in TLS.
|
||||||
|
*/
|
||||||
|
ArrayList<String> enabled = new ArrayList<String>(10);
|
||||||
|
Pattern exclude = Pattern.compile(".*(EXPORT|NULL).*");
|
||||||
|
for (String cipher : delegate.getEnabledCipherSuites()) {
|
||||||
|
if (!exclude.matcher(cipher).matches()) {
|
||||||
|
enabled.add(cipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.setEnabledCipherSuites(enabled.toArray(new String[enabled.size()]));
|
||||||
|
return;
|
||||||
|
} // else
|
||||||
|
|
||||||
|
// 16-19 support v1.1 and v1.2 but only by default starting in 20+
|
||||||
|
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
|
||||||
|
ArrayList<String> protocols = new ArrayList<String>(Arrays.asList(delegate
|
||||||
|
.getSupportedProtocols()));
|
||||||
|
protocols.remove("SSLv2");
|
||||||
|
protocols.remove("SSLv3");
|
||||||
|
super.setEnabledProtocols(protocols.toArray(new String[protocols.size()]));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Exclude weak ciphers, like EXPORT, MD5, DES, and DH. NULL ciphers
|
||||||
|
* should never even have been an option in TLS.
|
||||||
|
*/
|
||||||
|
ArrayList<String> enabledCiphers = new ArrayList<String>(10);
|
||||||
|
Pattern exclude = Pattern.compile(".*(_DES|DH_|DSS|EXPORT|MD5|NULL|RC4).*");
|
||||||
|
for (String cipher : delegate.getSupportedCipherSuites()) {
|
||||||
|
if (!exclude.matcher(cipher).matches()) {
|
||||||
|
enabledCiphers.add(cipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.setEnabledCipherSuites(enabledCiphers.toArray(new String[enabledCiphers.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This works around a bug in Android < 19 where SSLv3 is forced
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setEnabledProtocols(String[] protocols) {
|
||||||
|
if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
|
||||||
|
List<String> systemProtocols;
|
||||||
|
if (this.compatible) {
|
||||||
|
systemProtocols = Arrays.asList(delegate.getEnabledProtocols());
|
||||||
|
} else {
|
||||||
|
systemProtocols = Arrays.asList(delegate.getSupportedProtocols());
|
||||||
|
}
|
||||||
|
List<String> enabledProtocols = new ArrayList<String>(systemProtocols);
|
||||||
|
if (enabledProtocols.size() > 1) {
|
||||||
|
enabledProtocols.remove("SSLv2");
|
||||||
|
enabledProtocols.remove("SSLv3");
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "SSL stuck with protocol available for "
|
||||||
|
+ String.valueOf(enabledProtocols));
|
||||||
|
}
|
||||||
|
protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
|
||||||
|
}
|
||||||
|
super.setEnabledProtocols(protocols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DelegateSSLSocket extends SSLSocket {
|
||||||
|
|
||||||
|
protected final SSLSocket delegate;
|
||||||
|
|
||||||
|
DelegateSSLSocket(SSLSocket delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportedCipherSuites() {
|
||||||
|
return delegate.getSupportedCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getEnabledCipherSuites() {
|
||||||
|
return delegate.getEnabledCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabledCipherSuites(String[] suites) {
|
||||||
|
delegate.setEnabledCipherSuites(suites);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportedProtocols() {
|
||||||
|
return delegate.getSupportedProtocols();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getEnabledProtocols() {
|
||||||
|
return delegate.getEnabledProtocols();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabledProtocols(String[] protocols) {
|
||||||
|
delegate.setEnabledProtocols(protocols);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSession getSession() {
|
||||||
|
return delegate.getSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
||||||
|
delegate.addHandshakeCompletedListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
||||||
|
delegate.removeHandshakeCompletedListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startHandshake() throws IOException {
|
||||||
|
delegate.startHandshake();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseClientMode(boolean mode) {
|
||||||
|
delegate.setUseClientMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getUseClientMode() {
|
||||||
|
return delegate.getUseClientMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNeedClientAuth(boolean need) {
|
||||||
|
delegate.setNeedClientAuth(need);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWantClientAuth(boolean want) {
|
||||||
|
delegate.setWantClientAuth(want);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getNeedClientAuth() {
|
||||||
|
return delegate.getNeedClientAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getWantClientAuth() {
|
||||||
|
return delegate.getWantClientAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnableSessionCreation(boolean flag) {
|
||||||
|
delegate.setEnableSessionCreation(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getEnableSessionCreation() {
|
||||||
|
return delegate.getEnableSessionCreation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bind(SocketAddress localAddr) throws IOException {
|
||||||
|
delegate.bind(localAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close() throws IOException {
|
||||||
|
delegate.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(SocketAddress remoteAddr) throws IOException {
|
||||||
|
delegate.connect(remoteAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
|
||||||
|
delegate.connect(remoteAddr, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketChannel getChannel() {
|
||||||
|
return delegate.getChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress getInetAddress() {
|
||||||
|
return delegate.getInetAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
return delegate.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getKeepAlive() throws SocketException {
|
||||||
|
return delegate.getKeepAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress getLocalAddress() {
|
||||||
|
return delegate.getLocalAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLocalPort() {
|
||||||
|
return delegate.getLocalPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress getLocalSocketAddress() {
|
||||||
|
return delegate.getLocalSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getOOBInline() throws SocketException {
|
||||||
|
return delegate.getOOBInline();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() throws IOException {
|
||||||
|
return delegate.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPort() {
|
||||||
|
return delegate.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int getReceiveBufferSize() throws SocketException {
|
||||||
|
return delegate.getReceiveBufferSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress getRemoteSocketAddress() {
|
||||||
|
return delegate.getRemoteSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getReuseAddress() throws SocketException {
|
||||||
|
return delegate.getReuseAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int getSendBufferSize() throws SocketException {
|
||||||
|
return delegate.getSendBufferSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSoLinger() throws SocketException {
|
||||||
|
return delegate.getSoLinger();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int getSoTimeout() throws SocketException {
|
||||||
|
return delegate.getSoTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getTcpNoDelay() throws SocketException {
|
||||||
|
return delegate.getTcpNoDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTrafficClass() throws SocketException {
|
||||||
|
return delegate.getTrafficClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBound() {
|
||||||
|
return delegate.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return delegate.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return delegate.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInputShutdown() {
|
||||||
|
return delegate.isInputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOutputShutdown() {
|
||||||
|
return delegate.isOutputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendUrgentData(int value) throws IOException {
|
||||||
|
delegate.sendUrgentData(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setKeepAlive(boolean keepAlive) throws SocketException {
|
||||||
|
delegate.setKeepAlive(keepAlive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOOBInline(boolean oobinline) throws SocketException {
|
||||||
|
delegate.setOOBInline(oobinline);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
|
||||||
|
delegate.setPerformancePreferences(connectionTime,
|
||||||
|
latency, bandwidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setReceiveBufferSize(int size) throws SocketException {
|
||||||
|
delegate.setReceiveBufferSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReuseAddress(boolean reuse) throws SocketException {
|
||||||
|
delegate.setReuseAddress(reuse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setSendBufferSize(int size) throws SocketException {
|
||||||
|
delegate.setSendBufferSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSoLinger(boolean on, int timeout) throws SocketException {
|
||||||
|
delegate.setSoLinger(on, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setSoTimeout(int timeout) throws SocketException {
|
||||||
|
delegate.setSoTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTcpNoDelay(boolean on) throws SocketException {
|
||||||
|
delegate.setTcpNoDelay(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTrafficClass(int value) throws SocketException {
|
||||||
|
delegate.setTrafficClass(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdownInput() throws IOException {
|
||||||
|
delegate.shutdownInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdownOutput() throws IOException {
|
||||||
|
delegate.shutdownOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspired by https://github.com/k9mail/k-9/commit/54f9fd36a77423a55f63fbf9b1bcea055a239768
|
||||||
|
|
||||||
|
public DelegateSSLSocket setHostname(String host) {
|
||||||
|
try {
|
||||||
|
delegate
|
||||||
|
.getClass()
|
||||||
|
.getMethod("setHostname", String.class)
|
||||||
|
.invoke(delegate, host);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Could not enable SNI", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return delegate.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return delegate.equals(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,701 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2014-2016 Hans-Christoph Steiner
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
* Portions Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to simplify setting up a proxy connection
|
||||||
|
* to Orbot.
|
||||||
|
*
|
||||||
|
* If you are using classes in the info.guardianproject.netcipher.client
|
||||||
|
* package, call OrbotHelper.get(this).init(); from onCreate()
|
||||||
|
* of a custom Application subclass, or from some other guaranteed
|
||||||
|
* entry point to your app. At that point, the
|
||||||
|
* info.guardianproject.netcipher.client classes will be ready
|
||||||
|
* for use.
|
||||||
|
*/
|
||||||
|
public class OrbotHelper implements ProxyHelper {
|
||||||
|
|
||||||
|
private final static int REQUEST_CODE_STATUS = 100;
|
||||||
|
|
||||||
|
public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
|
||||||
|
public final static String ORBOT_MARKET_URI = "market://details?id=" + ORBOT_PACKAGE_NAME;
|
||||||
|
public final static String ORBOT_FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
|
||||||
|
+ ORBOT_PACKAGE_NAME;
|
||||||
|
public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
|
||||||
|
+ ORBOT_PACKAGE_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to Orbot to transparently start Tor services
|
||||||
|
*/
|
||||||
|
public final static String ACTION_START = "org.torproject.android.intent.action.START";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status
|
||||||
|
* included as an {@link #EXTRA_STATUS} {@code String}. Your app should
|
||||||
|
* always receive {@code ACTION_STATUS Intent}s since any other app could
|
||||||
|
* start Orbot. Also, user-triggered starts and stops will also cause
|
||||||
|
* {@code ACTION_STATUS Intent}s to be broadcast.
|
||||||
|
*/
|
||||||
|
public final static String ACTION_STATUS = "org.torproject.android.intent.action.STATUS";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code String} that contains a status constant: {@link #STATUS_ON},
|
||||||
|
* {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
|
||||||
|
* {@link #STATUS_STOPPING}
|
||||||
|
*/
|
||||||
|
public final static String EXTRA_STATUS = "org.torproject.android.intent.extra.STATUS";
|
||||||
|
/**
|
||||||
|
* A {@link String} {@code packageName} for Orbot to direct its status reply
|
||||||
|
* to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
|
||||||
|
*/
|
||||||
|
public final static String EXTRA_PACKAGE_NAME = "org.torproject.android.intent.extra.PACKAGE_NAME";
|
||||||
|
|
||||||
|
public final static String EXTRA_PROXY_PORT_HTTP = "org.torproject.android.intent.extra.HTTP_PROXY_PORT";
|
||||||
|
public final static String EXTRA_PROXY_PORT_SOCKS = "org.torproject.android.intent.extra.SOCKS_PROXY_PORT";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All tor-related services and daemons are stopped
|
||||||
|
*/
|
||||||
|
public final static String STATUS_OFF = "OFF";
|
||||||
|
/**
|
||||||
|
* All tor-related services and daemons have completed starting
|
||||||
|
*/
|
||||||
|
public final static String STATUS_ON = "ON";
|
||||||
|
public final static String STATUS_STARTING = "STARTING";
|
||||||
|
public final static String STATUS_STOPPING = "STOPPING";
|
||||||
|
/**
|
||||||
|
* The user has disabled the ability for background starts triggered by
|
||||||
|
* apps. Fallback to the old Intent that brings up Orbot.
|
||||||
|
*/
|
||||||
|
public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";
|
||||||
|
|
||||||
|
public final static String ACTION_START_TOR = "org.torproject.android.START_TOR";
|
||||||
|
public final static String ACTION_REQUEST_HS = "org.torproject.android.REQUEST_HS_PORT";
|
||||||
|
public final static int START_TOR_RESULT = 0x9234;
|
||||||
|
public final static int HS_REQUEST_CODE = 9999;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
private OrbotHelper() {
|
||||||
|
// only static utility methods, do not instantiate
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a {@link URL} is a Tor Hidden Service host name, also known
|
||||||
|
* as an ".onion address".
|
||||||
|
*
|
||||||
|
* @return whether the host name is a Tor .onion address
|
||||||
|
*/
|
||||||
|
public static boolean isOnionAddress(URL url) {
|
||||||
|
return url.getHost().endsWith(".onion");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a URL {@link String} is a Tor Hidden Service host name, also known
|
||||||
|
* as an ".onion address".
|
||||||
|
*
|
||||||
|
* @return whether the host name is a Tor .onion address
|
||||||
|
*/
|
||||||
|
public static boolean isOnionAddress(String urlString) {
|
||||||
|
try {
|
||||||
|
return isOnionAddress(new URL(urlString));
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a {@link Uri} is a Tor Hidden Service host name, also known
|
||||||
|
* as an ".onion address".
|
||||||
|
*
|
||||||
|
* @return whether the host name is a Tor .onion address
|
||||||
|
*/
|
||||||
|
public static boolean isOnionAddress(Uri uri) {
|
||||||
|
return uri.getHost().endsWith(".onion");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the tor process is running. This method is very
|
||||||
|
* brittle, and is therefore deprecated in favor of using the
|
||||||
|
* {@link #ACTION_STATUS} {@code Intent} along with the
|
||||||
|
* {@link #requestStartTor(Context)} method.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static boolean isOrbotRunning(Context context) {
|
||||||
|
int procId = TorServiceUtils.findProcessId(context);
|
||||||
|
|
||||||
|
return (procId != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isOrbotInstalled(Context context) {
|
||||||
|
return isAppInstalled(context, ORBOT_PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAppInstalled(Context context, String uri) {
|
||||||
|
try {
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
|
||||||
|
return true;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void requestHiddenServiceOnPort(Activity activity, int port) {
|
||||||
|
Intent intent = new Intent(ACTION_REQUEST_HS);
|
||||||
|
intent.setPackage(ORBOT_PACKAGE_NAME);
|
||||||
|
intent.putExtra("hs_port", port);
|
||||||
|
|
||||||
|
activity.startActivityForResult(intent, HS_REQUEST_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First, checks whether Orbot is installed. If Orbot is installed, then a
|
||||||
|
* broadcast {@link Intent} is sent to request Orbot to start
|
||||||
|
* transparently in the background. When Orbot receives this {@code
|
||||||
|
* Intent}, it will immediately reply to the app that called this method
|
||||||
|
* with an {@link #ACTION_STATUS} {@code Intent} that is broadcast to the
|
||||||
|
* {@code packageName} of the provided {@link Context} (i.e. {@link
|
||||||
|
* Context#getPackageName()}.
|
||||||
|
* <p>
|
||||||
|
* That reply {@link #ACTION_STATUS} {@code Intent} could say that the user
|
||||||
|
* has disabled background starts with the status
|
||||||
|
* {@link #STATUS_STARTS_DISABLED}. That means that Orbot ignored this
|
||||||
|
* request. To directly prompt the user to start Tor, use
|
||||||
|
* {@link #requestShowOrbotStart(Activity)}, which will bring up
|
||||||
|
* Orbot itself for the user to manually start Tor. Orbot always broadcasts
|
||||||
|
* it's status, so your app will receive those no matter how Tor gets
|
||||||
|
* started.
|
||||||
|
*
|
||||||
|
* @param context the app {@link Context} will receive the reply
|
||||||
|
* @return whether the start request was sent to Orbot
|
||||||
|
* @see #requestShowOrbotStart(Activity activity)
|
||||||
|
*/
|
||||||
|
public static boolean requestStartTor(Context context) {
|
||||||
|
if (OrbotHelper.isOrbotInstalled(context)) {
|
||||||
|
Log.i("OrbotHelper", "requestStartTor " + context.getPackageName());
|
||||||
|
Intent intent = getOrbotStartIntent(context);
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an {@link Intent} for starting Orbot. Orbot will reply with the
|
||||||
|
* current status to the {@code packageName} of the app in the provided
|
||||||
|
* {@link Context} (i.e. {@link Context#getPackageName()}.
|
||||||
|
*/
|
||||||
|
public static Intent getOrbotStartIntent(Context context) {
|
||||||
|
Intent intent = new Intent(ACTION_START);
|
||||||
|
intent.setPackage(ORBOT_PACKAGE_NAME);
|
||||||
|
intent.putExtra(EXTRA_PACKAGE_NAME, context.getPackageName());
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a barebones {@link Intent} for starting Orbot. This is deprecated
|
||||||
|
* in favor of {@link #getOrbotStartIntent(Context)}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static Intent getOrbotStartIntent() {
|
||||||
|
Intent intent = new Intent(ACTION_START);
|
||||||
|
intent.setPackage(ORBOT_PACKAGE_NAME);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First, checks whether Orbot is installed, then checks whether Orbot is
|
||||||
|
* running. If Orbot is installed and not running, then an {@link Intent} is
|
||||||
|
* sent to request the user to start Orbot, which will show the main Orbot screen.
|
||||||
|
* The result will be returned in
|
||||||
|
* {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
|
||||||
|
* with a {@code requestCode} of {@code START_TOR_RESULT}
|
||||||
|
* <p>
|
||||||
|
* Orbot will also always broadcast the status of starting Tor via the
|
||||||
|
* {@link #ACTION_STATUS} Intent, no matter how it is started.
|
||||||
|
*
|
||||||
|
* @param activity the {@code Activity} that gets the result of the
|
||||||
|
* {@link #START_TOR_RESULT} request
|
||||||
|
* @return whether the start request was sent to Orbot
|
||||||
|
* @see #requestStartTor(Context context)
|
||||||
|
*/
|
||||||
|
public static boolean requestShowOrbotStart(Activity activity) {
|
||||||
|
if (OrbotHelper.isOrbotInstalled(activity)) {
|
||||||
|
if (!OrbotHelper.isOrbotRunning(activity)) {
|
||||||
|
Intent intent = getShowOrbotStartIntent();
|
||||||
|
activity.startActivityForResult(intent, START_TOR_RESULT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent getShowOrbotStartIntent() {
|
||||||
|
Intent intent = new Intent(ACTION_START_TOR);
|
||||||
|
intent.setPackage(ORBOT_PACKAGE_NAME);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent getOrbotInstallIntent(Context context) {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(ORBOT_MARKET_URI));
|
||||||
|
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
|
||||||
|
|
||||||
|
String foundPackageName = null;
|
||||||
|
for (ResolveInfo r : resInfos) {
|
||||||
|
Log.i("OrbotHelper", "market: " + r.activityInfo.packageName);
|
||||||
|
if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
|
||||||
|
|| TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
|
||||||
|
foundPackageName = r.activityInfo.packageName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundPackageName == null) {
|
||||||
|
intent.setData(Uri.parse(ORBOT_FDROID_URI));
|
||||||
|
} else {
|
||||||
|
intent.setPackage(foundPackageName);
|
||||||
|
}
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInstalled(Context context) {
|
||||||
|
return isOrbotInstalled(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestStatus(Context context) {
|
||||||
|
isOrbotRunning(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requestStart(Context context) {
|
||||||
|
return requestStartTor(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getInstallIntent(Context context) {
|
||||||
|
return getOrbotInstallIntent(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getStartIntent(Context context) {
|
||||||
|
return getOrbotStartIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Orbot";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MLM additions */
|
||||||
|
|
||||||
|
private final Context ctxt;
|
||||||
|
private final Handler handler;
|
||||||
|
private boolean isInstalled=false;
|
||||||
|
private Intent lastStatusIntent=null;
|
||||||
|
private Set<StatusCallback> statusCallbacks=
|
||||||
|
newSetFromMap(new WeakHashMap<StatusCallback, Boolean>());
|
||||||
|
private Set<InstallCallback> installCallbacks=
|
||||||
|
newSetFromMap(new WeakHashMap<InstallCallback, Boolean>());
|
||||||
|
private long statusTimeoutMs=30000L;
|
||||||
|
private long installTimeoutMs=60000L;
|
||||||
|
private boolean validateOrbot=true;
|
||||||
|
|
||||||
|
abstract public static class SimpleStatusCallback
|
||||||
|
implements StatusCallback {
|
||||||
|
@Override
|
||||||
|
public void onEnabled(Intent statusIntent) {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStarting() {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopping() {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisabled() {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotYetInstalled() {
|
||||||
|
// no-op; extend and override if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface used for reporting the results of an
|
||||||
|
* attempt to install Orbot
|
||||||
|
*/
|
||||||
|
public interface InstallCallback {
|
||||||
|
void onInstalled();
|
||||||
|
void onInstallTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static volatile OrbotHelper INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the singleton, initializing if if needed
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do, as we will hold onto
|
||||||
|
* the Application
|
||||||
|
* @return the singleton
|
||||||
|
*/
|
||||||
|
synchronized public static OrbotHelper get(Context ctxt) {
|
||||||
|
if (INSTANCE==null) {
|
||||||
|
INSTANCE=new OrbotHelper(ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard constructor
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; OrbotInitializer will hold
|
||||||
|
* onto the Application context
|
||||||
|
*/
|
||||||
|
private OrbotHelper(Context ctxt) {
|
||||||
|
this.ctxt=ctxt.getApplicationContext();
|
||||||
|
this.handler=new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a StatusCallback to be called when we find out that
|
||||||
|
* Orbot is ready. If Orbot is ready for use, your callback
|
||||||
|
* will be called with onEnabled() immediately, before this
|
||||||
|
* method returns.
|
||||||
|
*
|
||||||
|
* @param cb a callback
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper addStatusCallback(StatusCallback cb) {
|
||||||
|
statusCallbacks.add(cb);
|
||||||
|
|
||||||
|
if (lastStatusIntent!=null) {
|
||||||
|
String status=
|
||||||
|
lastStatusIntent.getStringExtra(OrbotHelper.EXTRA_STATUS);
|
||||||
|
|
||||||
|
if (status.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
cb.onEnabled(lastStatusIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an existing registered StatusCallback.
|
||||||
|
*
|
||||||
|
* @param cb the callback to remove
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper removeStatusCallback(StatusCallback cb) {
|
||||||
|
statusCallbacks.remove(cb);
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an InstallCallback to be called when we find out that
|
||||||
|
* Orbot is installed
|
||||||
|
*
|
||||||
|
* @param cb a callback
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper addInstallCallback(InstallCallback cb) {
|
||||||
|
installCallbacks.add(cb);
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an existing registered InstallCallback.
|
||||||
|
*
|
||||||
|
* @param cb the callback to remove
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper removeInstallCallback(InstallCallback cb) {
|
||||||
|
installCallbacks.remove(cb);
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets how long of a delay, in milliseconds, after trying
|
||||||
|
* to get a status from Orbot before we give up.
|
||||||
|
* Defaults to 30000ms = 30 seconds = 0.000347222 days
|
||||||
|
*
|
||||||
|
* @param timeoutMs delay period in milliseconds
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper statusTimeout(long timeoutMs) {
|
||||||
|
statusTimeoutMs=timeoutMs;
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets how long of a delay, in milliseconds, after trying
|
||||||
|
* to install Orbot do we assume that it's not happening.
|
||||||
|
* Defaults to 60000ms = 60 seconds = 1 minute = 1.90259e-6 years
|
||||||
|
*
|
||||||
|
* @param timeoutMs delay period in milliseconds
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper installTimeout(long timeoutMs) {
|
||||||
|
installTimeoutMs=timeoutMs;
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, NetCipher ensures that the Orbot on the
|
||||||
|
* device is one of the official builds. Call this method
|
||||||
|
* to skip that validation. Mostly, this is for developers
|
||||||
|
* who have their own custom Orbot builds (e.g., for
|
||||||
|
* dedicated hardware).
|
||||||
|
*
|
||||||
|
* @return the singleton, for chaining
|
||||||
|
*/
|
||||||
|
public OrbotHelper skipOrbotValidation() {
|
||||||
|
validateOrbot=false;
|
||||||
|
|
||||||
|
return(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if Orbot is installed (the last time we checked),
|
||||||
|
* false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isInstalled() {
|
||||||
|
return(isInstalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the connection to Orbot, revalidating that it
|
||||||
|
* is installed and requesting fresh status broadcasts.
|
||||||
|
*
|
||||||
|
* @return true if initialization is proceeding, false if
|
||||||
|
* Orbot is not installed
|
||||||
|
*/
|
||||||
|
public boolean init() {
|
||||||
|
Intent orbot=OrbotHelper.getOrbotStartIntent(ctxt);
|
||||||
|
|
||||||
|
if (validateOrbot) {
|
||||||
|
ArrayList<String> hashes=new ArrayList<String>();
|
||||||
|
|
||||||
|
hashes.add("A4:54:B8:7A:18:47:A8:9E:D7:F5:E7:0F:BA:6B:BA:96:F3:EF:29:C2:6E:09:81:20:4F:E3:47:BF:23:1D:FD:5B");
|
||||||
|
hashes.add("A7:02:07:92:4F:61:FF:09:37:1D:54:84:14:5C:4B:EE:77:2C:55:C1:9E:EE:23:2F:57:70:E1:82:71:F7:CB:AE");
|
||||||
|
|
||||||
|
orbot=
|
||||||
|
SignatureUtils.validateBroadcastIntent(ctxt, orbot,
|
||||||
|
hashes, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orbot!=null) {
|
||||||
|
isInstalled=true;
|
||||||
|
handler.postDelayed(onStatusTimeout, statusTimeoutMs);
|
||||||
|
ctxt.registerReceiver(orbotStatusReceiver,
|
||||||
|
new IntentFilter(OrbotHelper.ACTION_STATUS));
|
||||||
|
ctxt.sendBroadcast(orbot);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isInstalled=false;
|
||||||
|
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onNotYetInstalled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(isInstalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given that init() returned false, calling installOrbot()
|
||||||
|
* will trigger an attempt to install Orbot from an available
|
||||||
|
* distribution channel (e.g., the Play Store). Only call this
|
||||||
|
* if the user is expecting it, such as in response to tapping
|
||||||
|
* a dialog button or an action bar item.
|
||||||
|
*
|
||||||
|
* Note that installation may take a long time, even if
|
||||||
|
* the user is proceeding with the installation, due to network
|
||||||
|
* speeds, waiting for user input, and so on. Either specify
|
||||||
|
* a long timeout, or consider the timeout to be merely advisory
|
||||||
|
* and use some other user input to cause you to try
|
||||||
|
* init() again after, presumably, Orbot has been installed
|
||||||
|
* and configured by the user.
|
||||||
|
*
|
||||||
|
* If the user does install Orbot, we will attempt init()
|
||||||
|
* again automatically. Hence, you will probably need user input
|
||||||
|
* to tell you when the user has gotten Orbot up and going.
|
||||||
|
*
|
||||||
|
* @param host the Activity that is triggering this work
|
||||||
|
*/
|
||||||
|
public void installOrbot(Activity host) {
|
||||||
|
handler.postDelayed(onInstallTimeout, installTimeoutMs);
|
||||||
|
|
||||||
|
IntentFilter filter=
|
||||||
|
new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||||
|
|
||||||
|
filter.addDataScheme("package");
|
||||||
|
|
||||||
|
ctxt.registerReceiver(orbotInstallReceiver, filter);
|
||||||
|
host.startActivity(OrbotHelper.getOrbotInstallIntent(ctxt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BroadcastReceiver orbotStatusReceiver=new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctxt, Intent intent) {
|
||||||
|
if (TextUtils.equals(intent.getAction(),
|
||||||
|
OrbotHelper.ACTION_STATUS)) {
|
||||||
|
String status=intent.getStringExtra(OrbotHelper.EXTRA_STATUS);
|
||||||
|
|
||||||
|
if (status.equals(OrbotHelper.STATUS_ON)) {
|
||||||
|
lastStatusIntent=intent;
|
||||||
|
handler.removeCallbacks(onStatusTimeout);
|
||||||
|
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onEnabled(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (status.equals(OrbotHelper.STATUS_OFF)) {
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onDisabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (status.equals(OrbotHelper.STATUS_STARTING)) {
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onStarting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (status.equals(OrbotHelper.STATUS_STOPPING)) {
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onStopping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private Runnable onStatusTimeout=new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ctxt.unregisterReceiver(orbotStatusReceiver);
|
||||||
|
|
||||||
|
for (StatusCallback cb : statusCallbacks) {
|
||||||
|
cb.onStatusTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private BroadcastReceiver orbotInstallReceiver=new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctxt, Intent intent) {
|
||||||
|
if (TextUtils.equals(intent.getAction(),
|
||||||
|
Intent.ACTION_PACKAGE_ADDED)) {
|
||||||
|
String pkgName=intent.getData().getEncodedSchemeSpecificPart();
|
||||||
|
|
||||||
|
if (OrbotHelper.ORBOT_PACKAGE_NAME.equals(pkgName)) {
|
||||||
|
isInstalled=true;
|
||||||
|
handler.removeCallbacks(onInstallTimeout);
|
||||||
|
ctxt.unregisterReceiver(orbotInstallReceiver);
|
||||||
|
|
||||||
|
for (InstallCallback cb : installCallbacks) {
|
||||||
|
cb.onInstalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private Runnable onInstallTimeout=new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ctxt.unregisterReceiver(orbotInstallReceiver);
|
||||||
|
|
||||||
|
for (InstallCallback cb : installCallbacks) {
|
||||||
|
cb.onInstallTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
|
||||||
|
if (map.isEmpty()) {
|
||||||
|
return new SetFromMap<E>(map);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("map not empty");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package info.guardianproject.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
public interface ProxyHelper {
|
||||||
|
|
||||||
|
public boolean isInstalled (Context context);
|
||||||
|
public void requestStatus (Context context);
|
||||||
|
public boolean requestStart (Context context);
|
||||||
|
public Intent getInstallIntent (Context context);
|
||||||
|
public Intent getStartIntent (Context context);
|
||||||
|
public String getName ();
|
||||||
|
|
||||||
|
public final static String FDROID_PACKAGE_NAME = "org.fdroid.fdroid";
|
||||||
|
public final static String PLAY_PACKAGE_NAME = "com.android.vending";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to Orbot to transparently start Tor services
|
||||||
|
*/
|
||||||
|
public final static String ACTION_START = "android.intent.action.PROXY_START";
|
||||||
|
/**
|
||||||
|
* {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status
|
||||||
|
*/
|
||||||
|
public final static String ACTION_STATUS = "android.intent.action.PROXY_STATUS";
|
||||||
|
/**
|
||||||
|
* {@code String} that contains a status constant: {@link #STATUS_ON},
|
||||||
|
* {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
|
||||||
|
* {@link #STATUS_STOPPING}
|
||||||
|
*/
|
||||||
|
public final static String EXTRA_STATUS = "android.intent.extra.PROXY_STATUS";
|
||||||
|
|
||||||
|
public final static String EXTRA_PROXY_PORT_HTTP = "android.intent.extra.PROXY_PORT_HTTP";
|
||||||
|
public final static String EXTRA_PROXY_PORT_SOCKS = "android.intent.extra.PROXY_PORT_SOCKS";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link String} {@code packageName} for Orbot to direct its status reply
|
||||||
|
* to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
|
||||||
|
*/
|
||||||
|
public final static String EXTRA_PACKAGE_NAME = "android.intent.extra.PROXY_PACKAGE_NAME";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All tor-related services and daemons are stopped
|
||||||
|
*/
|
||||||
|
public final static String STATUS_OFF = "OFF";
|
||||||
|
/**
|
||||||
|
* All tor-related services and daemons have completed starting
|
||||||
|
*/
|
||||||
|
public final static String STATUS_ON = "ON";
|
||||||
|
public final static String STATUS_STARTING = "STARTING";
|
||||||
|
public final static String STATUS_STOPPING = "STOPPING";
|
||||||
|
/**
|
||||||
|
* The user has disabled the ability for background starts triggered by
|
||||||
|
* apps. Fallback to the old Intent that brings up Orbot.
|
||||||
|
*/
|
||||||
|
public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package info.guardianproject.netcipher.proxy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class ProxySelector extends java.net.ProxySelector {
|
||||||
|
|
||||||
|
private ArrayList<Proxy> listProxies;
|
||||||
|
|
||||||
|
public ProxySelector ()
|
||||||
|
{
|
||||||
|
super ();
|
||||||
|
|
||||||
|
listProxies = new ArrayList<Proxy>();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addProxy (Proxy.Type type,String host, int port)
|
||||||
|
{
|
||||||
|
Proxy proxy = new Proxy(type,new InetSocketAddress(host, port));
|
||||||
|
listProxies.add(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectFailed(URI uri, SocketAddress address,
|
||||||
|
IOException failure) {
|
||||||
|
Log.w("ProxySelector","could not connect to " + address.toString() + ": " + failure.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Proxy> select(URI uri) {
|
||||||
|
|
||||||
|
return listProxies;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package info.guardianproject.netcipher.proxy;
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
public class PsiphonHelper implements ProxyHelper {
|
||||||
|
|
||||||
|
public final static String PACKAGE_NAME = "com.psiphon3";
|
||||||
|
public final static String COMPONENT_NAME = "com.psiphon3.StatusActivity";
|
||||||
|
|
||||||
|
|
||||||
|
public final static String MARKET_URI = "market://details?id=" + PACKAGE_NAME;
|
||||||
|
public final static String FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
|
||||||
|
+ PACKAGE_NAME;
|
||||||
|
public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
|
||||||
|
+ PACKAGE_NAME;
|
||||||
|
|
||||||
|
public final static int DEFAULT_SOCKS_PORT = 1080;
|
||||||
|
public final static int DEFAULT_HTTP_PORT = 8080;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInstalled(Context context) {
|
||||||
|
return isAppInstalled(context, PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean isAppInstalled(Context context, String uri) {
|
||||||
|
try {
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
|
||||||
|
return true;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestStatus(final Context context) {
|
||||||
|
|
||||||
|
Thread thread = new Thread ()
|
||||||
|
{
|
||||||
|
public void run ()
|
||||||
|
{
|
||||||
|
//can connect to default HTTP proxy port?
|
||||||
|
boolean isSocksOpen = false;
|
||||||
|
boolean isHttpOpen = false;
|
||||||
|
|
||||||
|
int socksPort = DEFAULT_SOCKS_PORT;
|
||||||
|
int httpPort = DEFAULT_HTTP_PORT;
|
||||||
|
|
||||||
|
for (int i = 0; i < 10 && (!isSocksOpen); i++)
|
||||||
|
isSocksOpen = isPortOpen("127.0.0.1",socksPort++,100);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10 && (!isHttpOpen); i++)
|
||||||
|
isHttpOpen = isPortOpen("127.0.0.1",httpPort++,100);
|
||||||
|
|
||||||
|
//any other check?
|
||||||
|
|
||||||
|
Intent intent = new Intent(ProxyHelper.ACTION_STATUS);
|
||||||
|
intent.putExtra(EXTRA_PACKAGE_NAME, PACKAGE_NAME);
|
||||||
|
|
||||||
|
if (isSocksOpen && isHttpOpen)
|
||||||
|
{
|
||||||
|
intent.putExtra(EXTRA_STATUS, STATUS_ON);
|
||||||
|
|
||||||
|
intent.putExtra(EXTRA_PROXY_PORT_HTTP, httpPort-1);
|
||||||
|
intent.putExtra(EXTRA_PROXY_PORT_SOCKS, socksPort-1);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intent.putExtra(EXTRA_STATUS, STATUS_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requestStart(Context context) {
|
||||||
|
|
||||||
|
Intent intent = getStartIntent(context);
|
||||||
|
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getInstallIntent(Context context) {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(MARKET_URI));
|
||||||
|
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
|
||||||
|
|
||||||
|
String foundPackageName = null;
|
||||||
|
for (ResolveInfo r : resInfos) {
|
||||||
|
if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
|
||||||
|
|| TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
|
||||||
|
foundPackageName = r.activityInfo.packageName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundPackageName == null) {
|
||||||
|
intent.setData(Uri.parse(FDROID_URI));
|
||||||
|
} else {
|
||||||
|
intent.setPackage(foundPackageName);
|
||||||
|
}
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getStartIntent(Context context) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setComponent(new ComponentName(PACKAGE_NAME, COMPONENT_NAME));
|
||||||
|
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPortOpen(final String ip, final int port, final int timeout) {
|
||||||
|
try {
|
||||||
|
Socket socket = new Socket();
|
||||||
|
socket.connect(new InetSocketAddress(ip, port), timeout);
|
||||||
|
socket.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch(ConnectException ce){
|
||||||
|
ce.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return PACKAGE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.proxy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.AbstractSet;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
class SetFromMap<E> extends AbstractSet<E>
|
||||||
|
implements Serializable {
|
||||||
|
private static final long serialVersionUID = 2454657854757543876L;
|
||||||
|
// Must be named as is, to pass serialization compatibility test.
|
||||||
|
private final Map<E, Boolean> m;
|
||||||
|
private transient Set<E> backingSet;
|
||||||
|
SetFromMap(final Map<E, Boolean> map) {
|
||||||
|
m = map;
|
||||||
|
backingSet = map.keySet();
|
||||||
|
}
|
||||||
|
@Override public boolean equals(Object object) {
|
||||||
|
return backingSet.equals(object);
|
||||||
|
}
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return backingSet.hashCode();
|
||||||
|
}
|
||||||
|
@Override public boolean add(E object) {
|
||||||
|
return m.put(object, Boolean.TRUE) == null;
|
||||||
|
}
|
||||||
|
@Override public void clear() {
|
||||||
|
m.clear();
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return backingSet.toString();
|
||||||
|
}
|
||||||
|
@Override public boolean contains(Object object) {
|
||||||
|
return backingSet.contains(object);
|
||||||
|
}
|
||||||
|
@Override public boolean containsAll(Collection<?> collection) {
|
||||||
|
return backingSet.containsAll(collection);
|
||||||
|
}
|
||||||
|
@Override public boolean isEmpty() {
|
||||||
|
return m.isEmpty();
|
||||||
|
}
|
||||||
|
@Override public boolean remove(Object object) {
|
||||||
|
return m.remove(object) != null;
|
||||||
|
}
|
||||||
|
@Override public boolean retainAll(Collection<?> collection) {
|
||||||
|
return backingSet.retainAll(collection);
|
||||||
|
}
|
||||||
|
@Override public Object[] toArray() {
|
||||||
|
return backingSet.toArray();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public <T> T[] toArray(T[] contents) {
|
||||||
|
return backingSet.toArray(contents);
|
||||||
|
}
|
||||||
|
@Override public Iterator<E> iterator() {
|
||||||
|
return backingSet.iterator();
|
||||||
|
}
|
||||||
|
@Override public int size() {
|
||||||
|
return m.size();
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void readObject(ObjectInputStream stream)
|
||||||
|
throws IOException, ClassNotFoundException {
|
||||||
|
stream.defaultReadObject();
|
||||||
|
backingSet = m.keySet();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,476 @@
|
||||||
|
/***
|
||||||
|
Copyright (c) 2014 CommonsWare, LLC
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
not use this file except in compliance with the License. You may obtain
|
||||||
|
a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.Signature;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SignatureUtils {
|
||||||
|
public static String getOwnSignatureHash(Context ctxt)
|
||||||
|
throws
|
||||||
|
NameNotFoundException,
|
||||||
|
NoSuchAlgorithmException {
|
||||||
|
return(getSignatureHash(ctxt, ctxt.getPackageName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSignatureHash(Context ctxt, String packageName)
|
||||||
|
throws
|
||||||
|
NameNotFoundException,
|
||||||
|
NoSuchAlgorithmException {
|
||||||
|
MessageDigest md=MessageDigest.getInstance("SHA-256");
|
||||||
|
Signature sig=
|
||||||
|
ctxt.getPackageManager()
|
||||||
|
.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures[0];
|
||||||
|
|
||||||
|
return(toHexStringWithColons(md.digest(sig.toByteArray())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on https://stackoverflow.com/a/2197650/115145
|
||||||
|
|
||||||
|
public static String toHexStringWithColons(byte[] bytes) {
|
||||||
|
char[] hexArray=
|
||||||
|
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
|
||||||
|
'C', 'D', 'E', 'F' };
|
||||||
|
char[] hexChars=new char[(bytes.length * 3) - 1];
|
||||||
|
int v;
|
||||||
|
|
||||||
|
for (int j=0; j < bytes.length; j++) {
|
||||||
|
v=bytes[j] & 0xFF;
|
||||||
|
hexChars[j * 3]=hexArray[v / 16];
|
||||||
|
hexChars[j * 3 + 1]=hexArray[v % 16];
|
||||||
|
|
||||||
|
if (j < bytes.length - 1) {
|
||||||
|
hexChars[j * 3 + 2]=':';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(hexChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the broadcast receiver for a given Intent
|
||||||
|
* has the desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the receiver, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some receiver whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* receiver was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no receiver was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching receiver
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching receiver, so the "broadcast" will only go to this
|
||||||
|
* one component.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to broadcast
|
||||||
|
* @param sigHash the signature hash of the app that you expect
|
||||||
|
* to handle this broadcast
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching receiver is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching receiver with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target receiver added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateBroadcastIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
String sigHash,
|
||||||
|
boolean failIfHack) {
|
||||||
|
ArrayList<String> sigHashes=new ArrayList<String>();
|
||||||
|
|
||||||
|
sigHashes.add(sigHash);
|
||||||
|
|
||||||
|
return(validateBroadcastIntent(ctxt, toValidate, sigHashes,
|
||||||
|
failIfHack));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the broadcast receiver for a given Intent
|
||||||
|
* has a desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the receiver, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has a proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some receiver whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* receiver was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no receiver was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching receiver
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching receiver, so the "broadcast" will only go to this
|
||||||
|
* one component.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to broadcast
|
||||||
|
* @param sigHashes the possible signature hashes of the app
|
||||||
|
* that you expect to handle this broadcast
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching receiver is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching receiver with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target receiver added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateBroadcastIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
List<String> sigHashes,
|
||||||
|
boolean failIfHack) {
|
||||||
|
PackageManager pm=ctxt.getPackageManager();
|
||||||
|
Intent result=null;
|
||||||
|
List<ResolveInfo> receivers=
|
||||||
|
pm.queryBroadcastReceivers(toValidate, 0);
|
||||||
|
|
||||||
|
if (receivers!=null) {
|
||||||
|
for (ResolveInfo info : receivers) {
|
||||||
|
try {
|
||||||
|
if (sigHashes.contains(getSignatureHash(ctxt,
|
||||||
|
info.activityInfo.packageName))) {
|
||||||
|
ComponentName cn=
|
||||||
|
new ComponentName(info.activityInfo.packageName,
|
||||||
|
info.activityInfo.name);
|
||||||
|
|
||||||
|
result=new Intent(toValidate).setComponent(cn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (failIfHack) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Package has signature hash mismatch: "+
|
||||||
|
info.activityInfo.packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
catch (NameNotFoundException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the activity for a given Intent has the
|
||||||
|
* desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the activity, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some activity whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* activity was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no activity was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching activity
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching activity, so a call to startActivity() for this
|
||||||
|
* Intent is guaranteed to go to this specific activity.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to use with
|
||||||
|
* startActivity()
|
||||||
|
* @param sigHash the signature hash of the app that you expect
|
||||||
|
* to handle this activity
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching activity is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching activity with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target activity added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateActivityIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
String sigHash,
|
||||||
|
boolean failIfHack) {
|
||||||
|
ArrayList<String> sigHashes=new ArrayList<String>();
|
||||||
|
|
||||||
|
sigHashes.add(sigHash);
|
||||||
|
|
||||||
|
return(validateActivityIntent(ctxt, toValidate, sigHashes,
|
||||||
|
failIfHack));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the activity for a given Intent has the
|
||||||
|
* desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the activity, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some activity whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* activity was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no activity was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching activity
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching activity, so a call to startActivity() for this
|
||||||
|
* Intent is guaranteed to go to this specific activity.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to use with
|
||||||
|
* startActivity()
|
||||||
|
* @param sigHashes the signature hashes of the app that you expect
|
||||||
|
* to handle this activity
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching activity is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching activity with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target activity added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateActivityIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
List<String> sigHashes,
|
||||||
|
boolean failIfHack) {
|
||||||
|
PackageManager pm=ctxt.getPackageManager();
|
||||||
|
Intent result=null;
|
||||||
|
List<ResolveInfo> activities=
|
||||||
|
pm.queryIntentActivities(toValidate, 0);
|
||||||
|
|
||||||
|
if (activities!=null) {
|
||||||
|
for (ResolveInfo info : activities) {
|
||||||
|
try {
|
||||||
|
if (sigHashes.contains(getSignatureHash(ctxt,
|
||||||
|
info.activityInfo.packageName))) {
|
||||||
|
ComponentName cn=
|
||||||
|
new ComponentName(info.activityInfo.packageName,
|
||||||
|
info.activityInfo.name);
|
||||||
|
|
||||||
|
result=new Intent(toValidate).setComponent(cn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (failIfHack) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Package has signature hash mismatch: "+
|
||||||
|
info.activityInfo.packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
catch (NameNotFoundException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the service for a given Intent has the
|
||||||
|
* desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the service, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some service whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* service was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no service was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching service
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching service, so a call to startService() or
|
||||||
|
* bindService() for this Intent is guaranteed to go to this
|
||||||
|
* specific service.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to use with
|
||||||
|
* startService() or bindService()
|
||||||
|
* @param sigHash the signature hash of the app that you expect
|
||||||
|
* to handle this service
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching service is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching service with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target service added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateServiceIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
String sigHash,
|
||||||
|
boolean failIfHack) {
|
||||||
|
ArrayList<String> sigHashes=new ArrayList<String>();
|
||||||
|
|
||||||
|
sigHashes.add(sigHash);
|
||||||
|
|
||||||
|
return(validateServiceIntent(ctxt, toValidate, sigHashes,
|
||||||
|
failIfHack));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the service for a given Intent has the
|
||||||
|
* desired signature hash.
|
||||||
|
*
|
||||||
|
* If you know the package name of the service, call
|
||||||
|
* setPackage() on the Intent before passing into this method.
|
||||||
|
* That will validate whether the package is installed and whether
|
||||||
|
* it has the proper signature hash. You can distinguish between
|
||||||
|
* these cases by passing true for the failIfHack parameter.
|
||||||
|
*
|
||||||
|
* In general, there are three possible outcomes of calling
|
||||||
|
* this method:
|
||||||
|
*
|
||||||
|
* 1. You get a SecurityException, because failIfHack is true,
|
||||||
|
* and we found some service whose app does not match the
|
||||||
|
* desired hash. The user may have installed a repackaged
|
||||||
|
* version of this app that is signed by the wrong key.
|
||||||
|
*
|
||||||
|
* 2. You get null. If failIfHack is true, this means that no
|
||||||
|
* service was found that matches the Intent. If failIfHack
|
||||||
|
* is false, this means that no service was found that matches
|
||||||
|
* the Intent and has a valid matching signature.
|
||||||
|
*
|
||||||
|
* 3. You get an Intent. This means we found a matching service
|
||||||
|
* that has a matching signature. The Intent will be a copy of
|
||||||
|
* the passed-in Intent, with the component name set to the
|
||||||
|
* matching service, so a call to startService() or
|
||||||
|
* bindService() for this Intent is guaranteed to go to this
|
||||||
|
* specific service.
|
||||||
|
*
|
||||||
|
* @param ctxt any Context will do; the value is not retained
|
||||||
|
* @param toValidate the Intent that you intend to use with
|
||||||
|
* startService() or bindService()
|
||||||
|
* @param sigHashes the signature hash of the app that you expect
|
||||||
|
* to handle this service
|
||||||
|
* @param failIfHack true if you want a SecurityException if
|
||||||
|
* a matching service is found but it has
|
||||||
|
* the wrong signature hash, false otherwise
|
||||||
|
* @return null if there is no matching service with the correct
|
||||||
|
* hash, or a copy of the toValidate parameter with the full component
|
||||||
|
* name of the target service added to the Intent
|
||||||
|
*/
|
||||||
|
public static Intent validateServiceIntent(Context ctxt,
|
||||||
|
Intent toValidate,
|
||||||
|
List<String> sigHashes,
|
||||||
|
boolean failIfHack) {
|
||||||
|
PackageManager pm=ctxt.getPackageManager();
|
||||||
|
Intent result=null;
|
||||||
|
List<ResolveInfo> services=
|
||||||
|
pm.queryIntentServices(toValidate, 0);
|
||||||
|
|
||||||
|
if (services!=null) {
|
||||||
|
for (ResolveInfo info : services) {
|
||||||
|
try {
|
||||||
|
if (sigHashes.contains(getSignatureHash(ctxt,
|
||||||
|
info.serviceInfo.packageName))) {
|
||||||
|
ComponentName cn=
|
||||||
|
new ComponentName(info.serviceInfo.packageName,
|
||||||
|
info.serviceInfo.name);
|
||||||
|
|
||||||
|
result=new Intent(toValidate).setComponent(cn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (failIfHack) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Package has signature hash mismatch: "+
|
||||||
|
info.activityInfo.packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
catch (NameNotFoundException e) {
|
||||||
|
Log.w("SignatureUtils",
|
||||||
|
"Exception when computing signature hash", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016 CommonsWare, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface used for reporting Orbot status
|
||||||
|
*/
|
||||||
|
public interface StatusCallback {
|
||||||
|
/**
|
||||||
|
* Called when Orbot is operational
|
||||||
|
*
|
||||||
|
* @param statusIntent an Intent containing information about
|
||||||
|
* Orbot, including proxy ports
|
||||||
|
*/
|
||||||
|
void onEnabled(Intent statusIntent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when Orbot reports that it is starting up
|
||||||
|
*/
|
||||||
|
void onStarting();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when Orbot reports that it is shutting down
|
||||||
|
*/
|
||||||
|
void onStopping();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when Orbot reports that it is no longer running
|
||||||
|
*/
|
||||||
|
void onDisabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if our attempt to get a status from Orbot failed
|
||||||
|
* after a defined period of time. See statusTimeout() on
|
||||||
|
* OrbotInitializer.
|
||||||
|
*/
|
||||||
|
void onStatusTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if Orbot is not yet installed. Usually, you handle
|
||||||
|
* this by checking the return value from init() on OrbotInitializer
|
||||||
|
* or calling isInstalled() on OrbotInitializer. However, if
|
||||||
|
* you have need for it, if a callback is registered before
|
||||||
|
* an init() call determines that Orbot is not installed, your
|
||||||
|
* callback will be called with onNotYetInstalled().
|
||||||
|
*/
|
||||||
|
void onNotYetInstalled();
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009-2016 Nathan Freitas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.proxy;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
public class TorServiceUtils {
|
||||||
|
|
||||||
|
private final static String TAG = "TorUtils";
|
||||||
|
// various console cmds
|
||||||
|
public final static String SHELL_CMD_CHMOD = "chmod";
|
||||||
|
public final static String SHELL_CMD_KILL = "kill -9";
|
||||||
|
public final static String SHELL_CMD_RM = "rm";
|
||||||
|
public final static String SHELL_CMD_PS = "ps";
|
||||||
|
public final static String SHELL_CMD_PIDOF = "pidof";
|
||||||
|
|
||||||
|
public final static String CHMOD_EXE_VALUE = "700";
|
||||||
|
|
||||||
|
public static boolean isRootPossible()
|
||||||
|
{
|
||||||
|
|
||||||
|
StringBuilder log = new StringBuilder();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Check if Superuser.apk exists
|
||||||
|
File fileSU = new File("/system/app/Superuser.apk");
|
||||||
|
if (fileSU.exists())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fileSU = new File("/system/app/superuser.apk");
|
||||||
|
if (fileSU.exists())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fileSU = new File("/system/bin/su");
|
||||||
|
if (fileSU.exists())
|
||||||
|
{
|
||||||
|
String[] cmd = {
|
||||||
|
"su"
|
||||||
|
};
|
||||||
|
int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true);
|
||||||
|
if (exitCode != 0)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for 'su' binary
|
||||||
|
String[] cmd = {
|
||||||
|
"which su"
|
||||||
|
};
|
||||||
|
int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true);
|
||||||
|
|
||||||
|
if (exitCode == 0) {
|
||||||
|
Log.d(TAG, "root exists, but not sure about permissions");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
// this means that there is no root to be had (normally) so we won't
|
||||||
|
// log anything
|
||||||
|
Log.e(TAG, "Error checking for root access", e);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error checking for root access", e);
|
||||||
|
// this means that there is no root to be had (normally)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e(TAG, "Could not acquire root permissions");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int findProcessId(Context context) {
|
||||||
|
String dataPath = context.getFilesDir().getParentFile().getParentFile().getAbsolutePath();
|
||||||
|
String command = dataPath + "/" + OrbotHelper.ORBOT_PACKAGE_NAME + "/app_bin/tor";
|
||||||
|
int procId = -1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
procId = findProcessIdWithPidOf(command);
|
||||||
|
|
||||||
|
if (procId == -1)
|
||||||
|
procId = findProcessIdWithPS(command);
|
||||||
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
|
procId = findProcessIdWithPS(command);
|
||||||
|
} catch (Exception e2) {
|
||||||
|
Log.e(TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return procId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use 'pidof' command
|
||||||
|
public static int findProcessIdWithPidOf(String command) throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
int procId = -1;
|
||||||
|
|
||||||
|
Runtime r = Runtime.getRuntime();
|
||||||
|
|
||||||
|
Process procPs = null;
|
||||||
|
|
||||||
|
String baseName = new File(command).getName();
|
||||||
|
// fix contributed my mikos on 2010.12.10
|
||||||
|
procPs = r.exec(new String[] {
|
||||||
|
SHELL_CMD_PIDOF, baseName
|
||||||
|
});
|
||||||
|
// procPs = r.exec(SHELL_CMD_PIDOF);
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
|
||||||
|
String line = null;
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// this line should just be the process id
|
||||||
|
procId = Integer.parseInt(line.trim());
|
||||||
|
break;
|
||||||
|
} catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
Log.e("TorServiceUtils", "unable to parse process pid: " + line, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return procId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// use 'ps' command
|
||||||
|
public static int findProcessIdWithPS(String command) throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
int procId = -1;
|
||||||
|
|
||||||
|
Runtime r = Runtime.getRuntime();
|
||||||
|
|
||||||
|
Process procPs = null;
|
||||||
|
|
||||||
|
procPs = r.exec(SHELL_CMD_PS);
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
|
||||||
|
String line = null;
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) != null)
|
||||||
|
{
|
||||||
|
if (line.indexOf(' ' + command) != -1)
|
||||||
|
{
|
||||||
|
|
||||||
|
StringTokenizer st = new StringTokenizer(line, " ");
|
||||||
|
st.nextToken(); // proc owner
|
||||||
|
|
||||||
|
procId = Integer.parseInt(st.nextToken().trim());
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return procId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int doShellCommand(String[] cmds, StringBuilder log, boolean runAsRoot,
|
||||||
|
boolean waitFor) throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
Process proc = null;
|
||||||
|
int exitCode = -1;
|
||||||
|
|
||||||
|
if (runAsRoot)
|
||||||
|
proc = Runtime.getRuntime().exec("su");
|
||||||
|
else
|
||||||
|
proc = Runtime.getRuntime().exec("sh");
|
||||||
|
|
||||||
|
OutputStreamWriter out = new OutputStreamWriter(proc.getOutputStream());
|
||||||
|
|
||||||
|
for (int i = 0; i < cmds.length; i++)
|
||||||
|
{
|
||||||
|
// TorService.logMessage("executing shell cmd: " + cmds[i] +
|
||||||
|
// "; runAsRoot=" + runAsRoot + ";waitFor=" + waitFor);
|
||||||
|
|
||||||
|
out.write(cmds[i]);
|
||||||
|
out.write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
out.write("exit\n");
|
||||||
|
out.flush();
|
||||||
|
|
||||||
|
if (waitFor)
|
||||||
|
{
|
||||||
|
|
||||||
|
final char buf[] = new char[10];
|
||||||
|
|
||||||
|
// Consume the "stdout"
|
||||||
|
InputStreamReader reader = new InputStreamReader(proc.getInputStream());
|
||||||
|
int read = 0;
|
||||||
|
while ((read = reader.read(buf)) != -1) {
|
||||||
|
if (log != null)
|
||||||
|
log.append(buf, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the "stderr"
|
||||||
|
reader = new InputStreamReader(proc.getErrorStream());
|
||||||
|
read = 0;
|
||||||
|
while ((read = reader.read(buf)) != -1) {
|
||||||
|
if (log != null)
|
||||||
|
log.append(buf, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = proc.waitFor();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,833 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Anthony Restaino
|
||||||
|
* Copyright 2012-2016 Nathan Freitas
|
||||||
|
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package info.guardianproject.netcipher.web;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Proxy;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
public class WebkitProxy {
|
||||||
|
|
||||||
|
private final static String DEFAULT_HOST = "localhost";//"127.0.0.1";
|
||||||
|
private final static int DEFAULT_PORT = 8118;
|
||||||
|
private final static int DEFAULT_SOCKS_PORT = 9050;
|
||||||
|
|
||||||
|
private final static int REQUEST_CODE = 0;
|
||||||
|
|
||||||
|
private final static String TAG = "OrbotHelpher";
|
||||||
|
|
||||||
|
public static boolean setProxy(String appClass, Context ctx, WebView wView, String host, int port) throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
setSystemProperties(host, port);
|
||||||
|
|
||||||
|
boolean worked = false;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < 13)
|
||||||
|
{
|
||||||
|
// worked = setWebkitProxyGingerbread(ctx, host, port);
|
||||||
|
setProxyUpToHC(wView, host, port);
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT < 19)
|
||||||
|
{
|
||||||
|
worked = setWebkitProxyICS(ctx, host, port);
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT < 20)
|
||||||
|
{
|
||||||
|
worked = setKitKatProxy(appClass, ctx, host, port);
|
||||||
|
|
||||||
|
if (!worked) //some kitkat's still use ICS browser component (like Cyanogen 11)
|
||||||
|
worked = setWebkitProxyICS(ctx, host, port);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT >= 21)
|
||||||
|
{
|
||||||
|
worked = setWebkitProxyLollipop(ctx, host, port);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return worked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setSystemProperties(String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
System.setProperty("proxyHost", host);
|
||||||
|
System.setProperty("proxyPort", Integer.toString(port));
|
||||||
|
|
||||||
|
System.setProperty("http.proxyHost", host);
|
||||||
|
System.setProperty("http.proxyPort", Integer.toString(port));
|
||||||
|
|
||||||
|
System.setProperty("https.proxyHost", host);
|
||||||
|
System.setProperty("https.proxyPort", Integer.toString(port));
|
||||||
|
|
||||||
|
|
||||||
|
System.setProperty("socks.proxyHost", host);
|
||||||
|
System.setProperty("socks.proxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
||||||
|
|
||||||
|
System.setProperty("socksProxyHost", host);
|
||||||
|
System.setProperty("socksProxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
ProxySelector pSelect = new ProxySelector();
|
||||||
|
pSelect.addProxy(Proxy.Type.HTTP, host, port);
|
||||||
|
ProxySelector.setDefault(pSelect);
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
System.setProperty("http_proxy", "http://" + host + ":" + port);
|
||||||
|
System.setProperty("proxy-server", "http://" + host + ":" + port);
|
||||||
|
System.setProperty("host-resolver-rules","MAP * 0.0.0.0 , EXCLUDE myproxy");
|
||||||
|
|
||||||
|
System.getProperty("networkaddress.cache.ttl", "-1");
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetSystemProperties()
|
||||||
|
{
|
||||||
|
|
||||||
|
System.setProperty("proxyHost", "");
|
||||||
|
System.setProperty("proxyPort", "");
|
||||||
|
|
||||||
|
System.setProperty("http.proxyHost", "");
|
||||||
|
System.setProperty("http.proxyPort", "");
|
||||||
|
|
||||||
|
System.setProperty("https.proxyHost", "");
|
||||||
|
System.setProperty("https.proxyPort", "");
|
||||||
|
|
||||||
|
|
||||||
|
System.setProperty("socks.proxyHost", "");
|
||||||
|
System.setProperty("socks.proxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
||||||
|
|
||||||
|
System.setProperty("socksProxyHost", "");
|
||||||
|
System.setProperty("socksProxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override WebKit Proxy settings
|
||||||
|
*
|
||||||
|
* @param ctx Android ApplicationContext
|
||||||
|
* @param host
|
||||||
|
* @param port
|
||||||
|
* @return true if Proxy was successfully set
|
||||||
|
*/
|
||||||
|
private static boolean setWebkitProxyGingerbread(Context ctx, String host, int port)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
boolean ret = false;
|
||||||
|
|
||||||
|
Object requestQueueObject = getRequestQueue(ctx);
|
||||||
|
if (requestQueueObject != null) {
|
||||||
|
// Create Proxy config object and set it into request Q
|
||||||
|
HttpHost httpHost = new HttpHost(host, port, "http");
|
||||||
|
setDeclaredField(requestQueueObject, "mProxyHost", httpHost);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Proxy for Android 3.2 and below.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private static boolean setProxyUpToHC(WebView webview, String host, int port) {
|
||||||
|
Log.d(TAG, "Setting proxy with <= 3.2 API.");
|
||||||
|
|
||||||
|
HttpHost proxyServer = new HttpHost(host, port);
|
||||||
|
// Getting network
|
||||||
|
Class networkClass = null;
|
||||||
|
Object network = null;
|
||||||
|
try {
|
||||||
|
networkClass = Class.forName("android.webkit.Network");
|
||||||
|
if (networkClass == null) {
|
||||||
|
Log.e(TAG, "failed to get class for android.webkit.Network");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Method getInstanceMethod = networkClass.getMethod("getInstance", Context.class);
|
||||||
|
if (getInstanceMethod == null) {
|
||||||
|
Log.e(TAG, "failed to get getInstance method");
|
||||||
|
}
|
||||||
|
network = getInstanceMethod.invoke(networkClass, new Object[]{webview.getContext()});
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "error getting network: " + ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (network == null) {
|
||||||
|
Log.e(TAG, "error getting network: network is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Object requestQueue = null;
|
||||||
|
try {
|
||||||
|
Field requestQueueField = networkClass
|
||||||
|
.getDeclaredField("mRequestQueue");
|
||||||
|
requestQueue = getFieldValueSafely(requestQueueField, network);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "error getting field value");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (requestQueue == null) {
|
||||||
|
Log.e(TAG, "Request queue is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Field proxyHostField = null;
|
||||||
|
try {
|
||||||
|
Class requestQueueClass = Class.forName("android.net.http.RequestQueue");
|
||||||
|
proxyHostField = requestQueueClass
|
||||||
|
.getDeclaredField("mProxyHost");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "error getting proxy host field");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean temp = proxyHostField.isAccessible();
|
||||||
|
try {
|
||||||
|
proxyHostField.setAccessible(true);
|
||||||
|
proxyHostField.set(requestQueue, proxyServer);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "error setting proxy host");
|
||||||
|
} finally {
|
||||||
|
proxyHostField.setAccessible(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Setting proxy with <= 3.2 API successful!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Object getFieldValueSafely(Field field, Object classInstance) throws IllegalArgumentException, IllegalAccessException {
|
||||||
|
boolean oldAccessibleValue = field.isAccessible();
|
||||||
|
field.setAccessible(true);
|
||||||
|
Object result = field.get(classInstance);
|
||||||
|
field.setAccessible(oldAccessibleValue);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean setWebkitProxyICS(Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
// PSIPHON: added support for Android 4.x WebView proxy
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("android.webkit.WebViewCore");
|
||||||
|
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (webViewCoreClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("sendStaticMessage", Integer.TYPE,
|
||||||
|
Object.class);
|
||||||
|
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (m != null && c != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
c.setAccessible(true);
|
||||||
|
Object properties = c.newInstance(host, port, null);
|
||||||
|
|
||||||
|
// android.webkit.WebViewCore.EventHub.PROXY_CHANGED = 193;
|
||||||
|
m.invoke(null, 193, properties);
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(19)
|
||||||
|
public static boolean resetKitKatProxy(String appClass, Context appContext) {
|
||||||
|
|
||||||
|
return setKitKatProxy(appClass, appContext,null,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(19)
|
||||||
|
private static boolean setKitKatProxy(String appClass, Context appContext, String host, int port) {
|
||||||
|
//Context appContext = webView.getContext().getApplicationContext();
|
||||||
|
|
||||||
|
if (host != null)
|
||||||
|
{
|
||||||
|
System.setProperty("http.proxyHost", host);
|
||||||
|
System.setProperty("http.proxyPort", Integer.toString(port));
|
||||||
|
System.setProperty("https.proxyHost", host);
|
||||||
|
System.setProperty("https.proxyPort", Integer.toString(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class applictionCls = Class.forName(appClass);
|
||||||
|
Field loadedApkField = applictionCls.getField("mLoadedApk");
|
||||||
|
loadedApkField.setAccessible(true);
|
||||||
|
Object loadedApk = loadedApkField.get(appContext);
|
||||||
|
Class loadedApkCls = Class.forName("android.app.LoadedApk");
|
||||||
|
Field receiversField = loadedApkCls.getDeclaredField("mReceivers");
|
||||||
|
receiversField.setAccessible(true);
|
||||||
|
ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
|
||||||
|
for (Object receiverMap : receivers.values()) {
|
||||||
|
for (Object rec : ((ArrayMap) receiverMap).keySet()) {
|
||||||
|
Class clazz = rec.getClass();
|
||||||
|
if (clazz.getName().contains("ProxyChangeListener")) {
|
||||||
|
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
|
||||||
|
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
|
||||||
|
|
||||||
|
if (host != null)
|
||||||
|
{
|
||||||
|
/*********** optional, may be need in future *************/
|
||||||
|
final String CLASS_NAME = "android.net.ProxyProperties";
|
||||||
|
Class cls = Class.forName(CLASS_NAME);
|
||||||
|
Constructor constructor = cls.getConstructor(String.class, Integer.TYPE, String.class);
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
Object proxyProperties = constructor.newInstance(host, port, null);
|
||||||
|
intent.putExtra("proxy", (Parcelable) proxyProperties);
|
||||||
|
/*********** optional, may be need in future *************/
|
||||||
|
}
|
||||||
|
|
||||||
|
onReceiveMethod.invoke(rec, appContext, intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
e.printStackTrace(new PrintWriter(sw));
|
||||||
|
String exceptionAsString = sw.toString();
|
||||||
|
Log.v(TAG, e.getMessage());
|
||||||
|
Log.v(TAG, exceptionAsString);
|
||||||
|
}
|
||||||
|
return false; }
|
||||||
|
|
||||||
|
@TargetApi(21)
|
||||||
|
public static boolean resetLollipopProxy(String appClass, Context appContext) {
|
||||||
|
|
||||||
|
return setWebkitProxyLollipop(appContext,null,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://stackanswers.com/questions/25272393/android-webview-set-proxy-programmatically-on-android-l
|
||||||
|
@TargetApi(21) // for android.util.ArrayMap methods
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private static boolean setWebkitProxyLollipop(Context appContext, String host, int port)
|
||||||
|
{
|
||||||
|
System.setProperty("http.proxyHost", host);
|
||||||
|
System.setProperty("http.proxyPort", Integer.toString(port));
|
||||||
|
System.setProperty("https.proxyHost", host);
|
||||||
|
System.setProperty("https.proxyPort", Integer.toString(port));
|
||||||
|
try {
|
||||||
|
Class applictionClass = Class.forName("android.app.Application");
|
||||||
|
Field mLoadedApkField = applictionClass.getDeclaredField("mLoadedApk");
|
||||||
|
mLoadedApkField.setAccessible(true);
|
||||||
|
Object mloadedApk = mLoadedApkField.get(appContext);
|
||||||
|
Class loadedApkClass = Class.forName("android.app.LoadedApk");
|
||||||
|
Field mReceiversField = loadedApkClass.getDeclaredField("mReceivers");
|
||||||
|
mReceiversField.setAccessible(true);
|
||||||
|
ArrayMap receivers = (ArrayMap) mReceiversField.get(mloadedApk);
|
||||||
|
for (Object receiverMap : receivers.values())
|
||||||
|
{
|
||||||
|
for (Object receiver : ((ArrayMap) receiverMap).keySet())
|
||||||
|
{
|
||||||
|
Class clazz = receiver.getClass();
|
||||||
|
if (clazz.getName().contains("ProxyChangeListener"))
|
||||||
|
{
|
||||||
|
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
|
||||||
|
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
|
||||||
|
onReceiveMethod.invoke(receiver, appContext, intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
catch (NoSuchFieldException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
catch (IllegalAccessException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException e)
|
||||||
|
{
|
||||||
|
Log.d("ProxySettings","Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean sendProxyChangedIntent(Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (c != null)
|
||||||
|
{
|
||||||
|
c.setAccessible(true);
|
||||||
|
Object properties = c.newInstance(host, port, null);
|
||||||
|
|
||||||
|
Intent intent = new Intent(android.net.Proxy.PROXY_CHANGE_ACTION);
|
||||||
|
intent.putExtra("proxy",(Parcelable)properties);
|
||||||
|
ctx.sendBroadcast(intent);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception sending Intent ",e);
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception sending Intent ",e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
private static boolean setKitKatProxy0(Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class cmClass = Class.forName("android.net.ConnectivityManager");
|
||||||
|
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (cmClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (c != null)
|
||||||
|
{
|
||||||
|
c.setAccessible(true);
|
||||||
|
|
||||||
|
Object proxyProps = c.newInstance(host, port, null);
|
||||||
|
ConnectivityManager cm =
|
||||||
|
(ConnectivityManager)ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
|
||||||
|
Method mSetGlobalProxy = cmClass.getDeclaredMethod("setGlobalProxy", proxyPropertiesClass);
|
||||||
|
|
||||||
|
mSetGlobalProxy.invoke(cm, proxyProps);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"ConnectivityManager.setGlobalProxy ",e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
//CommandLine.initFromFile(COMMAND_LINE_FILE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
private static boolean setKitKatProxy2 (Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
String commandLinePath = "/data/local/tmp/orweb.conf";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("org.chromium.content.common.CommandLine");
|
||||||
|
|
||||||
|
if (webViewCoreClass != null)
|
||||||
|
{
|
||||||
|
for (Method method : webViewCoreClass.getDeclaredMethods())
|
||||||
|
{
|
||||||
|
Log.d("Orweb","Proxy methods: " + method.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("initFromFile",
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (m != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
m.invoke(null, commandLinePath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
private static boolean setKitKatProxy (Context ctx, String host, int port)
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("android.net.Proxy");
|
||||||
|
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (webViewCoreClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
for (Method method : webViewCoreClass.getDeclaredMethods())
|
||||||
|
{
|
||||||
|
Log.d("Orweb","Proxy methods: " + method.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("setHttpProxySystemProperty",
|
||||||
|
proxyPropertiesClass);
|
||||||
|
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (m != null && c != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
c.setAccessible(true);
|
||||||
|
Object properties = c.newInstance(host, port, null);
|
||||||
|
|
||||||
|
m.invoke(null, properties);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean resetProxyForKitKat ()
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("android.net.Proxy");
|
||||||
|
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (webViewCoreClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
for (Method method : webViewCoreClass.getDeclaredMethods())
|
||||||
|
{
|
||||||
|
Log.d("Orweb","Proxy methods: " + method.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("setHttpProxySystemProperty",
|
||||||
|
proxyPropertiesClass);
|
||||||
|
|
||||||
|
if (m != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
|
||||||
|
m.invoke(null, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}**/
|
||||||
|
|
||||||
|
public static void resetProxy(String appClass, Context ctx) throws Exception {
|
||||||
|
|
||||||
|
resetSystemProperties();
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < 14)
|
||||||
|
{
|
||||||
|
resetProxyForGingerBread(ctx);
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT < 19)
|
||||||
|
{
|
||||||
|
resetProxyForICS();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resetKitKatProxy(appClass, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetProxyForICS() throws Exception{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class webViewCoreClass = Class.forName("android.webkit.WebViewCore");
|
||||||
|
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
||||||
|
if (webViewCoreClass != null && proxyPropertiesClass != null)
|
||||||
|
{
|
||||||
|
Method m = webViewCoreClass.getDeclaredMethod("sendStaticMessage", Integer.TYPE,
|
||||||
|
Object.class);
|
||||||
|
|
||||||
|
if (m != null)
|
||||||
|
{
|
||||||
|
m.setAccessible(true);
|
||||||
|
|
||||||
|
// android.webkit.WebViewCore.EventHub.PROXY_CHANGED = 193;
|
||||||
|
m.invoke(null, 193, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
||||||
|
+ e.toString());
|
||||||
|
throw e;
|
||||||
|
} catch (Error e)
|
||||||
|
{
|
||||||
|
Log.e("ProxySettings",
|
||||||
|
"Exception setting WebKit proxy through android.webkit.Network: "
|
||||||
|
+ e.toString());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetProxyForGingerBread(Context ctx) throws Exception {
|
||||||
|
Object requestQueueObject = getRequestQueue(ctx);
|
||||||
|
if (requestQueueObject != null) {
|
||||||
|
setDeclaredField(requestQueueObject, "mProxyHost", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getRequestQueue(Context ctx) throws Exception {
|
||||||
|
Object ret = null;
|
||||||
|
Class networkClass = Class.forName("android.webkit.Network");
|
||||||
|
if (networkClass != null) {
|
||||||
|
Object networkObj = invokeMethod(networkClass, "getInstance", new Object[] {
|
||||||
|
ctx
|
||||||
|
}, Context.class);
|
||||||
|
if (networkObj != null) {
|
||||||
|
ret = getDeclaredField(networkObj, "mRequestQueue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getDeclaredField(Object obj, String name)
|
||||||
|
throws SecurityException, NoSuchFieldException,
|
||||||
|
IllegalArgumentException, IllegalAccessException {
|
||||||
|
Field f = obj.getClass().getDeclaredField(name);
|
||||||
|
f.setAccessible(true);
|
||||||
|
Object out = f.get(obj);
|
||||||
|
// System.out.println(obj.getClass().getName() + "." + name + " = "+
|
||||||
|
// out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setDeclaredField(Object obj, String name, Object value)
|
||||||
|
throws SecurityException, NoSuchFieldException,
|
||||||
|
IllegalArgumentException, IllegalAccessException {
|
||||||
|
Field f = obj.getClass().getDeclaredField(name);
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(obj, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object invokeMethod(Object object, String methodName, Object[] params,
|
||||||
|
Class... types) throws Exception {
|
||||||
|
Object out = null;
|
||||||
|
Class c = object instanceof Class ? (Class) object : object.getClass();
|
||||||
|
if (types != null) {
|
||||||
|
Method method = c.getMethod(methodName, types);
|
||||||
|
out = method.invoke(object, params);
|
||||||
|
} else {
|
||||||
|
Method method = c.getMethod(methodName);
|
||||||
|
out = method.invoke(object);
|
||||||
|
}
|
||||||
|
// System.out.println(object.getClass().getName() + "." + methodName +
|
||||||
|
// "() = "+ out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Socket getSocket(Context context, String proxyHost, int proxyPort)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
Socket sock = new Socket();
|
||||||
|
|
||||||
|
sock.connect(new InetSocketAddress(proxyHost, proxyPort), 10000);
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Socket getSocket(Context context) throws IOException
|
||||||
|
{
|
||||||
|
return getSocket(context, DEFAULT_HOST, DEFAULT_SOCKS_PORT);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertDialog initOrbot(Activity activity,
|
||||||
|
CharSequence stringTitle,
|
||||||
|
CharSequence stringMessage,
|
||||||
|
CharSequence stringButtonYes,
|
||||||
|
CharSequence stringButtonNo,
|
||||||
|
CharSequence stringDesiredBarcodeFormats) {
|
||||||
|
Intent intentScan = new Intent("org.torproject.android.START_TOR");
|
||||||
|
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
|
||||||
|
|
||||||
|
try {
|
||||||
|
activity.startActivityForResult(intentScan, REQUEST_CODE);
|
||||||
|
return null;
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
return showDownloadDialog(activity, stringTitle, stringMessage, stringButtonYes,
|
||||||
|
stringButtonNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AlertDialog showDownloadDialog(final Activity activity,
|
||||||
|
CharSequence stringTitle,
|
||||||
|
CharSequence stringMessage,
|
||||||
|
CharSequence stringButtonYes,
|
||||||
|
CharSequence stringButtonNo) {
|
||||||
|
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
|
||||||
|
downloadDialog.setTitle(stringTitle);
|
||||||
|
downloadDialog.setMessage(stringMessage);
|
||||||
|
downloadDialog.setPositiveButton(stringButtonYes, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
Uri uri = Uri.parse("market://search?q=pname:org.torproject.android");
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
downloadDialog.setNegativeButton(stringButtonNo, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return downloadDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue