HttpClient: Enable SSL pinning for google domains

.pem file obtained from https://pki.goog/roots.pem

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-09-06 18:59:28 +05:30
parent 49a22ceff3
commit d727333202
5 changed files with 1196 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
package com.aurora.store.data.model
enum class Algorithm(var value: String) {
SHA("SHA"),
SHA1("SHA-1"),
SHA256("SHA-256")
}

View File

@@ -43,18 +43,17 @@ object HttpClient {
val proxyEnabled = Preferences.getBoolean(context, Preferences.PREFERENCE_PROXY_ENABLED)
val proxyInfoString = Preferences.getString(context, Preferences.PREFERENCE_PROXY_INFO)
return if (proxyEnabled && proxyInfoString.isNotBlank() && proxyInfoString != "{}") {
val okHttpClient = OkHttpClient.builder(context)
if (proxyEnabled && proxyInfoString.isNotBlank() && proxyInfoString != "{}") {
val proxyInfo = gson.fromJson(proxyInfoString, ProxyInfo::class.java)
if (proxyInfo != null) {
OkHttpClient.setProxy(proxyInfo)
} else {
Log.e(TAG, "Proxy info is unavailable, using default client")
OkHttpClient
}
} else {
Log.i(TAG, "Proxy is disabled")
OkHttpClient
}
return okHttpClient.build()
}
}

View File

@@ -19,10 +19,12 @@
package com.aurora.store.data.network
import android.content.Context
import android.util.Log
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.store.BuildConfig
import com.aurora.store.data.model.ProxyInfo
import com.aurora.store.util.CertUtil
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -47,7 +49,7 @@ object OkHttpClient : IProxyHttpClient {
override val responseCode: StateFlow<Int>
get() = _responseCode.asStateFlow()
private var okHttpClient = OkHttpClient()
private lateinit var okHttpClient: okhttp3.OkHttpClient
private val okHttpClientBuilder = OkHttpClient().newBuilder()
.connectTimeout(25, TimeUnit.SECONDS)
.readTimeout(25, TimeUnit.SECONDS)
@@ -56,6 +58,16 @@ object OkHttpClient : IProxyHttpClient {
.followRedirects(true)
.followSslRedirects(true)
fun builder(context: Context): OkHttpClient {
setupSSLPinning(context)
return this
}
fun build(): OkHttpClient {
okHttpClient = okHttpClientBuilder.build()
return this
}
override fun setProxy(proxyInfo: ProxyInfo): OkHttpClient {
val proxy = Proxy(
if (proxyInfo.protocol == "SOCKS") Proxy.Type.SOCKS else Proxy.Type.HTTP,
@@ -80,10 +92,22 @@ object OkHttpClient : IProxyHttpClient {
}
okHttpClientBuilder.proxy(proxy)
okHttpClient = okHttpClientBuilder.build()
return this
}
private fun setupSSLPinning(context: Context) {
// Google needs special handling, see: https://pki.goog/faq/#faq-27
val googleRootCerts = CertUtil.getGoogleRootCertHashes(context).map { "sha256/$it" }
.toTypedArray()
val certificatePinner = CertificatePinner.Builder()
.add("*.googleapis.com", *googleRootCerts)
.add("*.google.com", *googleRootCerts)
.build()
okHttpClientBuilder.certificatePinner(certificatePinner)
}
@Throws(IOException::class)
fun post(url: String, headers: Map<String, String>, requestBody: RequestBody): PlayResponse {
val request = Request.Builder()

View File

@@ -27,8 +27,13 @@ import android.util.Log
import com.aurora.extensions.generateX509Certificate
import com.aurora.extensions.getInstallerPackageNameCompat
import com.aurora.extensions.isPAndAbove
import com.aurora.store.R
import com.aurora.store.data.model.Algorithm
import com.aurora.store.util.PackageUtil.getPackageInfo
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
object CertUtil {
@@ -52,7 +57,7 @@ object CertUtil {
return try {
val certificates = getX509Certificates(context, packageName)
certificates.map {
val messageDigest = MessageDigest.getInstance("SHA")
val messageDigest = MessageDigest.getInstance(Algorithm.SHA.value)
messageDigest.update(it.encoded)
Base64.encodeToString(
messageDigest.digest(),
@@ -65,6 +70,20 @@ object CertUtil {
}
}
fun getGoogleRootCertHashes(context: Context): List<String> {
return try {
val certs = getX509Certificates(context.resources.openRawResource(R.raw.google_roots_ca))
certs.map {
val messageDigest = MessageDigest.getInstance(Algorithm.SHA256.value)
messageDigest.update(it.publicKey.encoded)
Base64.encodeToString(messageDigest.digest(), Base64.NO_WRAP)
}
} catch (exception: Exception) {
Log.e(TAG, "Failed to get SHA256 certificate hash", exception)
emptyList()
}
}
private fun isSignedByFDroid(context: Context, packageName: String): Boolean {
return try {
getX509Certificates(context, packageName).any { cert ->
@@ -88,6 +107,24 @@ object CertUtil {
)
}
private fun getX509Certificates(inputStream: InputStream): List<X509Certificate> {
val certificateFactory = CertificateFactory.getInstance("X509")
val rawCerts = inputStream
.bufferedReader()
.use { it.readText() }
.split("-----END CERTIFICATE-----")
.map {
it.substringAfter("-----BEGIN CERTIFICATE-----")
.substringBefore("-----END CERTIFICATE-----")
.replace("\n", "")
}
.filterNot { it.isBlank() }
val decodedCerts = rawCerts.map { Base64.decode(it, Base64.DEFAULT) }
return decodedCerts.map {
certificateFactory.generateCertificate(ByteArrayInputStream(it)) as X509Certificate
}
}
private fun getX509Certificates(context: Context, packageName: String): List<X509Certificate> {
return try {
val packageInfo = getPackageInfoWithSignature(context, packageName)

View File

File diff suppressed because it is too large Load Diff