Restore custom user certificate provider (#6451)
It was removed because we temporarily lost this functionality in the Rust SDK
This commit is contained in:
parent
e8a2f97cf5
commit
a512f7ff21
7 changed files with 114 additions and 1 deletions
|
|
@ -17,6 +17,7 @@ import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.impl.analytics.UtdTracker
|
import io.element.android.libraries.matrix.impl.analytics.UtdTracker
|
||||||
|
import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider
|
||||||
import io.element.android.libraries.matrix.impl.paths.SessionPaths
|
import io.element.android.libraries.matrix.impl.paths.SessionPaths
|
||||||
import io.element.android.libraries.matrix.impl.paths.getSessionPaths
|
import io.element.android.libraries.matrix.impl.paths.getSessionPaths
|
||||||
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
|
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
|
||||||
|
|
@ -57,6 +58,7 @@ class RustMatrixClientFactory(
|
||||||
private val sessionStore: SessionStore,
|
private val sessionStore: SessionStore,
|
||||||
private val userAgentProvider: UserAgentProvider,
|
private val userAgentProvider: UserAgentProvider,
|
||||||
private val proxyProvider: ProxyProvider,
|
private val proxyProvider: ProxyProvider,
|
||||||
|
private val userCertificatesProvider: UserCertificatesProvider,
|
||||||
private val clock: SystemClock,
|
private val clock: SystemClock,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
|
|
@ -141,6 +143,7 @@ class RustMatrixClientFactory(
|
||||||
}
|
}
|
||||||
.setSessionDelegate(sessionDelegate)
|
.setSessionDelegate(sessionDelegate)
|
||||||
.userAgent(userAgentProvider.provide())
|
.userAgent(userAgentProvider.provide())
|
||||||
|
.addRootCertificates(userCertificatesProvider.provides())
|
||||||
.autoEnableBackups(true)
|
.autoEnableBackups(true)
|
||||||
.autoEnableCrossSigning(true)
|
.autoEnableCrossSigning(true)
|
||||||
.roomKeyRecipientStrategy(
|
.roomKeyRecipientStrategy(
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,19 @@ import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
|
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
|
||||||
import io.element.android.libraries.matrix.impl.ClientBuilderProvider
|
import io.element.android.libraries.matrix.impl.ClientBuilderProvider
|
||||||
|
import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class RustHomeServerLoginCompatibilityChecker(
|
class RustHomeServerLoginCompatibilityChecker(
|
||||||
private val clientBuilderProvider: ClientBuilderProvider,
|
private val clientBuilderProvider: ClientBuilderProvider,
|
||||||
) : HomeServerLoginCompatibilityChecker {
|
private val userCertificatesProvider: UserCertificatesProvider,
|
||||||
|
) : HomeServerLoginCompatibilityChecker {
|
||||||
override suspend fun check(url: String): Result<Boolean> = runCatchingExceptions {
|
override suspend fun check(url: String): Result<Boolean> = runCatchingExceptions {
|
||||||
clientBuilderProvider.provide()
|
clientBuilderProvider.provide()
|
||||||
.inMemoryStore()
|
.inMemoryStore()
|
||||||
.serverNameOrHomeserverUrl(url)
|
.serverNameOrHomeserverUrl(url)
|
||||||
|
.addRootCertificates(userCertificatesProvider.provides())
|
||||||
.build()
|
.build()
|
||||||
.use {
|
.use {
|
||||||
it.homeserverLoginDetails()
|
it.homeserverLoginDetails()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.certificates
|
||||||
|
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.KeyStoreException
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultUserCertificatesProvider : UserCertificatesProvider {
|
||||||
|
/**
|
||||||
|
* Get additional user-installed certificates from the `AndroidCAStore` `Keystore`.
|
||||||
|
*
|
||||||
|
* The Rust HTTP client doesn't include user-installed certificates in its internal certificate
|
||||||
|
* store. This means that whatever the user installs will be ignored.
|
||||||
|
*
|
||||||
|
* While most users don't need user-installed certificates some special deployments or debugging
|
||||||
|
* setups using a proxy might want to use them.
|
||||||
|
*
|
||||||
|
* @return A list of byte arrays where each byte array is a single user-installed certificate
|
||||||
|
* in encoded form.
|
||||||
|
*/
|
||||||
|
override fun provides(): List<ByteArray> {
|
||||||
|
// At least for API 34 the `AndroidCAStore` `Keystore` type contained user certificates as well.
|
||||||
|
// I have not found this to be documented anywhere.
|
||||||
|
val keyStore: KeyStore = try {
|
||||||
|
KeyStore.getInstance("AndroidCAStore")
|
||||||
|
} catch (e: KeyStoreException) {
|
||||||
|
Timber.w(e, "Failed to get AndroidCAStore keystore")
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
val aliases = try {
|
||||||
|
keyStore.load(null)
|
||||||
|
keyStore.aliases()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.w(e, "Failed to load and get aliases AndroidCAStore keystore")
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
return aliases.toList()
|
||||||
|
.filter { alias ->
|
||||||
|
// The certificate alias always contains the prefix `system` or
|
||||||
|
// `user` and the MD5 subject hash separated by a colon.
|
||||||
|
//
|
||||||
|
// The subject hash can be calculated using openssl as such:
|
||||||
|
// openssl x509 -subject_hash_old -noout -in mycert.cer
|
||||||
|
//
|
||||||
|
// Again, I have not found this to be documented somewhere.
|
||||||
|
alias.startsWith("user")
|
||||||
|
}
|
||||||
|
.mapNotNull { alias ->
|
||||||
|
try {
|
||||||
|
keyStore.getEntry(alias, null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.w(e, "Failed to get entry for alias $alias")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filterIsInstance<KeyStore.TrustedCertificateEntry>()
|
||||||
|
.map { trustedCertificateEntry ->
|
||||||
|
trustedCertificateEntry.trustedCertificate.encoded
|
||||||
|
}
|
||||||
|
.also {
|
||||||
|
// Let's at least log the number of user-installed certificates we found,
|
||||||
|
// since the alias isn't particularly useful nor does the issuer seem to
|
||||||
|
// be easily available.
|
||||||
|
Timber.i("Found ${it.size} additional user-provided certificates.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.certificates
|
||||||
|
|
||||||
|
interface UserCertificatesProvider {
|
||||||
|
fun provides(): List<ByteArray>
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
|
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
|
||||||
|
import io.element.android.libraries.matrix.impl.auth.FakeUserCertificatesProvider
|
||||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
|
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
|
||||||
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
|
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
|
||||||
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
|
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
|
||||||
|
|
@ -57,6 +58,7 @@ fun TestScope.createRustMatrixClientFactory(
|
||||||
coroutineDispatchers = testCoroutineDispatchers(),
|
coroutineDispatchers = testCoroutineDispatchers(),
|
||||||
sessionStore = sessionStore,
|
sessionStore = sessionStore,
|
||||||
userAgentProvider = SimpleUserAgentProvider(),
|
userAgentProvider = SimpleUserAgentProvider(),
|
||||||
|
userCertificatesProvider = FakeUserCertificatesProvider(),
|
||||||
proxyProvider = FakeProxyProvider(),
|
proxyProvider = FakeProxyProvider(),
|
||||||
clock = FakeSystemClock(),
|
clock = FakeSystemClock(),
|
||||||
analyticsService = FakeAnalyticsService(),
|
analyticsService = FakeAnalyticsService(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.auth
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider
|
||||||
|
|
||||||
|
class FakeUserCertificatesProvider : UserCertificatesProvider {
|
||||||
|
override fun provides(): List<ByteArray> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,5 +49,6 @@ class RustHomeserverLoginCompatibilityCheckerTest {
|
||||||
FakeFfiClient(homeserverLoginDetailsResult = result)
|
FakeFfiClient(homeserverLoginDetailsResult = result)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
userCertificatesProvider = FakeUserCertificatesProvider(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue