@@ -43,7 +43,6 @@ import java.security.cert.X509Certificate
4343
4444
4545const val START_VPN_REQUEST = 123
46- const val START_VPN_REQUEST_NO_CERT = 124
4746const val INSTALL_CERT_REQUEST = 456
4847const val SCAN_REQUEST = 789
4948const 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