Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit ee098c4a authored by Ricki Hirner's avatar Ricki Hirner Committed by GitHub
Browse files

Explicitly integrate Conscrypt (#1796)

* Merge HttpClient and HttpClientBuilder

- Remove `HttpClient` class and replace with `OkHttpClient`
- Update all references to `HttpClient` to use `OkHttpClient`
- Add new `HttpClientBuilder` class for building `OkHttpClient` instances
- Update all builder usages to use the new `HttpClientBuilder` class

* KDoc

* Integrate Conscrypt for TLS

- Add Conscrypt dependency
- Initialize Conscrypt in HttpClientBuilder
- Create ConscryptIntegration utility

* KDoc

* Make object a class, better test

* Update cert4android to the latest version (doesn't bundle Conscrypt anymore)
parent a8bd2965
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -186,7 +186,7 @@ dependencies {
    }

    // third-party libs
    @Suppress("RedundantSuppression")
    implementation(libs.conscrypt)
    implementation(libs.dnsjava)
    implementation(libs.guava)
    implementation(libs.mikepenz.aboutLibraries.m3)
+31 −0
Original line number Diff line number Diff line
/*
 * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
 */

package at.bitfire.davdroid.network

import org.conscrypt.Conscrypt
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.security.Security

class ConscryptIntegrationTest {

    val integration = ConscryptIntegration()

    @Test
    fun testInitialize_InstallsConscrypt() {
        uninstallConscrypt()
        assertFalse(integration.conscryptInstalled())

        integration.initialize()
        assertTrue(integration.conscryptInstalled())
    }

    private fun uninstallConscrypt() {
        for (conscrypt in Security.getProviders().filter { Conscrypt.isConscrypt(it) })
            Security.removeProvider(conscrypt.name)
    }

}
 No newline at end of file
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
 */

package at.bitfire.davdroid.network

import androidx.annotation.VisibleForTesting
import org.conscrypt.Conscrypt
import java.security.Security
import java.util.logging.Logger
import javax.net.ssl.SSLContext

/**
 * Integration with the Conscrypt library that provides recent TLS versions and ciphers,
 * regardless of the device Android version.
 */
class ConscryptIntegration {

    private val logger
        get() = Logger.getLogger(javaClass.name)

    private var initialized = false

    /**
     * Loads and initializes Conscrypt (if not already done). Safe to be called multiple times.
     */
    fun initialize() {
        synchronized(ConscryptIntegration::javaClass) {
            if (initialized)
                return

            val alreadyInstalled = conscryptInstalled()
            if (!alreadyInstalled) {
                // install Conscrypt as most preferred provider
                Security.insertProviderAt(Conscrypt.newProvider(), 1)

                val version = Conscrypt.version()
                logger.info("Using Conscrypt/${version.major()}.${version.minor()}.${version.patch()} for TLS")

                val engine = SSLContext.getDefault().createSSLEngine()
                logger.info("Enabled protocols: ${engine.enabledProtocols.joinToString(", ")}")
                logger.info("Enabled ciphers: ${engine.enabledCipherSuites.joinToString(", ")}")
            }

            initialized = true
        }
    }

    @VisibleForTesting
    internal fun conscryptInstalled() =
        Security.getProviders().any { Conscrypt.isConscrypt(it) }

}
 No newline at end of file
+8 −1
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ import javax.net.ssl.SSLContext
 * Builder for the [OkHttpClient].
 *
 * **Attention:** If the builder is injected, it shouldn't be used from multiple locations to generate different clients because then
 * there's only one [Builder] object and setting properties from one location would influence the others.
 * there's only one [HttpClientBuilder] object and setting properties from one location would influence the others.
 *
 * To generate multiple clients, inject and use `Provider<HttpClientBuilder>` instead.
 */
@@ -58,6 +58,13 @@ class HttpClientBuilder @Inject constructor(
    private val settingsManager: SettingsManager
) {

    companion object {
        init {
            // make sure Conscrypt is available when the HttpClientBuilder class is loaded the first time
            ConscryptIntegration().initialize()
        }
    }

    /**
     * Flag to prevent multiple [build] calls
     */
+3 −1
Original line number Diff line number Diff line
@@ -18,11 +18,12 @@ androidx-test-runner = "1.7.0"
androidx-test-rules = "1.7.0"
androidx-test-junit = "1.3.0"
androidx-work = "2.11.0"
bitfire-cert4android = "41009d48ed"
bitfire-cert4android = "b3160b02b8"
bitfire-dav4jvm = "f11523619b"
bitfire-synctools = "5fc6688ff6"
compose-accompanist = "0.37.3"
compose-bom = "2025.10.01"
conscrypt = "2.5.3"
dnsjava = "3.6.3"
glance = "1.1.1"
guava = "33.5.0-android"
@@ -80,6 +81,7 @@ compose-material3 = { group = "androidx.compose.material3", name = "material3" }
compose-materialIconsExtended = { module = "androidx.compose.material:material-icons-extended" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-toolingPreview = { module = "androidx.compose.ui:ui-tooling-preview" }
conscrypt = { module = "org.conscrypt:conscrypt-android", version.ref = "conscrypt" }
dnsjava = { module = "dnsjava:dnsjava", version.ref = "dnsjava" }
glance-base = { module = "androidx.glance:glance-appwidget", version.ref = "glance" }
glance-material = { module = "androidx.glance:glance-material", version.ref = "glance" }