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:
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
1128
app/src/main/res/raw/google_roots_ca.pem
Normal file
1128
app/src/main/res/raw/google_roots_ca.pem
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user