mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-01-01 13:58:32 +03:00
crypto: Initial support to answer to-device verification requests
This commit is contained in:
parent
e46578a087
commit
a4e1a5bbcb
3 changed files with 724 additions and 213 deletions
|
@ -49,6 +49,7 @@ import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
|
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
@ -86,7 +87,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
|
import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||||
|
@ -131,9 +132,6 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
private val mxCryptoConfig: MXCryptoConfig,
|
private val mxCryptoConfig: MXCryptoConfig,
|
||||||
// The key backup service.
|
// The key backup service.
|
||||||
private val keysBackupService: DefaultKeysBackupService,
|
private val keysBackupService: DefaultKeysBackupService,
|
||||||
// The verification service.
|
|
||||||
private val verificationService: DefaultVerificationService,
|
|
||||||
|
|
||||||
private val crossSigningService: DefaultCrossSigningService,
|
private val crossSigningService: DefaultCrossSigningService,
|
||||||
// Actions
|
// Actions
|
||||||
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
||||||
|
@ -156,6 +154,9 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
private val isStarting = AtomicBoolean(false)
|
private val isStarting = AtomicBoolean(false)
|
||||||
private val isStarted = AtomicBoolean(false)
|
private val isStarted = AtomicBoolean(false)
|
||||||
private var olmMachine: OlmMachine? = null
|
private var olmMachine: OlmMachine? = null
|
||||||
|
// The verification service.
|
||||||
|
private var verificationService: RustVerificationService? = null
|
||||||
|
|
||||||
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
|
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
|
||||||
|
|
||||||
// Locks for some of our operations
|
// Locks for some of our operations
|
||||||
|
@ -179,6 +180,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||||
|
else -> this.verificationService?.onEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +317,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setRustLogger()
|
setRustLogger()
|
||||||
this.olmMachine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver)
|
val machine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver)
|
||||||
|
this.olmMachine = machine
|
||||||
|
this.verificationService =
|
||||||
|
RustVerificationService(this.taskExecutor, machine, this.sendToDeviceTask)
|
||||||
Timber.v(
|
Timber.v(
|
||||||
"## CRYPTO | Successfully started up an Olm machine for " +
|
"## CRYPTO | Successfully started up an Olm machine for " +
|
||||||
"${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}")
|
"${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}")
|
||||||
|
@ -359,7 +364,32 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* @return the VerificationService
|
* @return the VerificationService
|
||||||
*/
|
*/
|
||||||
override fun verificationService() = verificationService
|
override fun verificationService(): VerificationService {
|
||||||
|
// TODO yet another problem because the CryptoService is started in the
|
||||||
|
// sync loop
|
||||||
|
//
|
||||||
|
// The `KeyRequestHandler` and `IncomingVerificationHandler` want to add
|
||||||
|
// listeners to the verification service, they are initialized in the
|
||||||
|
// `ActiveSessionHolder` class in the `setActiveSession()` method. In
|
||||||
|
// the `setActiveSession()` method we call the `start()` method of the
|
||||||
|
// handlers without first calling the `start()` method of the
|
||||||
|
// `DefaultCrytpoService`.
|
||||||
|
//
|
||||||
|
// The start method of the crypto service isn't part of the
|
||||||
|
// `CryptoService` interface so it currently can't be called there. I'm
|
||||||
|
// inclined to believe that it should be, and that it should be
|
||||||
|
// initialized before anything else tries to do something with it.
|
||||||
|
//
|
||||||
|
// Let's initialize here as a workaround until we figure out if the
|
||||||
|
// above conclusion is correct.
|
||||||
|
if (verificationService == null) {
|
||||||
|
runBlocking {
|
||||||
|
internalStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return verificationService!!
|
||||||
|
}
|
||||||
|
|
||||||
override fun crossSigningService() = crossSigningService
|
override fun crossSigningService() = crossSigningService
|
||||||
|
|
||||||
|
@ -677,6 +707,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
val sessionId = content.sessionId
|
val sessionId = content.sessionId
|
||||||
|
|
||||||
notifyRoomKeyReceival(roomId, sessionId)
|
notifyRoomKeyReceival(roomId, sessionId)
|
||||||
|
} else {
|
||||||
|
this.verificationService?.onEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,33 +26,44 @@ import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
|
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode
|
import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.DeviceListResponse
|
import org.matrix.android.sdk.internal.session.sync.model.DeviceListResponse
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.DeviceOneTimeKeysCountSyncResponse
|
import org.matrix.android.sdk.internal.session.sync.model.DeviceOneTimeKeysCountSyncResponse
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse
|
import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uniffi.olm.CancelCode as RustCancelCode
|
||||||
import uniffi.olm.CryptoStoreErrorException
|
import uniffi.olm.CryptoStoreErrorException
|
||||||
import uniffi.olm.DecryptionErrorException
|
import uniffi.olm.DecryptionErrorException
|
||||||
import uniffi.olm.Sas as InnerSas
|
|
||||||
import uniffi.olm.OutgoingVerificationRequest
|
|
||||||
import uniffi.olm.Device
|
import uniffi.olm.Device
|
||||||
import uniffi.olm.DeviceLists
|
import uniffi.olm.DeviceLists
|
||||||
import uniffi.olm.KeyRequestPair
|
import uniffi.olm.KeyRequestPair
|
||||||
import uniffi.olm.Logger
|
import uniffi.olm.Logger
|
||||||
import uniffi.olm.OlmMachine as InnerMachine
|
import uniffi.olm.OlmMachine as InnerMachine
|
||||||
|
import uniffi.olm.OutgoingVerificationRequest
|
||||||
import uniffi.olm.ProgressListener as RustProgressListener
|
import uniffi.olm.ProgressListener as RustProgressListener
|
||||||
import uniffi.olm.Request
|
import uniffi.olm.Request
|
||||||
import uniffi.olm.RequestType
|
import uniffi.olm.RequestType
|
||||||
|
import uniffi.olm.Sas as InnerSas
|
||||||
|
import uniffi.olm.VerificationRequest as InnerRequest
|
||||||
import uniffi.olm.setLogger
|
import uniffi.olm.setLogger
|
||||||
|
|
||||||
class CryptoLogger : Logger {
|
class CryptoLogger : Logger {
|
||||||
|
@ -89,13 +100,9 @@ fun setRustLogger() {
|
||||||
setLogger(CryptoLogger() as Logger)
|
setLogger(CryptoLogger() as Logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Convert a Rust Device into a Kotlin CryptoDeviceInfo */
|
||||||
* Convert a Rust Device into a Kotlin CryptoDeviceInfo
|
|
||||||
*/
|
|
||||||
private fun toCryptoDeviceInfo(device: Device): CryptoDeviceInfo {
|
private fun toCryptoDeviceInfo(device: Device): CryptoDeviceInfo {
|
||||||
val keys = device.keys.map { (keyId, key) ->
|
val keys = device.keys.map { (keyId, key) -> "$keyId:$device.deviceId" to key }.toMap()
|
||||||
"$keyId:$device.deviceId" to key
|
|
||||||
}.toMap()
|
|
||||||
|
|
||||||
return CryptoDeviceInfo(
|
return CryptoDeviceInfo(
|
||||||
device.deviceId,
|
device.deviceId,
|
||||||
|
@ -110,8 +117,7 @@ private fun toCryptoDeviceInfo(device: Device): CryptoDeviceInfo {
|
||||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false),
|
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false),
|
||||||
device.isBlocked,
|
device.isBlocked,
|
||||||
// TODO
|
// TODO
|
||||||
null
|
null)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DeviceUpdateObserver {
|
internal class DeviceUpdateObserver {
|
||||||
|
@ -126,6 +132,150 @@ internal class DeviceUpdateObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class VerificationRequest(
|
||||||
|
private val machine: InnerMachine,
|
||||||
|
private var inner: InnerRequest
|
||||||
|
) {
|
||||||
|
private fun refreshData() {
|
||||||
|
val request = this.machine.getVerificationRequest(this.inner.otherUserId, this.inner.flowId)
|
||||||
|
|
||||||
|
if (request != null) {
|
||||||
|
this.inner = request
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fun accept_with_methods(methods: List<VerificationMethod>): OutgoingVerificationRequest? {
|
||||||
|
val stringMethods: MutableList<String> =
|
||||||
|
methods.map {
|
||||||
|
when (it) {
|
||||||
|
VerificationMethod.QR_CODE_SCAN -> VERIFICATION_METHOD_QR_CODE_SCAN
|
||||||
|
VerificationMethod.QR_CODE_SHOW -> VERIFICATION_METHOD_QR_CODE_SHOW
|
||||||
|
VerificationMethod.SAS -> VERIFICATION_METHOD_SAS
|
||||||
|
}
|
||||||
|
}.toMutableList()
|
||||||
|
|
||||||
|
if (stringMethods.contains(VERIFICATION_METHOD_QR_CODE_SHOW) ||
|
||||||
|
stringMethods.contains(VERIFICATION_METHOD_QR_CODE_SCAN)) {
|
||||||
|
stringMethods.add(VERIFICATION_METHOD_RECIPROCATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.machine.acceptVerificationRequest(
|
||||||
|
this.inner.otherUserId, this.inner.flowId, stringMethods)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isCanceled(): Boolean {
|
||||||
|
refreshData()
|
||||||
|
return this.inner.isCancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isDone(): Boolean {
|
||||||
|
refreshData()
|
||||||
|
return this.inner.isDone
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isReady(): Boolean {
|
||||||
|
refreshData()
|
||||||
|
return this.inner.isReady
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toPendingVerificationRequest(): PendingVerificationRequest {
|
||||||
|
refreshData()
|
||||||
|
val code = this.inner.cancelCode
|
||||||
|
|
||||||
|
val cancelCode =
|
||||||
|
if (code != null) {
|
||||||
|
toCancelCode(code)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val ourMethods = this.inner.ourMethods
|
||||||
|
val theirMethods = this.inner.theirMethods
|
||||||
|
val otherDeviceId = this.inner.otherDeviceId
|
||||||
|
|
||||||
|
var requestInfo: ValidVerificationInfoRequest? = null
|
||||||
|
var readyInfo: ValidVerificationInfoReady? = null
|
||||||
|
|
||||||
|
if (this.inner.weStarted && ourMethods != null) {
|
||||||
|
requestInfo =
|
||||||
|
ValidVerificationInfoRequest(
|
||||||
|
this.inner.flowId,
|
||||||
|
this.machine.deviceId(),
|
||||||
|
ourMethods,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
} else if (!this.inner.weStarted && ourMethods != null) {
|
||||||
|
readyInfo =
|
||||||
|
ValidVerificationInfoReady(
|
||||||
|
this.inner.flowId,
|
||||||
|
this.machine.deviceId(),
|
||||||
|
ourMethods,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
||||||
|
readyInfo =
|
||||||
|
ValidVerificationInfoReady(
|
||||||
|
this.inner.flowId,
|
||||||
|
otherDeviceId,
|
||||||
|
theirMethods,
|
||||||
|
)
|
||||||
|
} else if (!this.inner.weStarted && theirMethods != null && otherDeviceId != null) {
|
||||||
|
requestInfo =
|
||||||
|
ValidVerificationInfoRequest(
|
||||||
|
this.inner.flowId,
|
||||||
|
otherDeviceId,
|
||||||
|
theirMethods,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingVerificationRequest(
|
||||||
|
// Creation time
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
// Who initiated the request
|
||||||
|
!this.inner.weStarted,
|
||||||
|
// Local echo id, what to do here?
|
||||||
|
this.inner.flowId,
|
||||||
|
// other user
|
||||||
|
this.inner.otherUserId,
|
||||||
|
// room id
|
||||||
|
this.inner.roomId,
|
||||||
|
// transaction id
|
||||||
|
this.inner.flowId,
|
||||||
|
// val requestInfo: ValidVerificationInfoRequest? = null,
|
||||||
|
requestInfo,
|
||||||
|
// val readyInfo: ValidVerificationInfoReady? = null,
|
||||||
|
readyInfo,
|
||||||
|
// cancel code if there is one
|
||||||
|
cancelCode,
|
||||||
|
// are we done/successful
|
||||||
|
this.inner.isDone,
|
||||||
|
// did another device answer the request
|
||||||
|
this.inner.isPassive,
|
||||||
|
// devices that should receive the events we send out
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toCancelCode(cancelCode: RustCancelCode): CancelCode {
|
||||||
|
return when (cancelCode) {
|
||||||
|
RustCancelCode.USER -> CancelCode.User
|
||||||
|
RustCancelCode.TIMEOUT -> CancelCode.Timeout
|
||||||
|
RustCancelCode.UNKNOWN_TRANSACTION -> CancelCode.UnknownTransaction
|
||||||
|
RustCancelCode.UNKNOWN_METHOD -> CancelCode.UnknownMethod
|
||||||
|
RustCancelCode.UNEXPECTED_MESSAGE -> CancelCode.UnexpectedMessage
|
||||||
|
RustCancelCode.KEY_MISMATCH -> CancelCode.MismatchedKeys
|
||||||
|
RustCancelCode.USER_MISMATCH -> CancelCode.MismatchedKeys
|
||||||
|
RustCancelCode.INVALID_MESSAGE -> CancelCode.InvalidMessage
|
||||||
|
// TODO why don't the ruma codes match what's in EA?
|
||||||
|
RustCancelCode.ACCEPTED -> CancelCode.User
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal class Sas(private val machine: InnerMachine, private var inner: InnerSas) {
|
internal class Sas(private val machine: InnerMachine, private var inner: InnerSas) {
|
||||||
private fun refreshData() {
|
private fun refreshData() {
|
||||||
val sas = this.machine.getVerification(this.inner.flowId)
|
val sas = this.machine.getVerification(this.inner.flowId)
|
||||||
|
@ -139,7 +289,7 @@ internal class Sas(private val machine: InnerMachine, private var inner: InnerSa
|
||||||
|
|
||||||
fun isCanceled(): Boolean {
|
fun isCanceled(): Boolean {
|
||||||
refreshData()
|
refreshData()
|
||||||
return this.inner.isCanceled
|
return this.inner.isCancelled
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDone(): Boolean {
|
fun isDone(): Boolean {
|
||||||
|
@ -162,9 +312,8 @@ internal class Sas(private val machine: InnerMachine, private var inner: InnerSa
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CryptoStoreErrorException::class)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun confirm(): OutgoingVerificationRequest? = withContext(Dispatchers.IO) {
|
suspend fun confirm(): OutgoingVerificationRequest? =
|
||||||
machine.confirmVerification(inner.flowId)
|
withContext(Dispatchers.IO) { machine.confirmVerification(inner.flowId) }
|
||||||
}
|
|
||||||
|
|
||||||
fun cancel(): OutgoingVerificationRequest? {
|
fun cancel(): OutgoingVerificationRequest? {
|
||||||
return this.machine.cancelVerification(inner.flowId)
|
return this.machine.cancelVerification(inner.flowId)
|
||||||
|
@ -185,27 +334,26 @@ internal class Sas(private val machine: InnerMachine, private var inner: InnerSa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class OlmMachine(user_id: String, device_id: String, path: File, deviceObserver: DeviceUpdateObserver) {
|
internal class OlmMachine(
|
||||||
|
user_id: String,
|
||||||
|
device_id: String,
|
||||||
|
path: File,
|
||||||
|
deviceObserver: DeviceUpdateObserver
|
||||||
|
) {
|
||||||
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString())
|
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString())
|
||||||
private val deviceUpdateObserver = deviceObserver
|
private val deviceUpdateObserver = deviceObserver
|
||||||
|
|
||||||
/**
|
/** Get our own user ID. */
|
||||||
* Get our own user ID.
|
|
||||||
*/
|
|
||||||
fun userId(): String {
|
fun userId(): String {
|
||||||
return this.inner.userId()
|
return this.inner.userId()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Get our own device ID. */
|
||||||
* Get our own device ID.
|
|
||||||
*/
|
|
||||||
fun deviceId(): String {
|
fun deviceId(): String {
|
||||||
return this.inner.deviceId()
|
return this.inner.deviceId()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Get our own public identity keys ID. */
|
||||||
* Get our own public identity keys ID.
|
|
||||||
*/
|
|
||||||
fun identityKeys(): Map<String, String> {
|
fun identityKeys(): Map<String, String> {
|
||||||
return this.inner.identityKeys()
|
return this.inner.identityKeys()
|
||||||
}
|
}
|
||||||
|
@ -213,9 +361,7 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
fun ownDevice(): CryptoDeviceInfo {
|
fun ownDevice(): CryptoDeviceInfo {
|
||||||
val deviceId = this.deviceId()
|
val deviceId = this.deviceId()
|
||||||
|
|
||||||
val keys = this.identityKeys().map { (keyId, key) ->
|
val keys = this.identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
|
||||||
"$keyId:$deviceId" to key
|
|
||||||
}.toMap()
|
|
||||||
|
|
||||||
return CryptoDeviceInfo(
|
return CryptoDeviceInfo(
|
||||||
this.deviceId(),
|
this.deviceId(),
|
||||||
|
@ -227,22 +373,19 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
UnsignedDeviceInfo(),
|
UnsignedDeviceInfo(),
|
||||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||||
false,
|
false,
|
||||||
null
|
null)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of outgoing requests that need to be sent to the homeserver.
|
* Get the list of outgoing requests that need to be sent to the homeserver.
|
||||||
*
|
*
|
||||||
* After the request was sent out and a successful response was received
|
* After the request was sent out and a successful response was received the response body
|
||||||
* the response body should be passed back to the state machine using the
|
* should be passed back to the state machine using the markRequestAsSent() method.
|
||||||
* markRequestAsSent() method.
|
|
||||||
*
|
*
|
||||||
* @return the list of requests that needs to be sent to the homeserver
|
* @return the list of requests that needs to be sent to the homeserver
|
||||||
*/
|
*/
|
||||||
suspend fun outgoingRequests(): List<Request> = withContext(Dispatchers.IO) {
|
suspend fun outgoingRequests(): List<Request> =
|
||||||
inner.outgoingRequests()
|
withContext(Dispatchers.IO) { inner.outgoingRequests() }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a request that was sent to the server as sent.
|
* Mark a request that was sent to the server as sent.
|
||||||
|
@ -258,7 +401,8 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
requestId: String,
|
requestId: String,
|
||||||
requestType: RequestType,
|
requestType: RequestType,
|
||||||
responseBody: String
|
responseBody: String
|
||||||
) = withContext(Dispatchers.IO) {
|
) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
inner.markRequestAsSent(requestId, requestType, responseBody)
|
inner.markRequestAsSent(requestId, requestType, responseBody)
|
||||||
|
|
||||||
if (requestType == RequestType.KEYS_QUERY) {
|
if (requestType == RequestType.KEYS_QUERY) {
|
||||||
|
@ -267,17 +411,15 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Let the state machine know about E2EE related sync changes that we
|
* Let the state machine know about E2EE related sync changes that we received from the server.
|
||||||
* received from the server.
|
|
||||||
*
|
*
|
||||||
* This needs to be called after every sync, ideally before processing
|
* This needs to be called after every sync, ideally before processing any other sync changes.
|
||||||
* any other sync changes.
|
|
||||||
*
|
*
|
||||||
* @param toDevice A serialized array of to-device events we received in the
|
* @param toDevice A serialized array of to-device events we received in the current sync
|
||||||
* current sync resposne.
|
* resposne.
|
||||||
*
|
*
|
||||||
* @param deviceChanges The list of devices that have changed in some way
|
* @param deviceChanges The list of devices that have changed in some way since the previous
|
||||||
* since the previous sync.
|
* sync.
|
||||||
*
|
*
|
||||||
* @param keyCounts The map of uploaded one-time key types and counts.
|
* @param keyCounts The map of uploaded one-time key types and counts.
|
||||||
*/
|
*/
|
||||||
|
@ -286,104 +428,97 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
toDevice: ToDeviceSyncResponse?,
|
toDevice: ToDeviceSyncResponse?,
|
||||||
deviceChanges: DeviceListResponse?,
|
deviceChanges: DeviceListResponse?,
|
||||||
keyCounts: DeviceOneTimeKeysCountSyncResponse?
|
keyCounts: DeviceOneTimeKeysCountSyncResponse?
|
||||||
): ToDeviceSyncResponse = withContext(Dispatchers.IO) {
|
): ToDeviceSyncResponse =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val counts: MutableMap<String, Int> = mutableMapOf()
|
val counts: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
|
||||||
if (keyCounts?.signedCurve25519 != null) {
|
if (keyCounts?.signedCurve25519 != null) {
|
||||||
counts["signed_curve25519"] = keyCounts.signedCurve25519
|
counts["signed_curve25519"] = keyCounts.signedCurve25519
|
||||||
}
|
}
|
||||||
|
|
||||||
val devices = DeviceLists(deviceChanges?.changed ?: listOf(), deviceChanges?.left ?: listOf())
|
val devices =
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(ToDeviceSyncResponse::class.java)
|
DeviceLists(
|
||||||
|
deviceChanges?.changed ?: listOf(), deviceChanges?.left ?: listOf())
|
||||||
|
val adapter =
|
||||||
|
MoshiProvider.providesMoshi().adapter(ToDeviceSyncResponse::class.java)
|
||||||
val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!!
|
val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!!
|
||||||
|
|
||||||
adapter.fromJson(inner.receiveSyncChanges(events, devices, counts))!!
|
adapter.fromJson(inner.receiveSyncChanges(events, devices, counts))!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark the given list of users to be tracked, triggering a key query request
|
* Mark the given list of users to be tracked, triggering a key query request for them.
|
||||||
* for them.
|
|
||||||
*
|
*
|
||||||
* *Note*: Only users that aren't already tracked will be considered for an
|
* *Note*: Only users that aren't already tracked will be considered for an update. It's safe to
|
||||||
* update. It's safe to call this with already tracked users, it won't
|
* call this with already tracked users, it won't result in excessive keys query requests.
|
||||||
* result in excessive keys query requests.
|
|
||||||
*
|
*
|
||||||
* @param users The users that should be queued up for a key query.
|
* @param users The users that should be queued up for a key query.
|
||||||
*/
|
*/
|
||||||
suspend fun updateTrackedUsers(users: List<String>) = withContext(Dispatchers.IO) {
|
suspend fun updateTrackedUsers(users: List<String>) =
|
||||||
inner.updateTrackedUsers(users)
|
withContext(Dispatchers.IO) { inner.updateTrackedUsers(users) }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate one-time key claiming requests for all the users we are missing
|
* Generate one-time key claiming requests for all the users we are missing sessions for.
|
||||||
* sessions for.
|
|
||||||
*
|
*
|
||||||
* After the request was sent out and a successful response was received
|
* After the request was sent out and a successful response was received the response body
|
||||||
* the response body should be passed back to the state machine using the
|
* should be passed back to the state machine using the markRequestAsSent() method.
|
||||||
* markRequestAsSent() method.
|
|
||||||
*
|
*
|
||||||
* This method should be called every time before a call to
|
* This method should be called every time before a call to shareRoomKey() is made.
|
||||||
* shareRoomKey() is made.
|
|
||||||
*
|
*
|
||||||
* @param users The list of users for which we would like to establish 1:1
|
* @param users The list of users for which we would like to establish 1:1 Olm sessions for.
|
||||||
* Olm sessions for.
|
|
||||||
*
|
*
|
||||||
* @return A keys claim request that needs to be sent out to the server.
|
* @return A keys claim request that needs to be sent out to the server.
|
||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreErrorException::class)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun getMissingSessions(users: List<String>): Request? = withContext(Dispatchers.IO) {
|
suspend fun getMissingSessions(users: List<String>): Request? =
|
||||||
inner.getMissingSessions(users)
|
withContext(Dispatchers.IO) { inner.getMissingSessions(users) }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share a room key with the given list of users for the given room.
|
* Share a room key with the given list of users for the given room.
|
||||||
*
|
*
|
||||||
* After the request was sent out and a successful response was received
|
* After the request was sent out and a successful response was received the response body
|
||||||
* the response body should be passed back to the state machine using the
|
* should be passed back to the state machine using the markRequestAsSent() method.
|
||||||
* markRequestAsSent() method.
|
|
||||||
*
|
*
|
||||||
* This method should be called every time before a call to
|
* This method should be called every time before a call to `encrypt()` with the given `room_id`
|
||||||
* `encrypt()` with the given `room_id` is made.
|
* is made.
|
||||||
*
|
*
|
||||||
* @param roomId The unique id of the room, note that this doesn't strictly
|
* @param roomId The unique id of the room, note that this doesn't strictly need to be a Matrix
|
||||||
* need to be a Matrix room, it just needs to be an unique identifier for
|
* room, it just needs to be an unique identifier for the group that will participate in the
|
||||||
* the group that will participate in the conversation.
|
* conversation.
|
||||||
*
|
*
|
||||||
* @param users The list of users which are considered to be members of the
|
* @param users The list of users which are considered to be members of the room and should
|
||||||
* room and should receive the room key.
|
* receive the room key.
|
||||||
*
|
*
|
||||||
* @return The list of requests that need to be sent out.
|
* @return The list of requests that need to be sent out.
|
||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreErrorException::class)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> = withContext(Dispatchers.IO) {
|
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> =
|
||||||
inner.shareRoomKey(roomId, users)
|
withContext(Dispatchers.IO) { inner.shareRoomKey(roomId, users) }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt the given event with the given type and content for the given
|
* Encrypt the given event with the given type and content for the given room.
|
||||||
* room.
|
|
||||||
*
|
*
|
||||||
* **Note**: A room key needs to be shared with the group of users that are
|
* **Note**: A room key needs to be shared with the group of users that are members in the given
|
||||||
* members in the given room. If this is not done this method will panic.
|
* room. If this is not done this method will panic.
|
||||||
*
|
*
|
||||||
* The usual flow to encrypt an evnet using this state machine is as
|
* The usual flow to encrypt an evnet using this state machine is as follows:
|
||||||
* follows:
|
|
||||||
*
|
*
|
||||||
* 1. Get the one-time key claim request to establish 1:1 Olm sessions for
|
* 1. Get the one-time key claim request to establish 1:1 Olm sessions for
|
||||||
|
* ```
|
||||||
* the room members of the room we wish to participate in. This is done
|
* the room members of the room we wish to participate in. This is done
|
||||||
* using the [`get_missing_sessions()`](#method.get_missing_sessions)
|
* using the [`get_missing_sessions()`](#method.get_missing_sessions)
|
||||||
* method. This method call should be locked per call.
|
* method. This method call should be locked per call.
|
||||||
*
|
* ```
|
||||||
* 2. Share a room key with all the room members using the shareRoomKey().
|
* 2. Share a room key with all the room members using the shareRoomKey().
|
||||||
|
* ```
|
||||||
* This method call should be locked per room.
|
* This method call should be locked per room.
|
||||||
*
|
* ```
|
||||||
* 3. Encrypt the event using this method.
|
* 3. Encrypt the event using this method.
|
||||||
*
|
*
|
||||||
* 4. Send the encrypted event to the server.
|
* 4. Send the encrypted event to the server.
|
||||||
*
|
*
|
||||||
* After the room key is shared steps 1 and 2 will become noops, unless
|
* After the room key is shared steps 1 and 2 will become noops, unless there's some changes in
|
||||||
* there's some changes in the room membership or in the list of devices a
|
* the room membership or in the list of devices a member has.
|
||||||
* member has.
|
|
||||||
*
|
*
|
||||||
* @param roomId the ID of the room where the encrypted event will be sent to
|
* @param roomId the ID of the room where the encrypted event will be sent to
|
||||||
*
|
*
|
||||||
|
@ -394,7 +529,8 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
* @return The encrypted version of the content
|
* @return The encrypted version of the content
|
||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreErrorException::class)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content = withContext(Dispatchers.IO) {
|
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
|
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
|
||||||
val contentString = adapter.toJson(content)
|
val contentString = adapter.toJson(content)
|
||||||
val encrypted = inner.encrypt(roomId, eventType, contentString)
|
val encrypted = inner.encrypt(roomId, eventType, contentString)
|
||||||
|
@ -411,41 +547,45 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
* @return the decrypted version of the event.
|
* @return the decrypted version of the event.
|
||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult = withContext(Dispatchers.IO) {
|
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
||||||
val serializedEvent = adapter.toJson(event)
|
val serializedEvent = adapter.toJson(event)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId!!)
|
val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId!!)
|
||||||
|
|
||||||
val deserializationAdapter = MoshiProvider.providesMoshi().adapter<JsonDict>(Map::class.java)
|
val deserializationAdapter =
|
||||||
|
MoshiProvider.providesMoshi().adapter<JsonDict>(Map::class.java)
|
||||||
val clearEvent = deserializationAdapter.fromJson(decrypted.clearEvent)!!
|
val clearEvent = deserializationAdapter.fromJson(decrypted.clearEvent)!!
|
||||||
|
|
||||||
MXEventDecryptionResult(
|
MXEventDecryptionResult(
|
||||||
clearEvent,
|
clearEvent,
|
||||||
decrypted.senderCurve25519Key,
|
decrypted.senderCurve25519Key,
|
||||||
decrypted.claimedEd25519Key,
|
decrypted.claimedEd25519Key,
|
||||||
decrypted.forwardingCurve25519Chain
|
decrypted.forwardingCurve25519Chain)
|
||||||
)
|
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, throwable.message, "m.megolm.v1.aes-sha2")
|
val reason =
|
||||||
|
String.format(
|
||||||
|
MXCryptoError.UNABLE_TO_DECRYPT_REASON,
|
||||||
|
throwable.message,
|
||||||
|
"m.megolm.v1.aes-sha2")
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the room key that was used to encrypt the given undecrypted
|
* Request the room key that was used to encrypt the given undecrypted event.
|
||||||
* event.
|
|
||||||
*
|
*
|
||||||
* @param event The that we're not able to decrypt and want to request a
|
* @param event The that we're not able to decrypt and want to request a room key for.
|
||||||
* room key for.
|
|
||||||
*
|
*
|
||||||
* @return a key request pair, consisting of an optional key request
|
* @return a key request pair, consisting of an optional key request cancellation and the key
|
||||||
* cancellation and the key request itself. The cancellation *must* be sent
|
* request itself. The cancellation *must* be sent out before the request, otherwise devices
|
||||||
* out before the request, otherwise devices will ignore the key request.
|
* will ignore the key request.
|
||||||
*/
|
*/
|
||||||
@Throws(DecryptionErrorException::class)
|
@Throws(DecryptionErrorException::class)
|
||||||
suspend fun requestRoomKey(event: Event): KeyRequestPair = withContext(Dispatchers.IO) {
|
suspend fun requestRoomKey(event: Event): KeyRequestPair =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
||||||
val serializedEvent = adapter.toJson(event)
|
val serializedEvent = adapter.toJson(event)
|
||||||
|
|
||||||
|
@ -455,18 +595,16 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
/**
|
/**
|
||||||
* Export all of our room keys.
|
* Export all of our room keys.
|
||||||
*
|
*
|
||||||
* @param passphrase The passphrase that should be used to encrypt the key
|
* @param passphrase The passphrase that should be used to encrypt the key export.
|
||||||
* export.
|
|
||||||
*
|
*
|
||||||
* @param rounds The number of rounds that should be used when expanding the
|
* @param rounds The number of rounds that should be used when expanding the passphrase into an
|
||||||
* passphrase into an key.
|
* key.
|
||||||
*
|
*
|
||||||
* @return the encrypted key export as a bytearray.
|
* @return the encrypted key export as a bytearray.
|
||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreErrorException::class)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray = withContext(Dispatchers.IO) {
|
suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray =
|
||||||
inner.exportKeys(passphrase, rounds).toByteArray()
|
withContext(Dispatchers.IO) { inner.exportKeys(passphrase, rounds).toByteArray() }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import room keys from the given serialized key export.
|
* Import room keys from the given serialized key export.
|
||||||
|
@ -475,11 +613,15 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
*
|
*
|
||||||
* @param passphrase The passphrase that was used to encrypt the key export.
|
* @param passphrase The passphrase that was used to encrypt the key export.
|
||||||
*
|
*
|
||||||
* @param listener A callback that can be used to introspect the
|
* @param listener A callback that can be used to introspect the progress of the key import.
|
||||||
* progress of the key import.
|
|
||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreErrorException::class)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun importKeys(keys: ByteArray, passphrase: String, listener: ProgressListener?): ImportRoomKeysResult = withContext(Dispatchers.IO) {
|
suspend fun importKeys(
|
||||||
|
keys: ByteArray,
|
||||||
|
passphrase: String,
|
||||||
|
listener: ProgressListener?
|
||||||
|
): ImportRoomKeysResult =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val decodedKeys = String(keys, Charset.defaultCharset())
|
val decodedKeys = String(keys, Charset.defaultCharset())
|
||||||
|
|
||||||
val rustListener = CryptoProgressListener(listener)
|
val rustListener = CryptoProgressListener(listener)
|
||||||
|
@ -499,7 +641,8 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
* @return The Device if it found one.
|
* @return The Device if it found one.
|
||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreErrorException::class)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun getDevice(userId: String, deviceId: String): CryptoDeviceInfo? = withContext(Dispatchers.IO) {
|
suspend fun getDevice(userId: String, deviceId: String): CryptoDeviceInfo? =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
// Our own device isn't part of our store on the rust side, return it
|
// Our own device isn't part of our store on the rust side, return it
|
||||||
// using our ownDevice method
|
// using our ownDevice method
|
||||||
if (userId == userId() && deviceId == deviceId()) {
|
if (userId == userId() && deviceId == deviceId()) {
|
||||||
|
@ -561,9 +704,7 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
return plainDevices
|
return plainDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Update all of our live device listeners. */
|
||||||
* Update all of our live device listeners.
|
|
||||||
*/
|
|
||||||
private suspend fun updateLiveDevices() {
|
private suspend fun updateLiveDevices() {
|
||||||
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
|
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
|
||||||
val devices = getUserDevices(users)
|
val devices = getUserDevices(users)
|
||||||
|
@ -574,8 +715,8 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
/**
|
/**
|
||||||
* Get all the devices of multiple users as a live version.
|
* Get all the devices of multiple users as a live version.
|
||||||
*
|
*
|
||||||
* The live version will update the list of devices if some of the data
|
* The live version will update the list of devices if some of the data changes, or if new
|
||||||
* changes, or if new devices arrive for a certain user.
|
* devices arrive for a certain user.
|
||||||
*
|
*
|
||||||
* @param userIds The ids of the device owners.
|
* @param userIds The ids of the device owners.
|
||||||
*
|
*
|
||||||
|
@ -589,17 +730,29 @@ internal class OlmMachine(user_id: String, device_id: String, path: File, device
|
||||||
return devices
|
return devices
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Discard the currently active room key for the given room if there is one. */
|
||||||
* Discard the currently active room key for the given room if there is one.
|
|
||||||
*/
|
|
||||||
@Throws(CryptoStoreErrorException::class)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
fun discardRoomKey(roomId: String) {
|
fun discardRoomKey(roomId: String) {
|
||||||
runBlocking { inner.discardRoomKey(roomId) }
|
runBlocking { inner.discardRoomKey(roomId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun getVerificationRequests(userId: String): List<VerificationRequest> {
|
||||||
* Get an active verification
|
return this.inner.getVerificationRequests(userId).map {
|
||||||
*/
|
VerificationRequest(this.inner, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVerificationRequest(userId: String, flowId: String): VerificationRequest? {
|
||||||
|
val request = this.inner.getVerificationRequest(userId, flowId)
|
||||||
|
|
||||||
|
return if (request == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
VerificationRequest(this.inner, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get an active verification */
|
||||||
fun getVerification(flowId: String): Sas? {
|
fun getVerification(flowId: String): Sas? {
|
||||||
val sas = this.inner.getVerification(flowId)
|
val sas = this.inner.getVerification(flowId)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,326 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.verification
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.collections.set
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
|
import org.matrix.android.sdk.internal.crypto.OlmMachine
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationRequest
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
|
import timber.log.Timber
|
||||||
|
import uniffi.olm.OutgoingVerificationRequest
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class RustVerificationService
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val olmMachine: OlmMachine,
|
||||||
|
private val sendToDeviceTask: SendToDeviceTask,
|
||||||
|
) : DefaultVerificationTransaction.Listener, VerificationService {
|
||||||
|
|
||||||
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
private var listeners = ArrayList<VerificationService.Listener>()
|
||||||
|
|
||||||
|
override fun addListener(listener: VerificationService.Listener) {
|
||||||
|
uiHandler.post {
|
||||||
|
if (!listeners.contains(listener)) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeListener(listener: VerificationService.Listener) {
|
||||||
|
uiHandler.post { listeners.remove(listener) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchTxAdded(tx: VerificationTransaction) {
|
||||||
|
uiHandler.post {
|
||||||
|
listeners.forEach {
|
||||||
|
try {
|
||||||
|
it.transactionCreated(tx)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e, "## Error while notifying listeners")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchTxUpdated(tx: VerificationTransaction) {
|
||||||
|
uiHandler.post {
|
||||||
|
listeners.forEach {
|
||||||
|
try {
|
||||||
|
it.transactionUpdated(tx)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e, "## Error while notifying listeners")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchRequestAdded(tx: PendingVerificationRequest) {
|
||||||
|
Timber.v("## SAS dispatchRequestAdded txId:${tx.transactionId} ${tx}")
|
||||||
|
uiHandler.post {
|
||||||
|
listeners.forEach {
|
||||||
|
try {
|
||||||
|
it.verificationRequestCreated(tx)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e, "## Error while notifying listeners")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchRequestUpdated(tx: PendingVerificationRequest) {
|
||||||
|
uiHandler.post {
|
||||||
|
listeners.forEach {
|
||||||
|
try {
|
||||||
|
it.verificationRequestUpdated(tx)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e, "## Error while notifying listeners")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
||||||
|
TODO()
|
||||||
|
// setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
|
||||||
|
// userId,
|
||||||
|
// deviceID)
|
||||||
|
|
||||||
|
// listeners.forEach {
|
||||||
|
// try {
|
||||||
|
// it.markedAsManuallyVerified(userId, deviceID)
|
||||||
|
// } catch (e: Throwable) {
|
||||||
|
// Timber.e(e, "## Error while notifying listeners")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun onEvent(event: Event) {
|
||||||
|
when (event.getClearType()) {
|
||||||
|
EventType.KEY_VERIFICATION_START -> {}
|
||||||
|
EventType.KEY_VERIFICATION_CANCEL -> {}
|
||||||
|
EventType.KEY_VERIFICATION_ACCEPT -> {}
|
||||||
|
EventType.KEY_VERIFICATION_KEY -> {}
|
||||||
|
EventType.KEY_VERIFICATION_MAC -> {}
|
||||||
|
EventType.KEY_VERIFICATION_READY -> {}
|
||||||
|
EventType.KEY_VERIFICATION_DONE -> {}
|
||||||
|
MessageType.MSGTYPE_VERIFICATION_REQUEST -> {
|
||||||
|
onRequestReceived(event)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event == event
|
||||||
|
// TODO get the sender and flow id out of the event and depending on the
|
||||||
|
// event type either get the verification request or verification and
|
||||||
|
// dispatch updates here
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRequestReceived(event: Event) {
|
||||||
|
val content = event.getClearContent().toModel<KeyVerificationRequest>() ?: return
|
||||||
|
val flowId = content.transactionId
|
||||||
|
val sender = event.senderId ?: return
|
||||||
|
|
||||||
|
val request = this.getExistingVerificationRequest(sender, flowId) ?: return
|
||||||
|
|
||||||
|
dispatchRequestAdded(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event) {
|
||||||
|
// TODO This should be handled inside the rust-sdk decryption method
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO All this methods should be delegated to a TransactionStore
|
||||||
|
override fun getExistingTransaction(
|
||||||
|
otherUserId: String,
|
||||||
|
tid: String
|
||||||
|
): VerificationTransaction? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExistingVerificationRequests(
|
||||||
|
otherUserId: String
|
||||||
|
): List<PendingVerificationRequest> {
|
||||||
|
return this.olmMachine.getVerificationRequests(otherUserId).map {
|
||||||
|
it.toPendingVerificationRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExistingVerificationRequest(
|
||||||
|
otherUserId: String,
|
||||||
|
tid: String?
|
||||||
|
): PendingVerificationRequest? {
|
||||||
|
return if (tid != null) {
|
||||||
|
val request = this.olmMachine.getVerificationRequest(otherUserId, tid)
|
||||||
|
|
||||||
|
if (request != null) {
|
||||||
|
request.toPendingVerificationRequest()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExistingVerificationRequestInRoom(
|
||||||
|
roomId: String,
|
||||||
|
tid: String?
|
||||||
|
): PendingVerificationRequest? {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beginKeyVerification(
|
||||||
|
method: VerificationMethod,
|
||||||
|
otherUserId: String,
|
||||||
|
otherDeviceId: String,
|
||||||
|
transactionId: String?
|
||||||
|
): String? {
|
||||||
|
// should check if already one (and cancel it)
|
||||||
|
if (method == VerificationMethod.SAS) {
|
||||||
|
// TODO start SAS verification here, don't we need to see if there's
|
||||||
|
// a request?
|
||||||
|
TODO()
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Unknown verification method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun requestKeyVerificationInDMs(
|
||||||
|
methods: List<VerificationMethod>,
|
||||||
|
otherUserId: String,
|
||||||
|
roomId: String,
|
||||||
|
localId: String?
|
||||||
|
): PendingVerificationRequest {
|
||||||
|
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
||||||
|
|
||||||
|
// TODO cancel other active requests, create a new request here and
|
||||||
|
// dispatch it
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun requestKeyVerification(
|
||||||
|
methods: List<VerificationMethod>,
|
||||||
|
otherUserId: String,
|
||||||
|
otherDevices: List<String>?
|
||||||
|
): PendingVerificationRequest {
|
||||||
|
// This was mostly a copy paste of the InDMs method, do the same here
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelVerificationRequest(request: PendingVerificationRequest) {
|
||||||
|
// TODO get the request out of the olm machine and cancel here
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun declineVerificationRequestInDMs(
|
||||||
|
otherUserId: String,
|
||||||
|
transactionId: String,
|
||||||
|
roomId: String
|
||||||
|
) {
|
||||||
|
// TODO get an existing verification request out of the olm machine and
|
||||||
|
// cancel it. update the pending request afterwards
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beginKeyVerificationInDMs(
|
||||||
|
method: VerificationMethod,
|
||||||
|
transactionId: String,
|
||||||
|
roomId: String,
|
||||||
|
otherUserId: String,
|
||||||
|
otherDeviceId: String
|
||||||
|
): String {
|
||||||
|
// TODO fetch out the verification request nad start SAS, return the
|
||||||
|
// flow id
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readyPendingVerificationInDMs(
|
||||||
|
methods: List<VerificationMethod>,
|
||||||
|
otherUserId: String,
|
||||||
|
roomId: String,
|
||||||
|
transactionId: String
|
||||||
|
): Boolean {
|
||||||
|
Timber.e("## TRYING TO READY PENDING ROOM VERIFICATION")
|
||||||
|
// TODO do the same as readyPendingVerification
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readyPendingVerification(
|
||||||
|
methods: List<VerificationMethod>,
|
||||||
|
otherUserId: String,
|
||||||
|
transactionId: String
|
||||||
|
): Boolean {
|
||||||
|
val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId)
|
||||||
|
|
||||||
|
return if (request != null) {
|
||||||
|
val outgoingRequest = request.accept_with_methods(methods)
|
||||||
|
|
||||||
|
if (outgoingRequest != null) {
|
||||||
|
runBlocking { sendRequest(outgoingRequest) }
|
||||||
|
dispatchRequestUpdated(request.toPendingVerificationRequest())
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun sendRequest(request: OutgoingVerificationRequest) {
|
||||||
|
when (request) {
|
||||||
|
is OutgoingVerificationRequest.ToDevice -> {
|
||||||
|
val adapter =
|
||||||
|
MoshiProvider.providesMoshi()
|
||||||
|
.adapter<Map<String, HashMap<String, Any>>>(Map::class.java)
|
||||||
|
val body = adapter.fromJson(request.body)!!
|
||||||
|
|
||||||
|
val userMap = MXUsersDevicesMap<Any>()
|
||||||
|
userMap.join(body)
|
||||||
|
|
||||||
|
val sendToDeviceParams = SendToDeviceTask.Params(request.eventType, userMap)
|
||||||
|
sendToDeviceTask.execute(sendToDeviceParams)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO move this into the VerificationRequest and Verification classes?
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
dispatchTxUpdated(tx)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue