Merge pull request #1527 from vector-im/feature/migration_from_legacy

Feature/migration from legacy
This commit is contained in:
Benoit Marty 2020-06-29 14:08:53 +02:00 committed by GitHub
commit 5784c4c8b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 2579 additions and 38 deletions

View file

@ -60,4 +60,7 @@
-keep interface okhttp3.Interceptor.* { *; } -keep interface okhttp3.Interceptor.* { *; }
### OLM JNI ### ### OLM JNI ###
-keep class org.matrix.olm.** { *; } -keep class org.matrix.olm.** { *; }
### Webrtc
-keep class org.webrtc.** { *; }

View file

@ -0,0 +1,68 @@
/*
* 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.database
import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntity
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntity
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity
import im.vector.matrix.android.internal.crypto.store.db.model.GossipingEventEntity
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntity
import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import im.vector.matrix.android.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntity
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntity
import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntity
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.where
import timber.log.Timber
object RealmDebugTools {
/**
* Log info about the crypto DB
*/
fun dumpCryptoDb(realmConfiguration: RealmConfiguration) {
Realm.getInstance(realmConfiguration).use {
Timber.d("Realm located at : ${realmConfiguration.realmDirectory}/${realmConfiguration.realmFileName}")
val key = realmConfiguration.encryptionKey.joinToString("") { byte -> "%02x".format(byte) }
Timber.d("Realm encryption key : $key")
// Check if we have data
Timber.e("Realm is empty: ${it.isEmpty}")
Timber.d("Realm has CryptoMetadataEntity: ${it.where<CryptoMetadataEntity>().count()}")
Timber.d("Realm has CryptoRoomEntity: ${it.where<CryptoRoomEntity>().count()}")
Timber.d("Realm has DeviceInfoEntity: ${it.where<DeviceInfoEntity>().count()}")
Timber.d("Realm has KeysBackupDataEntity: ${it.where<KeysBackupDataEntity>().count()}")
Timber.d("Realm has OlmInboundGroupSessionEntity: ${it.where<OlmInboundGroupSessionEntity>().count()}")
Timber.d("Realm has OlmSessionEntity: ${it.where<OlmSessionEntity>().count()}")
Timber.d("Realm has UserEntity: ${it.where<UserEntity>().count()}")
Timber.d("Realm has KeyInfoEntity: ${it.where<KeyInfoEntity>().count()}")
Timber.d("Realm has CrossSigningInfoEntity: ${it.where<CrossSigningInfoEntity>().count()}")
Timber.d("Realm has TrustLevelEntity: ${it.where<TrustLevelEntity>().count()}")
Timber.d("Realm has GossipingEventEntity: ${it.where<GossipingEventEntity>().count()}")
Timber.d("Realm has IncomingGossipingRequestEntity: ${it.where<IncomingGossipingRequestEntity>().count()}")
Timber.d("Realm has OutgoingGossipingRequestEntity: ${it.where<OutgoingGossipingRequestEntity>().count()}")
Timber.d("Realm has MyDeviceLastSeenInfoEntity: ${it.where<MyDeviceLastSeenInfoEntity>().count()}")
}
}
}

View file

@ -23,6 +23,7 @@ import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.legacy.LegacySessionImporter
import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
@ -41,6 +42,7 @@ import javax.inject.Inject
*/ */
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) { class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
@Inject internal lateinit var authenticationService: AuthenticationService @Inject internal lateinit var authenticationService: AuthenticationService
@Inject internal lateinit var userAgentHolder: UserAgentHolder @Inject internal lateinit var userAgentHolder: UserAgentHolder
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@ -62,6 +64,10 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
return authenticationService return authenticationService
} }
fun legacySessionImporter(): LegacySessionImporter {
return legacySessionImporter
}
companion object { companion object {
private lateinit var instance: Matrix private lateinit var instance: Matrix

View file

@ -34,10 +34,10 @@ data class HomeServerConnectionConfig(
val homeServerUri: Uri, val homeServerUri: Uri,
val identityServerUri: Uri? = null, val identityServerUri: Uri? = null,
val antiVirusServerUri: Uri? = null, val antiVirusServerUri: Uri? = null,
val allowedFingerprints: MutableList<Fingerprint> = ArrayList(), val allowedFingerprints: List<Fingerprint> = emptyList(),
val shouldPin: Boolean = false, val shouldPin: Boolean = false,
val tlsVersions: MutableList<TlsVersion>? = null, val tlsVersions: List<TlsVersion>? = null,
val tlsCipherSuites: MutableList<CipherSuite>? = null, val tlsCipherSuites: List<CipherSuite>? = null,
val shouldAcceptTlsExtensions: Boolean = true, val shouldAcceptTlsExtensions: Boolean = true,
val allowHttpExtension: Boolean = false, val allowHttpExtension: Boolean = false,
val forceUsageTlsVersions: Boolean = false val forceUsageTlsVersions: Boolean = false

View file

@ -0,0 +1,25 @@
/*
* 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.api.legacy
interface LegacySessionImporter {
/**
* Will eventually import a session created by the legacy app.
*/
fun process()
}

View file

@ -40,6 +40,7 @@ data class CallHangupContent(
*/ */
@Json(name = "reason") val reason: Reason? = null @Json(name = "reason") val reason: Reason? = null
) { ) {
@JsonClass(generateAdapter = false)
enum class Reason { enum class Reason {
@Json(name = "ice_failed") @Json(name = "ice_failed")
ICE_FAILED, ICE_FAILED,

View file

@ -17,7 +17,9 @@
package im.vector.matrix.android.api.session.room.model.call package im.vector.matrix.android.api.session.room.model.call
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class SdpType { enum class SdpType {
@Json(name = "offer") @Json(name = "offer")
OFFER, OFFER,

View file

@ -21,6 +21,7 @@ import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.legacy.LegacySessionImporter
import im.vector.matrix.android.internal.auth.db.AuthRealmMigration import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
import im.vector.matrix.android.internal.auth.db.AuthRealmModule import im.vector.matrix.android.internal.auth.db.AuthRealmModule
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
@ -29,6 +30,7 @@ import im.vector.matrix.android.internal.auth.login.DefaultDirectLoginTask
import im.vector.matrix.android.internal.auth.login.DirectLoginTask import im.vector.matrix.android.internal.auth.login.DirectLoginTask
import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.AuthDatabase import im.vector.matrix.android.internal.di.AuthDatabase
import im.vector.matrix.android.internal.legacy.DefaultLegacySessionImporter
import im.vector.matrix.android.internal.wellknown.WellknownModule import im.vector.matrix.android.internal.wellknown.WellknownModule
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import java.io.File import java.io.File
@ -61,6 +63,9 @@ internal abstract class AuthModule {
} }
} }
@Binds
abstract fun bindLegacySessionImporter(importer: DefaultLegacySessionImporter): LegacySessionImporter
@Binds @Binds
abstract fun bindSessionParamsStore(store: RealmSessionParamsStore): SessionParamsStore abstract fun bindSessionParamsStore(store: RealmSessionParamsStore): SessionParamsStore

View file

@ -26,44 +26,182 @@ import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrap
import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper
import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.GossipingEventEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.GossipingEventEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntityFields
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields
import im.vector.matrix.android.internal.di.SerializeNulls import im.vector.matrix.android.internal.di.SerializeNulls
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo
internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration { internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration {
// Version 1L added Cross Signing info persistence
companion object { companion object {
const val CRYPTO_STORE_SCHEMA_VERSION = 6L // 0, 1, 2: legacy Riot-Android
// 3: migrate to RiotX schema
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
const val CRYPTO_STORE_SCHEMA_VERSION = 9L
} }
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Crypto from $oldVersion to $newVersion") Timber.v("Migrating Realm Crypto from $oldVersion to $newVersion")
if (oldVersion <= 0) migrateTo1(realm) if (oldVersion <= 0) migrateTo1Legacy(realm)
if (oldVersion <= 1) migrateTo2(realm) if (oldVersion <= 1) migrateTo2Legacy(realm)
if (oldVersion <= 2) migrateTo3(realm) if (oldVersion <= 2) migrateTo3RiotX(realm)
if (oldVersion <= 3) migrateTo4(realm) if (oldVersion <= 3) migrateTo4(realm)
if (oldVersion <= 4) migrateTo5(realm) if (oldVersion <= 4) migrateTo5(realm)
if (oldVersion <= 5) migrateTo6(realm) if (oldVersion <= 5) migrateTo6(realm)
if (oldVersion <= 6) migrateTo7(realm)
if (oldVersion <= 7) migrateTo8(realm)
if (oldVersion <= 8) migrateTo9(realm)
} }
private fun migrateTo1(realm: DynamicRealm) { private fun migrateTo1Legacy(realm: DynamicRealm) {
Timber.d("Step 0 -> 1") Timber.d("Step 0 -> 1")
Timber.d("Add field lastReceivedMessageTs (Long) and set the value to 0")
realm.schema.get("OlmSessionEntity")
?.addField(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Long::class.java)
?.transform {
it.setLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, 0)
}
}
private fun migrateTo2Legacy(realm: DynamicRealm) {
Timber.d("Step 1 -> 2")
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
realm.schema.get("IncomingRoomKeyRequestEntity")
?.addField("requestBodyAlgorithm", String::class.java)
?.addField("requestBodyRoomId", String::class.java)
?.addField("requestBodySenderKey", String::class.java)
?.addField("requestBodySessionId", String::class.java)
?.transform { dynamicObject ->
val requestBodyString = dynamicObject.getString("requestBodyString")
try {
// It was a map before
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
map?.let {
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
dynamicObject.setString("requestBodyRoomId", it["room_id"])
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
dynamicObject.setString("requestBodySessionId", it["session_id"])
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
?.removeField("requestBodyString")
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
realm.schema.get("OutgoingRoomKeyRequestEntity")
?.addField("requestBodyAlgorithm", String::class.java)
?.addField("requestBodyRoomId", String::class.java)
?.addField("requestBodySenderKey", String::class.java)
?.addField("requestBodySessionId", String::class.java)
?.transform { dynamicObject ->
val requestBodyString = dynamicObject.getString("requestBodyString")
try {
// It was a map before
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
map?.let {
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
dynamicObject.setString("requestBodyRoomId", it["room_id"])
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
dynamicObject.setString("requestBodySessionId", it["session_id"])
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
?.removeField("requestBodyString")
Timber.d("Create KeysBackupDataEntity")
realm.schema.create("KeysBackupDataEntity")
.addField(KeysBackupDataEntityFields.PRIMARY_KEY, Integer::class.java)
.addPrimaryKey(KeysBackupDataEntityFields.PRIMARY_KEY)
.setRequired(KeysBackupDataEntityFields.PRIMARY_KEY, true)
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_HASH, String::class.java)
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_NUMBER_OF_KEYS, Integer::class.java)
}
private fun migrateTo3RiotX(realm: DynamicRealm) {
Timber.d("Step 2 -> 3")
Timber.d("Migrate to RiotX model")
realm.schema.get("CryptoRoomEntity")
?.addField(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, Boolean::class.java)
?.setRequired(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, false)
// Convert format of MXDeviceInfo, package has to be the same.
realm.schema.get("DeviceInfoEntity")
?.transform { obj ->
try {
val oldSerializedData = obj.getString("deviceInfoData")
deserializeFromRealm<LegacyMXDeviceInfo>(oldSerializedData)?.let { legacyMxDeviceInfo ->
val newMxDeviceInfo = MXDeviceInfo(
deviceId = legacyMxDeviceInfo.deviceId,
userId = legacyMxDeviceInfo.userId,
algorithms = legacyMxDeviceInfo.algorithms,
keys = legacyMxDeviceInfo.keys,
signatures = legacyMxDeviceInfo.signatures,
unsigned = legacyMxDeviceInfo.unsigned,
verified = legacyMxDeviceInfo.mVerified
)
obj.setString("deviceInfoData", serializeForRealm(newMxDeviceInfo))
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper2
realm.schema.get("OlmInboundGroupSessionEntity")
?.transform { obj ->
try {
val oldSerializedData = obj.getString("olmInboundGroupSessionData")
deserializeFromRealm<MXOlmInboundGroupSession2>(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
val newOlmInboundGroupSessionWrapper2 = OlmInboundGroupSessionWrapper2()
.apply {
olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
roomId = mxOlmInboundGroupSession2.mRoomId
senderKey = mxOlmInboundGroupSession2.mSenderKey
keysClaimed = mxOlmInboundGroupSession2.mKeysClaimed
forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
}
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper2))
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
}
// Version 4L added Cross Signing info persistence
private fun migrateTo4(realm: DynamicRealm) {
Timber.d("Step 3 -> 4")
Timber.d("Create KeyInfoEntity") Timber.d("Create KeyInfoEntity")
val trustLevelentityEntitySchema = realm.schema.create("TrustLevelEntity") val trustLevelEntityEntitySchema = realm.schema.create("TrustLevelEntity")
.addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java) .addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
.setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true) .setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true)
.addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java) .addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
@ -73,7 +211,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
.addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java) .addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java)
.addField(KeyInfoEntityFields.SIGNATURES, String::class.java) .addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
.addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java) .addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
.addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema) .addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
Timber.d("Create CrossSigningInfoEntity") Timber.d("Create CrossSigningInfoEntity")
@ -112,7 +250,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java) ?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java) ?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true) ?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true)
?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema) ?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
?.transform { obj -> ?.transform { obj ->
try { try {
@ -158,8 +296,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
?.removeField("deviceInfoData") ?.removeField("deviceInfoData")
} }
private fun migrateTo2(realm: DynamicRealm) { private fun migrateTo5(realm: DynamicRealm) {
Timber.d("Step 1 -> 2") Timber.d("Step 4 -> 5")
realm.schema.remove("OutgoingRoomKeyRequestEntity") realm.schema.remove("OutgoingRoomKeyRequestEntity")
realm.schema.remove("IncomingRoomKeyRequestEntity") realm.schema.remove("IncomingRoomKeyRequestEntity")
@ -199,16 +337,16 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
.addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java) .addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
} }
private fun migrateTo3(realm: DynamicRealm) { private fun migrateTo6(realm: DynamicRealm) {
Timber.d("Step 2 -> 3") Timber.d("Step 5 -> 6")
Timber.d("Updating CryptoMetadataEntity table") Timber.d("Updating CryptoMetadataEntity table")
realm.schema.get("CryptoMetadataEntity") realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java) ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java)
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java) ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java)
} }
private fun migrateTo4(realm: DynamicRealm) { private fun migrateTo7(realm: DynamicRealm) {
Timber.d("Step 3 -> 4") Timber.d("Step 6 -> 7")
Timber.d("Updating KeyInfoEntity table") Timber.d("Updating KeyInfoEntity table")
val keyInfoEntities = realm.where("KeyInfoEntity").findAll() val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
try { try {
@ -239,8 +377,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
} }
} }
private fun migrateTo5(realm: DynamicRealm) { private fun migrateTo8(realm: DynamicRealm) {
Timber.d("Step 4 -> 5") Timber.d("Step 7 -> 8")
realm.schema.create("MyDeviceLastSeenInfoEntity") realm.schema.create("MyDeviceLastSeenInfoEntity")
.addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java) .addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java)
.addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID) .addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID)
@ -261,7 +399,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
} }
// Fixes duplicate devices in UserEntity#devices // Fixes duplicate devices in UserEntity#devices
private fun migrateTo6(realm: DynamicRealm) { private fun migrateTo9(realm: DynamicRealm) {
Timber.d("Step 8 -> 9")
val userEntities = realm.where("UserEntity").findAll() val userEntities = realm.where("UserEntity").findAll()
userEntities.forEach { userEntities.forEach {
try { try {
@ -273,7 +412,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
deviceList.addAll(distinct) deviceList.addAll(distinct)
} }
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.w(failure, "Crypto Data base migration error for migrateTo6") Timber.w(failure, "Crypto Data base migration error for migrateTo9")
} }
} }
} }

View file

@ -86,6 +86,13 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
} }
fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) { fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) {
val key = getRealmEncryptionKey(alias)
realmConfigurationBuilder.encryptionKey(key)
}
// Expose to handle Realm migration to riotX
fun getRealmEncryptionKey(alias: String) : ByteArray {
val key = if (hasKeyForDatabase(alias)) { val key = if (hasKeyForDatabase(alias)) {
Timber.i("Found key for alias:$alias") Timber.i("Found key for alias:$alias")
extractKeyForDatabase(alias) extractKeyForDatabase(alias)
@ -99,7 +106,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
Timber.w("Database key for alias `$alias`: $log") Timber.w("Database key for alias `$alias`: $log")
} }
realmConfigurationBuilder.encryptionKey(key) return key
} }
// Delete elements related to the alias // Delete elements related to the alias

View file

@ -0,0 +1,223 @@
/*
* 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.legacy
import android.content.Context
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.DiscoveryInformation
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.WellKnownBaseConfig
import im.vector.matrix.android.api.legacy.LegacySessionImporter
import im.vector.matrix.android.internal.auth.SessionParamsStore
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.database.RealmKeysUtils
import im.vector.matrix.android.internal.legacy.riot.LoginStorage
import im.vector.matrix.android.internal.network.ssl.Fingerprint
import im.vector.matrix.android.internal.util.md5
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import java.io.File
import javax.inject.Inject
import im.vector.matrix.android.internal.legacy.riot.Fingerprint as LegacyFingerprint
import im.vector.matrix.android.internal.legacy.riot.HomeServerConnectionConfig as LegacyHomeServerConnectionConfig
internal class DefaultLegacySessionImporter @Inject constructor(
private val context: Context,
private val sessionParamsStore: SessionParamsStore,
private val realmCryptoStoreMigration: RealmCryptoStoreMigration,
private val realmKeysUtils: RealmKeysUtils
) : LegacySessionImporter {
private val loginStorage = LoginStorage(context)
companion object {
// During development, set to false to play several times the migration
private var DELETE_PREVIOUS_DATA = true
}
override fun process() {
Timber.d("Migration: Importing legacy session")
val list = loginStorage.credentialsList
Timber.d("Migration: found ${list.size} session(s).")
val legacyConfig = list.firstOrNull() ?: return
runBlocking {
Timber.d("Migration: importing a session")
try {
importCredentials(legacyConfig)
} catch (t: Throwable) {
// It can happen in case of partial migration. To test, do not return
Timber.e(t, "Migration: Error importing credential")
}
Timber.d("Migration: importing crypto DB")
try {
importCryptoDb(legacyConfig)
} catch (t: Throwable) {
// It can happen in case of partial migration. To test, do not return
Timber.e(t, "Migration: Error importing crypto DB")
}
if (DELETE_PREVIOUS_DATA) {
try {
Timber.d("Migration: clear file system")
clearFileSystem(legacyConfig)
} catch (t: Throwable) {
Timber.e(t, "Migration: Error clearing filesystem")
}
try {
Timber.d("Migration: clear shared prefs")
clearSharedPrefs()
} catch (t: Throwable) {
Timber.e(t, "Migration: Error clearing shared prefs")
}
} else {
Timber.d("Migration: clear file system - DEACTIVATED")
Timber.d("Migration: clear shared prefs - DEACTIVATED")
}
}
}
private suspend fun importCredentials(legacyConfig: LegacyHomeServerConnectionConfig) {
@Suppress("DEPRECATION")
val sessionParams = SessionParams(
credentials = Credentials(
userId = legacyConfig.credentials.userId,
accessToken = legacyConfig.credentials.accessToken,
refreshToken = legacyConfig.credentials.refreshToken,
homeServer = legacyConfig.credentials.homeServer,
deviceId = legacyConfig.credentials.deviceId,
discoveryInformation = legacyConfig.credentials.wellKnown?.let { wellKnown ->
// Note credentials.wellKnown is not serialized in the LoginStorage, so this code is a bit useless...
if (wellKnown.homeServer?.baseURL != null || wellKnown.identityServer?.baseURL != null) {
DiscoveryInformation(
homeServer = wellKnown.homeServer?.baseURL?.let { WellKnownBaseConfig(baseURL = it) },
identityServer = wellKnown.identityServer?.baseURL?.let { WellKnownBaseConfig(baseURL = it) }
)
} else {
null
}
}
),
homeServerConnectionConfig = HomeServerConnectionConfig(
homeServerUri = legacyConfig.homeserverUri,
identityServerUri = legacyConfig.identityServerUri,
antiVirusServerUri = legacyConfig.antiVirusServerUri,
allowedFingerprints = legacyConfig.allowedFingerprints.map {
Fingerprint(
bytes = it.bytes,
hashType = when (it.type) {
LegacyFingerprint.HashType.SHA1,
null -> Fingerprint.HashType.SHA1
LegacyFingerprint.HashType.SHA256 -> Fingerprint.HashType.SHA256
}
)
},
shouldPin = legacyConfig.shouldPin(),
tlsVersions = legacyConfig.acceptedTlsVersions,
tlsCipherSuites = legacyConfig.acceptedTlsCipherSuites,
shouldAcceptTlsExtensions = legacyConfig.shouldAcceptTlsExtensions(),
allowHttpExtension = false, // TODO
forceUsageTlsVersions = legacyConfig.forceUsageOfTlsVersions()
),
// If token is not valid, this boolean will be updated later
isTokenValid = true
)
Timber.d("Migration: save session")
sessionParamsStore.save(sessionParams)
}
private fun importCryptoDb(legacyConfig: LegacyHomeServerConnectionConfig) {
// Here we migrate the DB, we copy the crypto DB to the location specific to RiotX, and we encrypt it.
val userMd5 = legacyConfig.credentials.userId.md5()
val sessionId = legacyConfig.credentials.let { (if (it.deviceId.isNullOrBlank()) it.userId else "${it.userId}|${it.deviceId}").md5() }
val newLocation = File(context.filesDir, sessionId)
val keyAlias = "crypto_module_$userMd5"
// Ensure newLocation does not exist (can happen in case of partial migration)
newLocation.deleteRecursively()
newLocation.mkdirs()
Timber.d("Migration: create legacy realm configuration")
val realmConfiguration = RealmConfiguration.Builder()
.directory(File(context.filesDir, userMd5))
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
.migration(realmCryptoStoreMigration)
.build()
Timber.d("Migration: copy DB to encrypted DB")
Realm.getInstance(realmConfiguration).use {
// Move the DB to the new location, handled by RiotX
it.writeEncryptedCopyTo(File(newLocation, realmConfiguration.realmFileName), realmKeysUtils.getRealmEncryptionKey(keyAlias))
}
}
// Delete all the files created by Riot Android which will not be used anymore by RiotX
private fun clearFileSystem(legacyConfig: LegacyHomeServerConnectionConfig) {
val cryptoFolder = legacyConfig.credentials.userId.md5()
listOf(
// Where session store was saved (we do not care about migrating that, an initial sync will be performed)
File(context.filesDir, "MXFileStore"),
// Previous (and very old) file crypto store
File(context.filesDir, "MXFileCryptoStore"),
// Draft. They will be lost, this is sad but we assume it
File(context.filesDir, "MXLatestMessagesStore"),
// Media storage
File(context.filesDir, "MXMediaStore"),
File(context.filesDir, "MXMediaStore2"),
File(context.filesDir, "MXMediaStore3"),
// Ext folder
File(context.filesDir, "ext_share"),
// Crypto store
File(context.filesDir, cryptoFolder)
).forEach { file ->
try {
file.deleteRecursively()
} catch (t: Throwable) {
Timber.e(t, "Migration: unable to delete $file")
}
}
}
private fun clearSharedPrefs() {
// Shared Pref. Note that we do not delete the default preferences, as it should be nearly the same (TODO check that)
listOf(
"Vector.LoginStorage",
"GcmRegistrationManager",
"IntegrationManager.Storage"
).forEach { prefName ->
context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
.edit()
.clear()
.apply()
}
}
}

View file

@ -0,0 +1,284 @@
/*
* 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.legacy.riot;
import android.util.Pair;
import androidx.annotation.NonNull;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.TlsVersion;
import timber.log.Timber;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* Various utility classes for dealing with X509Certificates
*/
public class CertUtil {
/**
* Generates the SHA-256 fingerprint of the given certificate
*
* @param cert the certificate.
* @return the finger print
* @throws CertificateException the certificate exception
*/
public static byte[] generateSha256Fingerprint(X509Certificate cert) throws CertificateException {
return generateFingerprint(cert, "SHA-256");
}
/**
* Generates the SHA-1 fingerprint of the given certificate
*
* @param cert the certificated
* @return the SHA1 fingerprint
* @throws CertificateException the certificate exception
*/
public static byte[] generateSha1Fingerprint(X509Certificate cert) throws CertificateException {
return generateFingerprint(cert, "SHA-1");
}
/**
* Generate the fingerprint for a dedicated type.
*
* @param cert the certificate
* @param type the type
* @return the fingerprint
* @throws CertificateException certificate exception
*/
private static byte[] generateFingerprint(X509Certificate cert, String type) throws CertificateException {
final byte[] fingerprint;
final MessageDigest md;
try {
md = MessageDigest.getInstance(type);
} catch (Exception e) {
// This really *really* shouldn't throw, as java should always have a SHA-256 and SHA-1 impl.
throw new CertificateException(e);
}
fingerprint = md.digest(cert.getEncoded());
return fingerprint;
}
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
/**
* Convert the fingerprint to an hexa string.
*
* @param fingerprint the fingerprint
* @return the hexa string.
*/
public static String fingerprintToHexString(byte[] fingerprint) {
return fingerprintToHexString(fingerprint, ' ');
}
public static String fingerprintToHexString(byte[] fingerprint, char sep) {
char[] hexChars = new char[fingerprint.length * 3];
for (int j = 0; j < fingerprint.length; j++) {
int v = fingerprint[j] & 0xFF;
hexChars[j * 3] = hexArray[v >>> 4];
hexChars[j * 3 + 1] = hexArray[v & 0x0F];
hexChars[j * 3 + 2] = sep;
}
return new String(hexChars, 0, hexChars.length - 1);
}
/**
* Recursively checks the exception to see if it was caused by an
* UnrecognizedCertificateException
*
* @param e the throwable.
* @return The UnrecognizedCertificateException if exists, else null.
*/
public static UnrecognizedCertificateException getCertificateException(Throwable e) {
int i = 0; // Just in case there is a getCause loop
while (e != null && i < 10) {
if (e instanceof UnrecognizedCertificateException) {
return (UnrecognizedCertificateException) e;
}
e = e.getCause();
i++;
}
return null;
}
/**
* Create a SSLSocket factory for a HS config.
*
* @param hsConfig the HS config.
* @return SSLSocket factory
*/
public static Pair<SSLSocketFactory, X509TrustManager> newPinnedSSLSocketFactory(HomeServerConnectionConfig hsConfig) {
X509TrustManager defaultTrustManager = null;
// If we haven't specified that we wanted to pin the certs, fallback to standard
// X509 checks if fingerprints don't match.
if (!hsConfig.shouldPin()) {
TrustManagerFactory trustManagerFactory = null;
// get the PKIX instance
try {
trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
} catch (NoSuchAlgorithmException e) {
Timber.e(e, "## newPinnedSSLSocketFactory() : TrustManagerFactory.getInstance failed");
}
// it doesn't exist, use the default one.
if (trustManagerFactory == null) {
try {
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
} catch (NoSuchAlgorithmException e) {
Timber.e(e, "## newPinnedSSLSocketFactory() : TrustManagerFactory.getInstance with default algorithm failed");
}
}
if (trustManagerFactory != null) {
try {
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
for (int i = 0; i < trustManagers.length; i++) {
if (trustManagers[i] instanceof X509TrustManager) {
defaultTrustManager = (X509TrustManager) trustManagers[i];
break;
}
}
} catch (KeyStoreException e) {
Timber.e(e, "## newPinnedSSLSocketFactory()");
}
}
}
X509TrustManager trustManager = new PinnedTrustManager(hsConfig.getAllowedFingerprints(), defaultTrustManager);
TrustManager[] trustManagers = new TrustManager[]{
trustManager
};
SSLSocketFactory sslSocketFactory;
try {
if (hsConfig.forceUsageOfTlsVersions() && hsConfig.getAcceptedTlsVersions() != null) {
// Force usage of accepted Tls Versions for Android < 20
sslSocketFactory = new TLSSocketFactory(trustManagers, hsConfig.getAcceptedTlsVersions());
} else {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, new java.security.SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
}
} catch (Exception e) {
// This is too fatal
throw new RuntimeException(e);
}
return new Pair<>(sslSocketFactory, trustManager);
}
/**
* Create a Host name verifier for a hs config.
*
* @param hsConfig the hs config.
* @return a new HostnameVerifier.
*/
public static HostnameVerifier newHostnameVerifier(HomeServerConnectionConfig hsConfig) {
final HostnameVerifier defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
final List<Fingerprint> trusted_fingerprints = hsConfig.getAllowedFingerprints();
return new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
if (defaultVerifier.verify(hostname, session)) return true;
if (trusted_fingerprints == null || trusted_fingerprints.size() == 0) return false;
// If remote cert matches an allowed fingerprint, just accept it.
try {
for (Certificate cert : session.getPeerCertificates()) {
for (Fingerprint allowedFingerprint : trusted_fingerprints) {
if (allowedFingerprint != null && cert instanceof X509Certificate && allowedFingerprint.matchesCert((X509Certificate) cert)) {
return true;
}
}
}
} catch (SSLPeerUnverifiedException e) {
return false;
} catch (CertificateException e) {
return false;
}
return false;
}
};
}
/**
* Create a list of accepted TLS specifications for a hs config.
*
* @param hsConfig the hs config.
* @param url the url of the end point, used to check if we have to enable CLEARTEXT communication.
* @return a list of accepted TLS specifications.
*/
public static List<ConnectionSpec> newConnectionSpecs(@NonNull HomeServerConnectionConfig hsConfig, @NonNull String url) {
final ConnectionSpec.Builder builder = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS);
final List<TlsVersion> tlsVersions = hsConfig.getAcceptedTlsVersions();
if (null != tlsVersions) {
builder.tlsVersions(tlsVersions.toArray(new TlsVersion[0]));
}
final List<CipherSuite> tlsCipherSuites = hsConfig.getAcceptedTlsCipherSuites();
if (null != tlsCipherSuites) {
builder.cipherSuites(tlsCipherSuites.toArray(new CipherSuite[0]));
}
builder.supportsTlsExtensions(hsConfig.shouldAcceptTlsExtensions());
List<ConnectionSpec> list = new ArrayList<>();
list.add(builder.build());
if (url.startsWith("http://")) {
list.add(ConnectionSpec.CLEARTEXT);
}
return list;
}
}

View file

@ -0,0 +1,112 @@
/*
* 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.legacy.riot;
import android.text.TextUtils;
import org.jetbrains.annotations.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* The user's credentials.
*/
public class Credentials {
public String userId;
// This is the server name and not a URI, e.g. "matrix.org". Spec says it's now deprecated
@Deprecated
public String homeServer;
public String accessToken;
public String refreshToken;
public String deviceId;
// Optional data that may contain info to override home server and/or identity server
public WellKnown wellKnown;
public JSONObject toJson() throws JSONException {
JSONObject json = new JSONObject();
json.put("user_id", userId);
json.put("home_server", homeServer);
json.put("access_token", accessToken);
json.put("refresh_token", TextUtils.isEmpty(refreshToken) ? JSONObject.NULL : refreshToken);
json.put("device_id", deviceId);
return json;
}
public static Credentials fromJson(JSONObject obj) throws JSONException {
Credentials creds = new Credentials();
creds.userId = obj.getString("user_id");
creds.homeServer = obj.getString("home_server");
creds.accessToken = obj.getString("access_token");
if (obj.has("device_id")) {
creds.deviceId = obj.getString("device_id");
}
// refresh_token is mandatory
if (obj.has("refresh_token")) {
try {
creds.refreshToken = obj.getString("refresh_token");
} catch (Exception e) {
creds.refreshToken = null;
}
} else {
throw new RuntimeException("refresh_token is required.");
}
return creds;
}
@Override
public String toString() {
return "Credentials{" +
"userId='" + userId + '\'' +
", homeServer='" + homeServer + '\'' +
", refreshToken.length='" + (refreshToken != null ? refreshToken.length() : "null") + '\'' +
", accessToken.length='" + (accessToken != null ? accessToken.length() : "null") + '\'' +
'}';
}
@Nullable
public String getUserId() {
return userId;
}
@Nullable
public String getHomeServer() {
return homeServer;
}
@Nullable
public String getAccessToken() {
return accessToken;
}
@Nullable
public String getDeviceId() {
return deviceId;
}
}

View file

@ -0,0 +1,134 @@
/*
* 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.legacy.riot;
import android.util.Base64;
import org.json.JSONException;
import org.json.JSONObject;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* Represents a X509 Certificate fingerprint.
*/
public class Fingerprint {
public enum HashType {
SHA1,
SHA256
}
private final HashType mHashType;
private final byte[] mBytes;
private String mDisplayableHexRepr;
public Fingerprint(HashType hashType, byte[] bytes) {
mHashType = hashType;
mBytes = bytes;
mDisplayableHexRepr = null;
}
public static Fingerprint newSha256Fingerprint(X509Certificate cert) throws CertificateException {
return new Fingerprint(HashType.SHA256, CertUtil.generateSha256Fingerprint(cert));
}
public static Fingerprint newSha1Fingerprint(X509Certificate cert) throws CertificateException {
return new Fingerprint(HashType.SHA1, CertUtil.generateSha1Fingerprint(cert));
}
public HashType getType() {
return mHashType;
}
public byte[] getBytes() {
return mBytes;
}
public String getBytesAsHexString() {
if (mDisplayableHexRepr == null) {
mDisplayableHexRepr = CertUtil.fingerprintToHexString(mBytes);
}
return mDisplayableHexRepr;
}
public JSONObject toJson() throws JSONException {
JSONObject obj = new JSONObject();
obj.put("bytes", Base64.encodeToString(getBytes(), Base64.DEFAULT));
obj.put("hash_type", mHashType.toString());
return obj;
}
public static Fingerprint fromJson(JSONObject obj) throws JSONException {
String hashTypeStr = obj.getString("hash_type");
byte[] fingerprintBytes = Base64.decode(obj.getString("bytes"), Base64.DEFAULT);
final HashType hashType;
if ("SHA256".equalsIgnoreCase(hashTypeStr)) {
hashType = HashType.SHA256;
} else if ("SHA1".equalsIgnoreCase(hashTypeStr)) {
hashType = HashType.SHA1;
} else {
throw new JSONException("Unrecognized hash type: " + hashTypeStr);
}
return new Fingerprint(hashType, fingerprintBytes);
}
public boolean matchesCert(X509Certificate cert) throws CertificateException {
Fingerprint o = null;
switch (mHashType) {
case SHA256:
o = Fingerprint.newSha256Fingerprint(cert);
break;
case SHA1:
o = Fingerprint.newSha1Fingerprint(cert);
break;
}
return equals(o);
}
public String toString() {
return String.format("Fingerprint{type: '%s', fingeprint: '%s'}", mHashType.toString(), getBytesAsHexString());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Fingerprint that = (Fingerprint) o;
if (!Arrays.equals(mBytes, that.mBytes)) return false;
return mHashType == that.mHashType;
}
@Override
public int hashCode() {
int result = mBytes != null ? Arrays.hashCode(mBytes) : 0;
result = 31 * result + (mHashType != null ? mHashType.hashCode() : 0);
return result;
}
}

View file

@ -0,0 +1,676 @@
/*
* 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.legacy.riot;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.List;
import okhttp3.CipherSuite;
import okhttp3.TlsVersion;
import timber.log.Timber;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* Represents how to connect to a specific Homeserver, may include credentials to use.
*/
public class HomeServerConnectionConfig {
// the home server URI
private Uri mHomeServerUri;
// the jitsi server URI. Can be null
@Nullable
private Uri mJitsiServerUri;
// the identity server URI. Can be null
@Nullable
private Uri mIdentityServerUri;
// the anti-virus server URI
private Uri mAntiVirusServerUri;
// allowed fingerprints
private List<Fingerprint> mAllowedFingerprints = new ArrayList<>();
// the credentials
private Credentials mCredentials;
// tell whether we should reject X509 certs that were issued by trusts CAs and only trustcerts with matching fingerprints.
private boolean mPin;
// the accepted TLS versions
private List<TlsVersion> mTlsVersions;
// the accepted TLS cipher suites
private List<CipherSuite> mTlsCipherSuites;
// should accept TLS extensions
private boolean mShouldAcceptTlsExtensions = true;
// Force usage of TLS versions
private boolean mForceUsageTlsVersions;
// the proxy hostname
private String mProxyHostname;
// the proxy port
private int mProxyPort = -1;
/**
* Private constructor. Please use the Builder
*/
private HomeServerConnectionConfig() {
// Private constructor
}
/**
* Update the home server URI.
*
* @param uri the new HS uri
*/
public void setHomeserverUri(Uri uri) {
mHomeServerUri = uri;
}
/**
* @return the home server uri
*/
public Uri getHomeserverUri() {
return mHomeServerUri;
}
/**
* @return the jitsi server uri
*/
public Uri getJitsiServerUri() {
return mJitsiServerUri;
}
/**
* @return the identity server uri, or null if not defined
*/
@Nullable
public Uri getIdentityServerUri() {
return mIdentityServerUri;
}
/**
* @return the anti-virus server uri
*/
public Uri getAntiVirusServerUri() {
if (null != mAntiVirusServerUri) {
return mAntiVirusServerUri;
}
// Else consider the HS uri by default.
return mHomeServerUri;
}
/**
* @return the allowed fingerprints.
*/
public List<Fingerprint> getAllowedFingerprints() {
return mAllowedFingerprints;
}
/**
* @return the credentials
*/
public Credentials getCredentials() {
return mCredentials;
}
/**
* Update the credentials.
*
* @param credentials the new credentials
*/
public void setCredentials(Credentials credentials) {
mCredentials = credentials;
// Override home server url and/or identity server url if provided
if (credentials.wellKnown != null) {
if (credentials.wellKnown.homeServer != null) {
String homeServerUrl = credentials.wellKnown.homeServer.baseURL;
if (!TextUtils.isEmpty(homeServerUrl)) {
// remove trailing "/"
if (homeServerUrl.endsWith("/")) {
homeServerUrl = homeServerUrl.substring(0, homeServerUrl.length() - 1);
}
Timber.d("Overriding homeserver url to " + homeServerUrl);
mHomeServerUri = Uri.parse(homeServerUrl);
}
}
if (credentials.wellKnown.identityServer != null) {
String identityServerUrl = credentials.wellKnown.identityServer.baseURL;
if (!TextUtils.isEmpty(identityServerUrl)) {
// remove trailing "/"
if (identityServerUrl.endsWith("/")) {
identityServerUrl = identityServerUrl.substring(0, identityServerUrl.length() - 1);
}
Timber.d("Overriding identity server url to " + identityServerUrl);
mIdentityServerUri = Uri.parse(identityServerUrl);
}
}
if (credentials.wellKnown.jitsiServer != null) {
String jitsiServerUrl = credentials.wellKnown.jitsiServer.preferredDomain;
if (!TextUtils.isEmpty(jitsiServerUrl)) {
// add trailing "/"
if (!jitsiServerUrl.endsWith("/")) {
jitsiServerUrl =jitsiServerUrl + "/";
}
Timber.d("Overriding jitsi server url to " + jitsiServerUrl);
mJitsiServerUri = Uri.parse(jitsiServerUrl);
}
}
}
}
/**
* @return whether we should reject X509 certs that were issued by trusts CAs and only trust
* certs with matching fingerprints.
*/
public boolean shouldPin() {
return mPin;
}
/**
* TLS versions accepted for TLS connections with the home server.
*/
@Nullable
public List<TlsVersion> getAcceptedTlsVersions() {
return mTlsVersions;
}
/**
* TLS cipher suites accepted for TLS connections with the home server.
*/
@Nullable
public List<CipherSuite> getAcceptedTlsCipherSuites() {
return mTlsCipherSuites;
}
/**
* @return whether we should accept TLS extensions.
*/
public boolean shouldAcceptTlsExtensions() {
return mShouldAcceptTlsExtensions;
}
/**
* @return true if the usage of TlsVersions has to be forced
*/
public boolean forceUsageOfTlsVersions() {
return mForceUsageTlsVersions;
}
/**
* @return proxy config if available
*/
@Nullable
public Proxy getProxyConfig() {
if (mProxyHostname == null || mProxyHostname.length() == 0 || mProxyPort == -1) {
return null;
}
return new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(mProxyHostname, mProxyPort));
}
@Override
public String toString() {
return "HomeserverConnectionConfig{" +
"mHomeServerUri=" + mHomeServerUri +
", mJitsiServerUri=" + mJitsiServerUri +
", mIdentityServerUri=" + mIdentityServerUri +
", mAntiVirusServerUri=" + mAntiVirusServerUri +
", mAllowedFingerprints size=" + mAllowedFingerprints.size() +
", mCredentials=" + mCredentials +
", mPin=" + mPin +
", mShouldAcceptTlsExtensions=" + mShouldAcceptTlsExtensions +
", mProxyHostname=" + (null == mProxyHostname ? "" : mProxyHostname) +
", mProxyPort=" + (-1 == mProxyPort ? "" : mProxyPort) +
", mTlsVersions=" + (null == mTlsVersions ? "" : mTlsVersions.size()) +
", mTlsCipherSuites=" + (null == mTlsCipherSuites ? "" : mTlsCipherSuites.size()) +
'}';
}
/**
* Convert the object instance into a JSon object
*
* @return the JSon representation
* @throws JSONException the JSON conversion failure reason
*/
public JSONObject toJson() throws JSONException {
JSONObject json = new JSONObject();
json.put("home_server_url", mHomeServerUri.toString());
Uri jitsiServerUri = getJitsiServerUri();
if (jitsiServerUri != null) {
json.put("jitsi_server_url", jitsiServerUri.toString());
}
Uri identityServerUri = getIdentityServerUri();
if (identityServerUri != null) {
json.put("identity_server_url", identityServerUri.toString());
}
if (mAntiVirusServerUri != null) {
json.put("antivirus_server_url", mAntiVirusServerUri.toString());
}
json.put("pin", mPin);
if (mCredentials != null) json.put("credentials", mCredentials.toJson());
if (mAllowedFingerprints != null) {
List<JSONObject> fingerprints = new ArrayList<>(mAllowedFingerprints.size());
for (Fingerprint fingerprint : mAllowedFingerprints) {
fingerprints.add(fingerprint.toJson());
}
json.put("fingerprints", new JSONArray(fingerprints));
}
json.put("tls_extensions", mShouldAcceptTlsExtensions);
if (mTlsVersions != null) {
List<String> tlsVersions = new ArrayList<>(mTlsVersions.size());
for (TlsVersion tlsVersion : mTlsVersions) {
tlsVersions.add(tlsVersion.javaName());
}
json.put("tls_versions", new JSONArray(tlsVersions));
}
json.put("force_usage_of_tls_versions", mForceUsageTlsVersions);
if (mTlsCipherSuites != null) {
List<String> tlsCipherSuites = new ArrayList<>(mTlsCipherSuites.size());
for (CipherSuite tlsCipherSuite : mTlsCipherSuites) {
tlsCipherSuites.add(tlsCipherSuite.javaName());
}
json.put("tls_cipher_suites", new JSONArray(tlsCipherSuites));
}
if (mProxyPort != -1) {
json.put("proxy_port", mProxyPort);
}
if (mProxyHostname != null && mProxyHostname.length() > 0) {
json.put("proxy_hostname", mProxyHostname);
}
return json;
}
/**
* Create an object instance from the json object.
*
* @param jsonObject the json object
* @return a HomeServerConnectionConfig instance
* @throws JSONException the conversion failure reason
*/
public static HomeServerConnectionConfig fromJson(JSONObject jsonObject) throws JSONException {
JSONObject credentialsObj = jsonObject.optJSONObject("credentials");
Credentials creds = credentialsObj != null ? Credentials.fromJson(credentialsObj) : null;
Builder builder = new Builder()
.withHomeServerUri(Uri.parse(jsonObject.getString("home_server_url")))
.withJitsiServerUri(jsonObject.has("jitsi_server_url") ? Uri.parse(jsonObject.getString("jitsi_server_url")) : null)
.withIdentityServerUri(jsonObject.has("identity_server_url") ? Uri.parse(jsonObject.getString("identity_server_url")) : null)
.withCredentials(creds)
.withPin(jsonObject.optBoolean("pin", false));
JSONArray fingerprintArray = jsonObject.optJSONArray("fingerprints");
if (fingerprintArray != null) {
for (int i = 0; i < fingerprintArray.length(); i++) {
builder.addAllowedFingerPrint(Fingerprint.fromJson(fingerprintArray.getJSONObject(i)));
}
}
// Set the anti-virus server uri if any
if (jsonObject.has("antivirus_server_url")) {
builder.withAntiVirusServerUri(Uri.parse(jsonObject.getString("antivirus_server_url")));
}
builder.withShouldAcceptTlsExtensions(jsonObject.optBoolean("tls_extensions", true));
// Set the TLS versions if any
if (jsonObject.has("tls_versions")) {
JSONArray tlsVersionsArray = jsonObject.optJSONArray("tls_versions");
if (tlsVersionsArray != null) {
for (int i = 0; i < tlsVersionsArray.length(); i++) {
builder.addAcceptedTlsVersion(TlsVersion.forJavaName(tlsVersionsArray.getString(i)));
}
}
}
builder.forceUsageOfTlsVersions(jsonObject.optBoolean("force_usage_of_tls_versions", false));
// Set the TLS cipher suites if any
if (jsonObject.has("tls_cipher_suites")) {
JSONArray tlsCipherSuitesArray = jsonObject.optJSONArray("tls_cipher_suites");
if (tlsCipherSuitesArray != null) {
for (int i = 0; i < tlsCipherSuitesArray.length(); i++) {
builder.addAcceptedTlsCipherSuite(CipherSuite.forJavaName(tlsCipherSuitesArray.getString(i)));
}
}
}
// Set the proxy options right if any
if (jsonObject.has("proxy_hostname") && jsonObject.has("proxy_port")) {
builder.withProxy(jsonObject.getString("proxy_hostname"), jsonObject.getInt("proxy_port"));
}
return builder.build();
}
/**
* Builder
*/
public static class Builder {
private HomeServerConnectionConfig mHomeServerConnectionConfig;
/**
* Builder constructor
*/
public Builder() {
mHomeServerConnectionConfig = new HomeServerConnectionConfig();
}
/**
* create a Builder from an existing HomeServerConnectionConfig
*/
public Builder(HomeServerConnectionConfig from) {
try {
mHomeServerConnectionConfig = HomeServerConnectionConfig.fromJson(from.toJson());
} catch (JSONException e) {
// Should not happen
throw new RuntimeException("Unable to create a HomeServerConnectionConfig", e);
}
}
/**
* @param homeServerUri The URI to use to connect to the homeserver. Cannot be null
* @return this builder
*/
public Builder withHomeServerUri(final Uri homeServerUri) {
if (homeServerUri == null || (!"http".equals(homeServerUri.getScheme()) && !"https".equals(homeServerUri.getScheme()))) {
throw new RuntimeException("Invalid home server URI: " + homeServerUri);
}
// remove trailing /
if (homeServerUri.toString().endsWith("/")) {
try {
String url = homeServerUri.toString();
mHomeServerConnectionConfig.mHomeServerUri = Uri.parse(url.substring(0, url.length() - 1));
} catch (Exception e) {
throw new RuntimeException("Invalid home server URI: " + homeServerUri);
}
} else {
mHomeServerConnectionConfig.mHomeServerUri = homeServerUri;
}
return this;
}
/**
* @param jitsiServerUri The URI to use to manage identity. Can be null
* @return this builder
*/
public Builder withJitsiServerUri(@Nullable final Uri jitsiServerUri) {
if (jitsiServerUri != null
&& !jitsiServerUri.toString().isEmpty()
&& !"http".equals(jitsiServerUri.getScheme())
&& !"https".equals(jitsiServerUri.getScheme())) {
throw new RuntimeException("Invalid jitsi server URI: " + jitsiServerUri);
}
// add trailing /
if ((null != jitsiServerUri) && !jitsiServerUri.toString().endsWith("/")) {
try {
String url = jitsiServerUri.toString();
mHomeServerConnectionConfig.mJitsiServerUri = Uri.parse(url + "/");
} catch (Exception e) {
throw new RuntimeException("Invalid jitsi server URI: " + jitsiServerUri);
}
} else {
if (jitsiServerUri != null && jitsiServerUri.toString().isEmpty()) {
mHomeServerConnectionConfig.mJitsiServerUri = null;
} else {
mHomeServerConnectionConfig.mJitsiServerUri = jitsiServerUri;
}
}
return this;
}
/**
* @param identityServerUri The URI to use to manage identity. Can be null
* @return this builder
*/
public Builder withIdentityServerUri(@Nullable final Uri identityServerUri) {
if (identityServerUri != null
&& !identityServerUri.toString().isEmpty()
&& !"http".equals(identityServerUri.getScheme())
&& !"https".equals(identityServerUri.getScheme())) {
throw new RuntimeException("Invalid identity server URI: " + identityServerUri);
}
// remove trailing /
if ((null != identityServerUri) && identityServerUri.toString().endsWith("/")) {
try {
String url = identityServerUri.toString();
mHomeServerConnectionConfig.mIdentityServerUri = Uri.parse(url.substring(0, url.length() - 1));
} catch (Exception e) {
throw new RuntimeException("Invalid identity server URI: " + identityServerUri);
}
} else {
if (identityServerUri != null && identityServerUri.toString().isEmpty()) {
mHomeServerConnectionConfig.mIdentityServerUri = null;
} else {
mHomeServerConnectionConfig.mIdentityServerUri = identityServerUri;
}
}
return this;
}
/**
* @param credentials The credentials to use, if needed. Can be null.
* @return this builder
*/
public Builder withCredentials(@Nullable Credentials credentials) {
mHomeServerConnectionConfig.mCredentials = credentials;
return this;
}
/**
* @param allowedFingerprint If using SSL, allow server certs that match this fingerprint.
* @return this builder
*/
public Builder addAllowedFingerPrint(@Nullable Fingerprint allowedFingerprint) {
if (allowedFingerprint != null) {
mHomeServerConnectionConfig.mAllowedFingerprints.add(allowedFingerprint);
}
return this;
}
/**
* @param pin If true only allow certs matching given fingerprints, otherwise fallback to
* standard X509 checks.
* @return this builder
*/
public Builder withPin(boolean pin) {
mHomeServerConnectionConfig.mPin = pin;
return this;
}
/**
* @param shouldAcceptTlsExtension
* @return this builder
*/
public Builder withShouldAcceptTlsExtensions(boolean shouldAcceptTlsExtension) {
mHomeServerConnectionConfig.mShouldAcceptTlsExtensions = shouldAcceptTlsExtension;
return this;
}
/**
* Add an accepted TLS version for TLS connections with the home server.
*
* @param tlsVersion the tls version to add to the set of TLS versions accepted.
* @return this builder
*/
public Builder addAcceptedTlsVersion(@NonNull TlsVersion tlsVersion) {
if (mHomeServerConnectionConfig.mTlsVersions == null) {
mHomeServerConnectionConfig.mTlsVersions = new ArrayList<>();
}
mHomeServerConnectionConfig.mTlsVersions.add(tlsVersion);
return this;
}
/**
* Force the usage of TlsVersion. This can be usefull for device on Android version < 20
*
* @param forceUsageOfTlsVersions set to true to force the usage of specified TlsVersions (with {@link #addAcceptedTlsVersion(TlsVersion)}
* @return this builder
*/
public Builder forceUsageOfTlsVersions(boolean forceUsageOfTlsVersions) {
mHomeServerConnectionConfig.mForceUsageTlsVersions = forceUsageOfTlsVersions;
return this;
}
/**
* Add a TLS cipher suite to the list of accepted TLS connections with the home server.
*
* @param tlsCipherSuite the tls cipher suite to add.
* @return this builder
*/
public Builder addAcceptedTlsCipherSuite(@NonNull CipherSuite tlsCipherSuite) {
if (mHomeServerConnectionConfig.mTlsCipherSuites == null) {
mHomeServerConnectionConfig.mTlsCipherSuites = new ArrayList<>();
}
mHomeServerConnectionConfig.mTlsCipherSuites.add(tlsCipherSuite);
return this;
}
/**
* Update the anti-virus server URI.
*
* @param antivirusServerUri the new anti-virus uri. Can be null
* @return this builder
*/
public Builder withAntiVirusServerUri(@Nullable Uri antivirusServerUri) {
if ((null != antivirusServerUri) && (!"http".equals(antivirusServerUri.getScheme()) && !"https".equals(antivirusServerUri.getScheme()))) {
throw new RuntimeException("Invalid antivirus server URI: " + antivirusServerUri);
}
mHomeServerConnectionConfig.mAntiVirusServerUri = antivirusServerUri;
return this;
}
/**
* Convenient method to limit the TLS versions and cipher suites for this Builder
* Ref:
* - https://www.ssi.gouv.fr/uploads/2017/02/security-recommendations-for-tls_v1.1.pdf
* - https://developer.android.com/reference/javax/net/ssl/SSLEngine
*
* @param tlsLimitations true to use Tls limitations
* @param enableCompatibilityMode set to true for Android < 20
* @return this builder
*/
public Builder withTlsLimitations(boolean tlsLimitations, boolean enableCompatibilityMode) {
if (tlsLimitations) {
withShouldAcceptTlsExtensions(false);
// Tls versions
addAcceptedTlsVersion(TlsVersion.TLS_1_2);
addAcceptedTlsVersion(TlsVersion.TLS_1_3);
forceUsageOfTlsVersions(enableCompatibilityMode);
// Cipher suites
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256);
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256);
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256);
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256);
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384);
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384);
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256);
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256);
if (enableCompatibilityMode) {
// Adopt some preceding cipher suites for Android < 20 to be able to negotiate
// a TLS session.
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA);
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
}
}
return this;
}
/**
* @param proxyHostname Proxy Hostname
* @param proxyPort Proxy Port
* @return this builder
*/
public Builder withProxy(@Nullable String proxyHostname, int proxyPort) {
mHomeServerConnectionConfig.mProxyHostname = proxyHostname;
mHomeServerConnectionConfig.mProxyPort = proxyPort;
return this;
}
/**
* @return the {@link HomeServerConnectionConfig}
*/
public HomeServerConnectionConfig build() {
// Check mandatory parameters
if (mHomeServerConnectionConfig.mHomeServerUri == null) {
throw new RuntimeException("Home server URI not set");
}
return mHomeServerConnectionConfig;
}
}
}

View file

@ -0,0 +1,206 @@
/*
* 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.legacy.riot;
import android.content.Context;
import android.content.SharedPreferences;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* Stores login credentials in SharedPreferences.
*/
public class LoginStorage {
private static final String PREFS_LOGIN = "Vector.LoginStorage";
// multi accounts + home server config
private static final String PREFS_KEY_CONNECTION_CONFIGS = "PREFS_KEY_CONNECTION_CONFIGS";
private final Context mContext;
public LoginStorage(Context appContext) {
mContext = appContext.getApplicationContext();
}
/**
* @return the list of home server configurations.
*/
public List<HomeServerConnectionConfig> getCredentialsList() {
SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE);
String connectionConfigsString = prefs.getString(PREFS_KEY_CONNECTION_CONFIGS, null);
Timber.d("Got connection json: ");
if (connectionConfigsString == null) {
return new ArrayList<>();
}
try {
JSONArray connectionConfigsStrings = new JSONArray(connectionConfigsString);
List<HomeServerConnectionConfig> configList = new ArrayList<>(
connectionConfigsStrings.length()
);
for (int i = 0; i < connectionConfigsStrings.length(); i++) {
configList.add(
HomeServerConnectionConfig.fromJson(connectionConfigsStrings.getJSONObject(i))
);
}
return configList;
} catch (JSONException e) {
Timber.e(e, "Failed to deserialize accounts");
throw new RuntimeException("Failed to deserialize accounts");
}
}
/**
* Add a credentials to the credentials list
*
* @param config the home server config to add.
*/
public void addCredentials(HomeServerConnectionConfig config) {
if (null != config && config.getCredentials() != null) {
SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
List<HomeServerConnectionConfig> configs = getCredentialsList();
configs.add(config);
List<JSONObject> serialized = new ArrayList<>(configs.size());
try {
for (HomeServerConnectionConfig c : configs) {
serialized.add(c.toJson());
}
} catch (JSONException e) {
throw new RuntimeException("Failed to serialize connection config");
}
String ser = new JSONArray(serialized).toString();
Timber.d("Storing " + serialized.size() + " credentials");
editor.putString(PREFS_KEY_CONNECTION_CONFIGS, ser);
editor.apply();
}
}
/**
* Remove the credentials from credentials list
*
* @param config the credentials to remove
*/
public void removeCredentials(HomeServerConnectionConfig config) {
if (null != config && config.getCredentials() != null) {
Timber.d("Removing account: " + config.getCredentials().userId);
SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
List<HomeServerConnectionConfig> configs = getCredentialsList();
List<JSONObject> serialized = new ArrayList<>(configs.size());
boolean found = false;
try {
for (HomeServerConnectionConfig c : configs) {
if (c.getCredentials().userId.equals(config.getCredentials().userId)) {
found = true;
} else {
serialized.add(c.toJson());
}
}
} catch (JSONException e) {
throw new RuntimeException("Failed to serialize connection config");
}
if (!found) return;
String ser = new JSONArray(serialized).toString();
Timber.d("Storing " + serialized.size() + " credentials");
editor.putString(PREFS_KEY_CONNECTION_CONFIGS, ser);
editor.apply();
}
}
/**
* Replace the credential from credentials list, based on credentials.userId.
* If it does not match an existing credential it does *not* insert the new credentials.
*
* @param config the credentials to insert
*/
public void replaceCredentials(HomeServerConnectionConfig config) {
if (null != config && config.getCredentials() != null) {
SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
List<HomeServerConnectionConfig> configs = getCredentialsList();
List<JSONObject> serialized = new ArrayList<>(configs.size());
boolean found = false;
try {
for (HomeServerConnectionConfig c : configs) {
if (c.getCredentials().userId.equals(config.getCredentials().userId)) {
serialized.add(config.toJson());
found = true;
} else {
serialized.add(c.toJson());
}
}
} catch (JSONException e) {
throw new RuntimeException("Failed to serialize connection config");
}
if (!found) return;
String ser = new JSONArray(serialized).toString();
Timber.d("Storing " + serialized.size() + " credentials");
editor.putString(PREFS_KEY_CONNECTION_CONFIGS, ser);
editor.apply();
}
}
/**
* Clear the stored values
*/
public void clear() {
SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.remove(PREFS_KEY_CONNECTION_CONFIGS);
//Need to commit now because called before forcing an app restart
editor.commit();
}
}

View file

@ -0,0 +1,107 @@
/*
* 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.legacy.riot;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.annotation.Nullable;
import javax.net.ssl.X509TrustManager;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* Implements a TrustManager that checks Certificates against an explicit list of known
* fingerprints.
*/
public class PinnedTrustManager implements X509TrustManager {
private final List<Fingerprint> mFingerprints;
@Nullable
private final X509TrustManager mDefaultTrustManager;
/**
* @param fingerprints An array of SHA256 cert fingerprints
* @param defaultTrustManager Optional trust manager to fall back on if cert does not match
* any of the fingerprints. Can be null.
*/
public PinnedTrustManager(List<Fingerprint> fingerprints, @Nullable X509TrustManager defaultTrustManager) {
mFingerprints = fingerprints;
mDefaultTrustManager = defaultTrustManager;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String s) throws CertificateException {
try {
if (mDefaultTrustManager != null) {
mDefaultTrustManager.checkClientTrusted(
chain, s
);
return;
}
} catch (CertificateException e) {
// If there is an exception we fall back to checking fingerprints
if (mFingerprints == null || mFingerprints.size() == 0) {
throw new UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.getCause());
}
}
checkTrusted("client", chain);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String s) throws CertificateException {
try {
if (mDefaultTrustManager != null) {
mDefaultTrustManager.checkServerTrusted(
chain, s
);
return;
}
} catch (CertificateException e) {
// If there is an exception we fall back to checking fingerprints
if (mFingerprints == null || mFingerprints.isEmpty()) {
throw new UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.getCause());
}
}
checkTrusted("server", chain);
}
private void checkTrusted(String type, X509Certificate[] chain) throws CertificateException {
X509Certificate cert = chain[0];
boolean found = false;
if (mFingerprints != null) {
for (Fingerprint allowedFingerprint : mFingerprints) {
if (allowedFingerprint != null && allowedFingerprint.matchesCert(cert)) {
found = true;
break;
}
}
}
if (!found) {
throw new UnrecognizedCertificateException(cert, Fingerprint.newSha256Fingerprint(cert), null);
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}

View file

@ -0,0 +1,135 @@
/*
* 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.legacy.riot;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import okhttp3.TlsVersion;
import timber.log.Timber;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* Force the usage of Tls versions on every created socket
* Inspired from https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/
*/
/*package*/ class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;
private String[] enabledProtocols;
/**
* Constructor
*
* @param trustPinned
* @param acceptedTlsVersions
* @throws KeyManagementException
* @throws NoSuchAlgorithmException
*/
/*package*/ TLSSocketFactory(TrustManager[] trustPinned, List<TlsVersion> acceptedTlsVersions) throws KeyManagementException, NoSuchAlgorithmException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustPinned, new SecureRandom());
internalSSLSocketFactory = context.getSocketFactory();
enabledProtocols = new String[acceptedTlsVersions.size()];
int i = 0;
for (TlsVersion tlsVersion : acceptedTlsVersions) {
enabledProtocols[i] = tlsVersion.javaName();
i++;
}
}
@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSOnSocket(Socket socket) {
if (socket != null && (socket instanceof SSLSocket)) {
SSLSocket sslSocket = (SSLSocket) socket;
List<String> supportedProtocols = Arrays.asList(sslSocket.getSupportedProtocols());
List<String> filteredEnabledProtocols = new ArrayList<>();
for (String protocol : enabledProtocols) {
if (supportedProtocols.contains(protocol)) {
filteredEnabledProtocols.add(protocol);
}
}
if (!filteredEnabledProtocols.isEmpty()) {
try {
sslSocket.setEnabledProtocols(filteredEnabledProtocols.toArray(new String[filteredEnabledProtocols.size()]));
} catch (Exception e) {
Timber.e(e, "Exception");
}
}
}
return socket;
}
}

View file

@ -0,0 +1,47 @@
/*
* 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.legacy.riot;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* Thrown when we are given a certificate that does match the certificate we were told to
* expect.
*/
public class UnrecognizedCertificateException extends CertificateException {
private final X509Certificate mCert;
private final Fingerprint mFingerprint;
public UnrecognizedCertificateException(X509Certificate cert, Fingerprint fingerprint, Throwable cause) {
super("Unrecognized certificate with unknown fingerprint: " + cert.getSubjectDN(), cause);
mCert = cert;
mFingerprint = fingerprint;
}
public X509Certificate getCertificate() {
return mCert;
}
public Fingerprint getFingerprint() {
return mFingerprint;
}
}

View file

@ -0,0 +1,96 @@
/*
* 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.legacy.riot
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
* <pre>
* {
* "m.homeserver": {
* "base_url": "https://matrix.org"
* },
* "m.identity_server": {
* "base_url": "https://vector.im"
* }
* "m.integrations": {
* "managers": [
* {
* "api_url": "https://integrations.example.org",
* "ui_url": "https://integrations.example.org/ui"
* },
* {
* "api_url": "https://bots.example.org"
* }
* ]
* }
* "im.vector.riot.jitsi": {
* "preferredDomain": "https://jitsi.riot.im/"
* }
* }
* </pre>
*/
@JsonClass(generateAdapter = true)
class WellKnown {
@JvmField
@Json(name = "m.homeserver")
var homeServer: WellKnownBaseConfig? = null
@JvmField
@Json(name = "m.identity_server")
var identityServer: WellKnownBaseConfig? = null
@JvmField
@Json(name = "m.integrations")
var integrations: Map<String, *>? = null
/**
* Returns the list of integration managers proposed
*/
fun getIntegrationManagers(): List<WellKnownManagerConfig> {
val managers = ArrayList<WellKnownManagerConfig>()
integrations?.get("managers")?.let {
(it as? ArrayList<*>)?.let { configs ->
configs.forEach { config ->
(config as? Map<*, *>)?.let { map ->
val apiUrl = map["api_url"] as? String
val uiUrl = map["ui_url"] as? String ?: apiUrl
if (apiUrl != null
&& apiUrl.startsWith("https://")
&& uiUrl!!.startsWith("https://")) {
managers.add(WellKnownManagerConfig(
apiUrl = apiUrl,
uiUrl = uiUrl
))
}
}
}
}
}
return managers
}
@JvmField
@Json(name = "im.vector.riot.jitsi")
var jitsiServer: WellKnownPreferredConfig? = null
}

View file

@ -0,0 +1,39 @@
/*
* 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.legacy.riot
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
* <pre>
* {
* "base_url": "https://vector.im"
* }
* </pre>
*/
@JsonClass(generateAdapter = true)
class WellKnownBaseConfig {
@JvmField
@Json(name = "base_url")
var baseURL: String? = null
}

View file

@ -0,0 +1,25 @@
/*
* 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.legacy.riot
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
data class WellKnownManagerConfig(
val apiUrl : String,
val uiUrl: String
)

View file

@ -0,0 +1,39 @@
/*
* 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.legacy.riot
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
* <pre>
* {
* "preferredDomain": "https://jitsi.riot.im/"
* }
* </pre>
*/
@JsonClass(generateAdapter = true)
class WellKnownPreferredConfig {
@JvmField
@Json(name = "preferredDomain")
var preferredDomain: String? = null
}

View file

@ -0,0 +1,84 @@
/*
* 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 org.matrix.androidsdk.crypto.data;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
public class MXDeviceInfo implements Serializable {
private static final long serialVersionUID = 20129670646382964L;
// This device is a new device and the user was not warned it has been added.
public static final int DEVICE_VERIFICATION_UNKNOWN = -1;
// The user has not yet verified this device.
public static final int DEVICE_VERIFICATION_UNVERIFIED = 0;
// The user has verified this device.
public static final int DEVICE_VERIFICATION_VERIFIED = 1;
// The user has blocked this device.
public static final int DEVICE_VERIFICATION_BLOCKED = 2;
/**
* The id of this device.
*/
public String deviceId;
/**
* the user id
*/
public String userId;
/**
* The list of algorithms supported by this device.
*/
public List<String> algorithms;
/**
* A map from <key type>:<id> to <base64-encoded key>>.
*/
public Map<String, String> keys;
/**
* The signature of this MXDeviceInfo.
* A map from <key type>:<device_id> to <base64-encoded key>>.
*/
public Map<String, Map<String, String>> signatures;
/*
* Additional data from the home server.
*/
public Map<String, Object> unsigned;
/**
* Verification state of this device.
*/
public int mVerified;
/**
* Constructor
*/
public MXDeviceInfo() {
mVerified = DEVICE_VERIFICATION_UNKNOWN;
}
}

View file

@ -0,0 +1,52 @@
/*
* 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 org.matrix.androidsdk.crypto.data;
import org.matrix.olm.OlmInboundGroupSession;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/*
* IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
*/
/**
* This class adds more context to a OLMInboundGroupSession object.
* This allows additional checks. The class implements NSCoding so that the context can be stored.
*/
public class MXOlmInboundGroupSession2 implements Serializable {
// define a serialVersionUID to avoid having to redefine the class after updates
private static final long serialVersionUID = 201702011617L;
// The associated olm inbound group session.
public OlmInboundGroupSession mSession;
// The room in which this session is used.
public String mRoomId;
// The base64-encoded curve25519 key of the sender.
public String mSenderKey;
// Other keys the sender claims.
public Map<String, String> mKeysClaimed;
// Devices which forwarded this session to us (normally empty).
public List<String> mForwardingCurve25519KeyChain = new ArrayList<>();
}

View file

@ -162,3 +162,6 @@ Formatter\.formatShortFileSize===1
### Use kotlin stdlib to test or compare strings ### Use kotlin stdlib to test or compare strings
# DISABLED # DISABLED
# android\.text\.TextUtils # android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If it is ok, change the value in file forbidden_strings_in_code.txt
enum class===72

View file

@ -18,7 +18,7 @@ PARAM_APK=$2
# Other params # Other params
BUILD_TOOLS_VERSION="29.0.3" BUILD_TOOLS_VERSION="29.0.3"
MIN_SDK_VERSION=19 MIN_SDK_VERSION=21
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..." echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..."

View file

@ -24,7 +24,7 @@ PARAM_KEY_PASS=$4
# Other params # Other params
BUILD_TOOLS_VERSION="29.0.3" BUILD_TOOLS_VERSION="29.0.3"
MIN_SDK_VERSION=19 MIN_SDK_VERSION=21
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..." echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..."

View file

@ -14,9 +14,10 @@ androidExtensions {
experimental = true experimental = true
} }
// Note: 2 digits max for each value
ext.versionMajor = 0 ext.versionMajor = 0
ext.versionMinor = 23 ext.versionMinor = 91
ext.versionPatch = 0 ext.versionPatch = 2
static def getGitTimestamp() { static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct' def cmd = 'git show -s --format=%ct'
@ -106,7 +107,7 @@ def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
android { android {
compileSdkVersion 29 compileSdkVersion 29
defaultConfig { defaultConfig {
applicationId "im.vector.riotx" applicationId "im.vector.app"
// Set to API 21: see #405 // Set to API 21: see #405
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
@ -177,7 +178,7 @@ android {
buildTypes { buildTypes {
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
resValue "string", "app_name", "RiotX dbg" resValue "string", "app_name", "Riot.imX dbg"
resValue "bool", "debug_mode", "true" resValue "bool", "debug_mode", "true"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
@ -186,7 +187,7 @@ android {
} }
release { release {
resValue "string", "app_name", "RiotX" resValue "string", "app_name", "Riot.imX"
resValue "bool", "debug_mode", "false" resValue "bool", "debug_mode", "false"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
@ -230,6 +231,9 @@ android {
lintOptions { lintOptions {
lintConfig file("lint.xml") lintConfig file("lint.xml")
// TODO Restore true once pb with WorkManager is fixed
abortOnError false
} }
compileOptions { compileOptions {

View file

@ -10,7 +10,7 @@
"client_info": { "client_info": {
"mobilesdk_app_id": "1:912726360885:android:4ef8f3a0021e774d", "mobilesdk_app_id": "1:912726360885:android:4ef8f3a0021e774d",
"android_client_info": { "android_client_info": {
"package_name": "im.vector.riotx.debug" "package_name": "im.vector.app.debug"
} }
}, },
"oauth_client": [ "oauth_client": [

View file

@ -10,7 +10,7 @@
"client_info": { "client_info": {
"mobilesdk_app_id": "1:912726360885:android:4ef8f3a0021e774d", "mobilesdk_app_id": "1:912726360885:android:4ef8f3a0021e774d",
"android_client_info": { "android_client_info": {
"package_name": "im.vector.riotx" "package_name": "im.vector.app"
} }
}, },
"oauth_client": [ "oauth_client": [

View file

@ -15,7 +15,8 @@
<!-- Call feature --> <!-- Call feature -->
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" /> <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" /> <!-- Commented because Google PlayStore does not like we add permission if we are not requiring it. And it was added for future use -->
<!--uses-permission android:name="android.permission.READ_CALL_LOG" /-->
<!-- Needed for voice call to toggle speaker on or off --> <!-- Needed for voice call to toggle speaker on or off -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- READ_PHONE_STATE is needed only if your calling app reads numbers from the `PHONE_STATE` <!-- READ_PHONE_STATE is needed only if your calling app reads numbers from the `PHONE_STATE`
@ -47,6 +48,11 @@
android:theme="@style/AppTheme.Light" android:theme="@style/AppTheme.Light"
tools:replace="android:allowBackup"> tools:replace="android:allowBackup">
<!-- No limit for screen ratio: avoid black strips -->
<meta-data
android:name="android.max_aspect"
android:value="9.9" />
<activity <activity
android:name=".features.MainActivity" android:name=".features.MainActivity"
android:theme="@style/AppTheme.Launcher" /> android:theme="@style/AppTheme.Launcher" />

View file

@ -37,6 +37,7 @@ import com.github.piasy.biv.loader.glide.GlideImageLoader
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixConfiguration import im.vector.matrix.android.api.MatrixConfiguration
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.legacy.LegacySessionImporter
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.DaggerVectorComponent import im.vector.riotx.core.di.DaggerVectorComponent
import im.vector.riotx.core.di.HasVectorInjector import im.vector.riotx.core.di.HasVectorInjector
@ -57,15 +58,15 @@ import im.vector.riotx.features.version.VersionProvider
import im.vector.riotx.push.fcm.FcmHelper import im.vector.riotx.push.fcm.FcmHelper
import timber.log.Timber import timber.log.Timber
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.concurrent.Executors
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider { class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
lateinit var appContext: Context lateinit var appContext: Context
// font thread handler @Inject lateinit var legacySessionImporter: LegacySessionImporter
@Inject lateinit var authenticationService: AuthenticationService @Inject lateinit var authenticationService: AuthenticationService
@Inject lateinit var vectorConfiguration: VectorConfiguration @Inject lateinit var vectorConfiguration: VectorConfiguration
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider @Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
@ -84,6 +85,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
@Inject lateinit var webRtcPeerConnectionManager: WebRtcPeerConnectionManager @Inject lateinit var webRtcPeerConnectionManager: WebRtcPeerConnectionManager
lateinit var vectorComponent: VectorComponent lateinit var vectorComponent: VectorComponent
// font thread handler
private var fontThreadHandler: Handler? = null private var fontThreadHandler: Handler? = null
override fun onCreate() { override fun onCreate() {
@ -121,6 +123,10 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
emojiCompatWrapper.init(fontRequest) emojiCompatWrapper.init(fontRequest)
notificationUtils.createNotificationChannels() notificationUtils.createNotificationChannels()
// It can takes time, but do we care?
legacySessionImporter.process()
if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!!
activeSessionHolder.setActiveSession(lastAuthenticatedSession) activeSessionHolder.setActiveSession(lastAuthenticatedSession)

View file

@ -25,6 +25,7 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.legacy.LegacySessionImporter
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.riotx.core.error.DefaultErrorFormatter import im.vector.riotx.core.error.DefaultErrorFormatter
import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.error.ErrorFormatter
@ -64,6 +65,12 @@ abstract class VectorModule {
return activeSessionHolder.getActiveSession() return activeSessionHolder.getActiveSession()
} }
@Provides
@JvmStatic
fun providesLegacySessionImporter(matrix: Matrix): LegacySessionImporter {
return matrix.legacySessionImporter()
}
@Provides @Provides
@JvmStatic @JvmStatic
fun providesAuthenticationService(matrix: Matrix): AuthenticationService { fun providesAuthenticationService(matrix: Matrix): AuthenticationService {

View file

@ -220,7 +220,7 @@ class BugReporter @Inject constructor(
} }
if (!mIsCancelled) { if (!mIsCancelled) {
val text = "[RiotX] " + val text = "[Riot.imX] " +
if (forSuggestion) { if (forSuggestion) {
"[Suggestion] " "[Suggestion] "
} else { } else {
@ -292,7 +292,7 @@ class BugReporter @Inject constructor(
builder.addFormDataPart("label", context.getString(R.string.git_branch_name)) builder.addFormDataPart("label", context.getString(R.string.git_branch_name))
// Special for RiotX // Special for RiotX
builder.addFormDataPart("label", "[RiotX]") builder.addFormDataPart("label", "[Riot.imX]")
// Suggestion // Suggestion
if (forSuggestion) { if (forSuggestion) {