Merge sql crypto store implementation

This commit is contained in:
ganfra 2020-04-23 18:58:07 +02:00
commit 9a4cad1e45
24 changed files with 1787 additions and 163 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@
/tmp
ktlint
.idea

View File

@ -2,52 +2,40 @@ Changes in RiotX 0.19.0 (2020-XX-XX)
===================================================
Features ✨:
- Change password (#528)
- Cross-Signing | Support SSSS secret sharing (#944)
- Cross-Signing | Verify new session from existing session (#1134)
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
- Save media files to Gallery (#973)
Improvements 🙌:
- Verification DM / Handle concurrent .start after .ready (#794)
- Reimplementation of multiple attachment picker
- Cross-Signing | Update Shield Logic for DM (#963)
- Cross-Signing | Complete security new session design update (#1135)
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
- Cross-Signing | Gossip key backup recovery key (#1200)
- Show room encryption status as a bubble tile (#1078)
- UX/UI | Add indicator to home tab on invite (#957)
- Cross-Signing | Restore history after recover from passphrase (#1214)
- Cross-Sign | QR code scan confirmation screens design update (#1187)
- Emoji Verification | It's not the same butterfly! (#1220)
- Cross-Signing | Composer decoration: shields (#1077)
- Cross-Signing | Migrate existing keybackup to cross signing with 4S from mobile (#1197)
Bugfix 🐛:
- Fix summary notification staying after "mark as read"
- Missing avatar/displayname after verification request message (#841)
- Crypto | RiotX sometimes rotate the current device keys (#1170)
- RiotX can't restore cross signing keys saved by web in SSSS (#1174)
- Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191)
- Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925)
- Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210)
- Fix crash when trying to download file without internet connection (#1229)
- Local echo are not updated in timeline (for failed & encrypted states)
- Render image event even if thumbnail_info does not have mimetype defined (#1209)
- RiotX now uses as many threads as it needs to do work and send messages (#1221)
- Fix issue with media path (#1227)
Translations 🗣:
-
SDK API changes ⚠️:
- Increase targetSdkVersion to 29
- Implementation of SqlCryptoStore on top of SQLDelight
Build 🧱:
- Compile with Android SDK 29 (Android Q)
-
Other changes:
- Add a setting to prevent screenshots of the application, disabled by default (#1027)
- Increase File Logger capacities ( + use dev log preferences)
Changes in RiotX 0.18.1 (2020-03-17)
@ -456,7 +444,6 @@ Bugfix:
- Fix messages with empty `in_reply_to` not rendering (#447)
- Fix clear cache (#408) and Logout (#205)
- Fix `(edited)` link can be copied to clipboard (#402)
- KeyBackup / SSSS | Should get the key from SSSS instead of asking recovery Key (#1163)
Build:
- Split APK: generate one APK per arch, to reduce APK size of about 30%

View File

@ -71,6 +71,10 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
packagingOptions {
pickFirst 'META-INF/kotlinx-coroutines-core.kotlin_module'
}
}
static def gitRevision() {

View File

@ -18,14 +18,18 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.TestConstants
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
import org.amshove.kluent.shouldBe
import org.junit.Assert.assertEquals
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@ -229,4 +233,46 @@ class VerificationTest : InstrumentedTest {
cryptoTestData.cleanUp(mTestHelper)
}
@Test
fun test_alice_sends_text_message_and_bob_can_decrypt() {
val lock = CountDownLatch(1)
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val roomFromBobPOV = bobSession.getRoom(cryptoTestData.roomId)!!
aliceSession.getRoom(cryptoTestData.roomId)!!.sendTextMessage("test")
val bobEventsListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
// noop
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
if (snapshot.isNotEmpty() && snapshot.first().root.isEncrypted()) {
lock.countDown()
}
}
}
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
bobTimeline.start()
bobTimeline.addListener(bobEventsListener)
mTestHelper.await(lock)
bobTimeline.getTimelineEventAtIndex(0)?.root?.let {
val decryptionResult = bobSession.cryptoService().decryptEvent(it, "")
val text = (decryptionResult.clearEvent["content"] as Map<*, *>)["body"]
assertEquals("test", text)
}
}
}

View File

@ -16,9 +16,15 @@
package im.vector.matrix.android.internal.crypto
import android.content.Context
import android.util.Log
import com.squareup.sqldelight.ColumnAdapter
import com.squareup.sqldelight.android.AndroidSqliteDriver
import com.squareup.sqldelight.logs.LogSqliteDriver
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.internal.crypto.api.CryptoApi
@ -55,9 +61,9 @@ import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessio
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
import im.vector.matrix.android.internal.crypto.store.db.SqlCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask
@ -87,12 +93,13 @@ import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.SessionFilesDirectory
import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
import im.vector.matrix.sqldelight.crypto.CrossSigningInfoEntity
import im.vector.matrix.sqldelight.crypto.CryptoDatabase
import io.realm.RealmConfiguration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
@ -108,7 +115,7 @@ internal abstract class CryptoModule {
@JvmStatic
@Provides
@CryptoDatabase
@im.vector.matrix.android.internal.di.CryptoDatabase
@SessionScope
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String,
@ -125,6 +132,31 @@ internal abstract class CryptoModule {
.build()
}
@JvmStatic
@Provides
@SessionScope
fun providesCryptoDatabase(context: Context, @SessionFilesDirectory directory: File): CryptoDatabase {
val name = "${directory.name}-matrix-sdk-crypto.db"
val driver = if (BuildConfig.DEBUG) {
LogSqliteDriver(AndroidSqliteDriver(CryptoDatabase.Schema, context, name)) { log ->
Log.d("SQLite", log)
}
} else {
AndroidSqliteDriver(CryptoDatabase.Schema, context, name)
}
val listOfStringAdapter = object : ColumnAdapter<List<String>, String> {
override fun decode(databaseValue: String) = databaseValue.split(",")
override fun encode(value: List<String>) = value.joinToString(separator = ",")
}
return CryptoDatabase(
driver = driver,
crossSigningInfoEntityAdapter = CrossSigningInfoEntity.Adapter(
usagesAdapter = listOfStringAdapter
)
)
}
@JvmStatic
@Provides
@SessionScope
@ -134,8 +166,8 @@ internal abstract class CryptoModule {
@JvmStatic
@Provides
@CryptoDatabase
fun providesClearCacheTask(@CryptoDatabase
@im.vector.matrix.android.internal.di.CryptoDatabase
fun providesClearCacheTask(@im.vector.matrix.android.internal.di.CryptoDatabase
realmConfiguration: RealmConfiguration): ClearCacheTask {
return RealmClearCacheTask(realmConfiguration)
}
@ -243,7 +275,7 @@ internal abstract class CryptoModule {
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
@Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
abstract fun bindCryptoStore(store: SqlCryptoStore): IMXCryptoStore
@Binds
abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask

View File

@ -69,7 +69,6 @@ import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.extensions.foldToCallback
@ -1154,11 +1153,11 @@ internal class DefaultKeysBackupService @Inject constructor(
* Update the DB with data fetch from the server
*/
private fun onServerDataRetrieved(count: Int?, hash: String?) {
cryptoStore.setKeysBackupData(KeysBackupDataEntity()
.apply {
backupLastServerNumberOfKeys = count
backupLastServerHash = hash
}
cryptoStore.setKeysBackupData(
im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData(
backupLastServerNumberOfKeys = count,
backupLastServerHash = hash
)
)
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (c) 2020 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 im.vector.matrix.android.internal.crypto.model.rest
internal data class KeysBackupData(val backupLastServerHash: String?, val backupLastServerNumberOfKeys: Int?)

View File

@ -32,8 +32,8 @@ import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.olm.OlmAccount
/**
@ -104,14 +104,14 @@ internal interface IMXCryptoStore {
/**
* Get the current keys backup local data
*/
fun getKeysBackupData(): KeysBackupDataEntity?
fun getKeysBackupData(): KeysBackupData?
/**
* Set the keys backup local data
*
* @param keysBackupData the keys backup local data, or null to erase data
*/
fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?)
fun setKeysBackupData(keysBackupData: KeysBackupData?)
/**
* @return the devices statuses map (userId -> tracking status)

View File

@ -41,6 +41,7 @@ import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.model.toEntity
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@ -705,13 +706,18 @@ internal class RealmCryptoStore @Inject constructor(
}
}
override fun getKeysBackupData(): KeysBackupDataEntity? {
override fun getKeysBackupData(): KeysBackupData? {
return doRealmQueryAndCopy(realmConfiguration) {
it.where<KeysBackupDataEntity>().findFirst()
}?.let { entity ->
return KeysBackupData(
backupLastServerHash = entity.backupLastServerHash,
backupLastServerNumberOfKeys = entity.backupLastServerNumberOfKeys
)
}
}
override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) {
override fun setKeysBackupData(keysBackupData: KeysBackupData?) {
doRealmTransaction(realmConfiguration) {
if (keysBackupData == null) {
// Clear the table
@ -720,7 +726,10 @@ internal class RealmCryptoStore @Inject constructor(
.deleteAllFromRealm()
} else {
// Only one object
it.copyToRealmOrUpdate(keysBackupData)
it.copyToRealmOrUpdate(it.createObject(KeysBackupDataEntity::class.java).apply {
backupLastServerHash = keysBackupData.backupLastServerHash
backupLastServerNumberOfKeys = keysBackupData.backupLastServerNumberOfKeys
})
}
}
}

View File

@ -0,0 +1,242 @@
/*
* Copyright (c) 2020 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 im.vector.matrix.android.internal.crypto.store.db
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import im.vector.matrix.android.api.extensions.tryThis
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.GossipRequestType
import im.vector.matrix.android.internal.crypto.GossipingRequestState
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon
import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequest
import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.SerializeNulls
import im.vector.matrix.sqldelight.crypto.CrossSigningInfoEntity
import im.vector.matrix.sqldelight.crypto.DeviceInfoEntity
import im.vector.matrix.sqldelight.crypto.GetByUserId
import im.vector.matrix.sqldelight.crypto.IncomingGossipingRequestEntity
import im.vector.matrix.sqldelight.crypto.OutgoingGossipingRequestEntity
import timber.log.Timber
object SqliteCryptoMapper {
private val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
private val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
List::class.java,
String::class.java,
Any::class.java
))
private val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
))
private val mapOfStringMigrationAdapter = moshi.adapter<Map<String, Map<String, String>>>(Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
))
private val recipientsDataMapper: JsonAdapter<Map<String, List<String>>> =
MoshiProvider
.providesMoshi()
.adapter<Map<String, List<String>>>(
Types.newParameterizedType(Map::class.java, String::class.java, List::class.java)
)
internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity {
return DeviceInfoEntity.Impl(
user_id = deviceInfo.userId,
device_id = deviceInfo.deviceId,
identity_key = deviceInfo.identityKey(),
algorithm_list_json = listMigrationAdapter.toJson(deviceInfo.algorithms),
keys_map_json = mapMigrationAdapter.toJson(deviceInfo.keys),
signature_map_json = mapMigrationAdapter.toJson(deviceInfo.signatures),
is_blocked = deviceInfo.isBlocked,
locally_verified = deviceInfo.trustLevel?.locallyVerified == true,
cross_signed_verified = deviceInfo.trustLevel?.crossSigningVerified == true,
unsigned_map_json = mapMigrationAdapter.toJson(deviceInfo.unsigned)
)
}
internal fun mapToModel(deviceInfoEntity: DeviceInfoEntity): CryptoDeviceInfo {
return CryptoDeviceInfo(
deviceId = deviceInfoEntity.device_id,
userId = deviceInfoEntity.user_id,
algorithms = deviceInfoEntity.algorithm_list_json?.let {
try {
listMigrationAdapter.fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
keys = deviceInfoEntity.keys_map_json?.let {
try {
moshi.adapter<Map<String, String>>(Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)).fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
signatures = deviceInfoEntity.signature_map_json?.let {
try {
mapOfStringMigrationAdapter.fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
unsigned = deviceInfoEntity.unsigned_map_json?.let {
try {
mapMigrationAdapter.fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
trustLevel = DeviceTrustLevel(deviceInfoEntity.cross_signed_verified, deviceInfoEntity.locally_verified),
isBlocked = deviceInfoEntity.is_blocked
)
}
internal fun mapToModel(crossSigningInfoEntity: GetByUserId): CryptoCrossSigningKey {
val pubKey = crossSigningInfoEntity.public_key_base64 ?: ""
return CryptoCrossSigningKey(
userId = crossSigningInfoEntity.user_id,
signatures = crossSigningInfoEntity.signatures?.let { deserializeSignaturesFromSqlite(it) },
trustLevel = DeviceTrustLevel(
crossSigningInfoEntity.cross_signed_verified,
crossSigningInfoEntity.locally_verified
),
keys = mapOf("ed25519:$pubKey" to pubKey),
usages = crossSigningInfoEntity.usages
)
}
internal fun mapToEntity(cryptoCrossSigningKey: CryptoCrossSigningKey): CrossSigningInfoEntity {
return CrossSigningInfoEntity.Impl(
user_id = cryptoCrossSigningKey.userId,
signatures = serializeSignaturesForSqlite(cryptoCrossSigningKey.signatures),
public_key_base64 = cryptoCrossSigningKey.unpaddedBase64PublicKey,
usages = cryptoCrossSigningKey.usages ?: emptyList(),
locally_verified = false,
cross_signed_verified = false
)
}
fun serializeSignaturesForSqlite(signatures: Map<String, Map<String, String>>?): String {
return mapMigrationAdapter.toJson(signatures)
}
fun deserializeSignaturesFromSqlite(signatures: String):Map<String, Map<String, String>>? {
return try {
mapOfStringMigrationAdapter.fromJson(signatures)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
}
fun toOutgoingGossipingRequest(entity: OutgoingGossipingRequestEntity): OutgoingGossipingRequest {
return when (entity.type) {
GossipRequestType.KEY.name -> {
OutgoingRoomKeyRequest(
requestBody = getRequestedKeyInfo(entity.type, entity.requested_info),
recipients = getRecipientsMap(entity) ?: emptyMap(),
requestId = entity.request_id,
state = tryThis { OutgoingGossipingRequestState.valueOf(entity.requested_info!!) }
?: OutgoingGossipingRequestState.UNSENT
)
}
GossipRequestType.SECRET.name -> {
OutgoingSecretRequest(
secretName = getRequestedSecretName(entity.type, entity.requested_info),
recipients = getRecipientsMap(entity) ?: emptyMap(),
requestId = entity.request_id,
state = tryThis { OutgoingGossipingRequestState.valueOf(entity.requested_info!!) }
?: OutgoingGossipingRequestState.UNSENT
)
}
else -> OutgoingRoomKeyRequest(
requestBody = getRequestedKeyInfo(entity.type, entity.requested_info),
recipients = getRecipientsMap(entity) ?: emptyMap(),
requestId = entity.request_id,
state = OutgoingGossipingRequestState.UNSENT
)
}
}
fun toIncomingGossipingRequest(entity: IncomingGossipingRequestEntity): IncomingShareRequestCommon {
return when (GossipRequestType.valueOf(entity.type)) {
GossipRequestType.KEY -> {
IncomingRoomKeyRequest(
requestBody = getRequestedKeyInfo(entity.type, entity.requested_info_str),
deviceId = entity.other_device_id,
userId = entity.other_user_id,
requestId = entity.request_id,
state = entity.request_state?.let { GossipingRequestState.valueOf(it) }
?: GossipingRequestState.NONE,
localCreationTimestamp = entity.local_creation_timestamp
)
}
GossipRequestType.SECRET -> {
IncomingSecretShareRequest(
secretName = getRequestedSecretName(entity.type, entity.requested_info_str),
deviceId = entity.other_device_id,
userId = entity.other_user_id,
requestId = entity.request_id,
localCreationTimestamp = entity.local_creation_timestamp
)
}
}
}
private fun getRecipientsMap(entity: OutgoingGossipingRequestEntity): Map<String, List<String>>? {
return entity.recipients_data?.let { recipientsDataMapper.fromJson(it) }
}
fun getRecipientsData(recipients: Map<String, List<String>>): String? {
return recipientsDataMapper.toJson(recipients)
}
fun getRequestedKeyInfo(type: String, requestedInfoStr: String?): RoomKeyRequestBody? {
return if (type == GossipRequestType.KEY.name) {
RoomKeyRequestBody.fromJson(requestedInfoStr)
} else null
}
fun getRequestedSecretName(type: String, requestedInfoStr: String?): String? {
return if (type == GossipRequestType.SECRET.name) {
requestedInfoStr
} else null
}
}

View File

@ -0,0 +1,49 @@
package im.vector.matrix.android.internal.crypto.store.db
import android.util.Base64
import im.vector.matrix.android.internal.util.CompatUtil
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.util.zip.GZIPInputStream
/**
* Serialize any Serializable object, zip it and convert to Base64 String
*/
fun serializeForSqlite(o: Any?): String? {
if (o == null) {
return null
}
val baos = ByteArrayOutputStream()
val gzis = CompatUtil.createGzipOutputStream(baos)
val out = ObjectOutputStream(gzis)
out.writeObject(o)
out.close()
return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
}
/**
* Do the opposite of serializeForSqlite.
*/
fun <T> deserializeFromSqlite(string: String?): T? {
if (string == null) {
return null
}
val decodedB64 = Base64.decode(string.toByteArray(), Base64.DEFAULT)
val bais = ByteArrayInputStream(decodedB64)
val gzis = GZIPInputStream(bais)
val ois = ObjectInputStream(gzis)
@Suppress("UNCHECKED_CAST")
val result = ois.readObject() as T
ois.close()
return result
}

View File

@ -0,0 +1,82 @@
import kotlin.collections.List;
CREATE TABLE crossSigningInfoEntity (
user_id TEXT NOT NULL,
usages TEXT AS List<String> NOT NULL,
public_key_base64 TEXT,
signatures TEXT,
cross_signed_verified INTEGER AS Boolean DEFAULT 0 NOT NULL,
locally_verified INTEGER AS Boolean DEFAULT 0 NOT NULL,
PRIMARY KEY(user_id, usages)
);
insertOrUpdate:
INSERT OR REPLACE INTO crossSigningInfoEntity VALUES ?;
getAll:
SELECT * FROM crossSigningInfoEntity;
getByUserId:
SELECT rowid AS _id,
user_id,
usages,
public_key_base64,
signatures,
cross_signed_verified,
locally_verified
FROM crossSigningInfoEntity
WHERE user_id = :userId;
updateLocallyVerifiedWithRowId:
UPDATE crossSigningInfoEntity
SET locally_verified = :locallyVerified
WHERE rowid = :rowId;
updateLocallyVerified:
UPDATE crossSigningInfoEntity
SET locally_verified = :locallyVerified
WHERE user_id = :userId;
updateCrossSignedVerified:
UPDATE crossSigningInfoEntity
SET cross_signed_verified = :crossSignedVerified
WHERE user_id = :userId;
updateVerified:
UPDATE crossSigningInfoEntity
SET locally_verified = :locallyVerified,
cross_signed_verified = :crossSignedVerified
WHERE user_id = :userId;
updateSignatures:
UPDATE crossSigningInfoEntity
SET signatures = :signatures
WHERE user_id = :userId;
updateSignaturesWithRowId:
UPDATE crossSigningInfoEntity
SET signatures = :signatures
WHERE rowid = :rowId;
updateUsages:
UPDATE crossSigningInfoEntity
SET usages = :usages
WHERE user_id = :userId;
updateUsagesWithRowId:
UPDATE crossSigningInfoEntity
SET usages = :usages
WHERE rowid = :rowId;
clearOtherUserTrust:
UPDATE crossSigningInfoEntity
SET locally_verified = 0,
cross_signed_verified = 0
WHERE user_id != :userId;
deleteByUserId:
DELETE FROM crossSigningInfoEntity
WHERE user_id = :userId;
deleteAll:
DELETE FROM crossSigningInfoEntity;

View File

@ -0,0 +1,66 @@
import java.lang.Boolean;
CREATE TABLE cryptoMetadataEntity (
user_id TEXT PRIMARY KEY NOT NULL,
device_id TEXT,
olm_account_data TEXT,
device_sync_token TEXT,
global_blacklist_unverified_devices INTEGER AS Boolean DEFAULT 0 NOT NULL,
backup_version TEXT,
x_sign_master_private_key TEXT,
x_sign_user_private_key TEXT,
x_sign_self_signed_private_key TEXT,
key_backup_recovery_key TEXT,
key_backup_recovery_key_version TEXT
);
insertOrUpdate:
INSERT OR REPLACE INTO cryptoMetadataEntity VALUES ?;
getAll:
SELECT * FROM cryptoMetadataEntity;
getByUserId:
SELECT * FROM cryptoMetadataEntity
WHERE user_id = :userId;
count:
SELECT COUNT(*) FROM cryptoMetadataEntity;
updateOlmAccountData:
UPDATE cryptoMetadataEntity
SET olm_account_data = :olmAccountData;
updateGlobalBlacklistUnverifiedDevices:
UPDATE cryptoMetadataEntity
SET global_blacklist_unverified_devices = :globalBlacklistUnverifiedDevices;
updateKeyBackupVersion:
UPDATE cryptoMetadataEntity
SET backup_version = :backupVersion;
updateDeviceId:
UPDATE cryptoMetadataEntity
SET device_id = :deviceId;
updateKeys:
UPDATE cryptoMetadataEntity
SET x_sign_master_private_key = :xSignMasterPrivateKey,
x_sign_user_private_key = :xSignUserPrivateKey,
x_sign_self_signed_private_key = :xSignSelfSignedPrivateKey;
updateSelfSignedPrivateKey:
UPDATE cryptoMetadataEntity
SET x_sign_self_signed_private_key = :xSignSelfSignedPrivateKey;
updateUserPrivateKey:
UPDATE cryptoMetadataEntity
SET x_sign_user_private_key = :xSignUserPrivateKey;
updateKeyBackupRecoveryKey:
UPDATE cryptoMetadataEntity
SET key_backup_recovery_key = :keyBackupRecoveryKey,
key_backup_recovery_key_version = :keyBackupRecoveryKeyVersion;
deleteAll:
DELETE FROM cryptoMetadataEntity;

View File

@ -0,0 +1,50 @@
import java.lang.Boolean;
CREATE TABLE cryptoRoomEntity (
room_id TEXT NOT NULL PRIMARY KEY,
algorithm TEXT,
should_encrypt_for_invited_members INTEGER AS Boolean DEFAULT 0 NOT NULL,
blacklist_unverified_devices INTEGER AS Boolean DEFAULT 0 NOT NULL
);
insertOrUpdate:
INSERT OR REPLACE INTO cryptoRoomEntity VALUES ?;
getAll:
SELECT * FROM cryptoRoomEntity;
getById:
SELECT * FROM cryptoRoomEntity
WHERE room_id = :roomId;
getBlacklistUnverifiedDevices:
SELECT * FROM cryptoRoomEntity
WHERE blacklist_unverified_devices = :blacklistUnverifiedDevices;
getRoomAlgorithm:
SELECT algorithm
FROM cryptoRoomEntity
WHERE room_id = :roomId;
shouldEncryptForInvitedMembers:
SELECT should_encrypt_for_invited_members
FROM cryptoRoomEntity
WHERE room_id = :roomId;
updateShouldEncryptForInvitedMembers:
UPDATE cryptoRoomEntity
SET should_encrypt_for_invited_members = :shouldEncryptForInvitedMembers
WHERE room_id = :roomId;
updateBlacklistUnverifiedDevices:
UPDATE cryptoRoomEntity
SET blacklist_unverified_devices = :blacklistUnverifiedDevices
WHERE room_id = :roomId;
updateRoomAlgorithm:
UPDATE cryptoRoomEntity
SET algorithm = :algorithm
WHERE room_id = :roomId;
deleteAll:
DELETE FROM cryptoRoomEntity;

View File

@ -0,0 +1,23 @@
import java.lang.Integer;
CREATE TABLE cryptoUserEntity (
user_id TEXT NOT NULL PRIMARY KEY,
device_tracking_status INTEGER AS Integer DEFAULT 0 NOT NULL
);
insertOrUpdate:
INSERT OR REPLACE INTO cryptoUserEntity VALUES ?;
getAll:
SELECT * FROM cryptoUserEntity;
getByUserId:
SELECT * FROM cryptoUserEntity
WHERE user_id = :userId;
deleteByUserId:
DELETE FROM cryptoUserEntity
WHERE user_id = :userId;
deleteAll:
DELETE FROM cryptoUserEntity;

View File

@ -0,0 +1,56 @@
import java.lang.Boolean;
CREATE TABLE deviceInfoEntity (
user_id TEXT NOT NULL,
device_id TEXT NOT NULL,
identity_key TEXT,
is_blocked INTEGER AS Boolean DEFAULT 0 NOT NULL,
algorithm_list_json TEXT,
keys_map_json TEXT,
signature_map_json TEXT,
unsigned_map_json TEXT,
cross_signed_verified INTEGER AS Boolean DEFAULT 0 NOT NULL,
locally_verified INTEGER AS Boolean DEFAULT 0 NOT NULL,
PRIMARY KEY(user_id, device_id)
);
insertOrUpdate:
INSERT OR REPLACE INTO deviceInfoEntity VALUES ?;
getAll:
SELECT * FROM deviceInfoEntity;
getByUserIdAndDeviceId:
SELECT * FROM deviceInfoEntity
WHERE user_id = :userId
AND device_id = :deviceId;
getByUserId:
SELECT * FROM deviceInfoEntity
WHERE user_id = :userId;
getByIdentityKey:
SELECT * FROM deviceInfoEntity
WHERE identity_key = :identityKey;
updateLocallyVerified:
UPDATE deviceInfoEntity
SET locally_verified = :locallyVerified
WHERE user_id = :userId
AND device_id = :deviceId;
updateCrossSignedVerified:
UPDATE deviceInfoEntity
SET cross_signed_verified = :crossSignedVerified
WHERE user_id = :userId
AND device_id = :deviceId;
updateVerified:
UPDATE deviceInfoEntity
SET locally_verified = :locallyVerified,
cross_signed_verified = :crossSignedVerified
WHERE user_id = :userId
AND device_id = :deviceId;
deleteAll:
DELETE FROM deviceInfoEntity;

View File

@ -0,0 +1,31 @@
import java.lang.Long;
CREATE TABLE gossipingEventEntity (
type TEXT NOT NULL,
sender TEXT NOT NULL,
send_state TEXT,
content TEXT,
decryption_result_json TEXT,
decryption_error_code TEXT,
age_local_ts INTEGER AS Long DEFAULT 0 NOT NULL
);
CREATE INDEX gossiping_event_type ON gossipingEventEntity(type);
CREATE INDEX gossiping_event_sender ON gossipingEventEntity(sender);
insertOrUpdate:
INSERT OR REPLACE INTO gossipingEventEntity VALUES ?;
getAll:
SELECT * FROM gossipingEventEntity;
getByType:
SELECT * FROM gossipingEventEntity
WHERE type = :type;
getBySender:
SELECT * FROM gossipingEventEntity
WHERE sender = :sender;
deleteAll:
DELETE FROM gossipingEventEntity;

View File

@ -0,0 +1,49 @@
import java.lang.Long;
CREATE TABLE incomingGossipingRequestEntity (
request_id TEXT NOT NULL,
type TEXT NOT NULL,
other_user_id TEXT,
requested_info_str TEXT,
request_state TEXT,
other_device_id TEXT,
local_creation_timestamp INTEGER AS Long DEFAULT 0 NOT NULL
);
CREATE INDEX incoming_gossiping_request_request_id ON incomingGossipingRequestEntity(request_id);
CREATE INDEX incoming_gossiping_request_type ON incomingGossipingRequestEntity(type);
insertOrUpdate:
INSERT OR REPLACE INTO incomingGossipingRequestEntity VALUES ?;
getAll:
SELECT * FROM incomingGossipingRequestEntity;
getByType:
SELECT * FROM incomingGossipingRequestEntity
WHERE type = :type;
getByRequestState:
SELECT * FROM incomingGossipingRequestEntity
WHERE request_state = :requestState;
getByTypeAndRequestState:
SELECT * FROM incomingGossipingRequestEntity
WHERE type = :type
AND request_state = :requestState;
getIncomingRoomKeyRequest:
SELECT * FROM incomingGossipingRequestEntity
WHERE type = :type
AND other_user_id = :otherUserId
AND other_device_id = :otherDeviceId;
updateRequestState:
UPDATE incomingGossipingRequestEntity
SET request_state = :requestState
WHERE other_user_id = :otherUserId
AND other_device_id = :otherDeviceId
AND request_id = :requestId;
deleteAll:
DELETE FROM incomingGossipingRequestEntity;

View File

@ -0,0 +1,14 @@
CREATE TABLE keysBackupDataEntity (
static_id INTEGER PRIMARY KEY CHECK (static_id = 0),
backup_last_server_hash TEXT,
backup_last_server_number_of_keys INTEGER DEFAULT 0 NOT NULL
);
insertOrUpdate:
INSERT OR REPLACE INTO keysBackupDataEntity VALUES ?;
getAll:
SELECT * FROM keysBackupDataEntity;
deleteAll:
DELETE FROM keysBackupDataEntity;

View File

@ -0,0 +1,52 @@
import java.lang.Boolean;
CREATE TABLE olmInboundGroupSessionEntity (
session_id TEXT NOT NULL,
sender_key TEXT NOT NULL,
olm_inbound_group_session_data TEXT,
backed_up INTEGER AS Boolean DEFAULT 0 NOT NULL,
PRIMARY KEY(session_id, sender_key)
);
insertOrUpdate:
INSERT OR REPLACE INTO olmInboundGroupSessionEntity VALUES ?;
getAll:
SELECT * FROM olmInboundGroupSessionEntity;
getBySessionIdAndSenderKey:
SELECT * FROM olmInboundGroupSessionEntity
WHERE session_id = :sessionId
AND sender_key = :senderKey;
getAllBackedUp:
SELECT * FROM olmInboundGroupSessionEntity
WHERE backed_up = :isbackedUp
LIMIT :limit;
countAll:
SELECT COUNT(*)
FROM olmInboundGroupSessionEntity;
countBackedUp:
SELECT COUNT(*)
FROM olmInboundGroupSessionEntity
WHERE backed_up = :backedUp;
deleteBySessionIdAndSenderKey:
DELETE FROM olmInboundGroupSessionEntity
WHERE session_id = :sessionId
AND sender_key = :senderKey;
setAllBackedUp:
UPDATE olmInboundGroupSessionEntity
SET backed_up = :isBackedUp;
setBackedUp:
UPDATE olmInboundGroupSessionEntity
SET backed_up = :isBackedUp
WHERE session_id = :sessionId
AND sender_key = :senderKey;
deleteAll:
DELETE FROM olmInboundGroupSessionEntity;

View File

@ -0,0 +1,34 @@
import java.lang.Long;
CREATE TABLE olmSessionEntity (
session_id TEXT NOT NULL,
device_key TEXT NOT NULL,
olm_session_data TEXT,
last_received_message_ts INTEGER AS Long DEFAULT 0 NOT NULL,
PRIMARY KEY(session_id, device_key)
);
CREATE INDEX olm_session_device_key ON olmSessionEntity(device_key);
insertOrUpdate:
INSERT OR REPLACE INTO olmSessionEntity VALUES ?;
getAll:
SELECT * FROM olmSessionEntity;
getBySessionIdAndDeviceKey:
SELECT * FROM olmSessionEntity
WHERE session_id = :sessionId
AND device_key = :deviceKey;
getByDeviceKey:
SELECT * FROM olmSessionEntity
WHERE device_key = :deviceKey;
getByDeviceKeyDescending:
SELECT * FROM olmSessionEntity
WHERE device_key = :deviceKey
ORDER BY last_received_message_ts DESC;
deleteAll:
DELETE FROM olmSessionEntity;

View File

@ -0,0 +1,37 @@
CREATE TABLE outgoingGossipingRequestEntity (
request_id TEXT NOT NULL,
type TEXT NOT NULL,
recipients_data TEXT,
requested_info TEXT,
request_state TEXT
);
CREATE INDEX outgoing_gossiping_request_request_id ON outgoingGossipingRequestEntity(request_id);
CREATE INDEX outgoing_gossiping_request_type ON outgoingGossipingRequestEntity(type);
insertOrUpdate:
INSERT OR REPLACE INTO outgoingGossipingRequestEntity VALUES ?;
getAll:
SELECT * FROM outgoingGossipingRequestEntity;
getByRequestId:
SELECT * FROM outgoingGossipingRequestEntity
WHERE request_id = :requestId;
getByType:
SELECT * FROM outgoingGossipingRequestEntity
WHERE type = :type;
getByTypeAndRequestedInfo:
SELECT * FROM outgoingGossipingRequestEntity
WHERE type = :type
AND requested_info = :requestedInfo;
updateRequestState:
UPDATE outgoingGossipingRequestEntity
SET request_state = :requestState
WHERE request_id = :requestId;
deleteAll:
DELETE FROM outgoingGossipingRequestEntity;

View File

@ -1,2 +1,3 @@
include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':diff-match-patch', ':matrix-sdk-sqldelight'
include ':multipicker'
include ':matrix-sdk-sqldelight'