Merge pull request #2430 from element-hq/feature/bma/userCertificates
Pass user certificates to the Rust SDK
This commit is contained in:
commit
550fb725e2
7 changed files with 140 additions and 1 deletions
1
changelog.d/2992.feature
Normal file
1
changelog.d/2992.feature
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow user-installed certificates to be used by the HTTP client
|
||||
|
|
@ -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
|
||||
|
|
@ -37,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) {
|
||||
|
|
@ -46,6 +48,7 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
.username(sessionData.userId)
|
||||
.passphrase(sessionData.passphrase)
|
||||
.userAgent(userAgentProvider.provide())
|
||||
.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() }
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ 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.keys.PassphraseGenerator
|
||||
import io.element.android.libraries.matrix.impl.mapper.toSessionData
|
||||
|
|
@ -56,6 +57,7 @@ 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
|
||||
|
|
@ -65,7 +67,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
basePath = baseDirectory.absolutePath,
|
||||
passphrase = pendingPassphrase,
|
||||
userAgent = userAgentProvider.provide(),
|
||||
additionalRootCertificates = emptyList(),
|
||||
additionalRootCertificates = userCertificatesProvider.provides(),
|
||||
oidcConfiguration = oidcConfiguration,
|
||||
customSlidingSyncProxy = null,
|
||||
sessionDelegate = null,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 java.security.KeyStoreException
|
||||
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<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,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<ByteArray>
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ByteArray> = emptyList()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue