mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
Merge pull request #1527 from vector-im/feature/migration_from_legacy
Feature/migration from legacy
This commit is contained in:
commit
5784c4c8b3
35 changed files with 2579 additions and 38 deletions
5
matrix-sdk-android/proguard-rules.pro
vendored
5
matrix-sdk-android/proguard-rules.pro
vendored
|
@ -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.** { *; }
|
||||||
|
|
|
@ -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()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<>();
|
||||||
|
}
|
|
@ -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
|
|
@ -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}..."
|
||||||
|
|
||||||
|
|
|
@ -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}..."
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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": [
|
||||||
|
|
|
@ -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": [
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue