Crypto: continue cleaning + fix some issues.

This commit is contained in:
ganfra 2019-06-07 16:01:24 +02:00
parent c4d7711d2f
commit 664e5354d3
16 changed files with 154 additions and 151 deletions

View File

@ -92,34 +92,10 @@ interface CryptoService {
roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>)

/*
fun start(isInitialSync: Boolean, aCallback: MatrixCallback<Unit>?)

fun isStarted(): Boolean

fun isStarting(): Boolean

fun close()


fun getOlmDevice(): MXOlmDevice?

fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>)

fun warnOnUnknownDevices(): Boolean

@Throws(MXDecryptionException::class)
fun decryptEvent(event: Event, timelineId: String?): MXEventDecryptionResult?

fun resetReplayAttackCheckInTimeline(timelineId: String)


@VisibleForTesting
fun ensureOlmSessionsForUsers(users: List<String>, callback: MatrixCallback<MXUsersDevicesMap<MXOlmSessionResult>>)
*/

fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?

fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult?>)

fun getEncryptionAlgorithm(roomId: String): String?

fun shouldEncryptForInvitedMembers(roomId: String): Boolean

View File

@ -23,5 +23,5 @@ import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class RoomHistoryVisibilityContent(
@Json(name = "history_visibility") val historyVisibility: RoomHistoryVisibility
@Json(name = "history_visibility") val historyVisibility: RoomHistoryVisibility? = null
)

View File

@ -73,6 +73,7 @@ import org.matrix.olm.OlmManager
import timber.log.Timber
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.EmptyCoroutineContext

/**
* A `CryptoService` class instance manages the end-to-end crypto for a session.
@ -134,22 +135,8 @@ internal class CryptoManager(

// MXEncrypting instance for each room.
private val roomEncryptors: MutableMap<String, IMXEncrypting> = HashMap()

// the encryption is starting
private var isStarting = AtomicBoolean(false)

// tell if the crypto is started
private var isStarted = AtomicBoolean(false)

// TODO
//private val mNetworkListener = object : IMXNetworkEventListener {
// override fun onNetworkConnectionUpdate(isConnected: Boolean) {
// if (isConnected && !isStarted()) {
// Timber.v("Start MXCrypto because a network connection has been retrieved ")
// start(false, null)
// }
// }
//}
private val isStarting = AtomicBoolean(false)
private val isStarted = AtomicBoolean(false)

fun onStateEvent(roomId: String, event: Event) {
when {
@ -262,8 +249,6 @@ internal class CryptoManager(
internalStart(isInitialSync)
},
{
isStarting.set(false)
isStarted.set(true)
outgoingRoomKeyRequestManager.start()
keysBackup.checkAndStartKeysBackup()
if (isInitialSync) {
@ -273,6 +258,8 @@ internal class CryptoManager(
} else {
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
}
isStarting.set(false)
isStarted.set(true)
}
)
}
@ -589,21 +576,54 @@ internal class CryptoManager(
*/
@Throws(MXDecryptionException::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
val eventContent = event.content
if (eventContent == null) {
Timber.e("## decryptEvent : empty event content")
return null
}
return runBlocking {
withContext(coroutineDispatchers.crypto) {
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
Timber.e("## decryptEvent() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
} else {
alg.decryptEvent(event, timeline)
}
internalDecryptEvent(event, timeline).fold(
{ throw it },
{ it }
)
}
}

/**
* Decrypt an event asynchronously
*
* @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @param callback the callback to return data or null
*/
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult?>) {
GlobalScope.launch(EmptyCoroutineContext) {
val result = withContext(coroutineDispatchers.crypto) {
internalDecryptEvent(event, timeline)
}
result.fold(
{ callback.onFailure(it) },
{ callback.onSuccess(it) }
)
}
}

