mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -06:00
Added CertificateProviderExtensions to deduplicate code
Fixed tests
This commit is contained in:
parent
dd62b790f7
commit
9421e94219
@ -1,11 +1,13 @@
|
||||
package com.x8bit.bitwarden.ui.platform.glide
|
||||
|
||||
import android.content.Context
|
||||
import com.bitwarden.network.ssl.CertificateProvider
|
||||
import com.bitwarden.network.ssl.createMtlsOkHttpClient
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.ModelLoader
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||
@ -21,16 +23,6 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.Socket
|
||||
import java.security.KeyStore
|
||||
import java.security.Principal
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509ExtendedKeyManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
/**
|
||||
* Custom Glide module for the Bitwarden app that configures Glide to use an OkHttpClient
|
||||
@ -64,8 +56,7 @@ class BitwardenAppGlideModule : AppGlideModule() {
|
||||
)
|
||||
val certificateManager = entryPoint.certificateManager()
|
||||
|
||||
// Create OkHttpClient with mTLS configuration
|
||||
val okHttpClient = createMtlsOkHttpClient(certificateManager)
|
||||
val okHttpClient = certificateManager.createMtlsOkHttpClient()
|
||||
|
||||
// Register custom ModelLoader that uses our mTLS OkHttpClient
|
||||
registry.replace(
|
||||
@ -116,13 +107,13 @@ class BitwardenAppGlideModule : AppGlideModule() {
|
||||
private class OkHttpDataFetcher(
|
||||
private val client: OkHttpClient,
|
||||
private val url: GlideUrl,
|
||||
) : com.bumptech.glide.load.data.DataFetcher<InputStream> {
|
||||
) : DataFetcher<InputStream> {
|
||||
|
||||
private var call: Call? = null
|
||||
|
||||
override fun loadData(
|
||||
priority: com.bumptech.glide.Priority,
|
||||
callback: com.bumptech.glide.load.data.DataFetcher.DataCallback<in InputStream>,
|
||||
priority: Priority,
|
||||
callback: DataFetcher.DataCallback<in InputStream>,
|
||||
) {
|
||||
val request = Request.Builder()
|
||||
.url(url.toStringUrl())
|
||||
@ -155,94 +146,4 @@ class BitwardenAppGlideModule : AppGlideModule() {
|
||||
override fun getDataSource(): com.bumptech.glide.load.DataSource =
|
||||
com.bumptech.glide.load.DataSource.REMOTE
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an OkHttpClient configured with mTLS using the same SSL setup as RetrofitsImpl.
|
||||
*
|
||||
* This client will present the client certificate stored in the Android KeyStore during
|
||||
* the TLS handshake.
|
||||
*/
|
||||
private fun createMtlsOkHttpClient(certificateProvider: CertificateProvider): OkHttpClient {
|
||||
val sslContext = createSslContext(certificateProvider)
|
||||
val trustManagers = createSslTrustManagers()
|
||||
|
||||
return OkHttpClient.Builder()
|
||||
.sslSocketFactory(
|
||||
sslContext.socketFactory,
|
||||
trustManagers.first() as X509TrustManager,
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SSLContext configured with a custom X509ExtendedKeyManager.
|
||||
*
|
||||
* This wraps our CertificateProvider to handle client certificate selection during
|
||||
* the TLS handshake.
|
||||
*/
|
||||
private fun createSslContext(certificateProvider: CertificateProvider): SSLContext =
|
||||
SSLContext.getInstance("TLS").apply {
|
||||
init(
|
||||
arrayOf(
|
||||
CertificateProviderKeyManager(certificateProvider = certificateProvider),
|
||||
),
|
||||
createSslTrustManagers(),
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* X509ExtendedKeyManager implementation that delegates to a CertificateProvider.
|
||||
*
|
||||
* This is equivalent to BitwardenX509ExtendedKeyManager but defined locally since
|
||||
* that class is internal to the :network module.
|
||||
*/
|
||||
private class CertificateProviderKeyManager(
|
||||
private val certificateProvider: CertificateProvider,
|
||||
) : X509ExtendedKeyManager() {
|
||||
override fun chooseClientAlias(
|
||||
keyType: Array<out String>?,
|
||||
issuers: Array<out Principal>?,
|
||||
socket: Socket?,
|
||||
): String = certificateProvider.chooseClientAlias(
|
||||
keyType = keyType,
|
||||
issuers = issuers,
|
||||
socket = socket,
|
||||
)
|
||||
|
||||
override fun getCertificateChain(
|
||||
alias: String?,
|
||||
): Array<X509Certificate>? = certificateProvider.getCertificateChain(alias)
|
||||
|
||||
override fun getPrivateKey(alias: String?): PrivateKey? =
|
||||
certificateProvider.getPrivateKey(alias)
|
||||
|
||||
// Unused server side methods
|
||||
override fun getServerAliases(
|
||||
alias: String?,
|
||||
issuers: Array<out Principal>?,
|
||||
): Array<String> = emptyArray()
|
||||
|
||||
override fun getClientAliases(
|
||||
keyType: String?,
|
||||
issuers: Array<out Principal>?,
|
||||
): Array<String> = emptyArray()
|
||||
|
||||
override fun chooseServerAlias(
|
||||
alias: String?,
|
||||
issuers: Array<out Principal>?,
|
||||
socket: Socket?,
|
||||
): String = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates default TrustManagers for verifying server certificates.
|
||||
*
|
||||
* This uses the system's default trust anchors (trusted CA certificates).
|
||||
*/
|
||||
private fun createSslTrustManagers(): Array<TrustManager> =
|
||||
TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
.apply { init(null as KeyStore?) }
|
||||
.trustManagers
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
package com.x8bit.bitwarden.ui.platform.glide
|
||||
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* Test class for [BitwardenAppGlideModule] to verify mTLS configuration is properly applied
|
||||
@ -20,18 +19,7 @@ class BitwardenAppGlideModuleTest {
|
||||
// Verify the module can be created
|
||||
val module = BitwardenAppGlideModule()
|
||||
|
||||
assertNotNull("BitwardenAppGlideModule should be instantiable", module)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BitwardenAppGlideModule should extend AppGlideModule`() {
|
||||
// Verify the module properly extends AppGlideModule for Glide integration
|
||||
val module = BitwardenAppGlideModule()
|
||||
|
||||
assertTrue(
|
||||
"BitwardenAppGlideModule must extend AppGlideModule",
|
||||
module is AppGlideModule,
|
||||
)
|
||||
assertNotNull(module)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -41,10 +29,7 @@ class BitwardenAppGlideModuleTest {
|
||||
.declaredClasses
|
||||
.firstOrNull { it.simpleName == "BitwardenGlideEntryPoint" }
|
||||
|
||||
assertNotNull(
|
||||
"BitwardenAppGlideModule must define BitwardenGlideEntryPoint interface for Hilt",
|
||||
entryPointInterface,
|
||||
)
|
||||
assertNotNull(entryPointInterface)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -54,14 +39,11 @@ class BitwardenAppGlideModuleTest {
|
||||
.declaredClasses
|
||||
.firstOrNull { it.simpleName == "BitwardenGlideEntryPoint" }
|
||||
|
||||
assertNotNull("BitwardenGlideEntryPoint must exist", entryPointInterface)
|
||||
assertNotNull(entryPointInterface, "BitwardenGlideEntryPoint must exist")
|
||||
|
||||
val methods = entryPointInterface!!.declaredMethods
|
||||
val hasCertificateManagerMethod = methods.any { it.name == "certificateManager" }
|
||||
|
||||
assertTrue(
|
||||
"BitwardenGlideEntryPoint must have certificateManager() method",
|
||||
hasCertificateManagerMethod,
|
||||
)
|
||||
assertTrue(hasCertificateManagerMethod)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,8 @@ import com.bitwarden.network.interceptor.AuthTokenManager
|
||||
import com.bitwarden.network.interceptor.BaseUrlInterceptor
|
||||
import com.bitwarden.network.interceptor.BaseUrlInterceptors
|
||||
import com.bitwarden.network.interceptor.HeadersInterceptor
|
||||
import com.bitwarden.network.ssl.BitwardenX509ExtendedKeyManager
|
||||
import com.bitwarden.network.ssl.CertificateProvider
|
||||
import com.bitwarden.network.ssl.createSslContext
|
||||
import com.bitwarden.network.util.HEADER_KEY_AUTHORIZATION
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
@ -16,8 +16,6 @@ import retrofit2.Retrofit
|
||||
import retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import timber.log.Timber
|
||||
import java.security.KeyStore
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
@ -149,28 +147,18 @@ internal class RetrofitsImpl(
|
||||
)
|
||||
.build()
|
||||
|
||||
private fun createSslTrustManagers(): Array<TrustManager> =
|
||||
TrustManagerFactory
|
||||
private fun OkHttpClient.Builder.configureSsl(): OkHttpClient.Builder {
|
||||
val sslContext = certificateProvider.createSslContext()
|
||||
val trustManagers = TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
.apply { init(null as KeyStore?) }
|
||||
.trustManagers
|
||||
|
||||
private fun createSslContext(certificateProvider: CertificateProvider): SSLContext = SSLContext
|
||||
.getInstance("TLS").apply {
|
||||
init(
|
||||
arrayOf(
|
||||
BitwardenX509ExtendedKeyManager(certificateProvider = certificateProvider),
|
||||
),
|
||||
createSslTrustManagers(),
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.configureSsl(): OkHttpClient.Builder =
|
||||
sslSocketFactory(
|
||||
createSslContext(certificateProvider = certificateProvider).socketFactory,
|
||||
createSslTrustManagers().first() as X509TrustManager,
|
||||
return sslSocketFactory(
|
||||
sslContext.socketFactory,
|
||||
trustManagers.first() as X509TrustManager,
|
||||
)
|
||||
}
|
||||
|
||||
//endregion Helper properties and functions
|
||||
}
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
package com.bitwarden.network.ssl
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import java.security.KeyStore
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
/**
|
||||
* Creates an [SSLContext] configured with mTLS support using this [CertificateProvider].
|
||||
*
|
||||
* The returned SSLContext will present the client certificate from this provider during
|
||||
* TLS handshakes, enabling mutual TLS authentication.
|
||||
*/
|
||||
fun CertificateProvider.createSslContext(): SSLContext =
|
||||
SSLContext.getInstance("TLS").apply {
|
||||
init(
|
||||
arrayOf(
|
||||
BitwardenX509ExtendedKeyManager(certificateProvider = this@createSslContext),
|
||||
),
|
||||
createSslTrustManagers(),
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an [OkHttpClient] configured with mTLS support using this [CertificateProvider].
|
||||
*
|
||||
* The returned client will present the client certificate from this provider during TLS
|
||||
* handshakes, allowing requests to pass through mTLS checks.
|
||||
*/
|
||||
fun CertificateProvider.createMtlsOkHttpClient(): OkHttpClient {
|
||||
val sslContext = createSslContext()
|
||||
val trustManagers = createSslTrustManagers()
|
||||
|
||||
return OkHttpClient.Builder()
|
||||
.sslSocketFactory(
|
||||
sslContext.socketFactory,
|
||||
trustManagers.first() as X509TrustManager,
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates default [TrustManager]s for verifying server certificates.
|
||||
*
|
||||
* Uses the system's default trust anchors (trusted CA certificates).
|
||||
*/
|
||||
private fun createSslTrustManagers(): Array<TrustManager> =
|
||||
TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
.apply { init(null as KeyStore?) }
|
||||
.trustManagers
|
||||
Loading…
x
Reference in New Issue
Block a user