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 package com.aurora.store.data.model
enum class Algorithm(var value: String) { enum class Algorithm(var value: String) {
SHA("SHA"),
SHA1("SHA-1"), SHA1("SHA-1"),
SHA256("SHA-256") SHA256("SHA-256")
} }

View File

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

View File

@@ -19,10 +19,12 @@
package com.aurora.store.data.network package com.aurora.store.data.network
import android.content.Context
import android.util.Log import android.util.Log
import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.store.BuildConfig import com.aurora.store.BuildConfig
import com.aurora.store.data.model.ProxyInfo import com.aurora.store.data.model.ProxyInfo
import com.aurora.store.util.CertUtil
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@@ -47,7 +49,7 @@ object OkHttpClient : IProxyHttpClient {
override val responseCode: StateFlow<Int> override val responseCode: StateFlow<Int>
get() = _responseCode.asStateFlow() get() = _responseCode.asStateFlow()
private var okHttpClient = OkHttpClient() private lateinit var okHttpClient: okhttp3.OkHttpClient
private val okHttpClientBuilder = OkHttpClient().newBuilder() private val okHttpClientBuilder = OkHttpClient().newBuilder()
.connectTimeout(25, TimeUnit.SECONDS) .connectTimeout(25, TimeUnit.SECONDS)
.readTimeout(25, TimeUnit.SECONDS) .readTimeout(25, TimeUnit.SECONDS)
@@ -56,6 +58,16 @@ object OkHttpClient : IProxyHttpClient {
.followRedirects(true) .followRedirects(true)
.followSslRedirects(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 { override fun setProxy(proxyInfo: ProxyInfo): OkHttpClient {
val proxy = Proxy( val proxy = Proxy(
if (proxyInfo.protocol == "SOCKS") Proxy.Type.SOCKS else Proxy.Type.HTTP, if (proxyInfo.protocol == "SOCKS") Proxy.Type.SOCKS else Proxy.Type.HTTP,
@@ -80,10 +92,22 @@ object OkHttpClient : IProxyHttpClient {
} }
okHttpClientBuilder.proxy(proxy) okHttpClientBuilder.proxy(proxy)
okHttpClient = okHttpClientBuilder.build()
return this 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) @Throws(IOException::class)
fun post(url: String, headers: Map<String, String>, requestBody: RequestBody): PlayResponse { fun post(url: String, headers: Map<String, String>, requestBody: RequestBody): PlayResponse {
val request = Request.Builder() val request = Request.Builder()

View File

@@ -27,8 +27,13 @@ import android.util.Log
import com.aurora.extensions.generateX509Certificate import com.aurora.extensions.generateX509Certificate
import com.aurora.extensions.getInstallerPackageNameCompat import com.aurora.extensions.getInstallerPackageNameCompat
import com.aurora.extensions.isPAndAbove 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 com.aurora.store.util.PackageUtil.getPackageInfo
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
object CertUtil { object CertUtil {
@@ -52,7 +57,7 @@ object CertUtil {
return try { return try {
val certificates = getX509Certificates(context, packageName) val certificates = getX509Certificates(context, packageName)
certificates.map { certificates.map {
val messageDigest = MessageDigest.getInstance("SHA") val messageDigest = MessageDigest.getInstance(Algorithm.SHA.value)
messageDigest.update(it.encoded) messageDigest.update(it.encoded)
Base64.encodeToString( Base64.encodeToString(
messageDigest.digest(), 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 { private fun isSignedByFDroid(context: Context, packageName: String): Boolean {
return try { return try {
getX509Certificates(context, packageName).any { cert -> 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> { private fun getX509Certificates(context: Context, packageName: String): List<X509Certificate> {
return try { return try {
val packageInfo = getPackageInfoWithSignature(context, packageName) val packageInfo = getPackageInfoWithSignature(context, packageName)

File diff suppressed because it is too large Load Diff