@@ -43,7 +43,6 @@ import java.security.cert.X509Certificate
43
43
44
44
45
45
const val START_VPN_REQUEST = 123
46
- const val START_VPN_REQUEST_NO_CERT = 124
47
46
const val INSTALL_CERT_REQUEST = 456
48
47
const val SCAN_REQUEST = 789
49
48
const val PICK_APPS_REQUEST = 499
@@ -343,48 +342,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
343
342
Log .i(TAG , if (vpnIntent != null ) " got intent" else " no intent" )
344
343
val vpnNotConfigured = vpnIntent != null
345
344
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
385
347
startActivityForResult(vpnIntent, START_VPN_REQUEST )
386
348
} else {
387
- // VPN is trusted & cert setup already, lets get to it.
349
+ // VPN is trusted already, continue
388
350
onActivityResult(START_VPN_REQUEST , RESULT_OK , null )
389
351
}
390
352
@@ -512,7 +474,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
512
474
Log .i(TAG , " onActivityResult: " + (
513
475
when (requestCode) {
514
476
START_VPN_REQUEST -> " start-vpn"
515
- START_VPN_REQUEST_NO_CERT -> " start-vpn-nocrt"
516
477
INSTALL_CERT_REQUEST -> " install-cert"
517
478
SCAN_REQUEST -> " scan-request"
518
479
PICK_APPS_REQUEST -> " pick-apps"
@@ -529,9 +490,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
529
490
if (requestCode == START_VPN_REQUEST && currentProxyConfig != null ) {
530
491
Log .i(TAG , " Installing cert..." )
531
492
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 )
535
493
} else if (requestCode == INSTALL_CERT_REQUEST ) {
536
494
Log .i(TAG ," Cert installed, checking notification perms..." )
537
495
ensureNotificationsEnabled()
@@ -656,26 +614,56 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
656
614
if (existingTrust == null ) {
657
615
Log .i(TAG , " Certificate not trusted, prompting to install" )
658
616
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 ) {
664
618
// Up until Android 11, we can prompt the user to install the CA cert into the user
665
619
// CA store. Notably, if the cert is already installed as a system cert but
666
620
// disabled, this will get triggered, and will enable the cert, rather than adding
667
621
// 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) }
672
627
}
673
628
} else {
674
629
Log .i(TAG , " Certificate already trusted, continuing" )
675
630
onActivityResult(INSTALL_CERT_REQUEST , RESULT_OK , null )
676
631
}
677
632
}
678
633
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
+
679
667
@RequiresApi(Build .VERSION_CODES .Q )
680
668
private suspend fun promptToManuallyInstallCert (cert : Certificate , repeatPrompt : Boolean = false) {
681
669
if (! repeatPrompt) {
@@ -713,7 +701,12 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
713
701
Html .fromHtml(
714
702
"""
715
703
<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
+ }
717
710
</p>
718
711
<p>
719
712
To allow HTTP Toolkit to intercept HTTPS traffic:
@@ -740,6 +733,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
740
733
.setPositiveButton(" Open security settings" ) { _, _ ->
741
734
startActivityForResult(Intent (Settings .ACTION_SECURITY_SETTINGS ), INSTALL_CERT_REQUEST )
742
735
}
736
+ .setNeutralButton(" Skip" ) { _, _ ->
737
+ onActivityResult(INSTALL_CERT_REQUEST , RESULT_OK , null )
738
+ }
743
739
.setNegativeButton(" Cancel" ) { _, _ ->
744
740
disconnect()
745
741
}
0 commit comments