From 237235c17e6256b4a3ad7b41900e09c53a39160a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 14 Feb 2024 10:16:01 +0100 Subject: [PATCH 1/2] Add the user certificates as additional certificates to the ClientBuilder Now, this is a story all about how Certificates work in Android town And I'd like to take a minute Enter, close the door I'll tell you how I've figured out the inner workings of the Keystore Well it all boils down the fact that Google got scared It said, "You're certs are movin' to a place you won't find". So the directory, user certificates are stored, is hard to find, and possibly not readable by your application[1]. Instead, we need to use the Keystore[2] API, specifically we'll need to open the `AndroidCAStore` Keystore type. The various Keystore types are supposedly documented[3], but I'm failing to find a logical path that would lead you to conclude that: a) System certificates can or should be accessed using the Keystore, specifically the AndroidCAStore type b) User certificates can be found in the same Keystore type as the system certificates So this was mostly found using random googling, swearing, and a couple of educated guesses. [1]: https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html [2]: https://developer.android.com/reference/java/security/KeyStore [3]: https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#keystore-types --- .../matrix/impl/RustMatrixClientFactory.kt | 54 +++++++++++++++++++ .../auth/RustMatrixAuthenticationService.kt | 4 +- 2 files changed, 57 insertions(+), 1 deletion(-) 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, From eab9ffb6d05ff3c58f774de6f9b60f5898f5de24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 14 Feb 2024 14:40:40 +0100 Subject: [PATCH 2/2] Add a changelog fragment for the user-installed certs feature --- changelog.d/2992.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/2992.feature 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