/**
* Decrypt an event
*
* @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try]
*/
private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult?> {
return Try {
val eventContent = event.content
if (eventContent == null) {
Timber.e("## decryptEvent : empty event content")
return@Try null
}
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
Timber.e("## decryptEvent() : $reason")
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
} else {
alg.decryptEvent(event, timeline)
}
}
}
@ -624,10 +644,16 @@ internal class CryptoManager(
*/
fun onToDeviceEvent(event: Event) {
CoroutineScope(coroutineDispatchers.crypto).launch {
if (event.getClearType() == EventType.ROOM_KEY || event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
onRoomKeyEvent(event)
} else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
incomingRoomKeyRequestManager.onRoomKeyRequestEvent(event)
when (event.getClearType()) {
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
onRoomKeyEvent(event)
}
EventType.ROOM_KEY_REQUEST -> {
incomingRoomKeyRequestManager.onRoomKeyRequestEvent(event)
}
else -> {
//ignore
}
}
}
}
@ -638,7 +664,7 @@ internal class CryptoManager(
* @param event the key event.
*/
private fun onRoomKeyEvent(event: Event) {
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>()!!
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.algorithm)) {
Timber.e("## onRoomKeyEvent() : missing fields")
return
@ -722,7 +748,6 @@ internal class CryptoManager(

private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) {
val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()

eventContent?.historyVisibility?.let {
cryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED)
}

View File

@ -166,7 +166,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
* @param timelineId the timeline identifier
*/
private fun addEventToPendingList(event: Event, timelineId: String) {
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
val encryptedEventContent = event.content.toModel<EncryptedEventContent>() ?: return
val pendingEventsKey = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}"

if (!pendingEvents.containsKey(pendingEventsKey)) {
@ -190,7 +190,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/
override fun onRoomKeyEvent(event: Event, keysBackup: KeysBackup) {
var exportFormat = false
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>()!!
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return

var senderKey: String? = event.getSenderKey()
var keysClaimed: MutableMap<String, String> = HashMap()
@ -203,11 +203,11 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
Timber.v("## onRoomKeyEvent(), forward adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()!!
forwardingCurve25519KeyChain = if (null == forwardedRoomKeyContent.forwardingCurve25519KeyChain) {
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>() ?: return
forwardingCurve25519KeyChain = if (forwardedRoomKeyContent.forwardingCurve25519KeyChain == null) {
ArrayList()
} else {
ArrayList(forwardedRoomKeyContent.forwardingCurve25519KeyChain!!)
ArrayList(forwardedRoomKeyContent.forwardingCurve25519KeyChain)
}
forwardingCurve25519KeyChain.add(senderKey!!)

@ -262,6 +262,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/
override fun onNewSession(senderKey: String, sessionId: String) {
//TODO see how to handle this
Timber.v("ON NEW SESSION $sessionId - $senderKey")
/*val k = "$senderKey|$sessionId"

val pending = pendingEvents[k]

View File

@ -28,29 +28,29 @@ data class EncryptedEventContent(
* the used algorithm
*/
@Json(name = "algorithm")
var algorithm: String? = null,
val algorithm: String? = null,

/**
* The encrypted event
*/
@Json(name = "ciphertext")
var ciphertext: String? = null,
val ciphertext: String? = null,

/**
* The device id
*/
@Json(name = "device_id")
var deviceId: String? = null,
val deviceId: String? = null,

/**
* the sender key
*/
@Json(name = "sender_key")
var senderKey: String? = null,
val senderKey: String? = null,

/**
* The session id
*/
@Json(name = "session_id")
var sessionId: String? = null
val sessionId: String? = null
)

View File

@ -25,18 +25,18 @@ import com.squareup.moshi.JsonClass
data class RoomKeyContent(

@Json(name = "algorithm")
var algorithm: String? = null,
val algorithm: String? = null,

@Json(name = "room_id")
var roomId: String? = null,
val roomId: String? = null,

@Json(name = "session_id")
var sessionId: String? = null,
val sessionId: String? = null,

@Json(name = "session_key")
var sessionKey: String? = null,
val sessionKey: String? = null,

// should be a Long but it is sometimes a double
@Json(name = "chain_index")
var chainIndex: Any? = null
val chainIndex: Any? = null
)

View File

@ -25,23 +25,23 @@ import com.squareup.moshi.JsonClass
data class ForwardedRoomKeyContent(

@Json(name = "algorithm")
var algorithm: String? = null,
val algorithm: String? = null,

@Json(name = "room_id")
var roomId: String? = null,
val roomId: String? = null,

@Json(name = "sender_key")
var senderKey: String? = null,
val senderKey: String? = null,

@Json(name = "session_id")
var sessionId: String? = null,
val sessionId: String? = null,

@Json(name = "session_key")
var sessionKey: String? = null,
val sessionKey: String? = null,

@Json(name = "forwarding_curve25519_key_chain")
var forwardingCurve25519KeyChain: List<String>? = null,
val forwardingCurve25519KeyChain: List<String>? = null,

@Json(name = "sender_claimed_ed25519_key")
var senderClaimedEd25519Key: String? = null
val senderClaimedEd25519Key: String? = null
)

View File

@ -54,14 +54,14 @@ import kotlin.collections.HashMap
* Short codes interactive verification is a more user friendly way of verifying devices
* that is still maintaining a good level of security (alternative to the 43-character strings compare method).
*/
internal class DefaultSasVerificationService(private val mCredentials: Credentials,
private val mCryptoStore: IMXCryptoStore,
private val mMyDeviceInfoHolder: MyDeviceInfoHolder,
internal class DefaultSasVerificationService(private val credentials: Credentials,
private val cryptoStore: IMXCryptoStore,
private val myDeviceInfoHolder: MyDeviceInfoHolder,
private val deviceListManager: DeviceListManager,
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val mSendToDeviceTask: SendToDeviceTask,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val mTaskExecutor: TaskExecutor)
private val taskExecutor: TaskExecutor)
: VerificationTransaction.Listener, SasVerificationService {

private val uiHandler = Handler(Looper.getMainLooper())
@ -194,11 +194,11 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
val tx = IncomingSASVerificationTransaction(
this,
setDeviceVerificationAction,
mCredentials,
mCryptoStore,
mSendToDeviceTask,
mTaskExecutor,
mMyDeviceInfoHolder.myDevice.fingerprint()!!,
credentials,
cryptoStore,
sendToDeviceTask,
taskExecutor,
myDeviceInfoHolder.myDevice.fingerprint()!!,
startReq.transactionID!!,
otherUserId)
addTransaction(tx)
@ -363,11 +363,11 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
val tx = OutgoingSASVerificationRequest(
this,
setDeviceVerificationAction,
mCredentials,
mCryptoStore,
mSendToDeviceTask,
mTaskExecutor,
mMyDeviceInfoHolder.myDevice.fingerprint()!!,
credentials,
cryptoStore,
sendToDeviceTask,
taskExecutor,
myDeviceInfoHolder.myDevice.fingerprint()!!,
txID,
userId,
deviceID)
@ -387,8 +387,8 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
private fun createUniqueIDForTransaction(userId: String, deviceID: String): String {
val buff = StringBuffer()
buff
.append(mCredentials.userId).append("|")
.append(mCredentials.deviceId).append("|")
.append(credentials.userId).append("|")
.append(credentials.deviceId).append("|")
.append(userId).append("|")
.append(deviceID).append("|")
.append(UUID.randomUUID().toString())
@ -413,7 +413,7 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(cancelMessage, userId, userDevice)

mSendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId))
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId))
.dispatchTo(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")
@ -423,6 +423,6 @@ internal class DefaultSasVerificationService(private val mCredentials: Credentia
Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.")
}
})
.executeBy(mTaskExecutor)
.executeBy(taskExecutor)
}
}

View File

@ -103,13 +103,13 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
val contentModule = ContentModule().definition
val cryptoModule = CryptoModule().definition
MatrixKoinHolder.instance.loadModules(listOf(sessionModule,
syncModule,
roomModule,
groupModule,
userModule,
signOutModule,
contentModule,
cryptoModule))
syncModule,
roomModule,
groupModule,
userModule,
signOutModule,
contentModule,
cryptoModule))
scope = getKoin().getOrCreateScope(SCOPE)
if (!monarchy.isMonarchyThreadOpen) {
monarchy.openManually()
@ -350,6 +350,10 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
return cryptoService.decryptEvent(event, timeline)
}

override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult?>) {
return cryptoService.decryptEventAsync(event, timeline, callback)
}

override fun getEncryptionAlgorithm(roomId: String): String? {
return cryptoService.getEncryptionAlgorithm(roomId)
}

View File

@ -16,7 +16,9 @@

package im.vector.matrix.android.internal.session.room.timeline

import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.MXDecryptionException
@ -44,23 +46,7 @@ internal class TimelineEventFactory(private val roomMemberExtractor: SenderRoomM
}
val event = eventEntity.asDomain()
if (event.getClearType() == EventType.ENCRYPTED) {
try {
Timber.v("Encrypted event: try to decrypt ${event.eventId}")
val result = if (decryptionCache.containsKey(eventEntity.localId)) {
Timber.v("Encrypted event ${event.eventId} cached")
decryptionCache[eventEntity.localId]
} else {
cryptoService.decryptEvent(event, timelineId)?.also {
decryptionCache[eventEntity.localId] = it
}
}
event.setClearData(result)
} catch (e: Exception) {
Timber.e(e, "Encrypted event: decryption failed")
if (e is MXDecryptionException) {
event.setCryptoError(e.cryptoError)
}
}
handleEncryptedEvent(event, eventEntity.localId)
}
return TimelineEvent(
event,
@ -72,6 +58,28 @@ internal class TimelineEventFactory(private val roomMemberExtractor: SenderRoomM
)
}

private fun handleEncryptedEvent(event: Event, eventId: String) {
Timber.v("Encrypted event: try to decrypt ${event.eventId}")
val cachedDecryption = decryptionCache[eventId]
if (cachedDecryption != null) {
Timber.v("Encrypted event ${event.eventId} cached")
event.setClearData(cachedDecryption)
} else {
try {
val result = cryptoService.decryptEvent(event, timelineId)
if (result != null) {
decryptionCache[eventId] = result
}
event.setClearData(result)
} catch (failure: Throwable) {
Timber.e(failure, "Encrypted event: decryption failed")
if (failure is MXDecryptionException) {
event.setCryptoError(failure.cryptoError)
}
}
}
}

fun clear() {
senderCache.clear()
}

View File

@ -47,7 +47,7 @@ internal class CryptoSyncHandler(private val cryptoManager: CryptoManager,
}
}

fun onSyncCompleted(syncResponse: SyncResponse, fromToken: String?, catchingUp: Boolean) {
fun onSyncCompleted(syncResponse: SyncResponse) {
cryptoManager.onSyncCompleted(syncResponse)
}


View File

@ -51,7 +51,7 @@ internal class SyncResponseHandler(private val roomSyncHandler: RoomSyncHandler,
userAccountDataSyncHandler.handle(syncResponse.accountData)
}
Timber.v("On sync completed")
cryptoSyncHandler.onSyncCompleted(syncResponse, fromToken, isCatchingUp)
cryptoSyncHandler.onSyncCompleted(syncResponse)
}
val isInitialSync = fromToken == null
if (!cryptoManager.isStarted()) {

View File

@ -67,7 +67,7 @@ class HomeModule {
roomHistoryVisibilityItemFactory = RoomHistoryVisibilityItemFactory(get()),
callItemFactory = CallItemFactory(get()),
encryptionItemFactory = EncryptionItemFactory(get()),
encryptedItemFactory = EncryptedItemFactory(get(), get(), messageItemFactory),
encryptedItemFactory = EncryptedItemFactory(get()),
defaultItemFactory = DefaultItemFactory()
)
TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider)

View File

@ -20,29 +20,18 @@ import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableString
import android.text.style.StyleSpan
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.MXDecryptionException
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_

class EncryptedItemFactory(
private val session: Session,
private val stringProvider: StringProvider,
private val messageItemFactory: MessageItemFactory) {

fun create(timelineEvent: TimelineEvent,
nextEvent: TimelineEvent?,
callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? {
// This class handles timeline event who haven't been successfully decrypted
class EncryptedItemFactory(private val stringProvider: StringProvider) {

fun create(timelineEvent: TimelineEvent): VectorEpoxyModel<*>? {
return when {
EventType.ENCRYPTED == timelineEvent.root.getClearType() -> {
val cryptoError = timelineEvent.root.mCryptoError
@ -54,7 +43,6 @@ class EncryptedItemFactory(
}

val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)

val spannableStr = SpannableString(message)
spannableStr.setSpan(StyleSpan(Typeface.ITALIC), 0, message.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// TODO This is not correct format for error, change it

View File

@ -39,8 +39,9 @@ class RoomHistoryVisibilityItemFactory(private val stringProvider: StringProvide
}

private fun buildNoticeText(event: Event, senderName: String?): CharSequence? {
val content = event.content.toModel<RoomHistoryVisibilityContent>() ?: return null
val formattedVisibility = when (content.historyVisibility) {
val historyVisibility = event.content.toModel<RoomHistoryVisibilityContent>()?.historyVisibility
?: return null
val formattedVisibility = when (historyVisibility) {
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited)
RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined)

View File

@ -51,7 +51,7 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,

EventType.ENCRYPTION -> encryptionItemFactory.create(event)

EventType.ENCRYPTED -> encryptedItemFactory.create(event, nextEvent, callback)
EventType.ENCRYPTED -> encryptedItemFactory.create(event)

EventType.STATE_ROOM_THIRD_PARTY_INVITE,
EventType.STICKER,