From 64611f2ed22e62166bfda08df3af746f8bd5c1c8 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Sat, 10 May 2025 12:00:25 +0200 Subject: [PATCH] Allow PCAPdroid capture control without prompt via API key It's now possible to generate an API key to be set in the Intent, allowing to control the PCAPdroid capture without prompt Closes #516 --- .../activities/CaptureCtrl.java | 12 +++ .../activities/MainActivity.java | 1 + .../activities/prefs/EditCtrlPermissions.java | 91 ++++++++++++++++++- .../activities/prefs/SettingsActivity.java | 13 +-- .../emanuelef/remote_capture/model/Prefs.java | 2 + .../main/res/menu/ctrl_permissions_menu.xml | 24 +++++ app/src/main/res/values/strings.xml | 5 + 7 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 app/src/main/res/menu/ctrl_permissions_menu.xml diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java b/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java index f80eb711..e765d905 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java @@ -106,6 +106,7 @@ public class CaptureCtrl extends AppCompatActivity { Intent intent = getIntent(); String action = intent.getStringExtra("action"); + String api_key = intent.getStringExtra("api_key"); if(action == null) { Log.e(TAG, "no action provided"); @@ -118,6 +119,17 @@ public class CaptureCtrl extends AppCompatActivity { return; } + if(api_key != null) { + // authenticate via API key + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + String my_key = Prefs.getApiKey(prefs); + + if (!my_key.isEmpty() && my_key.equals(api_key)) { + processRequest(intent, action); + return; + } + } + // Check if a control permission rule was set mPermissions = PCAPdroid.getInstance().getCtrlPermissions(); AppDescriptor app = getCallingApp(); diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java index a0991299..c774df07 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java @@ -132,6 +132,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig public static final String PAID_FEATURES_URL = DOCS_URL + "/paid_features"; public static final String FIREWALL_DOCS_URL = PAID_FEATURES_URL + "#51-firewall"; public static final String MALWARE_DETECTION_DOCS_URL = PAID_FEATURES_URL + "#52-malware-detection"; + public static final String API_DOCS_URL = GITHUB_PROJECT_URL + "/blob/master/docs/app_api.md"; public static final String PCAPNG_DOCS_URL = PAID_FEATURES_URL + "#53-pcapng-format"; private final ActivityResultLauncher sslkeyfileExportLauncher = diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/prefs/EditCtrlPermissions.java b/app/src/main/java/com/emanuelef/remote_capture/activities/prefs/EditCtrlPermissions.java index eecc0163..da5d3675 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/prefs/EditCtrlPermissions.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/prefs/EditCtrlPermissions.java @@ -18,6 +18,9 @@ */ package com.emanuelef.remote_capture.activities.prefs; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; import android.os.Bundle; import android.view.ActionMode; import android.view.Menu; @@ -28,21 +31,31 @@ import android.widget.AbsListView; import android.widget.ListView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.core.view.MenuProvider; +import androidx.preference.PreferenceManager; + import com.emanuelef.remote_capture.PCAPdroid; import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.Utils; import com.emanuelef.remote_capture.activities.BaseActivity; +import com.emanuelef.remote_capture.activities.MainActivity; import com.emanuelef.remote_capture.adapters.CtrlPermissionsAdapter; import com.emanuelef.remote_capture.model.CtrlPermissions; +import com.emanuelef.remote_capture.model.Prefs; +import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Base64; -public class EditCtrlPermissions extends BaseActivity { +public class EditCtrlPermissions extends BaseActivity implements MenuProvider { private static final String TAG = "EditCtrlPermissions"; private TextView mEmptyText; private CtrlPermissionsAdapter mAdapter; private ListView mListView; private CtrlPermissions mPermissions; + private MenuItem mShowApiKey; private final ArrayList mSelected = new ArrayList<>(); @Override @@ -51,9 +64,11 @@ public class EditCtrlPermissions extends BaseActivity { setTitle(R.string.control_permissions); setContentView(R.layout.simple_list_activity); + addMenuProvider(this); findViewById(R.id.simple_list).setFitsSystemWindows(true); mEmptyText = findViewById(R.id.list_empty); + mEmptyText.setText(R.string.no_permissions_set_info); mListView = findViewById(R.id.listview); mPermissions = PCAPdroid.getInstance().getCtrlPermissions(); @@ -126,7 +141,81 @@ public class EditCtrlPermissions extends BaseActivity { recheckListSize(); } + @Override + public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + inflater.inflate(R.menu.ctrl_permissions_menu, menu); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + mShowApiKey = menu.findItem(R.id.show_api_key); + + if (Prefs.getApiKey(prefs).isEmpty()) + mShowApiKey.setVisible(false); + } + + @Override + public boolean onMenuItemSelected(@NonNull MenuItem item) { + int id = item.getItemId(); + + if(id == R.id.user_guide) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(MainActivity.API_DOCS_URL)); + Utils.startActivity(this, browserIntent); + return true; + } else if (id == R.id.generate_api_key) { + generateApiKey(false); + return true; + } else if (id == R.id.show_api_key) { + showApiKey(); + return true; + } + + return false; + } + private void recheckListSize() { mEmptyText.setVisibility((mAdapter.getCount() == 0) ? View.VISIBLE : View.GONE); } + + private void generateApiKey(boolean confirmOverwrite) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + if (!confirmOverwrite && !Prefs.getApiKey(prefs).isEmpty()) { + new AlertDialog.Builder(this) + .setTitle(R.string.warning) + .setMessage(R.string.api_key_discard_confirm) + .setPositiveButton(R.string.ok, (dialog, whichButton) -> generateApiKey(true)) + .setNegativeButton(R.string.cancel_action, (dialog, whichButton) -> {}) + .show(); + return; + } + + final String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + final int key_length = 32; + SecureRandom random = new SecureRandom(); + StringBuilder apiKey = new StringBuilder(key_length); + for (int i = 0; i < key_length; i++) { + int index = random.nextInt(chars.length()); + apiKey.append(chars.charAt(index)); + } + + prefs.edit() + .putString(Prefs.PREF_API_KEY, apiKey.toString()) + .apply(); + + if (mShowApiKey != null) + mShowApiKey.setVisible(true); + showApiKey(); + } + + private void showApiKey() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + String key = Prefs.getApiKey(prefs); + if (key.isEmpty()) + return; + + new AlertDialog.Builder(this) + .setTitle(R.string.api_key) + .setMessage(key) + .setPositiveButton(R.string.ok, (dialogInterface, i) -> {}) + .setNeutralButton(R.string.copy_to_clipboard, (dialogInterface, i) -> + Utils.copyToClipboard(this, key)).show(); + } } diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/prefs/SettingsActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/prefs/SettingsActivity.java index 2b77d271..867088b2 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/prefs/SettingsActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/prefs/SettingsActivity.java @@ -461,14 +461,11 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment mIpMode = requirePreference(Prefs.PREF_IP_MODE); Preference ctrlPerm = requirePreference("control_permissions"); - if(!PCAPdroid.getInstance().getCtrlPermissions().hasRules()) - ctrlPerm.setVisible(false); - else - ctrlPerm.setOnPreferenceClickListener(preference -> { - Intent intent = new Intent(requireContext(), EditCtrlPermissions.class); - startActivity(intent); - return true; - }); + ctrlPerm.setOnPreferenceClickListener(preference -> { + Intent intent = new Intent(requireContext(), EditCtrlPermissions.class); + startActivity(intent); + return true; + }); } private void rootCaptureHideShow(boolean enabled) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java b/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java index ad1451af..e71cb1a9 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java +++ b/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java @@ -110,6 +110,7 @@ public class Prefs { public static final String PREF_PCAPNG_ENABLED = "pcapng_format"; public static final String PREF_RESTART_ON_DISCONNECT = "restart_on_disconnect"; public static final String PREF_IGNORED_MITM_VERSION = "ignored_mitm_version"; + public static final String PREF_API_KEY = "api_key"; public enum DumpMode { NONE, @@ -238,6 +239,7 @@ public class Prefs { public static String getDnsServerV4(SharedPreferences p) { return(p.getString(PREF_DNS_SERVER_V4, "1.1.1.1")); } public static String getDnsServerV6(SharedPreferences p) { return(p.getString(PREF_DNS_SERVER_V6, "2606:4700:4700::1111")); } public static boolean isIgnoredMitmVersion(SharedPreferences p, String v) { return p.getString(PREF_IGNORED_MITM_VERSION, "").equals(v); } + public static String getApiKey(SharedPreferences p) { return(p.getString(PREF_API_KEY, "")); } // Gets a StringSet from the prefs // The preference should either be a StringSet or a String diff --git a/app/src/main/res/menu/ctrl_permissions_menu.xml b/app/src/main/res/menu/ctrl_permissions_menu.xml new file mode 100644 index 00000000..48545861 --- /dev/null +++ b/app/src/main/res/menu/ctrl_permissions_menu.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e599b6a..49f1a78a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -217,6 +217,11 @@ Control permissions Check which apps are allowed to control the PCAPdroid capture %1$s: %2$s + No permissions set. Invoke PCAPdroid via StartActivityForResult to show the permissions prompt + Generate API key + Show API key + API key + Do you really want to discard the current API key and generate a new one? Country ASN Country: %1$s