diff --git a/changelog.d/2992.feature b/changelog.d/2992.feature new file mode 100644 index 0000000000..622f6fdc69 --- /dev/null +++ b/changelog.d/2992.feature @@ -0,0 +1 @@ +Allow user-installed certificates to be used by the HTTP client 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 0f1c47445b..3d428afc0b 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 @@ -27,7 +27,9 @@ 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( @@ -46,6 +48,7 @@ class RustMatrixClientFactory @Inject constructor( .username(sessionData.userId) .passphrase(sessionData.passphrase) .userAgent(userAgentProvider.provide()) + .addRootCertificates(getAdditionalCertificates()) // 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() } @@ -68,6 +71,57 @@ 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 c27ace88f6..e24993417a 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 @@ -30,6 +30,7 @@ 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.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 @@ -61,11 +62,12 @@ class RustMatrixAuthenticationService @Inject constructor( // 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 = emptyList(), + additionalRootCertificates = additionalCertificates, oidcConfiguration = oidcConfiguration, customSlidingSyncProxy = null, sessionDelegate = null,