mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-18 20:29:10 +03:00
Incoming Secret Share request support
crypto DB migration
This commit is contained in:
parent
8ff31ac49d
commit
b67735c31a
30 changed files with 871 additions and 329 deletions
|
@ -34,7 +34,6 @@ import im.vector.matrix.android.common.assertDictEquals
|
|||
import im.vector.matrix.android.common.assertListEquals
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
|
@ -335,9 +334,9 @@ class KeysBackupTest : InstrumentedTest {
|
|||
// - Check the SDK sent key share requests
|
||||
val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
val unsentRequest = cryptoStore2
|
||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
|
||||
.getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT))
|
||||
val sentRequest = cryptoStore2
|
||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
|
||||
.getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT))
|
||||
|
||||
// Request is either sent or unsent
|
||||
assertTrue(unsentRequest != null || sentRequest != null)
|
||||
|
@ -357,9 +356,9 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
// - There must be no more pending key share requests
|
||||
val unsentRequestAfterRestoration = cryptoStore2
|
||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
|
||||
.getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT))
|
||||
val sentRequestAfterRestoration = cryptoStore2
|
||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
|
||||
.getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT))
|
||||
|
||||
// Request is either sent or unsent
|
||||
assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
|
||||
|
|
|
@ -22,7 +22,7 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
@ -91,9 +91,9 @@ interface CryptoService {
|
|||
|
||||
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody)
|
||||
|
||||
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener)
|
||||
fun addRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
|
||||
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener)
|
||||
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
|
||||
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
package im.vector.matrix.android.api.session.crypto.keyshare
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRequestCancellation
|
||||
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||
|
||||
/**
|
||||
* Room keys events listener
|
||||
*/
|
||||
interface RoomKeysRequestListener {
|
||||
interface GossipingRequestListener {
|
||||
/**
|
||||
* An room key request has been received.
|
||||
*
|
||||
|
@ -30,10 +31,16 @@ interface RoomKeysRequestListener {
|
|||
*/
|
||||
fun onRoomKeyRequest(request: IncomingRoomKeyRequest)
|
||||
|
||||
/**
|
||||
* Returns the secret value to be shared
|
||||
* @return true if is handled
|
||||
*/
|
||||
fun onSecretShareRequest(request: IncomingSecretShareRequest) : Boolean
|
||||
|
||||
/**
|
||||
* A room key request cancellation has been received.
|
||||
*
|
||||
* @param request the cancellation request
|
||||
*/
|
||||
fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation)
|
||||
fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation)
|
||||
}
|
|
@ -33,7 +33,7 @@ import im.vector.matrix.android.api.failure.Failure
|
|||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
|
@ -308,7 +308,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
deviceListManager.invalidateAllDeviceLists()
|
||||
deviceListManager.refreshOutdatedDeviceLists()
|
||||
} else {
|
||||
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
|
||||
incomingRoomKeyRequestManager.processReceivedGossipingRequests()
|
||||
}
|
||||
}.fold(
|
||||
{
|
||||
|
@ -369,7 +369,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
// Make sure we process to-device messages before generating new one-time-keys #2782
|
||||
deviceListManager.refreshOutdatedDeviceLists()
|
||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
|
||||
incomingRoomKeyRequestManager.processReceivedGossipingRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -694,8 +694,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
||||
onRoomKeyEvent(event)
|
||||
}
|
||||
|
||||
EventType.REQUEST_SECRET,
|
||||
EventType.ROOM_KEY_REQUEST -> {
|
||||
incomingRoomKeyRequestManager.onRoomKeyRequestEvent(event)
|
||||
incomingRoomKeyRequestManager.onGossipingRequestEvent(event)
|
||||
}
|
||||
EventType.SEND_SECRET -> {
|
||||
// incomingRoomKeyRequestManager.onGossipingRequestEvent(event)
|
||||
}
|
||||
else -> {
|
||||
// ignore
|
||||
|
@ -1031,20 +1036,20 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Add a RoomKeysRequestListener listener.
|
||||
* Add a GossipingRequestListener listener.
|
||||
*
|
||||
* @param listener listener
|
||||
*/
|
||||
override fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) {
|
||||
override fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
|
||||
incomingRoomKeyRequestManager.addRoomKeysRequestListener(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a RoomKeysRequestListener listener.
|
||||
* Add a GossipingRequestListener listener.
|
||||
*
|
||||
* @param listener listener
|
||||
*/
|
||||
override fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
|
||||
override fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
|
||||
incomingRoomKeyRequestManager.removeRoomKeysRequestListener(listener)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,12 +18,12 @@ package im.vector.matrix.android.internal.crypto
|
|||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareCancellation
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.ShareRequestCancellation
|
||||
|
||||
/**
|
||||
* IncomingRoomKeyRequestCancellation describes the incoming room key cancellation.
|
||||
* IncomingRequestCancellation describes the incoming room key cancellation.
|
||||
*/
|
||||
data class IncomingRoomKeyRequestCancellation(
|
||||
data class IncomingRequestCancellation(
|
||||
/**
|
||||
* The user id
|
||||
*/
|
||||
|
@ -38,18 +38,18 @@ data class IncomingRoomKeyRequestCancellation(
|
|||
* The request id
|
||||
*/
|
||||
override val requestId: String? = null
|
||||
) : IncomingRoomKeyRequestCommon {
|
||||
) : IncomingShareRequestCommon {
|
||||
companion object {
|
||||
/**
|
||||
* Factory
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
fun fromEvent(event: Event): IncomingRoomKeyRequestCancellation? {
|
||||
fun fromEvent(event: Event): IncomingRequestCancellation? {
|
||||
return event.getClearContent()
|
||||
.toModel<RoomKeyShareCancellation>()
|
||||
.toModel<ShareRequestCancellation>()
|
||||
?.let {
|
||||
IncomingRoomKeyRequestCancellation(
|
||||
IncomingRequestCancellation(
|
||||
userId = event.senderId,
|
||||
deviceId = it.requestingDeviceId,
|
||||
requestId = it.requestId
|
|
@ -57,7 +57,7 @@ data class IncomingRoomKeyRequest(
|
|||
*/
|
||||
@Transient
|
||||
var ignore: Runnable? = null
|
||||
) : IncomingRoomKeyRequestCommon {
|
||||
) : IncomingShareRequestCommon {
|
||||
companion object {
|
||||
/**
|
||||
* Factory
|
||||
|
|
|
@ -17,9 +17,14 @@
|
|||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
|
@ -29,18 +34,21 @@ import javax.inject.Inject
|
|||
internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||
private val credentials: Credentials,
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
private val crossSigningService: CrossSigningService,
|
||||
private val secrSecretCryptoProvider: ShareSecretCryptoProvider,
|
||||
private val roomDecryptorProvider: RoomDecryptorProvider) {
|
||||
|
||||
// list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
|
||||
// we received in the current sync.
|
||||
private val receivedRoomKeyRequests = ArrayList<IncomingRoomKeyRequest>()
|
||||
private val receivedRoomKeyRequestCancellations = ArrayList<IncomingRoomKeyRequestCancellation>()
|
||||
private val receiveGossipingRequests = ArrayList<IncomingShareRequestCommon>()
|
||||
private val receivedRequestCancellations = ArrayList<IncomingRequestCancellation>()
|
||||
|
||||
// the listeners
|
||||
private val roomKeysRequestListeners: MutableSet<RoomKeysRequestListener> = HashSet()
|
||||
private val gossipingRequestListeners: MutableSet<GossipingRequestListener> = HashSet()
|
||||
|
||||
init {
|
||||
receivedRoomKeyRequests.addAll(cryptoStore.getPendingIncomingRoomKeyRequests())
|
||||
receiveGossipingRequests.addAll(cryptoStore.getPendingIncomingSecretShareRequests())
|
||||
receiveGossipingRequests.addAll(cryptoStore.getPendingIncomingRoomKeyRequests())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,101 +57,53 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
|||
*
|
||||
* @param event the announcement event.
|
||||
*/
|
||||
fun onRoomKeyRequestEvent(event: Event) {
|
||||
when (val roomKeyShareAction = event.getClearContent()?.get("action") as? String) {
|
||||
RoomKeyShare.ACTION_SHARE_REQUEST -> IncomingRoomKeyRequest.fromEvent(event)?.let { receivedRoomKeyRequests.add(it) }
|
||||
RoomKeyShare.ACTION_SHARE_CANCELLATION -> IncomingRoomKeyRequestCancellation.fromEvent(event)?.let { receivedRoomKeyRequestCancellations.add(it) }
|
||||
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action $roomKeyShareAction")
|
||||
fun onGossipingRequestEvent(event: Event) {
|
||||
val roomKeyShare = event.getClearContent().toModel<GossipingToDeviceObject>()
|
||||
when (roomKeyShare?.action) {
|
||||
GossipingToDeviceObject.ACTION_SHARE_REQUEST -> {
|
||||
if (event.getClearType() == EventType.REQUEST_SECRET) {
|
||||
IncomingSecretShareRequest.fromEvent(event)?.let {
|
||||
receiveGossipingRequests.add(it)
|
||||
}
|
||||
} else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
|
||||
IncomingRoomKeyRequest.fromEvent(event)?.let {
|
||||
receiveGossipingRequests.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> IncomingRequestCancellation.fromEvent(event)?.let { receivedRequestCancellations.add(it) }
|
||||
else -> Timber.e("## onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process any m.room_key_request events which were queued up during the
|
||||
* Process any m.room_key_request or m.secret.request events which were queued up during the
|
||||
* current sync.
|
||||
* It must be called on CryptoThread
|
||||
*/
|
||||
fun processReceivedRoomKeyRequests() {
|
||||
val roomKeyRequestsToProcess = receivedRoomKeyRequests.toList()
|
||||
receivedRoomKeyRequests.clear()
|
||||
fun processReceivedGossipingRequests() {
|
||||
val roomKeyRequestsToProcess = receiveGossipingRequests.toList()
|
||||
receiveGossipingRequests.clear()
|
||||
for (request in roomKeyRequestsToProcess) {
|
||||
val userId = request.userId
|
||||
val deviceId = request.deviceId
|
||||
val body = request.requestBody
|
||||
val roomId = body!!.roomId
|
||||
val alg = body.algorithm
|
||||
|
||||
Timber.v("m.room_key_request from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
|
||||
if (userId == null || credentials.userId != userId) {
|
||||
// TODO: determine if we sent this device the keys already: in
|
||||
Timber.w("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now")
|
||||
return
|
||||
}
|
||||
// TODO: should we queue up requests we don't yet have keys for, in case they turn up later?
|
||||
// if we don't have a decryptor for this room/alg, we don't have
|
||||
// the keys for the requested events, and can drop the requests.
|
||||
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
|
||||
if (null == decryptor) {
|
||||
Timber.w("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId")
|
||||
continue
|
||||
}
|
||||
if (!decryptor.hasKeysForKeyRequest(request)) {
|
||||
Timber.w("## processReceivedRoomKeyRequests() : room key request for unknown session ${body.sessionId!!}")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
continue
|
||||
}
|
||||
|
||||
if (credentials.deviceId == deviceId && credentials.userId == userId) {
|
||||
Timber.v("## processReceivedRoomKeyRequests() : oneself device - ignored")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
continue
|
||||
}
|
||||
request.share = Runnable {
|
||||
decryptor.shareKeysWithDevice(request)
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
}
|
||||
request.ignore = Runnable {
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
}
|
||||
// if the device is verified already, share the keys
|
||||
val device = cryptoStore.getUserDevice(userId, deviceId!!)
|
||||
if (device != null) {
|
||||
if (device.isVerified) {
|
||||
Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
request.share?.run()
|
||||
continue
|
||||
}
|
||||
|
||||
if (device.isBlocked) {
|
||||
Timber.v("## processReceivedRoomKeyRequests() : device is blocked -> ignored")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If cross signing is available on account we automatically discard untrust devices request
|
||||
if (cryptoStore.getMyCrossSigningInfo() != null) {
|
||||
// At this point the device is unknown, we don't want to bother user with that
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
continue
|
||||
}
|
||||
|
||||
cryptoStore.storeIncomingRoomKeyRequest(request)
|
||||
onRoomKeyRequest(request)
|
||||
}
|
||||
|
||||
var receivedRoomKeyRequestCancellations: List<IncomingRoomKeyRequestCancellation>? = null
|
||||
|
||||
synchronized(this.receivedRoomKeyRequestCancellations) {
|
||||
if (this.receivedRoomKeyRequestCancellations.isNotEmpty()) {
|
||||
receivedRoomKeyRequestCancellations = this.receivedRoomKeyRequestCancellations.toList()
|
||||
this.receivedRoomKeyRequestCancellations.clear()
|
||||
if (request is IncomingRoomKeyRequest) {
|
||||
processIncomingRoomKeyRequest(request)
|
||||
} else if (request is IncomingSecretShareRequest) {
|
||||
processIncomingSecretShareRequest(request)
|
||||
}
|
||||
}
|
||||
|
||||
if (null != receivedRoomKeyRequestCancellations) {
|
||||
for (request in receivedRoomKeyRequestCancellations!!) {
|
||||
Timber.v("## ## processReceivedRoomKeyRequests() : m.room_key_request cancellation for " + request.userId
|
||||
var receivedRequestCancellations: List<IncomingRequestCancellation>? = null
|
||||
|
||||
synchronized(this.receivedRequestCancellations) {
|
||||
if (this.receivedRequestCancellations.isNotEmpty()) {
|
||||
receivedRequestCancellations = this.receivedRequestCancellations.toList()
|
||||
this.receivedRequestCancellations.clear()
|
||||
}
|
||||
}
|
||||
|
||||
if (null != receivedRequestCancellations) {
|
||||
for (request in receivedRequestCancellations!!) {
|
||||
Timber.v("## ## processReceivedGossipingRequests() : m.room_key_request cancellation for " + request.userId
|
||||
+ ":" + request.deviceId + " id " + request.requestId)
|
||||
|
||||
// we should probably only notify the app of cancellations we told it
|
||||
|
@ -155,14 +115,122 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun processIncomingRoomKeyRequest(request: IncomingRoomKeyRequest) {
|
||||
val userId = request.userId
|
||||
val deviceId = request.deviceId
|
||||
val body = request.requestBody
|
||||
val roomId = body!!.roomId
|
||||
val alg = body.algorithm
|
||||
|
||||
Timber.v("m.room_key_request from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
|
||||
if (userId == null || credentials.userId != userId) {
|
||||
// TODO: determine if we sent this device the keys already: in
|
||||
Timber.w("## processReceivedGossipingRequests() : Ignoring room key request from other user for now")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
return
|
||||
}
|
||||
// TODO: should we queue up requests we don't yet have keys for, in case they turn up later?
|
||||
// if we don't have a decryptor for this room/alg, we don't have
|
||||
// the keys for the requested events, and can drop the requests.
|
||||
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
|
||||
if (null == decryptor) {
|
||||
Timber.w("## processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId")
|
||||
return
|
||||
}
|
||||
if (!decryptor.hasKeysForKeyRequest(request)) {
|
||||
Timber.w("## processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
return
|
||||
}
|
||||
|
||||
if (credentials.deviceId == deviceId && credentials.userId == userId) {
|
||||
Timber.v("## processReceivedGossipingRequests() : oneself device - ignored")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
return
|
||||
}
|
||||
request.share = Runnable {
|
||||
decryptor.shareKeysWithDevice(request)
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
}
|
||||
request.ignore = Runnable {
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
}
|
||||
// if the device is verified already, share the keys
|
||||
val device = cryptoStore.getUserDevice(userId, deviceId!!)
|
||||
if (device != null) {
|
||||
if (device.isVerified) {
|
||||
Timber.v("## processReceivedGossipingRequests() : device is already verified: sharing keys")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
request.share?.run()
|
||||
return
|
||||
}
|
||||
|
||||
if (device.isBlocked) {
|
||||
Timber.v("## processReceivedGossipingRequests() : device is blocked -> ignored")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If cross signing is available on account we automatically discard untrust devices request
|
||||
if (cryptoStore.getMyCrossSigningInfo() != null) {
|
||||
// At this point the device is unknown, we don't want to bother user with that
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
return
|
||||
}
|
||||
|
||||
cryptoStore.storeIncomingRoomKeyRequest(request)
|
||||
|
||||
// Legacy, pass to application layer to decide what to do
|
||||
onRoomKeyRequest(request)
|
||||
}
|
||||
|
||||
private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) {
|
||||
val secretName = request.secretName ?: return Unit.also {
|
||||
cryptoStore.deleteIncomingSecretRequest(request)
|
||||
Timber.v("## processIncomingSecretShareRequest() : Missing secret name")
|
||||
}
|
||||
|
||||
val userId = request.userId
|
||||
if (userId == null || credentials.userId != userId) {
|
||||
Timber.e("## processIncomingSecretShareRequest() : Ignoring secret share request from other user")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
return
|
||||
}
|
||||
|
||||
when (secretName) {
|
||||
SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
|
||||
USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
|
||||
else -> null
|
||||
}?.let { secretValue ->
|
||||
// TODO check if locally trusted and not outdated
|
||||
if (cryptoStore.getUserDevice(userId, request.deviceId ?: "")?.trustLevel?.isLocallyVerified() == true) {
|
||||
secrSecretCryptoProvider.shareSecretWithDevice(request, secretValue)
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
request.ignore = Runnable {
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
}
|
||||
|
||||
request.share = { secretValue ->
|
||||
secrSecretCryptoProvider.shareSecretWithDevice(request, secretValue)
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
}
|
||||
|
||||
onShareRequest(request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch onRoomKeyRequest
|
||||
*
|
||||
* @param request the request
|
||||
*/
|
||||
private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
|
||||
synchronized(roomKeysRequestListeners) {
|
||||
for (listener in roomKeysRequestListeners) {
|
||||
synchronized(gossipingRequestListeners) {
|
||||
for (listener in gossipingRequestListeners) {
|
||||
try {
|
||||
listener.onRoomKeyRequest(request)
|
||||
} catch (e: Exception) {
|
||||
|
@ -172,14 +240,33 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for a value to the listeners, and take the first one
|
||||
*/
|
||||
private fun onShareRequest(request: IncomingSecretShareRequest) {
|
||||
synchronized(gossipingRequestListeners) {
|
||||
for (listener in gossipingRequestListeners) {
|
||||
try {
|
||||
if (listener.onSecretShareRequest(request)) {
|
||||
return
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## onRoomKeyRequest() failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Not handled, ignore
|
||||
request.ignore?.run()
|
||||
}
|
||||
|
||||
/**
|
||||
* A room key request cancellation has been received.
|
||||
*
|
||||
* @param request the cancellation request
|
||||
*/
|
||||
private fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) {
|
||||
synchronized(roomKeysRequestListeners) {
|
||||
for (listener in roomKeysRequestListeners) {
|
||||
private fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) {
|
||||
synchronized(gossipingRequestListeners) {
|
||||
for (listener in gossipingRequestListeners) {
|
||||
try {
|
||||
listener.onRoomKeyRequestCancellation(request)
|
||||
} catch (e: Exception) {
|
||||
|
@ -189,15 +276,15 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) {
|
||||
synchronized(roomKeysRequestListeners) {
|
||||
roomKeysRequestListeners.add(listener)
|
||||
fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
|
||||
synchronized(gossipingRequestListeners) {
|
||||
gossipingRequestListeners.add(listener)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
|
||||
synchronized(roomKeysRequestListeners) {
|
||||
roomKeysRequestListeners.remove(listener)
|
||||
fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
|
||||
synchronized(gossipingRequestListeners) {
|
||||
gossipingRequestListeners.remove(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.SecretShareRequest
|
||||
|
||||
/**
|
||||
* IncomingRoomKeyRequest class defines the incoming room keys request.
|
||||
*/
|
||||
data class IncomingSecretShareRequest(
|
||||
/**
|
||||
* The user id
|
||||
*/
|
||||
override val userId: String? = null,
|
||||
|
||||
/**
|
||||
* The device id
|
||||
*/
|
||||
override val deviceId: String? = null,
|
||||
|
||||
/**
|
||||
* The request id
|
||||
*/
|
||||
override val requestId: String? = null,
|
||||
|
||||
/**
|
||||
* The request body
|
||||
*/
|
||||
val secretName: String? = null,
|
||||
|
||||
/**
|
||||
* The runnable to call to accept to share the keys
|
||||
*/
|
||||
@Transient
|
||||
var share: ((String) -> Unit)? = null,
|
||||
|
||||
/**
|
||||
* The runnable to call to ignore the key share request.
|
||||
*/
|
||||
@Transient
|
||||
var ignore: Runnable? = null
|
||||
|
||||
) : IncomingShareRequestCommon {
|
||||
companion object {
|
||||
/**
|
||||
* Factory
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
fun fromEvent(event: Event): IncomingSecretShareRequest? {
|
||||
return event.getClearContent()
|
||||
.toModel<SecretShareRequest>()
|
||||
?.let {
|
||||
IncomingSecretShareRequest(
|
||||
userId = event.senderId,
|
||||
deviceId = it.requestingDeviceId,
|
||||
requestId = it.requestId,
|
||||
secretName = it.secretName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
interface IncomingRoomKeyRequestCommon {
|
||||
interface IncomingShareRequestCommon {
|
||||
/**
|
||||
* The user id
|
||||
*/
|
|
@ -25,14 +25,14 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
|||
class OutgoingRoomKeyRequest(
|
||||
// RequestBody
|
||||
var requestBody: RoomKeyRequestBody?, // list of recipients for the request
|
||||
var recipients: List<Map<String, String>>, // Unique id for this request. Used for both
|
||||
override var recipients: List<Map<String, String>>, // Unique id for this request. Used for both
|
||||
// an id within the request for later pairing with a cancellation, and for
|
||||
// the transaction id when sending the to_device messages to our local
|
||||
var requestId: String, // current state of this request
|
||||
var state: RequestState) {
|
||||
override var requestId: String, // current state of this request
|
||||
override var state: ShareRequestState) : OutgoingShareRequest {
|
||||
|
||||
// transaction id for the cancellation, if any
|
||||
var cancellationTxnId: String? = null
|
||||
override var cancellationTxnId: String? = null
|
||||
|
||||
/**
|
||||
* Used only for log.
|
||||
|
@ -53,66 +53,4 @@ class OutgoingRoomKeyRequest(
|
|||
get() = if (null != requestBody) {
|
||||
requestBody!!.sessionId
|
||||
} else null
|
||||
|
||||
/**
|
||||
* possible states for a room key request
|
||||
*
|
||||
*
|
||||
* The state machine looks like:
|
||||
* <pre>
|
||||
*
|
||||
* |
|
||||
* V
|
||||
* UNSENT -----------------------------+
|
||||
* | |
|
||||
* | (send successful) | (cancellation requested)
|
||||
* V |
|
||||
* SENT |
|
||||
* |-------------------------------- | --------------+
|
||||
* | | |
|
||||
* | | | (cancellation requested with intent
|
||||
* | | | to resend a new request)
|
||||
* | (cancellation requested) | |
|
||||
* V | V
|
||||
* CANCELLATION_PENDING | CANCELLATION_PENDING_AND_WILL_RESEND
|
||||
* | | |
|
||||
* | (cancellation sent) | | (cancellation sent. Create new request
|
||||
* | | | in the UNSENT state)
|
||||
* V | |
|
||||
* (deleted) <---------------------------+----------------+
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
enum class RequestState {
|
||||
/**
|
||||
* request not yet sent
|
||||
*/
|
||||
UNSENT,
|
||||
/**
|
||||
* request sent, awaiting reply
|
||||
*/
|
||||
SENT,
|
||||
/**
|
||||
* reply received, cancellation not yet sent
|
||||
*/
|
||||
CANCELLATION_PENDING,
|
||||
/**
|
||||
* Cancellation not yet sent, once sent, a new request will be done
|
||||
*/
|
||||
CANCELLATION_PENDING_AND_WILL_RESEND,
|
||||
/**
|
||||
* sending failed
|
||||
*/
|
||||
FAILED;
|
||||
|
||||
companion object {
|
||||
fun from(state: Int) = when (state) {
|
||||
0 -> UNSENT
|
||||
1 -> SENT
|
||||
2 -> CANCELLATION_PENDING
|
||||
3 -> CANCELLATION_PENDING_AND_WILL_RESEND
|
||||
else /*4*/ -> FAILED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareCancellation
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.ShareRequestCancellation
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||
|
@ -89,9 +89,9 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||
*/
|
||||
fun sendRoomKeyRequest(requestBody: RoomKeyRequestBody?, recipients: List<Map<String, String>>) {
|
||||
val req = cryptoStore.getOrAddOutgoingRoomKeyRequest(
|
||||
OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), OutgoingRoomKeyRequest.RequestState.UNSENT))
|
||||
OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), ShareRequestState.UNSENT))
|
||||
|
||||
if (req?.state == OutgoingRoomKeyRequest.RequestState.UNSENT) {
|
||||
if (req?.state == ShareRequestState.UNSENT) {
|
||||
startTimer()
|
||||
}
|
||||
}
|
||||
|
@ -132,20 +132,20 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||
Timber.v("cancelRoomKeyRequest: requestId: " + req.requestId + " state: " + req.state + " andResend: " + andResend)
|
||||
|
||||
when (req.state) {
|
||||
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING,
|
||||
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> {
|
||||
ShareRequestState.CANCELLATION_PENDING,
|
||||
ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> {
|
||||
// nothing to do here
|
||||
}
|
||||
OutgoingRoomKeyRequest.RequestState.UNSENT,
|
||||
OutgoingRoomKeyRequest.RequestState.FAILED -> {
|
||||
ShareRequestState.UNSENT,
|
||||
ShareRequestState.FAILED -> {
|
||||
Timber.v("## cancelRoomKeyRequest() : deleting unnecessary room key request for $requestBody")
|
||||
cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId)
|
||||
}
|
||||
OutgoingRoomKeyRequest.RequestState.SENT -> {
|
||||
ShareRequestState.SENT -> {
|
||||
if (andResend) {
|
||||
req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND
|
||||
req.state = ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND
|
||||
} else {
|
||||
req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING
|
||||
req.state = ShareRequestState.CANCELLATION_PENDING
|
||||
}
|
||||
req.cancellationTxnId = makeTxnId()
|
||||
cryptoStore.updateOutgoingRoomKeyRequest(req)
|
||||
|
@ -187,9 +187,9 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||
|
||||
Timber.v("## sendOutgoingRoomKeyRequests() : Looking for queued outgoing room key requests")
|
||||
val outgoingRoomKeyRequest = cryptoStore.getOutgoingRoomKeyRequestByState(
|
||||
setOf(OutgoingRoomKeyRequest.RequestState.UNSENT,
|
||||
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING,
|
||||
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND))
|
||||
setOf(ShareRequestState.UNSENT,
|
||||
ShareRequestState.CANCELLATION_PENDING,
|
||||
ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND))
|
||||
|
||||
if (null == outgoingRoomKeyRequest) {
|
||||
Timber.v("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests")
|
||||
|
@ -197,7 +197,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
if (OutgoingRoomKeyRequest.RequestState.UNSENT === outgoingRoomKeyRequest.state) {
|
||||
if (ShareRequestState.UNSENT === outgoingRoomKeyRequest.state) {
|
||||
sendOutgoingRoomKeyRequest(outgoingRoomKeyRequest)
|
||||
} else {
|
||||
sendOutgoingRoomKeyRequestCancellation(outgoingRoomKeyRequest)
|
||||
|
@ -220,8 +220,8 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||
)
|
||||
|
||||
sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback<Unit> {
|
||||
private fun onDone(state: OutgoingRoomKeyRequest.RequestState) {
|
||||
if (request.state !== OutgoingRoomKeyRequest.RequestState.UNSENT) {
|
||||
private fun onDone(state: ShareRequestState) {
|
||||
if (request.state !== ShareRequestState.UNSENT) {
|
||||
Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to ${request.state}")
|
||||
} else {
|
||||
request.state = state
|
||||
|
@ -234,12 +234,12 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.v("## sendOutgoingRoomKeyRequest succeed")
|
||||
onDone(OutgoingRoomKeyRequest.RequestState.SENT)
|
||||
onDone(ShareRequestState.SENT)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e("## sendOutgoingRoomKeyRequest failed")
|
||||
onDone(OutgoingRoomKeyRequest.RequestState.FAILED)
|
||||
onDone(ShareRequestState.FAILED)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||
+ " to " + request.recipients
|
||||
+ " cancellation id " + request.cancellationTxnId)
|
||||
|
||||
val roomKeyShareCancellation = RoomKeyShareCancellation(
|
||||
val roomKeyShareCancellation = ShareRequestCancellation(
|
||||
requestingDeviceId = cryptoStore.getDeviceId(),
|
||||
requestId = request.cancellationTxnId
|
||||
)
|
||||
|
@ -268,7 +268,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.v("## sendOutgoingRoomKeyRequestCancellation() : done")
|
||||
val resend = request.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND
|
||||
val resend = request.state === ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND
|
||||
|
||||
onDone()
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
/**
|
||||
* Represents an outgoing room key request
|
||||
*/
|
||||
class OutgoingSecretRequest(
|
||||
// Secret Name
|
||||
var secretName: String?,
|
||||
// list of recipients for the request
|
||||
override var recipients: List<Map<String, String>>,
|
||||
// Unique id for this request. Used for both
|
||||
// an id within the request for later pairing with a cancellation, and for
|
||||
// the transaction id when sending the to_device messages to our local
|
||||
override var requestId: String,
|
||||
// current state of this request
|
||||
override var state: ShareRequestState) : OutgoingShareRequest {
|
||||
|
||||
// transaction id for the cancellation, if any
|
||||
override var cancellationTxnId: 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.crypto
|
||||
|
||||
interface OutgoingShareRequest {
|
||||
var recipients: List<Map<String, String>>
|
||||
var requestId: String
|
||||
var state: ShareRequestState
|
||||
// transaction id for the cancellation, if any
|
||||
var cancellationTxnId: String?
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
/**
|
||||
* possible states for a room key request
|
||||
*
|
||||
*
|
||||
* The state machine looks like:
|
||||
* <pre>
|
||||
*
|
||||
* |
|
||||
* V
|
||||
* UNSENT -----------------------------+
|
||||
* | |
|
||||
* | (send successful) | (cancellation requested)
|
||||
* V |
|
||||
* SENT |
|
||||
* |-------------------------------- | --------------+
|
||||
* | | |
|
||||
* | | | (cancellation requested with intent
|
||||
* | | | to resend a new request)
|
||||
* | (cancellation requested) | |
|
||||
* V | V
|
||||
* CANCELLATION_PENDING | CANCELLATION_PENDING_AND_WILL_RESEND
|
||||
* | | |
|
||||
* | (cancellation sent) | | (cancellation sent. Create new request
|
||||
* | | | in the UNSENT state)
|
||||
* V | |
|
||||
* (deleted) <---------------------------+----------------+
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
enum class ShareRequestState {
|
||||
/**
|
||||
* request not yet sent
|
||||
*/
|
||||
UNSENT,
|
||||
/**
|
||||
* request sent, awaiting reply
|
||||
*/
|
||||
SENT,
|
||||
/**
|
||||
* reply received, cancellation not yet sent
|
||||
*/
|
||||
CANCELLATION_PENDING,
|
||||
/**
|
||||
* Cancellation not yet sent, once sent, a new request will be done
|
||||
*/
|
||||
CANCELLATION_PENDING_AND_WILL_RESEND,
|
||||
/**
|
||||
* sending failed
|
||||
*/
|
||||
FAILED;
|
||||
|
||||
companion object {
|
||||
fun from(state: Int) = when (state) {
|
||||
0 -> UNSENT
|
||||
1 -> SENT
|
||||
2 -> CANCELLATION_PENDING
|
||||
3 -> CANCELLATION_PENDING_AND_WILL_RESEND
|
||||
else /*4*/ -> FAILED
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ShareSecretCryptoProvider @Inject constructor(
|
||||
val messageEncrypter: MessageEncrypter,
|
||||
val sendToDeviceTask: SendToDeviceTask,
|
||||
val deviceListManager: DeviceListManager,
|
||||
val cryptoCoroutineScope: CoroutineScope,
|
||||
val cryptoStore: IMXCryptoStore,
|
||||
val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||
) {
|
||||
fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue: String) {
|
||||
val userId = request.userId ?: return
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
|
||||
.mapCatching {
|
||||
val deviceId = request.deviceId
|
||||
val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "") ?: throw RuntimeException()
|
||||
|
||||
Timber.i("## shareSecretWithDevice() : sharing secret ${request.secretName} with device $userId:$deviceId")
|
||||
|
||||
val payloadJson = mutableMapOf<String, Any>("type" to EventType.SEND_SECRET)
|
||||
payloadJson["content"] = SecretSendEventContent(
|
||||
requestId = request.requestId ?: "",
|
||||
secretValue = secretValue
|
||||
)
|
||||
|
||||
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
|
||||
Timber.i("## shareSecretWithDevice() : sending to $userId:$deviceId")
|
||||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
|
||||
sendToDeviceTask.execute(sendToDeviceParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.algorithms
|
|||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||
|
||||
|
@ -65,4 +66,6 @@ internal interface IMXDecrypting {
|
|||
* @param request keyRequest
|
||||
*/
|
||||
fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {}
|
||||
|
||||
fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue : String) {}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.internal.crypto.model.event
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Class representing an encrypted event content
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SecretSendEventContent(
|
||||
@Json(name = "request_id") val requestId: String,
|
||||
@Json(name = "secret") val secretValue: String
|
||||
)
|
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
|||
* Interface representing an room key action request
|
||||
* Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare]
|
||||
*/
|
||||
internal interface RoomKeyShare : SendToDeviceObject {
|
||||
internal interface GossipingToDeviceObject : SendToDeviceObject {
|
||||
|
||||
val action: String?
|
||||
|
|
@ -25,7 +25,7 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
internal data class RoomKeyShareRequest(
|
||||
@Json(name = "action")
|
||||
override val action: String? = RoomKeyShare.ACTION_SHARE_REQUEST,
|
||||
override val action: String? = GossipingToDeviceObject.ACTION_SHARE_REQUEST,
|
||||
|
||||
@Json(name = "requesting_device_id")
|
||||
override val requestingDeviceId: String? = null,
|
||||
|
@ -35,4 +35,4 @@ internal data class RoomKeyShareRequest(
|
|||
|
||||
@Json(name = "body")
|
||||
val body: RoomKeyRequestBody? = null
|
||||
) : RoomKeyShare
|
||||
) : GossipingToDeviceObject
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.internal.crypto.model.rest
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Class representing a room key request content
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class SecretShareRequest(
|
||||
@Json(name = "action")
|
||||
override val action: String? = GossipingToDeviceObject.ACTION_SHARE_REQUEST,
|
||||
|
||||
@Json(name = "requesting_device_id")
|
||||
override val requestingDeviceId: String? = null,
|
||||
|
||||
@Json(name = "request_id")
|
||||
override val requestId: String? = null,
|
||||
|
||||
@Json(name = "name")
|
||||
val secretName: String? = null
|
||||
) : GossipingToDeviceObject
|
|
@ -17,18 +17,19 @@ package im.vector.matrix.android.internal.crypto.model.rest
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject.Companion.ACTION_SHARE_CANCELLATION
|
||||
|
||||
/**
|
||||
* Class representing a room key request cancellation content
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class RoomKeyShareCancellation(
|
||||
internal data class ShareRequestCancellation(
|
||||
@Json(name = "action")
|
||||
override val action: String? = RoomKeyShare.ACTION_SHARE_CANCELLATION,
|
||||
override val action: String? = ACTION_SHARE_CANCELLATION,
|
||||
|
||||
@Json(name = "requesting_device_id")
|
||||
override val requestingDeviceId: String? = null,
|
||||
|
||||
@Json(name = "request_id")
|
||||
override val requestId: String? = null
|
||||
) : RoomKeyShare
|
||||
) : GossipingToDeviceObject
|
|
@ -20,10 +20,12 @@ package im.vector.matrix.android.internal.crypto.store
|
|||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCommon
|
||||
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.ShareRequestState
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||
|
@ -116,6 +118,7 @@ internal interface IMXCryptoStore {
|
|||
* @return the pending IncomingRoomKeyRequest requests
|
||||
*/
|
||||
fun getPendingIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||
fun getPendingIncomingSecretShareRequests(): List<IncomingSecretShareRequest>
|
||||
|
||||
/**
|
||||
* Indicate if the store contains data for the passed account.
|
||||
|
@ -355,7 +358,7 @@ internal interface IMXCryptoStore {
|
|||
* @param states the states
|
||||
* @return an OutgoingRoomKeyRequest or null
|
||||
*/
|
||||
fun getOutgoingRoomKeyRequestByState(states: Set<OutgoingRoomKeyRequest.RequestState>): OutgoingRoomKeyRequest?
|
||||
fun getOutgoingRoomKeyRequestByState(states: Set<ShareRequestState>): OutgoingRoomKeyRequest?
|
||||
|
||||
/**
|
||||
* Update an existing outgoing request.
|
||||
|
@ -383,7 +386,9 @@ internal interface IMXCryptoStore {
|
|||
*
|
||||
* @param incomingRoomKeyRequest the incoming key request
|
||||
*/
|
||||
fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequestCommon)
|
||||
fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon)
|
||||
|
||||
fun deleteIncomingSecretRequest(request: IncomingSecretShareRequest)
|
||||
|
||||
/**
|
||||
* Search an IncomingRoomKeyRequest
|
||||
|
|
|
@ -24,9 +24,11 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningIn
|
|||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCommon
|
||||
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||
import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon
|
||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.ShareRequestState
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
|
@ -46,6 +48,8 @@ import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity
|
|||
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntityFields
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntityFields
|
||||
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.OlmInboundGroupSessionEntity
|
||||
|
@ -832,7 +836,7 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
return request
|
||||
}
|
||||
|
||||
override fun getOutgoingRoomKeyRequestByState(states: Set<OutgoingRoomKeyRequest.RequestState>): OutgoingRoomKeyRequest? {
|
||||
override fun getOutgoingRoomKeyRequestByState(states: Set<ShareRequestState>): OutgoingRoomKeyRequest? {
|
||||
val statesIndex = states.map { it.ordinal }.toTypedArray()
|
||||
return doRealmQueryAndCopy(realmConfiguration) {
|
||||
it.where<OutgoingRoomKeyRequestEntity>()
|
||||
|
@ -889,7 +893,7 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequestCommon) {
|
||||
override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
it.where<IncomingRoomKeyRequestEntity>()
|
||||
.equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId)
|
||||
|
@ -900,6 +904,18 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun deleteIncomingSecretRequest(request: IncomingSecretShareRequest) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
it.where<IncomingSecretRequestEntity>()
|
||||
.equalTo(IncomingSecretRequestEntityFields.USER_ID, request.userId)
|
||||
.equalTo(IncomingSecretRequestEntityFields.DEVICE_ID, request.deviceId)
|
||||
.equalTo(IncomingSecretRequestEntityFields.REQUEST_ID, request.requestId)
|
||||
.equalTo(IncomingSecretRequestEntityFields.SECRET_NAME, request.secretName)
|
||||
.findAll()
|
||||
.deleteAllFromRealm()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest? {
|
||||
return doRealmQueryAndCopy(realmConfiguration) {
|
||||
it.where<IncomingRoomKeyRequestEntity>()
|
||||
|
@ -922,6 +938,15 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
.toMutableList()
|
||||
}
|
||||
|
||||
override fun getPendingIncomingSecretShareRequests(): List<IncomingSecretShareRequest> {
|
||||
return doRealmQueryAndCopyList(realmConfiguration) {
|
||||
it.where<IncomingSecretRequestEntity>()
|
||||
.findAll()
|
||||
}.map {
|
||||
it.toIncomingSecretShareRequest()
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Cross Signing
|
||||
* ========================================================================================== */
|
||||
|
|
|
@ -23,7 +23,9 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
|||
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.DeviceInfoEntityFields
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntityFields
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntityFields
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingSecretRequestEntityFields
|
||||
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.di.SerializeNulls
|
||||
|
@ -34,102 +36,125 @@ import timber.log.Timber
|
|||
internal object RealmCryptoStoreMigration : RealmMigration {
|
||||
|
||||
// Version 1L added Cross Signing info persistence
|
||||
const val CRYPTO_STORE_SCHEMA_VERSION = 1L
|
||||
const val CRYPTO_STORE_SCHEMA_VERSION = 2L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.v("Migrating Realm Crypto from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) {
|
||||
Timber.d("Step 0 -> 1")
|
||||
Timber.d("Create KeyInfoEntity")
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
if (oldVersion <= 1) migrateTo2(realm)
|
||||
}
|
||||
|
||||
val trustLevelentityEntitySchema = realm.schema.create("TrustLevelEntity")
|
||||
.addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
|
||||
.setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true)
|
||||
.addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
|
||||
.setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
Timber.d("Step 0 -> 1")
|
||||
Timber.d("Create KeyInfoEntity")
|
||||
|
||||
val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity")
|
||||
.addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java)
|
||||
.addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
|
||||
.addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
|
||||
.addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema)
|
||||
val trustLevelentityEntitySchema = realm.schema.create("TrustLevelEntity")
|
||||
.addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
|
||||
.setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true)
|
||||
.addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
|
||||
.setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
|
||||
|
||||
Timber.d("Create CrossSigningInfoEntity")
|
||||
val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity")
|
||||
.addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java)
|
||||
.addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
|
||||
.addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
|
||||
.addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema)
|
||||
|
||||
val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity")
|
||||
.addField(CrossSigningInfoEntityFields.USER_ID, String::class.java)
|
||||
.addPrimaryKey(CrossSigningInfoEntityFields.USER_ID)
|
||||
.addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema)
|
||||
Timber.d("Create CrossSigningInfoEntity")
|
||||
|
||||
Timber.d("Updating UserEntity table")
|
||||
realm.schema.get("UserEntity")
|
||||
?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema)
|
||||
val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity")
|
||||
.addField(CrossSigningInfoEntityFields.USER_ID, String::class.java)
|
||||
.addPrimaryKey(CrossSigningInfoEntityFields.USER_ID)
|
||||
.addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema)
|
||||
|
||||
Timber.d("Updating CryptoMetadataEntity table")
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
|
||||
Timber.d("Updating UserEntity table")
|
||||
realm.schema.get("UserEntity")
|
||||
?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema)
|
||||
|
||||
val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
|
||||
val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
|
||||
List::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
Timber.d("Updating CryptoMetadataEntity table")
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
|
||||
|
||||
realm.schema.get("DeviceInfoEntity")
|
||||
?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
|
||||
?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true)
|
||||
?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema)
|
||||
?.transform { obj ->
|
||||
val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
|
||||
val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
|
||||
List::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
|
||||
val oldSerializedData = obj.getString("deviceInfoData")
|
||||
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { oldDevice ->
|
||||
realm.schema.get("DeviceInfoEntity")
|
||||
?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
|
||||
?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true)
|
||||
?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema)
|
||||
?.transform { obj ->
|
||||
|
||||
val trustLevel = realm.createObject("TrustLevelEntity")
|
||||
when (oldDevice.verified) {
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> {
|
||||
obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> {
|
||||
trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED)
|
||||
trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED)
|
||||
obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> {
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false)
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> {
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
val oldSerializedData = obj.getString("deviceInfoData")
|
||||
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { oldDevice ->
|
||||
|
||||
val trustLevel = realm.createObject("TrustLevelEntity")
|
||||
when (oldDevice.verified) {
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> {
|
||||
obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> {
|
||||
trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED)
|
||||
trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED)
|
||||
obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> {
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false)
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> {
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
|
||||
obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId)
|
||||
obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey())
|
||||
obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms))
|
||||
obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys))
|
||||
obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures))
|
||||
obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned))
|
||||
}
|
||||
|
||||
obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId)
|
||||
obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey())
|
||||
obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms))
|
||||
obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys))
|
||||
obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures))
|
||||
obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned))
|
||||
}
|
||||
?.removeField("deviceInfoData")
|
||||
}
|
||||
}
|
||||
?.removeField("deviceInfoData")
|
||||
}
|
||||
|
||||
private fun migrateTo2(realm: DynamicRealm) {
|
||||
Timber.d("Step 1 -> 2")
|
||||
|
||||
realm.schema.create("IncomingSecretRequestEntity")
|
||||
.addField(IncomingSecretRequestEntityFields.DEVICE_ID, String::class.java)
|
||||
.addField(IncomingSecretRequestEntityFields.SECRET_NAME, String::class.java)
|
||||
.addField(IncomingSecretRequestEntityFields.REQUEST_ID, String::class.java)
|
||||
.addField(IncomingSecretRequestEntityFields.USER_ID, String::class.java)
|
||||
|
||||
|
||||
realm.schema.create("OutgoingSecretRequestEntity")
|
||||
.addField(OutgoingSecretRequestEntityFields.REQUEST_ID, String::class.java)
|
||||
.addPrimaryKey(OutgoingSecretRequestEntityFields.REQUEST_ID)
|
||||
.addField(OutgoingSecretRequestEntityFields.SECRET_NAME, String::class.java)
|
||||
.addField(OutgoingSecretRequestEntityFields.CANCELLATION_TXN_ID, String::class.java)
|
||||
.addField(OutgoingSecretRequestEntityFields.RECIPIENTS_DATA, String::class.java)
|
||||
.addField(OutgoingSecretRequestEntityFields.STATE, Int::class.java)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,13 @@ import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEnt
|
|||
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.IncomingRoomKeyRequestEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntity
|
||||
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.OlmInboundGroupSessionEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingRoomKeyRequestEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingSecretRequestEntity
|
||||
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.annotations.RealmModule
|
||||
|
@ -46,6 +48,8 @@ import io.realm.annotations.RealmModule
|
|||
UserEntity::class,
|
||||
KeyInfoEntity::class,
|
||||
CrossSigningInfoEntity::class,
|
||||
TrustLevelEntity::class
|
||||
TrustLevelEntity::class,
|
||||
IncomingSecretRequestEntity::class,
|
||||
OutgoingSecretRequestEntity::class
|
||||
])
|
||||
internal class RealmCryptoStoreModule
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.store.db.model
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||
import io.realm.RealmObject
|
||||
|
||||
internal open class IncomingSecretRequestEntity(
|
||||
var requestId: String? = null,
|
||||
var userId: String? = null,
|
||||
var deviceId: String? = null,
|
||||
var secretName: String? = null
|
||||
) : RealmObject() {
|
||||
|
||||
fun toIncomingSecretShareRequest(): IncomingSecretShareRequest {
|
||||
return IncomingSecretShareRequest(
|
||||
requestId = requestId,
|
||||
userId = userId,
|
||||
deviceId = deviceId,
|
||||
secretName = secretName
|
||||
)
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.internal.crypto.store.db.model
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.ShareRequestState
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
|
||||
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
|
||||
|
@ -51,7 +52,7 @@ internal open class OutgoingRoomKeyRequestEntity(
|
|||
),
|
||||
getRecipients()!!,
|
||||
requestId!!,
|
||||
OutgoingRoomKeyRequest.RequestState.from(state)
|
||||
ShareRequestState.from(state)
|
||||
).apply {
|
||||
this.cancellationTxnId = cancellationTxnId
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.store.db.model
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest
|
||||
import im.vector.matrix.android.internal.crypto.ShareRequestState
|
||||
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
|
||||
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class OutgoingSecretRequestEntity(
|
||||
@PrimaryKey var requestId: String? = null,
|
||||
var cancellationTxnId: String? = null,
|
||||
// Serialized Json
|
||||
var recipientsData: String? = null,
|
||||
// RoomKeyRequestBody fields
|
||||
var secretName: String? = null,
|
||||
// State
|
||||
var state: Int = 0
|
||||
) : RealmObject() {
|
||||
|
||||
/**
|
||||
* Convert to OutgoingRoomKeyRequest
|
||||
*/
|
||||
fun toOutgoingSecretRequest(): OutgoingSecretRequest {
|
||||
val cancellationTxnId = this.cancellationTxnId
|
||||
return OutgoingSecretRequest(
|
||||
secretName,
|
||||
getRecipients() ?: emptyList(),
|
||||
requestId!!,
|
||||
ShareRequestState.from(state)
|
||||
).apply {
|
||||
this.cancellationTxnId = cancellationTxnId
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRecipients(): List<Map<String, String>>? {
|
||||
return try {
|
||||
deserializeFromRealm(recipientsData)
|
||||
} catch (failure: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun putRecipients(recipients: List<Map<String, String>>?) {
|
||||
recipientsData = serializeForRealm(recipients)
|
||||
}
|
||||
}
|
|
@ -22,13 +22,14 @@ package im.vector.riotx.features.crypto.keysrequest
|
|||
import android.content.Context
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
|
||||
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRequestCancellation
|
||||
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
|
@ -54,7 +55,7 @@ import javax.inject.Singleton
|
|||
|
||||
@Singleton
|
||||
class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||
: RoomKeysRequestListener,
|
||||
: GossipingRequestListener,
|
||||
VerificationService.Listener {
|
||||
|
||||
private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>()
|
||||
|
@ -73,6 +74,13 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
|||
session = null
|
||||
}
|
||||
|
||||
override fun onSecretShareRequest(request: IncomingSecretShareRequest) : Boolean {
|
||||
// By default riotX will not prompt if the SDK has decided that the request should not be fulfilled
|
||||
Timber.v("## onSecretShareRequest() : Ignoring $request")
|
||||
request.ignore?.run()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming key request.
|
||||
*
|
||||
|
@ -194,20 +202,6 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
|||
denyAllRequests(mappingKey)
|
||||
}
|
||||
|
||||
// TODO send to the new profile page
|
||||
// alert.addButton(
|
||||
// context.getString(R.string.start_verification_short_label),
|
||||
// Runnable {
|
||||
// alert.weakCurrentActivity?.get()?.let {
|
||||
// val intent = SASVerificationActivity.outgoingIntent(it,
|
||||
// session?.myUserId ?: "",
|
||||
// userId, deviceId)
|
||||
// it.startActivity(intent)
|
||||
// }
|
||||
// },
|
||||
// false
|
||||
// )
|
||||
|
||||
alert.addButton(context.getString(R.string.share_without_verifying_short_label), Runnable {
|
||||
shareAllSessions(mappingKey)
|
||||
})
|
||||
|
@ -238,7 +232,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
|||
*
|
||||
* @param request the cancellation request.
|
||||
*/
|
||||
override fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) {
|
||||
override fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) {
|
||||
// see if we can find the request in the queue
|
||||
val userId = request.userId
|
||||
val deviceId = request.deviceId
|
||||
|
|
|
@ -187,7 +187,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
|
||||
fun refreshNotificationDrawer() {
|
||||
// Implement last throttler
|
||||
Timber.w("refreshNotificationDrawer()")
|
||||
Timber.v("refreshNotificationDrawer()")
|
||||
backgroundHandler.removeCallbacksAndMessages(null)
|
||||
backgroundHandler.postDelayed(
|
||||
{
|
||||
|
@ -197,7 +197,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
|
||||
@WorkerThread
|
||||
private fun refreshNotificationDrawerBg() {
|
||||
Timber.w("refreshNotificationDrawerBg()")
|
||||
Timber.v("refreshNotificationDrawerBg()")
|
||||
|
||||
val session = activeSessionHolder.getSafeActiveSession() ?: return
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue