Skip to content

Commit 15de244

Browse files
committed
Reorganize cert prompt flow & simplify 'skip' logic to match
1 parent b8bead4 commit 15de244

File tree

1 file changed

+51
-55
lines changed

1 file changed

+51
-55
lines changed

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

Lines changed: 51 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import java.security.cert.X509Certificate
4343

4444

4545
const val START_VPN_REQUEST = 123
46-
const val START_VPN_REQUEST_NO_CERT = 124
4746
const val INSTALL_CERT_REQUEST = 456
4847
const val SCAN_REQUEST = 789
4948
const val PICK_APPS_REQUEST = 499
@@ -343,48 +342,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
343342
Log.i(TAG, if (vpnIntent != null) "got intent" else "no intent")
344343
val vpnNotConfigured = vpnIntent != null
345344

346-
if (whereIsCertTrusted(config) == null && PROMPTED_CERT_SETUP_SUPPORTED) {
347-
// The cert isn't trusted, and the VPN may need setup, so there'll be a series of prompts
348-
// here. Explain them beforehand, so users understand what's going on.
349-
withContext(Dispatchers.Main) {
350-
MaterialAlertDialogBuilder(this@MainActivity)
351-
.setTitle("Enable interception")
352-
.setIcon(R.drawable.ic_info_circle)
353-
.setMessage(
354-
"To intercept traffic from this device, you need to " +
355-
(if (vpnNotConfigured) "activate HTTP Toolkit's VPN and " else "") +
356-
"trust your HTTP Toolkit's certificate authority. " +
357-
"\n\n" +
358-
"Please accept the following prompts to allow this." +
359-
if (!isDeviceSecured(applicationContext))
360-
"\n\n" +
361-
"Due to Android security requirements, trusting the certificate will " +
362-
"require you to set a PIN, password or pattern for this device."
363-
else " To trust the certificate, your device PIN will be required."
364-
)
365-
.setPositiveButton("Ok") { _, _ ->
366-
if (vpnNotConfigured) {
367-
startActivityForResult(vpnIntent, START_VPN_REQUEST)
368-
} else {
369-
onActivityResult(START_VPN_REQUEST, RESULT_OK, null)
370-
}
371-
}
372-
.setNegativeButton("Continue without certificate") { _, _ ->
373-
if (vpnNotConfigured) {
374-
startActivityForResult(vpnIntent, START_VPN_REQUEST_NO_CERT)
375-
} else {
376-
onActivityResult(START_VPN_REQUEST_NO_CERT, RESULT_OK, null)
377-
}
378-
}
379-
.show()
380-
}
381-
} else if (vpnNotConfigured) {
382-
// In this case the VPN needs setup, but the cert is trusted already, so it's
383-
// a single confirmation. Pretty clear, no need to explain. This happens if the
384-
// VPN/app was removed from the device in the past, or when using injected system certs.
345+
if (vpnNotConfigured) {
346+
// Show the 'Enable the VPN' prompt
385347
startActivityForResult(vpnIntent, START_VPN_REQUEST)
386348
} else {
387-
// VPN is trusted & cert setup already, lets get to it.
349+
// VPN is trusted already, continue
388350
onActivityResult(START_VPN_REQUEST, RESULT_OK, null)
389351
}
390352

@@ -512,7 +474,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
512474
Log.i(TAG, "onActivityResult: " + (
513475
when (requestCode) {
514476
START_VPN_REQUEST -> "start-vpn"
515-
START_VPN_REQUEST_NO_CERT -> "start-vpn-nocrt"
516477
INSTALL_CERT_REQUEST -> "install-cert"
517478
SCAN_REQUEST -> "scan-request"
518479
PICK_APPS_REQUEST -> "pick-apps"
@@ -529,9 +490,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
529490
if (requestCode == START_VPN_REQUEST && currentProxyConfig != null) {
530491
Log.i(TAG, "Installing cert...")
531492
ensureCertificateTrusted(currentProxyConfig!!)
532-
} else if (requestCode == START_VPN_REQUEST_NO_CERT && currentProxyConfig != null) {
533-
Log.i(TAG, "Ignore cert...")
534-
onActivityResult(INSTALL_CERT_REQUEST, RESULT_OK, null)
535493
} else if (requestCode == INSTALL_CERT_REQUEST) {
536494
Log.i(TAG ,"Cert installed, checking notification perms...")
537495
ensureNotificationsEnabled()
@@ -656,26 +614,56 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
656614
if (existingTrust == null) {
657615
Log.i(TAG, "Certificate not trusted, prompting to install")
658616

659-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
660-
// Android 11+, with no trusted cert: we need to download the cert to Downloads and
661-
// then tell the user how to install it manually:
662-
launch { promptToManuallyInstallCert(proxyConfig.certificate) }
663-
} else {
617+
if (PROMPTED_CERT_SETUP_SUPPORTED) {
664618
// Up until Android 11, we can prompt the user to install the CA cert into the user
665619
// CA store. Notably, if the cert is already installed as a system cert but
666620
// disabled, this will get triggered, and will enable the cert, rather than adding
667621
// a normal user cert.
668-
val certInstallIntent = KeyChain.createInstallIntent()
669-
certInstallIntent.putExtra(EXTRA_NAME, "HTTP Toolkit CA")
670-
certInstallIntent.putExtra(EXTRA_CERTIFICATE, proxyConfig.certificate.encoded)
671-
startActivityForResult(certInstallIntent, INSTALL_CERT_REQUEST)
622+
launch { promptToAutoInstallCert(proxyConfig.certificate) }
623+
} else {
624+
// Android 11+, with no trusted cert: we need to download the cert to Downloads and
625+
// then tell the user how to install it manually:
626+
launch { promptToManuallyInstallCert(proxyConfig.certificate) }
672627
}
673628
} else {
674629
Log.i(TAG, "Certificate already trusted, continuing")
675630
onActivityResult(INSTALL_CERT_REQUEST, RESULT_OK, null)
676631
}
677632
}
678633

634+
private suspend fun promptToAutoInstallCert(certificate: Certificate) {
635+
withContext(Dispatchers.Main) {
636+
MaterialAlertDialogBuilder(this@MainActivity)
637+
.setTitle("Enable HTTPS interception")
638+
.setIcon(R.drawable.ic_info_circle)
639+
.setMessage(
640+
"To intercept HTTPS traffic from this device, you need to " +
641+
"trust your HTTP Toolkit's certificate authority. " +
642+
"\n\n" +
643+
"Please accept the following prompts to allow this." +
644+
if (!isDeviceSecured(applicationContext))
645+
"\n\n" +
646+
"Due to Android security requirements, trusting the certificate will " +
647+
"require you to set a PIN, password or pattern for this device."
648+
else " To trust the certificate, your device PIN will be required."
649+
)
650+
.setPositiveButton("Install") { _, _ ->
651+
val certInstallIntent = KeyChain.createInstallIntent()
652+
certInstallIntent.putExtra(EXTRA_NAME, "HTTP Toolkit CA")
653+
certInstallIntent.putExtra(EXTRA_CERTIFICATE, certificate.encoded)
654+
startActivityForResult(certInstallIntent, INSTALL_CERT_REQUEST)
655+
}
656+
.setNeutralButton("Skip") { _, _ ->
657+
onActivityResult(INSTALL_CERT_REQUEST, RESULT_OK, null)
658+
}
659+
.setNegativeButton("Cancel") { _, _ ->
660+
disconnect()
661+
}
662+
.setCancelable(false)
663+
.show()
664+
}
665+
}
666+
679667
@RequiresApi(Build.VERSION_CODES.Q)
680668
private suspend fun promptToManuallyInstallCert(cert: Certificate, repeatPrompt: Boolean = false) {
681669
if (!repeatPrompt) {
@@ -713,7 +701,12 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
713701
Html.fromHtml(
714702
"""
715703
<p>
716-
Android ${Build.VERSION.RELEASE} doesn't allow automatic certificate setup.
704+
${
705+
if (PROMPTED_CERT_SETUP_SUPPORTED)
706+
"Automatic certificate installation failed, so it must be done manually."
707+
else
708+
"Android ${Build.VERSION.RELEASE} doesn't allow automatic certificate setup."
709+
}
717710
</p>
718711
<p>
719712
To allow HTTP Toolkit to intercept HTTPS traffic:
@@ -740,6 +733,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
740733
.setPositiveButton("Open security settings") { _, _ ->
741734
startActivityForResult(Intent(Settings.ACTION_SECURITY_SETTINGS), INSTALL_CERT_REQUEST)
742735
}
736+
.setNeutralButton("Skip") { _, _ ->
737+
onActivityResult(INSTALL_CERT_REQUEST, RESULT_OK, null)
738+
}
743739
.setNegativeButton("Cancel") { _, _ ->
744740
disconnect()
745741
}

0 commit comments

Comments
 (0)