From fd555b1070f4f5a561db89b662583959d5c3db7f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 22 Feb 2024 11:02:26 +0100 Subject: [PATCH] Move getAdditionalCertificates function to a dedicated class (no change in the implementation). --- .../matrix/impl/RustMatrixClientFactory.kt | 57 +------------- .../auth/RustMatrixAuthenticationService.kt | 6 +- .../DefaultUserCertificatesProvider.kt | 77 +++++++++++++++++++ .../certificates/UserCertificatesProvider.kt | 21 +++++ .../android/samples/minimal/MainActivity.kt | 3 + .../minimal/NoOpUserCertificatesProvider.kt | 23 ++++++ 6 files changed, 130 insertions(+), 57 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/DefaultUserCertificatesProvider.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/UserCertificatesProvider.kt create mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpUserCertificatesProvider.kt diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 3d428afc0b..96670d2a2d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.CacheDirectory +import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionStore @@ -27,9 +28,7 @@ import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.Session import org.matrix.rustcomponents.sdk.use -import timber.log.Timber import java.io.File -import java.security.KeyStore import javax.inject.Inject class RustMatrixClientFactory @Inject constructor( @@ -39,6 +38,7 @@ class RustMatrixClientFactory @Inject constructor( private val coroutineDispatchers: CoroutineDispatchers, private val sessionStore: SessionStore, private val userAgentProvider: UserAgentProvider, + private val userCertificatesProvider: UserCertificatesProvider, private val clock: SystemClock, ) { suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) { @@ -48,7 +48,7 @@ class RustMatrixClientFactory @Inject constructor( .username(sessionData.userId) .passphrase(sessionData.passphrase) .userAgent(userAgentProvider.provide()) - .addRootCertificates(getAdditionalCertificates()) + .addRootCertificates(userCertificatesProvider.provides()) // FIXME Quick and dirty fix for stopping version requests on startup https://github.com/matrix-org/matrix-rust-sdk/pull/1376 .serverVersions(listOf("v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5")) .use { it.build() } @@ -71,57 +71,6 @@ class RustMatrixClientFactory @Inject constructor( } } -/** -* 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. -*/ -fun getAdditionalCertificates(): List { - val certs = mutableListOf() - - // 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 = KeyStore.getInstance("AndroidCAStore").apply { - load(null) - } - - val aliases = keyStore.aliases() - - while (aliases.hasMoreElements()) { - val alias = aliases.nextElement() - val entry = keyStore.getEntry(alias, null) - - if (entry is KeyStore.TrustedCertificateEntry) { - // 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. - if (alias.startsWith("user")) { - certs.add(entry.trustedCertificate.encoded) - } - } - } - - // 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. - val certCount = certs.count() - - Timber.i("Found $certCount additional user-provided certificates.") - - return certs -} - private fun SessionData.toSession() = Session( accessToken = accessToken, refreshToken = refreshToken, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index e24993417a..afedc889da 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -29,8 +29,8 @@ import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.impl.RustMatrixClientFactory +import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider import io.element.android.libraries.matrix.impl.exception.mapClientException -import io.element.android.libraries.matrix.impl.getAdditionalCertificates import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.network.useragent.UserAgentProvider @@ -57,17 +57,17 @@ class RustMatrixAuthenticationService @Inject constructor( userAgentProvider: UserAgentProvider, private val rustMatrixClientFactory: RustMatrixClientFactory, private val passphraseGenerator: PassphraseGenerator, + userCertificatesProvider: UserCertificatesProvider, private val buildMeta: BuildMeta, ) : MatrixAuthenticationService { // Passphrase which will be used for new sessions. Existing sessions will use the passphrase // stored in the SessionData. private val pendingPassphrase = getDatabasePassphrase() - private val additionalCertificates = getAdditionalCertificates() private val authService: RustAuthenticationService = RustAuthenticationService( basePath = baseDirectory.absolutePath, passphrase = pendingPassphrase, userAgent = userAgentProvider.provide(), - additionalRootCertificates = additionalCertificates, + additionalRootCertificates = userCertificatesProvider.provides(), oidcConfiguration = oidcConfiguration, customSlidingSyncProxy = null, sessionDelegate = null, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/DefaultUserCertificatesProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/DefaultUserCertificatesProvider.kt new file mode 100644 index 0000000000..fdccde1cd0 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/DefaultUserCertificatesProvider.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.certificates + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import timber.log.Timber +import java.security.KeyStore +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultUserCertificatesProvider @Inject constructor() : 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 { + val certs = mutableListOf() + + // 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 = KeyStore.getInstance("AndroidCAStore").apply { + load(null) + } + + val aliases = keyStore.aliases() + + while (aliases.hasMoreElements()) { + val alias = aliases.nextElement() + val entry = keyStore.getEntry(alias, null) + + if (entry is KeyStore.TrustedCertificateEntry) { + // 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. + if (alias.startsWith("user")) { + certs.add(entry.trustedCertificate.encoded) + } + } + } + + // 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. + val certCount = certs.count() + + Timber.i("Found $certCount additional user-provided certificates.") + + return certs + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/UserCertificatesProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/UserCertificatesProvider.kt new file mode 100644 index 0000000000..330e29ee47 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/UserCertificatesProvider.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.certificates + +interface UserCertificatesProvider { + fun provides(): List +} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt index 30f5d2afe9..e87e0057d0 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt @@ -42,6 +42,7 @@ class MainActivity : ComponentActivity() { val baseDirectory = File(applicationContext.filesDir, "sessions") val userAgentProvider = SimpleUserAgentProvider("MinimalSample") val sessionStore = InMemorySessionStore() + val userCertificatesProvider = NoOpUserCertificatesProvider() RustMatrixAuthenticationService( baseDirectory = baseDirectory, coroutineDispatchers = Singleton.coroutineDispatchers, @@ -54,10 +55,12 @@ class MainActivity : ComponentActivity() { coroutineDispatchers = Singleton.coroutineDispatchers, sessionStore = sessionStore, userAgentProvider = userAgentProvider, + userCertificatesProvider = userCertificatesProvider, clock = DefaultSystemClock(), ), passphraseGenerator = NullPassphraseGenerator(), buildMeta = Singleton.buildMeta, + userCertificatesProvider = userCertificatesProvider, ) } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpUserCertificatesProvider.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpUserCertificatesProvider.kt new file mode 100644 index 0000000000..a34fb4dbe0 --- /dev/null +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpUserCertificatesProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.samples.minimal + +import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider + +class NoOpUserCertificatesProvider : UserCertificatesProvider { + override fun provides(): List = emptyList() +}