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
This commit is contained in:
parent
598bf96208
commit
73ba371a3e
2 changed files with 57 additions and 1 deletions
|
|
@ -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<ByteArray> {
|
||||
val certs = mutableListOf<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 = 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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue