Encrypt databases used by the Rust SDK.
The passphrase is stored in the SessionData, so that a Session created by Element Android can be restored. Existing sessions will have a null passphrase and will continue to work. New session will use a passphrase, only on Nightly and Debug build for now.
This commit is contained in:
1
changelog.d/2219.misc
Normal file
1
changelog.d/2219.misc
Normal file
@@ -0,0 +1 @@
|
||||
Encrypt databases used by the Rust SDK on Nightly and Debug builds.
|
||||
@@ -144,6 +144,7 @@ class RustMatrixClient(
|
||||
val newData = client.session().toSessionData(
|
||||
isTokenValid = false,
|
||||
loginType = existingData.loginType,
|
||||
passphrase = existingData.passphrase,
|
||||
)
|
||||
sessionStore.updateData(newData)
|
||||
}
|
||||
@@ -161,6 +162,7 @@ class RustMatrixClient(
|
||||
val newData = client.session().toSessionData(
|
||||
isTokenValid = existingData.isTokenValid,
|
||||
loginType = existingData.loginType,
|
||||
passphrase = existingData.passphrase,
|
||||
)
|
||||
sessionStore.updateData(newData)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ class RustMatrixClientFactory @Inject constructor(
|
||||
.basePath(baseDirectory.absolutePath)
|
||||
.homeserverUrl(sessionData.homeserverUrl)
|
||||
.username(sessionData.userId)
|
||||
.passphrase(sessionData.passphrase)
|
||||
.userAgent(userAgentProvider.provide())
|
||||
// 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"))
|
||||
|
||||
@@ -19,6 +19,8 @@ package io.element.android.libraries.matrix.impl.auth
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
@@ -28,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.keys.PassphraseGenerator
|
||||
import io.element.android.libraries.matrix.impl.mapper.toSessionData
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
import io.element.android.libraries.sessionstorage.api.LoggedInState
|
||||
@@ -39,6 +42,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.OidcAuthenticationData
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthenticationService
|
||||
@@ -51,10 +55,15 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
private val sessionStore: SessionStore,
|
||||
userAgentProvider: UserAgentProvider,
|
||||
private val rustMatrixClientFactory: RustMatrixClientFactory,
|
||||
private val passphraseGenerator: PassphraseGenerator,
|
||||
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 authService: RustAuthenticationService = RustAuthenticationService(
|
||||
basePath = baseDirectory.absolutePath,
|
||||
passphrase = null,
|
||||
passphrase = pendingPassphrase,
|
||||
userAgent = userAgentProvider.provide(),
|
||||
oidcConfiguration = oidcConfiguration,
|
||||
customSlidingSyncProxy = null,
|
||||
@@ -76,6 +85,12 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
val sessionData = sessionStore.getSession(sessionId.value)
|
||||
if (sessionData != null) {
|
||||
if (sessionData.isTokenValid) {
|
||||
// Use the sessionData.passphrase, which can be null for a previously created session
|
||||
if (sessionData.passphrase == null) {
|
||||
Timber.w("Restoring a session without a passphrase")
|
||||
} else {
|
||||
Timber.w("Restoring a session with a passphrase")
|
||||
}
|
||||
rustMatrixClientFactory.create(sessionData)
|
||||
} else {
|
||||
error("Token is not valid")
|
||||
@@ -88,6 +103,21 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDatabasePassphrase(): String? {
|
||||
// TODO Remove this if block at some point
|
||||
// Return a passphrase only for debug and nightly build for now
|
||||
if (buildMeta.buildType == BuildType.RELEASE) {
|
||||
Timber.w("New sessions will not be encrypted with a passphrase (release build)")
|
||||
return null
|
||||
}
|
||||
|
||||
val passphrase = passphraseGenerator.generatePassphrase()
|
||||
if (passphrase != null) {
|
||||
Timber.w("New sessions will be encrypted with a passphrase")
|
||||
}
|
||||
return passphrase
|
||||
}
|
||||
|
||||
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> = currentHomeserver
|
||||
|
||||
override suspend fun setHomeserver(homeserver: String): Result<Unit> =
|
||||
@@ -111,6 +141,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
it.session().toSessionData(
|
||||
isTokenValid = true,
|
||||
loginType = LoginType.PASSWORD,
|
||||
passphrase = pendingPassphrase,
|
||||
)
|
||||
}
|
||||
sessionStore.storeData(sessionData)
|
||||
@@ -158,6 +189,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
it.session().toSessionData(
|
||||
isTokenValid = true,
|
||||
loginType = LoginType.OIDC,
|
||||
passphrase = pendingPassphrase
|
||||
)
|
||||
}
|
||||
pendingOidcAuthenticationData?.close()
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.keys
|
||||
|
||||
import android.util.Base64
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import java.security.SecureRandom
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val SECRET_SIZE = 256
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPassphraseGenerator @Inject constructor() : PassphraseGenerator {
|
||||
override fun generatePassphrase(): String? {
|
||||
val key = ByteArray(size = SECRET_SIZE)
|
||||
SecureRandom().nextBytes(key)
|
||||
return Base64.encodeToString(key, Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.keys
|
||||
|
||||
interface PassphraseGenerator {
|
||||
/**
|
||||
* Generate a passphrase to encrypt the databases of a session.
|
||||
* Return null to not encrypt the databases.
|
||||
*/
|
||||
fun generatePassphrase(): String?
|
||||
}
|
||||
@@ -55,7 +55,9 @@ class MainActivity : ComponentActivity() {
|
||||
sessionStore = sessionStore,
|
||||
userAgentProvider = userAgentProvider,
|
||||
clock = DefaultSystemClock(),
|
||||
)
|
||||
),
|
||||
passphraseGenerator = NullPassphraseGenerator(),
|
||||
buildMeta = Singleton.buildMeta,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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.keys.PassphraseGenerator
|
||||
|
||||
class NullPassphraseGenerator : PassphraseGenerator {
|
||||
override fun generatePassphrase(): String? = null
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.plus
|
||||
|
||||
object Singleton {
|
||||
private val buildMeta = BuildMeta(
|
||||
val buildMeta = BuildMeta(
|
||||
isDebuggable = true,
|
||||
buildType = BuildType.DEBUG,
|
||||
applicationName = "EAX-Minimal",
|
||||
|
||||
Reference in New Issue
Block a user