Android 是否可以将DownloadManager.Request限制为特定的服务器证书?

Android 是否可以将DownloadManager.Request限制为特定的服务器证书?,android,ssl-certificate,android-download-manager,certificate-pinning,Android,Ssl Certificate,Android Download Manager,Certificate Pinning,作为我努力提高应用程序安全性的一部分,我想保护我的客户端免受“中间人”攻击 我有一个常见的用例,我的应用程序从CDN服务器下载大文件(10-50兆)。为此,我使用系统的DownloadMnager 有没有办法通过其API设置任何特定的TrustManager或特定的服务器证书密钥?是否有其他方法将请求锁定到特定的受信任服务器 看起来没有这样的API,但如果真的是这样,我会感到惊讶,因为GooglePlay使用系统的下载管理器下载apk,然后安装它们…作为一个系统范围内可用的API,我怀疑是否有任

作为我努力提高应用程序安全性的一部分,我想保护我的客户端免受“中间人”攻击

我有一个常见的用例,我的应用程序从CDN服务器下载大文件(10-50兆)。为此,我使用系统的
DownloadMnager

有没有办法通过其API设置任何特定的
TrustManager
或特定的服务器证书密钥?是否有其他方法将请求锁定到特定的受信任服务器


看起来没有这样的API,但如果真的是这样,我会感到惊讶,因为GooglePlay使用系统的下载管理器下载apk,然后安装它们…

作为一个系统范围内可用的API,我怀疑是否有任何可能的方法将
DownloadManager
限制为特定的服务器证书。针对您提到的示例,Google Play很可能是通过观察下载完成情况来安装下载的APK

但是,如果我的理解是正确的,您可以使用中讨论的库文件下载方法来实现目标,而证书固定可以通过使用以下
SelfSigningClientBuilder
类来构建改装客户端来实现:

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)
        }
    }
}
然后按照此处的代码段构造改装客户端,然后使用它下载文件:

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

在使用这个改型客户机时,我通过和解析了所有REST API请求,其中没有一个可以显示实际的请求文本,而不是一些乱七八糟的数据。因此,我希望在执行此过程时,您的文件内容将受到保护,免受MITM攻击。

我有几个后续问题。我们正在处理重定向吗?对于中间人来说,仅仅用HTTPS来限制你的应用程序就足够了吗?你可以用一些选项来完成这一点——虽然我知道很多人反对证书钉住,但是请考虑链接中的任何其他选项。假设我没有重定向,所有下载都是HTTPS。你能解释一下为什么设置安全配置阻止中间人攻击吗?此外,下载管理器是另一个进程,因此他无法对我自己的应用程序中定义的任何安全配置文件做出反应。如果您的设备没有恶意根ssl证书,您可以假定所有https通信都是安全的。因此,通过阻止http通信,您可以假定您是安全的。我很想听到有争议的论点。谢谢@Touhid。我知道如何编写代码,当请求从我自己的进程完成时,执行此证书签名检查。我还知道,我可以在下载后验证文件,我正在这样做。但是,这还不够:我首先要防止文件被下载。这对我来说还不够,我更喜欢继续使用下载管理器,而不是编写自己的下载管理器。然后我非常确定,使用
DownloadManager
,这在Android端是不可行的,但可能是通过AWS S3内容上的IAM控件之类的文件存储部分[。