Skip to content

Commit 52b5ee6

Browse files
committed
Add locked down ACTIVATE/DEACTIVATE intents
1 parent 3ed1f5d commit 52b5ee6

File tree

2 files changed

+75
-31
lines changed

2 files changed

+75
-31
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
1717
tools:node="remove" />
1818

19+
<permission
20+
android:name="tech.httptoolkit.android.REMOTE_CONTROL_INTERCEPTION"
21+
android:protectionLevel="signature"
22+
/>
23+
1924
<application
2025
android:name=".HttpToolkitApplication"
2126
android:allowBackup="true"
@@ -36,9 +41,9 @@
3641
</service>
3742

3843
<activity
39-
android:name=".MainActivity"
40-
android:label="@string/app_name"
41-
android:launchMode="singleTask">
44+
android:name=".MainActivity"
45+
android:label="@string/app_name"
46+
android:launchMode="singleTask">
4247
<intent-filter>
4348
<action android:name="android.intent.action.MAIN" />
4449
<category android:name="android.intent.category.LAUNCHER" />
@@ -57,6 +62,30 @@
5762
</intent-filter>
5863
</activity>
5964

65+
<!--
66+
Alias that allows remote intents of ACTIVATE/DEACTIVATE from other apps, but only
67+
if they have RC permissions (same signature only). In practice, this is only used
68+
by debugging calls using ADB. Important to lock down or other apps could activate
69+
the VPN and pass a cert, to intercept all traffic from the phone without prompts.
70+
-->
71+
<activity-alias
72+
android:name=".RemoteControlMainActivity"
73+
android:targetActivity=".MainActivity"
74+
android:permission="tech.httptoolkit.android.REMOTE_CONTROL_INTERCEPTION">
75+
<intent-filter>
76+
<action android:name="tech.httptoolkit.android.ACTIVATE" />
77+
<category android:name="android.intent.category.DEFAULT" />
78+
79+
<data android:scheme="https"
80+
android:host="android.httptoolkit.tech"
81+
android:pathPrefix="/connect" />
82+
</intent-filter>
83+
<intent-filter>
84+
<action android:name="tech.httptoolkit.android.DEACTIVATE" />
85+
<category android:name="android.intent.category.DEFAULT" />
86+
</intent-filter>
87+
</activity-alias>
88+
6089
<activity android:name=".ScanActivity" android:parentActivityName=".MainActivity">
6190
<meta-data
6291
android:name="android.support.PARENT_ACTIVITY"

app/src/main/java/tech/httptoolkit/android/MainActivity.kt

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ private fun getCertificateFingerprint(cert: X509Certificate): String {
5959
return Base64.encodeToString(fingerprint, Base64.NO_WRAP)
6060
}
6161

62+
private val ACTIVATE_INTENT = "tech.httptoolkit.android.ACTIVATE"
63+
private val DEACTIVATE_INTENT = "tech.httptoolkit.android.DEACTIVATE"
64+
6265
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
6366

6467
private val TAG = MainActivity::class.simpleName
@@ -137,38 +140,50 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
137140
override fun onNewIntent(intent: Intent) {
138141
super.onNewIntent(intent)
139142

140-
if (intent.action != Intent.ACTION_VIEW) {
141-
Log.w(TAG, "Unknown intent. Action ${intent.action}, data: ${intent.data}")
142-
return
143-
}
144-
145143
app.trackEvent("Setup", "action-view")
146144

147-
if (app.lastProxy != null && isVpnConfigured()) {
148-
Log.i(TAG, "Showing prompt for ACTION_VIEW intent")
149-
150-
// If we were started from an intent (e.g. another barcode scanner/link), and we
151-
// had a proxy before (so no prompts required) then confirm before starting the VPN.
152-
// Without this any QR code you scan could instantly MitM you.
153-
MaterialAlertDialogBuilder(this)
154-
.setTitle("Enable Interception")
155-
.setIcon(R.drawable.ic_exclamation_triangle)
156-
.setMessage(
157-
"Do you want to share all this device's HTTP traffic with HTTP Toolkit?" +
158-
"\n\n" +
159-
"Only accept this if you trust the source."
160-
)
161-
.setPositiveButton("Enable") { _, _ ->
162-
Log.i(TAG, "Prompt confirmed")
145+
when (intent.action) {
146+
// ACTION_VIEW means that somebody had the app installed, and scanned the barcode with
147+
// a separate barcode app anyway (or opened the QR code URL in a browser)
148+
Intent.ACTION_VIEW -> {
149+
if (app.lastProxy != null && isVpnConfigured()) {
150+
Log.i(TAG, "Showing prompt for ACTION_VIEW intent")
151+
152+
// If we were started from an intent (e.g. another barcode scanner/link), and we
153+
// had a proxy before (so no prompts required) then confirm before starting the VPN.
154+
// Without this any QR code you scan could instantly MitM you.
155+
MaterialAlertDialogBuilder(this)
156+
.setTitle("Enable Interception")
157+
.setIcon(R.drawable.ic_exclamation_triangle)
158+
.setMessage(
159+
"Do you want to share all this device's HTTP traffic with HTTP Toolkit?" +
160+
"\n\n" +
161+
"Only accept this if you trust the source."
162+
)
163+
.setPositiveButton("Enable") { _, _ ->
164+
Log.i(TAG, "Prompt confirmed")
165+
launch { connectToVpnFromUrl(intent.data!!) }
166+
}
167+
.setNegativeButton("Cancel") { _, _ ->
168+
Log.i(TAG, "Prompt cancelled")
169+
}
170+
.show()
171+
} else {
172+
Log.i(TAG, "Launching from ACTION_VIEW intent")
163173
launch { connectToVpnFromUrl(intent.data!!) }
164174
}
165-
.setNegativeButton("Cancel") { _, _ ->
166-
Log.i(TAG, "Prompt cancelled")
167-
}
168-
.show()
169-
} else {
170-
Log.i(TAG, "Launching from ACTION_VIEW intent")
171-
launch { connectToVpnFromUrl(intent.data!!) }
175+
}
176+
177+
// RPC setup API, used by ADB to enable/disable without prompts.
178+
// Permission required, checked for via activity-alias in the manifest
179+
ACTIVATE_INTENT -> {
180+
launch { connectToVpnFromUrl(intent.data!!) }
181+
}
182+
DEACTIVATE_INTENT -> {
183+
disconnect()
184+
}
185+
186+
else -> Log.w(TAG, "Unknown intent. Action ${intent.action}, data: ${intent.data}")
172187
}
173188
}
174189

0 commit comments

Comments
 (0)