Browse Source

Use mozilla included root ca certificates by default

世界 3 years ago
parent
commit
88fcbf4ee1

+ 3 - 1
.gitignore

@@ -15,6 +15,8 @@ build/
 .cxx
 local.properties
 /app/libs/
-/app/src/main/assets/v2ray
+/app/src/main/assets/v2ray/
+/app/src/main/assets/mozilla_included.pem
+/app/src/main/assets/mozilla_included.pem.sha256sum
 /service_account_credentials.json
 jniLibs/

+ 1 - 0
app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetService.aidl

@@ -16,5 +16,6 @@ interface ISagerNetService {
   int urlTest();
   oneway void resetTrafficStats();
   boolean getTrafficStatsEnabled();
+  oneway void updateSystemRoots(boolean useSystem);
 
 }

+ 6 - 0
app/src/main/java/io/nekohasekai/sagernet/Constants.kt

@@ -88,6 +88,7 @@ object Key {
     const val ALWAYS_SHOW_ADDRESS = "alwaysShowAddress"
 
     const val PROVIDER_TROJAN = "providerTrojan"
+    const val PROVIDER_ROOT_CA = "providerRootCA"
 
     const val TUN_IMPLEMENTATION = "tunImplementation"
     const val ENABLE_PCAP = "enablePcap"
@@ -209,6 +210,11 @@ object TrojanProvider {
     const val TROJAN_GO = 2
 }
 
+object RootCAProvider {
+    const val MOZILLA = 0
+    const val SYSTEM = 1
+}
+
 object IPv6Mode {
     const val DISABLE = 0
     const val ENABLE = 1

+ 6 - 9
app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt

@@ -53,10 +53,7 @@ import io.nekohasekai.sagernet.ktx.app
 import io.nekohasekai.sagernet.ktx.checkMT
 import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
 import io.nekohasekai.sagernet.ui.MainActivity
-import io.nekohasekai.sagernet.utils.CrashHandler
-import io.nekohasekai.sagernet.utils.DeviceStorageApp
-import io.nekohasekai.sagernet.utils.PackageCache
-import io.nekohasekai.sagernet.utils.Theme
+import io.nekohasekai.sagernet.utils.*
 import kotlinx.coroutines.DEBUG_PROPERTY_NAME
 import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
 import libcore.Libcore
@@ -85,11 +82,11 @@ class SagerNet : Application(),
         Seq.setContext(this)
 
         externalAssets.mkdirs()
-        Libcore.initializeV2Ray(
-            filesDir.absolutePath + "/", externalAssets.absolutePath + "/", "v2ray/"
-        ) {
-            DataStore.rulesProvider == 0
-        }
+        Libcore.initializeV2Ray(filesDir.absolutePath + "/",
+            externalAssets.absolutePath + "/",
+            "v2ray/",
+            { DataStore.rulesProvider == 0 },
+            { DataStore.providerRootCA == RootCAProvider.SYSTEM })
         Libcore.setenv("v2ray.conf.geoloader", "memconservative")
         Libcore.setUidDumper(UidDumper)
 

+ 5 - 0
app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt

@@ -334,6 +334,10 @@ class BaseService {
             return (data?.proxy?.service as? VpnService)?.getTun()?.trafficStatsEnabled ?: false
         }
 
+        override fun updateSystemRoots(useSystem: Boolean) {
+            Libcore.updateSystemRoots(useSystem)
+        }
+
         override fun close() {
             callbacks.kill()
             cancel()
@@ -352,6 +356,7 @@ class BaseService {
         fun forceLoad() {
             if (DataStore.selectedProxy == 0L) {
                 stopRunner(false, (this as Context).getString(R.string.profile_empty))
+                return
             }
             val s = data.state
             when {

+ 16 - 7
app/src/main/java/io/nekohasekai/sagernet/bg/proto/V2RayInstance.kt

@@ -26,6 +26,7 @@ import android.webkit.WebResourceError
 import android.webkit.WebResourceRequest
 import android.webkit.WebView
 import android.webkit.WebViewClient
+import io.nekohasekai.sagernet.RootCAProvider
 import io.nekohasekai.sagernet.SagerNet
 import io.nekohasekai.sagernet.TrojanProvider
 import io.nekohasekai.sagernet.bg.AbstractInstance
@@ -175,12 +176,20 @@ abstract class V2RayInstance(
     @SuppressLint("SetJavaScriptEnabled")
     override fun launch() {
         val context = if (Build.VERSION.SDK_INT < 24 || SagerNet.user.isUserUnlocked) SagerNet.application else SagerNet.deviceStorage
+        val useSystemCACerts = DataStore.providerRootCA == RootCAProvider.SYSTEM
+        val rootCaPem by lazy { File(app.filesDir, "root_ca_certs.pem").canonicalPath }
 
         for ((isBalancer, chain) in config.index) {
             chain.entries.forEachIndexed { index, (port, profile) ->
                 val bean = profile.requireBean()
                 val needChain = !isBalancer && index != chain.size - 1
                 val (profileType, config) = pluginConfigs[port] ?: 0 to ""
+                val env = mutableMapOf<String, String>()
+                if (!useSystemCACerts) {
+                    env["SSL_CERT_FILE"] = rootCaPem
+                    // disable system directories
+                    env["SSL_CERT_DIR"] = "/dev/null"
+                }
 
                 when {
                     externalInstances.containsKey(port) -> {
@@ -203,7 +212,7 @@ abstract class V2RayInstance(
                             }.path, "--config", configFile.absolutePath
                         )
 
-                        processes.start(commands)
+                        processes.start(commands, env)
                     }
                     bean is TrojanGoBean || bean is ConfigBean && bean.type == "trojan-go" -> {
                         val configFile = File(
@@ -218,7 +227,7 @@ abstract class V2RayInstance(
                             initPlugin("trojan-go-plugin").path, "-config", configFile.absolutePath
                         )
 
-                        processes.start(commands)
+                        processes.start(commands, env)
                     }
                     bean is NaiveBean -> {
                         val configFile = File(
@@ -234,7 +243,7 @@ abstract class V2RayInstance(
                             initPlugin("naive-plugin").path, configFile.absolutePath
                         )
 
-                        processes.start(commands)
+                        processes.start(commands, env)
                     }
                     bean is PingTunnelBean -> {
                         if (needChain) error("PingTunnel is incompatible with chain")
@@ -258,7 +267,7 @@ abstract class V2RayInstance(
                             commands.add(bean.key)
                         }
 
-                        processes.start(commands)
+                        processes.start(commands, env)
                     }
                     bean is RelayBatonBean -> {
                         val configFile = File(
@@ -277,7 +286,7 @@ abstract class V2RayInstance(
                             configFile.absolutePath
                         )
 
-                        processes.start(commands)
+                        processes.start(commands, env)
                     }
                     bean is BrookBean -> {
                         val commands = mutableListOf(initPlugin("brook-plugin").path)
@@ -307,7 +316,7 @@ abstract class V2RayInstance(
                         commands.add("--socks5")
                         commands.add("$LOCALHOST:$port")
 
-                        processes.start(commands)
+                        processes.start(commands, env)
                     }
                     bean is HysteriaBean -> {
                         val configFile = File(
@@ -333,7 +342,7 @@ abstract class V2RayInstance(
                             commands.addAll(0, listOf("su", "-c"))
                         }
 
-                        processes.start(commands)
+                        processes.start(commands, env)
                     }
                 }
             }

+ 1 - 0
app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt

@@ -205,6 +205,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
     // protocol
 
     var providerTrojan by configurationStore.stringToInt(Key.PROVIDER_TROJAN)
+    var providerRootCA by configurationStore.stringToInt(Key.PROVIDER_ROOT_CA)
 
     // cache
 

+ 11 - 4
app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt

@@ -30,15 +30,13 @@ import androidx.preference.SwitchPreference
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.takisoft.preferencex.PreferenceFragmentCompat
 import com.takisoft.preferencex.SimpleMenuPreference
-import io.nekohasekai.sagernet.Key
-import io.nekohasekai.sagernet.R
-import io.nekohasekai.sagernet.SagerNet
-import io.nekohasekai.sagernet.TunImplementation
+import io.nekohasekai.sagernet.*
 import io.nekohasekai.sagernet.database.DataStore
 import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers
 import io.nekohasekai.sagernet.ktx.*
 import io.nekohasekai.sagernet.utils.Theme
 import io.nekohasekai.sagernet.widget.ColorPickerPreference
+import libcore.Libcore
 import java.io.File
 
 class SettingsPreferenceFragment : PreferenceFragmentCompat() {
@@ -158,6 +156,7 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
         directDns.isEnabled = !DataStore.useLocalDnsAsDirectDns
         useLocalDnsAsDirectDns.setOnPreferenceChangeListener { _, newValue ->
             directDns.isEnabled = newValue == false
+            needReload()
             true
         }
 
@@ -222,6 +221,14 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
         val destinationOverride = findPreference<SwitchPreference>(Key.DESTINATION_OVERRIDE)!!
         val resolveDestination = findPreference<SwitchPreference>(Key.RESOLVE_DESTINATION)!!
         val enablePcap = findPreference<SwitchPreference>(Key.ENABLE_PCAP)!!
+        val providerRootCA = findPreference<SimpleMenuPreference>(Key.PROVIDER_ROOT_CA)!!
+        providerRootCA.setOnPreferenceChangeListener { _, newValue ->
+            val useSystem = (newValue as String) == "${RootCAProvider.SYSTEM}"
+            Libcore.updateSystemRoots(useSystem)
+            (requireActivity() as? MainActivity)?.connection?.service?.updateSystemRoots(useSystem)
+            needReload()
+            true
+        }
 
         speedInterval.onPreferenceChangeListener = reloadListener
         portSocks5.onPreferenceChangeListener = reloadListener

+ 10 - 0
app/src/main/res/drawable/baseline_flight_takeoff_24.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M2.5,19h19v2h-19V19zM22.07,9.64c-0.21,-0.8 -1.04,-1.28 -1.84,-1.06L14.92,10l-6.9,-6.43L6.09,4.08l4.14,7.17l-4.97,1.33l-1.97,-1.54l-1.45,0.39l2.59,4.49c0,0 7.12,-1.9 16.57,-4.43C21.81,11.26 22.28,10.44 22.07,9.64z" />
+</vector>

+ 5 - 0
app/src/main/res/values/arrays.xml

@@ -638,4 +638,9 @@
         <item>@string/route_profile</item>
     </string-array>
 
+    <string-array name="root_ca_provider_entry">
+        <item>@string/root_ca_mozilla</item>
+        <item>@string/root_ca_system</item>
+    </string-array>
+
 </resources>

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -497,4 +497,8 @@
     <string name="tasker_action_stop_service">Stop service</string>
     <string name="tasker_start_current_profile">Current profile</string>
     <string name="tasker_blurb_start_profile">Start profile %s</string>
+
+    <string name="root_ca_provider">Root CA Certificates Provider</string>
+    <string name="root_ca_mozilla">Mozilla</string>
+    <string name="root_ca_system">System</string>
 </resources>

+ 10 - 0
app/src/main/res/xml/global_preferences.xml

@@ -155,12 +155,21 @@
 
     <PreferenceCategory app:title="@string/protocol_settings">
         <com.takisoft.preferencex.SimpleMenuPreference
+            android:icon="@drawable/baseline_flight_takeoff_24"
             app:defaultValue="0"
             app:entries="@array/trojan_provider_experimental"
             app:entryValues="@array/int_array_3"
             app:key="providerTrojan"
             app:title="@string/trojan_provider"
             app:useSimpleSummaryProvider="true" />
+        <com.takisoft.preferencex.SimpleMenuPreference
+            android:icon="@drawable/ic_baseline_push_pin_24"
+            app:defaultValue="0"
+            app:entries="@array/root_ca_provider_entry"
+            app:entryValues="@array/int_array_2"
+            app:key="providerRootCA"
+            app:title="@string/root_ca_provider"
+            app:useSimpleSummaryProvider="true" />
     </PreferenceCategory>
 
     <PreferenceCategory app:title="@string/cag_dns">
@@ -207,6 +216,7 @@
             app:title="@string/port_local_dns"
             app:useSimpleSummaryProvider="true" />
         <SwitchPreference
+            app:defaultValue="true"
             app:icon="@drawable/ic_baseline_http_24"
             app:key="requireHttp"
             app:title="@string/require_http" />

+ 2 - 1
buildSrc/build.gradle.kts

@@ -14,6 +14,7 @@ dependencies {
     implementation("com.android.tools.build:gradle-api:$androidPluginVersion")
     implementation(kotlin("gradle-plugin", kotlinVersion))
     implementation(kotlin("stdlib", kotlinVersion))
+    implementation("cn.hutool:hutool-http:$hutoolVersion")
     implementation("cn.hutool:hutool-crypto:$hutoolVersion")
     implementation("org.tukaani:xz:1.9")
     implementation("com.github.triplet.gradle:play-publisher:3.6.0")
@@ -22,4 +23,4 @@ dependencies {
     implementation("com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:8.9.1")
     implementation("com.google.protobuf:protobuf-gradle-plugin:0.8.17")
     implementation("com.github.ben-manes:gradle-versions-plugin:0.39.0")
-}
+}

+ 1 - 0
buildSrc/src/main/kotlin/Helpers.kt

@@ -482,6 +482,7 @@ fun Project.setupApp() {
                 requireFlavor().endsWith("Debug")
             }
             doLast {
+                downloadRootCAList()
                 downloadAssets()
             }
         }

+ 25 - 0
buildSrc/src/main/kotlin/RootCAAsserts.kt

@@ -0,0 +1,25 @@
+import cn.hutool.core.text.csv.CsvReadConfig
+import cn.hutool.core.text.csv.CsvUtil
+import cn.hutool.crypto.digest.DigestUtil
+import cn.hutool.http.HttpUtil
+import org.gradle.api.Project
+import java.io.File
+
+fun Project.downloadRootCAList() {
+    val assets = File(projectDir, "src/main/assets")
+    val csv = HttpUtil.get("https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReportPEMCSV")
+    val data = CsvUtil.getReader(CsvReadConfig().setContainsHeader(true)).readFromStr(csv)
+    val list = mutableListOf<String>()
+    for (row in data) {
+        // skip China root CA
+        if (row.getByName("Geographic Focus").contains("China")) continue
+        if (!row.getByName("Trust Bits").contains("Websites")) continue
+
+        val name = row.getByName("Common Name or Certificate Name")
+        val cert = row.getByName("PEM Info")
+        list.add("$name\n" + cert.substring(1, cert.length - 1))
+    }
+    val pem = File(assets, "mozilla_included.pem")
+    pem.writeText(list.joinToString("\n\n"))
+    File(pem.parent, pem.name + ".sha256sum").writeText(DigestUtil.sha256Hex(pem))
+}

+ 1 - 1
library/core

@@ -1 +1 @@
-Subproject commit ead92eca380c4a03f70d49adb1711bfa181937df
+Subproject commit fb0ec2d6e2b7a92214c8b1c3450f71f1537baa5d