arblant
FoxyVPN-MultiAcc / app / src / main / java / com / vauth / foxyvpn / vpn / upstream / UpstreamTrust.kt
UpstreamTrust.kt83 lines · 3.2 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
package com.vauth.foxyvpn.vpn.upstream

import android.content.Context
import com.vauth.foxyvpn.R
import com.vauth.foxyvpn.data.AppLogger
import java.io.ByteArrayInputStream
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.TrustManagerFactory

private const val TAG = "UpstreamTrust"

private val PEM_BLOCK = Regex("-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----", RegexOption.DOT_MATCHES_ALL)

object UpstreamTrust {

    @Volatile private var appContext: Context? = null
    @Volatile private var cached: TrustManagerFactory? = null
    @Volatile private var built = false
    private val lock = Any()

    fun init(context: Context) {
        appContext = context.applicationContext
    }

    fun trustManagerFactory(): TrustManagerFactory? {
        if (built) return cached
        synchronized(lock) {
            if (!built) {
                cached = runCatching { build() }
                    .onFailure { AppLogger.e(TAG, "could not build the merged trust store; using the system default", it) }
                    .getOrNull()
                built = true
            }
            return cached
        }
    }

    private fun build(): TrustManagerFactory? {
        val merged = KeyStore.getInstance(KeyStore.getDefaultType()).apply { load(null, null) }

        var systemCount = 0
        runCatching {
            val system = KeyStore.getInstance("AndroidCAStore").apply { load(null, null) }
            for (alias in system.aliases()) {
                val cert = system.getCertificate(alias) as? X509Certificate ?: continue
                merged.setCertificateEntry("system:$alias", cert)
                systemCount++
            }
        }.onFailure {
            AppLogger.w(TAG, "could not read the system CA store; relying on the bundled roots only", it)
        }

        var bundledCount = 0
        val context = appContext
        if (context == null) {
            AppLogger.w(TAG, "no application context; the bundled roots were not loaded")
        } else {
            runCatching {
                val pem = context.resources.openRawResource(R.raw.globalsign_roots).use { it.readBytes() }
                    .toString(Charsets.US_ASCII)
                val factory = CertificateFactory.getInstance("X.509")
                for (block in PEM_BLOCK.findAll(pem)) {
                    runCatching {
                        val cert = factory.generateCertificate(
                            ByteArrayInputStream(block.value.toByteArray(Charsets.US_ASCII)),
                        ) as X509Certificate
                        merged.setCertificateEntry("bundled:$bundledCount", cert)
                        bundledCount++
                    }
                }
            }.onFailure {
                AppLogger.w(TAG, "could not load the bundled GlobalSign roots", it)
            }
        }

        if (systemCount == 0 && bundledCount == 0) return null

        AppLogger.i(TAG, "upstream TLS trust: $systemCount system root(s) + $bundledCount bundled root(s)")
        return TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { init(merged) }
    }
}