Можно ли ограничить DownloadManager.Request указанным сертификатом сервера c? - PullRequest
4 голосов
/ 09 июля 2020

В рамках моих усилий по повышению безопасности моего приложения я хотел защитить своего клиента от атак типа «Человек посередине». файлы (10-50 мега) с сервера CDN. Для этого - я использую DownloadMnager

системы. Есть ли способ установить какой-либо определенный c TrustManager или c ключ сертификата сервера через его API? Есть ли другой способ привязать запрос к указанному c доверенному серверу?

Похоже, такого API нет, но я буду удивлен, если это действительно так, потому что GooglePlay использует диспетчер загрузки системы для загрузки apk, а затем установите их ...

1 Ответ

1 голос
/ 19 июля 2020

Являясь общесистемным API, я сомневаюсь, что существует какой-либо способ ограничить DownloadManager конкретным c серверным сертификатом. В ответ на ваш упомянутый пример, Google-Play, скорее всего, устанавливает загруженный APK, наблюдая за завершением загрузки.

Но если я правильно понимаю, вы можете достичь своей цели, используя Retrofit метод загрузки файлов библиотеки, как описано в этой публикации SO , в то время как привязка сертификата может быть достигнута с помощью следующего класса SelfSigningClientBuilder для создания клиента Retrofit:

SelfSigningClientBuilder.kt

import android.content.Context
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
import java.security.*
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.*
import java.util.concurrent.TimeUnit
import javax.net.ssl.*

object SelfSigningClientBuilder {

    private const val NET_TIMEOUT_READ = 80L
    private const val NET_TIMEOUT_WRITE = 120L
    private const val NET_TIMEOUT_CONNECT = 75L

    @JvmStatic
    fun createClient(context: Context, isCertificateNeeded: Boolean = true): OkHttpClient {
        val interceptor = getInterceptor()
        if (isCertificateNeeded)
            try {
                val cf = CertificateFactory.getInstance("X.509")
                // assuming the CA certificate is put inside res/raw folder named as ca_cert.pem
                val cert = context.resources.openRawResource(R.raw.ca_cert)
                val ca = cf?.generateCertificate(cert)
                cert.close()
                val keyStoreType = KeyStore.getDefaultType()
                val keyStore = KeyStore.getInstance(keyStoreType)
                keyStore.load(null, null)
                keyStore.setCertificateEntry("ca", ca)
                val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
                val tmf = TrustManagerFactory.getInstance(tmfAlgorithm)
                tmf.init(keyStore)
                val trustManagers = tmf.trustManagers
                if (trustManagers.size != 1 || trustManagers[0] !is X509TrustManager) {
                        throw IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers))
                }
                val trustManager = trustManagers[0] as X509TrustManager
                val sslContext = SSLContext.getInstance("SSL")
                sslContext!!.init(null, trustManagers, null)

                return OkHttpClient.Builder()
                    .sslSocketFactory(sslContext.socketFactory, trustManager)
                    .readTimeout(NET_TIMEOUT_READ, TimeUnit.SECONDS)
                    .writeTimeout(NET_TIMEOUT_WRITE, TimeUnit.SECONDS)
                    .connectTimeout(NET_TIMEOUT_CONNECT, TimeUnit.SECONDS)
                    // .retryOnConnectionFailure(true)
                    .addInterceptor(interceptor)
                    .build()
            } catch (e: KeyStoreException) {
                e.printStackTrace()
            } catch (e: CertificateException) {
                e.printStackTrace()
            } catch (e: NoSuchAlgorithmException) {
                e.printStackTrace()
            } catch (e: IOException) {
                e.printStackTrace()
            } catch (e: KeyManagementException) {
                e.printStackTrace()
            }
        return OkHttpClient.Builder()
            .readTimeout(NET_TIMEOUT_READ, TimeUnit.SECONDS)
            .writeTimeout(NET_TIMEOUT_WRITE, TimeUnit.SECONDS)
            .connectTimeout(NET_TIMEOUT_CONNECT, TimeUnit.SECONDS)
            .addInterceptor(interceptor)
            .build()
    }

    private fun getInterceptor(): Interceptor {
        return Interceptor { chain ->
            val originalRequest = chain.request()
            val request: Request = originalRequest.newBuilder()
                .header("custom-header", "my-header-value")
                .method(originalRequest.method(), originalRequest.body())
                .build()
            chain.proceed(request)
        }
    }
}

Затем создайте Retrofit-client, следуя сегменту кода здесь, и затем используйте его для загрузки файла:

val retrofit = Retrofit.Builder()
            .client(SelfSigningClientBuilder.createClient(context, true)
            .baseUrl("https://yourdomain.com/")
            .build()

При использовании этого модифицированного клиента , Я проанализировал все свои запросы REST API с помощью Wireshark , Burp-suite и Charles Proxy - ни один из них не мог показать фактический текст запроса вместо некоторые gibberi sh данные. Итак, я надеюсь, что ваш файл-контент будет защищен от атаки MITM во время выполнения этого процесса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...