diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/NetCipherHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/NetCipherHelper.java index b5d765ea..17c18bb4 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/NetCipherHelper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/NetCipherHelper.java @@ -41,7 +41,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import info.guardianproject.netcipher.client.StrongOkHttpClientBuilder; -import info.guardianproject.netcipher.proxy.OrbotHelper; +import info.guardianproject.netcipher.proxy.MyOrbotHelper; import info.guardianproject.netcipher.proxy.SignatureUtils; import info.guardianproject.netcipher.proxy.StatusCallback; import lombok.RequiredArgsConstructor; @@ -75,7 +75,7 @@ public class NetCipherHelper implements StatusCallback { } final private Context context; - final private OrbotHelper orbot; + final private MyOrbotHelper orbot; @SuppressLint("StaticFieldLeak") private static NetCipherHelper Instance; @@ -85,7 +85,7 @@ public class NetCipherHelper implements StatusCallback { synchronized (NetCipherHelper.class) { if (Instance == null) { final Context applicationContext = context.getApplicationContext(); - Instance = new NetCipherHelper(applicationContext, OrbotHelper.get(context).statusTimeout(5000)); + Instance = new NetCipherHelper(applicationContext, MyOrbotHelper.get(context).statusTimeout(5000)); } } } @@ -99,9 +99,9 @@ public class NetCipherHelper implements StatusCallback { private OkHttpClient client; private void createTorClient(Intent statusIntent) { - String orbotStatus = statusIntent.getStringExtra(OrbotHelper.EXTRA_STATUS); + String orbotStatus = statusIntent.getStringExtra(MyOrbotHelper.EXTRA_STATUS); if (orbotStatus == null) throw new IllegalStateException("status is null"); - if (!orbotStatus.equals(OrbotHelper.STATUS_ON)) + if (!orbotStatus.equals(MyOrbotHelper.STATUS_ON)) throw new IllegalStateException("Orbot is not ON"); try { final OkHttpClient.Builder okBuilder = new OkHttpClient.Builder() @@ -146,7 +146,7 @@ public class NetCipherHelper implements StatusCallback { .addStatusCallback(me); // deal with org.torproject.android.intent.action.STATUS = STARTS_DISABLED - ContextCompat.registerReceiver(me.context, orbotStatusReceiver, new IntentFilter(OrbotHelper.ACTION_STATUS), ContextCompat.RECEIVER_NOT_EXPORTED); + ContextCompat.registerReceiver(me.context, orbotStatusReceiver, new IntentFilter(MyOrbotHelper.ACTION_STATUS), ContextCompat.RECEIVER_EXPORTED); me.startTor(); } @@ -272,7 +272,7 @@ public class NetCipherHelper implements StatusCallback { 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"); return null != SignatureUtils.validateBroadcastIntent(context, - OrbotHelper.getOrbotStartIntent(context), + MyOrbotHelper.getOrbotStartIntent(context), hashes, false); } @@ -382,9 +382,9 @@ public class NetCipherHelper implements StatusCallback { private static final BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - Timber.d("%s/%s", intent.getAction(), intent.getStringExtra(OrbotHelper.EXTRA_STATUS)); - if (OrbotHelper.ACTION_STATUS.equals(intent.getAction())) { - if (OrbotHelper.STATUS_STARTS_DISABLED.equals(intent.getStringExtra(OrbotHelper.EXTRA_STATUS))) { + Timber.d("%s/%s", intent.getAction(), intent.getStringExtra(MyOrbotHelper.EXTRA_STATUS)); + if (MyOrbotHelper.ACTION_STATUS.equals(intent.getAction())) { + if (MyOrbotHelper.STATUS_STARTS_DISABLED.equals(intent.getStringExtra(MyOrbotHelper.EXTRA_STATUS))) { getInstance().onNotEnabled(); } } @@ -392,6 +392,6 @@ public class NetCipherHelper implements StatusCallback { }; public void installOrbot(Activity host) { - host.startActivity(OrbotHelper.getOrbotInstallIntent(context)); + host.startActivity(MyOrbotHelper.getOrbotInstallIntent(context)); } } diff --git a/app/src/main/java/info/guardianproject/netcipher/proxy/MyOrbotHelper.java b/app/src/main/java/info/guardianproject/netcipher/proxy/MyOrbotHelper.java new file mode 100644 index 00000000..5bdcb96d --- /dev/null +++ b/app/src/main/java/info/guardianproject/netcipher/proxy/MyOrbotHelper.java @@ -0,0 +1,708 @@ +/* + * 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 androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +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 MyOrbotHelper 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; + + public final static String DEFAULT_PROXY_HOST = "localhost";//"127.0.0.1"; + public final static int DEFAULT_PROXY_HTTP_PORT = 8118; + public final static int DEFAULT_PROXY_SOCKS_PORT = 9050; + + /** + * 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()}. + *
+ * 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 (MyOrbotHelper.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} + *
+ * 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 (MyOrbotHelper.isOrbotInstalled(activity)) {
+ if (!MyOrbotHelper.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
+ * 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");
+
+ context.registerReceiver(orbotInstallReceiver, filter);
+ host.startActivity(MyOrbotHelper.getOrbotInstallIntent(context));
+ }
+
+ private BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (TextUtils.equals(intent.getAction(),
+ MyOrbotHelper.ACTION_STATUS)) {
+ String status = intent.getStringExtra(MyOrbotHelper.EXTRA_STATUS);
+
+ if (status.equals(MyOrbotHelper.STATUS_ON)) {
+ lastStatusIntent = intent;
+ handler.removeCallbacks(onStatusTimeout);
+
+ for (StatusCallback cb : statusCallbacks) {
+ cb.onEnabled(intent);
+ }
+ } else if (status.equals(MyOrbotHelper.STATUS_OFF)) {
+ for (StatusCallback cb : statusCallbacks) {
+ cb.onDisabled();
+ }
+ } else if (status.equals(MyOrbotHelper.STATUS_STARTING)) {
+ for (StatusCallback cb : statusCallbacks) {
+ cb.onStarting();
+ }
+ } else if (status.equals(MyOrbotHelper.STATUS_STOPPING)) {
+ for (StatusCallback cb : statusCallbacks) {
+ cb.onStopping();
+ }
+ }
+ }
+ }
+ };
+
+ private Runnable onStatusTimeout = new Runnable() {
+ @Override
+ public void run() {
+ context.unregisterReceiver(orbotStatusReceiver);
+
+ for (StatusCallback cb : statusCallbacks) {
+ cb.onStatusTimeout();
+ }
+ }
+ };
+
+ private BroadcastReceiver orbotInstallReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (TextUtils.equals(intent.getAction(),
+ Intent.ACTION_PACKAGE_ADDED)) {
+ String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+
+ if (MyOrbotHelper.ORBOT_PACKAGE_NAME.equals(pkgName)) {
+ isInstalled = true;
+ handler.removeCallbacks(onInstallTimeout);
+ context.unregisterReceiver(orbotInstallReceiver);
+
+ for (InstallCallback cb : installCallbacks) {
+ cb.onInstalled();
+ }
+
+ init();
+ }
+ }
+ }
+ };
+
+ private Runnable onInstallTimeout = new Runnable() {
+ @Override
+ public void run() {
+ context.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