Skip to content

Commit 87711f8

Browse files
committed
crypto: direct share key
1 parent c62bea9 commit 87711f8

File tree

14 files changed

+334
-15
lines changed

14 files changed

+334
-15
lines changed

sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ data class RoomKeyShareRequest(
3030
@Json(name = "requesting_device_id")
3131
override val requestingDeviceId: String? = null,
3232

33+
@Json(name = "requesting_device_key")
34+
val requestingDeviceKey: String? = null,
35+
36+
@Json(name = "requesting_device_otk")
37+
val requestingDeviceOtk: String? = null,
38+
39+
@Json(name = "recipients")
40+
var recipients: List<Pair<String, String>>? = null,
41+
3342
@Json(name = "request_id")
3443
override val requestId: String? = null,
3544

sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/events/model/Event.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ data class Event(
153153
* @return true if this event is encrypted.
154154
*/
155155
fun isEncrypted(): Boolean {
156-
return type == EventType.ENCRYPTED
156+
return type == EventType.ENCRYPTED || type == EventType.ROOM_KEY_REPLY
157157
}
158158

159159
/**

sdn-sdk-android/src/main/java/org/sdn/android/sdk/api/session/events/model/EventType.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ object EventType {
8888
// Key share events
8989
const val ROOM_KEY_REQUEST = "m.room_key_request"
9090
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
91+
const val ROOM_KEY_REQUIRE = "m.room_key_require"
92+
const val ROOM_KEY_REPLY = "m.room_key_reply"
9193
val ROOM_KEY_WITHHELD = StableUnstableId(stable = "m.room_key.withheld", unstable = "org.matrix.room_key.withheld")
9294

9395
const val REQUEST_SECRET = "m.secret.request"

sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/CryptoModule.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import org.sdn.android.sdk.internal.crypto.tasks.DefaultInitializeCrossSigningTa
7373
import org.sdn.android.sdk.internal.crypto.tasks.DefaultPullSessionKeysTask
7474
import org.sdn.android.sdk.internal.crypto.tasks.DefaultPutSessionMapTask
7575
import org.sdn.android.sdk.internal.crypto.tasks.DefaultSendEventTask
76+
import org.sdn.android.sdk.internal.crypto.tasks.DefaultSendSimpleEventTask
7677
import org.sdn.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask
7778
import org.sdn.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
7879
import org.sdn.android.sdk.internal.crypto.tasks.DefaultSetDeviceNameTask
@@ -89,6 +90,7 @@ import org.sdn.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
8990
import org.sdn.android.sdk.internal.crypto.tasks.PullSessionKeysTask
9091
import org.sdn.android.sdk.internal.crypto.tasks.PutSessionMapTask
9192
import org.sdn.android.sdk.internal.crypto.tasks.SendEventTask
93+
import org.sdn.android.sdk.internal.crypto.tasks.SendSimpleEventTask
9294
import org.sdn.android.sdk.internal.crypto.tasks.SendToDeviceTask
9395
import org.sdn.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
9496
import org.sdn.android.sdk.internal.crypto.tasks.SetDeviceNameTask
@@ -236,6 +238,9 @@ internal abstract class CryptoModule {
236238
@Binds
237239
abstract fun bindSendToDeviceTask(task: DefaultSendToDeviceTask): SendToDeviceTask
238240

241+
@Binds
242+
abstract fun bindSendSimpleEventTask(task: DefaultSendSimpleEventTask): SendSimpleEventTask
243+
239244
@Binds
240245
abstract fun bindPullRoomKeyTask(task: DefaultPullSessionKeysTask): PullSessionKeysTask
241246

sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DefaultCryptoService.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ import org.sdn.android.sdk.api.session.events.model.content.EncryptedEventConten
119119
import org.sdn.android.sdk.api.session.events.model.content.OlmEventContent
120120
import org.sdn.android.sdk.api.session.room.model.message.MessageContent
121121
import org.sdn.android.sdk.api.session.sync.model.ToDeviceSyncResponse
122+
import org.sdn.android.sdk.internal.crypto.model.rest.EncryptedMessage
122123
import org.sdn.android.sdk.internal.crypto.tasks.PullSessionKeysTask
123124
import timber.log.Timber
124125
import java.util.concurrent.atomic.AtomicBoolean
@@ -213,6 +214,33 @@ internal class DefaultCryptoService @Inject constructor(
213214
}
214215

215216
fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) {
217+
// handle key require
218+
if (event.type == EventType.ROOM_KEY_REQUIRE) {
219+
val keyShareRequest = event.content.toModel<RoomKeyShareRequest>()
220+
if (event.senderId == null || keyShareRequest == null) {
221+
return
222+
}
223+
val recipients = keyShareRequest.recipients
224+
recipients?.let {
225+
for (recipient in recipients) {
226+
if (this.userId == recipient.first && this.deviceId == recipient.second) {
227+
this.incomingKeyRequestManager.addNewIncomingRequest(event.senderId, keyShareRequest)
228+
}
229+
}
230+
}
231+
return
232+
}
233+
// handle key reply
234+
if (event.type == EventType.ROOM_KEY_REPLY) {
235+
val encryptedContent = event.content.toModel<EncryptedMessage>() ?: return
236+
if (encryptedContent.cipherText?.contains(this.olmDevice.deviceCurve25519Key) != true) {
237+
// not recipient
238+
return
239+
}
240+
// should have already been decrypted
241+
this.onRoomKeyEvent(event, true)
242+
}
243+
216244
// handle state events
217245
if (event.isStateEvent()) {
218246
when (event.type) {

sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/DeviceListManager.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import javax.inject.Inject
4343
@SessionScope
4444
internal class DeviceListManager @Inject constructor(
4545
private val cryptoStore: IMXCryptoStore,
46-
private val olmDevice: MXOlmDevice,
46+
val olmDevice: MXOlmDevice,
4747
private val syncTokenStore: SyncTokenStore,
4848
private val credentials: Credentials,
4949
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
@@ -82,6 +82,10 @@ internal class DeviceListManager @Inject constructor(
8282
return credentials.loginTime - 24 * 3600 * 1000
8383
}
8484

85+
fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? {
86+
return this.cryptoStore.getUserDevice(userId, deviceId)
87+
}
88+
8589
private fun dispatchDeviceChange(users: List<String>) {
8690
synchronized(deviceChangeListeners) {
8791
deviceChangeListeners.forEach {
@@ -573,7 +577,6 @@ internal class DeviceListManager @Inject constructor(
573577
}
574578
)
575579
}
576-
577580
companion object {
578581

579582
/**

sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/EventDecryptor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ internal class EventDecryptor @Inject constructor(
141141
mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) {
142142
// need to find sending device
143143
val olmContent = event.content.toModel<OlmEventContent>()
144-
if (event.senderId != null && olmContent?.senderKey != null) {
144+
if (event.type != EventType.ROOM_KEY_REPLY && event.senderId != null && olmContent?.senderKey != null) {
145145
markOlmSessionForUnwedging(event.senderId, olmContent.senderKey)
146146
} else {
147147
Timber.tag(loggerTag.value).d("Can't mark as wedge malformed")

sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/IncomingKeyRequestManager.kt

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.sdn.android.sdk.internal.crypto
1818

19+
import dagger.Lazy
1920
import kotlinx.coroutines.CoroutineScope
2021
import kotlinx.coroutines.SupervisorJob
2122
import kotlinx.coroutines.asCoroutineDispatcher
@@ -30,6 +31,7 @@ import org.sdn.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_RATCHET
3031
import org.sdn.android.sdk.api.crypto.MXCryptoConfig
3132
import org.sdn.android.sdk.api.extensions.tryOrNull
3233
import org.sdn.android.sdk.api.logger.LoggerTag
34+
import org.sdn.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
3335
import org.sdn.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
3436
import org.sdn.android.sdk.api.session.crypto.model.CryptoDeviceInfo
3537
import org.sdn.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
@@ -39,9 +41,11 @@ import org.sdn.android.sdk.api.session.crypto.model.RoomKeyShareRequest
3941
import org.sdn.android.sdk.api.session.events.model.EventType
4042
import org.sdn.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
4143
import org.sdn.android.sdk.api.session.events.model.content.WithHeldCode
44+
import org.sdn.android.sdk.api.session.events.model.toContent
4245
import org.sdn.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
4346
import org.sdn.android.sdk.internal.crypto.actions.MessageEncrypter
4447
import org.sdn.android.sdk.internal.crypto.store.IMXCryptoStore
48+
import org.sdn.android.sdk.internal.crypto.tasks.SendSimpleEventTask
4549
import org.sdn.android.sdk.internal.crypto.tasks.SendToDeviceTask
4650
import org.sdn.android.sdk.internal.session.SessionScope
4751
import org.sdn.android.sdk.internal.task.SemaphoreCoroutineSequencer
@@ -63,6 +67,7 @@ internal class IncomingKeyRequestManager @Inject constructor(
6367
private val messageEncrypter: MessageEncrypter,
6468
private val coroutineDispatchers: SDNCoroutineDispatchers,
6569
private val sendToDeviceTask: SendToDeviceTask,
70+
private val sendSimpleEventTask: SendSimpleEventTask,
6671
private val clock: Clock,
6772
) {
6873

@@ -83,6 +88,8 @@ internal class IncomingKeyRequestManager @Inject constructor(
8388
val requestId: String,
8489
val requestingUserId: String,
8590
val requestingDeviceId: String,
91+
val requestingDeviceKey: String?,
92+
val requestingDeviceOtk: String?,
8693
val roomId: String,
8794
val senderKey: String,
8895
val sessionId: String,
@@ -109,6 +116,8 @@ internal class IncomingKeyRequestManager @Inject constructor(
109116
requestId = requestId,
110117
requestingUserId = senderId,
111118
requestingDeviceId = deviceId,
119+
requestingDeviceKey = this.requestingDeviceKey,
120+
requestingDeviceOtk = this.requestingDeviceOtk,
112121
roomId = roomId,
113122
senderKey = senderKey,
114123
sessionId = sessionId,
@@ -209,11 +218,11 @@ internal class IncomingKeyRequestManager @Inject constructor(
209218

210219
private suspend fun handleIncomingRequest(request: ValidMegolmRequestBody) {
211220
// We don't want to download keys, if we don't know the device yet we won't share any how?
212-
val requestingDevice =
213-
cryptoStore.getUserDevice(request.requestingUserId, request.requestingDeviceId)
214-
?: return Unit.also {
215-
Timber.tag(loggerTag.value).d("Ignoring key request: ${request.shortDbgString()}")
216-
}
221+
val requestingDevice = cryptoStore.getUserDevice(request.requestingUserId, request.requestingDeviceId)
222+
if (requestingDevice == null || requestingDevice.identityKey() != request.requestingDeviceKey) {
223+
directShareMegolmKey(request)
224+
return
225+
}
217226

218227
cryptoStore.saveIncomingKeyRequestAuditTrail(
219228
request.requestId,
@@ -357,6 +366,8 @@ internal class IncomingKeyRequestManager @Inject constructor(
357366
requestId = request.requestId,
358367
requestingDeviceId = request.deviceId,
359368
requestingUserId = request.userId,
369+
requestingDeviceKey = null,
370+
requestingDeviceOtk = null,
360371
roomId = request.requestBody.roomId,
361372
senderKey = request.requestBody.senderKey,
362373
sessionId = request.requestBody.sessionId,
@@ -446,6 +457,85 @@ internal class IncomingKeyRequestManager @Inject constructor(
446457
}
447458
}
448459

460+
private suspend fun directShareMegolmKey(validRequest: ValidMegolmRequestBody): Boolean {
461+
val requestingDeviceKey = validRequest.requestingDeviceKey ?: return false.also {
462+
Timber.tag(loggerTag.value).e("directShareMegolmKey: no requestingDeviceKey from ${validRequest.requestingUserId} | ${validRequest.requestingDeviceId}")
463+
}
464+
val requestingDeviceInfo = CryptoDeviceInfo(
465+
userId = validRequest.requestingUserId,
466+
deviceId = validRequest.requestingDeviceId,
467+
keys = mapOf("curve25519:${validRequest.requestingDeviceId}" to requestingDeviceKey)
468+
)
469+
val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(validRequest.roomId, validRequest.sessionId, requestingDeviceInfo)
470+
if (wasSessionSharedWithUser.found && wasSessionSharedWithUser.chainIndex == 0) {
471+
Timber.tag(loggerTag.value).e("skip direct share Key for ${validRequest.shortDbgString()}")
472+
return false
473+
}
474+
475+
Timber.tag(loggerTag.value).d("try to direct share Megolm Key for ${validRequest.shortDbgString()}")
476+
if (validRequest.requestingDeviceKey == null || validRequest.requestingDeviceOtk == null) {
477+
Timber.tag(loggerTag.value).w("fail to direct share Megolm Key for ${validRequest.shortDbgString()}: no device keys")
478+
return false
479+
}
480+
481+
val sessionHolder = try {
482+
olmDevice.getInboundGroupSession(validRequest.sessionId, validRequest.senderKey, validRequest.roomId)
483+
} catch (failure: Throwable) {
484+
Timber.tag(loggerTag.value)
485+
.e(failure, "shareKeysWithDevice: failed to get session ${validRequest.requestingUserId}")
486+
// It's unavailable
487+
sendWithheldForRequest(validRequest, WithHeldCode.UNAVAILABLE)
488+
return false
489+
}
490+
491+
val export = sessionHolder.mutex.withLock {
492+
sessionHolder.wrapper.exportKeys(0)
493+
} ?: return false.also {
494+
Timber.tag(loggerTag.value)
495+
.e("shareKeysWithDevice: failed to export group session ${validRequest.sessionId}")
496+
}
497+
498+
val payloadJson = mapOf(
499+
"type" to EventType.FORWARDED_ROOM_KEY,
500+
"content" to export
501+
)
502+
503+
val encryptedPayload = messageEncrypter.directEncryptMessage(
504+
payloadJson, validRequest.requestingUserId, validRequest.requestingDeviceId, validRequest.requestingDeviceKey, validRequest.requestingDeviceOtk)
505+
encryptedPayload.traceId = validRequest.sessionId
506+
507+
return try {
508+
sendSimpleEventTask.execute(SendSimpleEventTask.Params(
509+
roomId = validRequest.roomId,
510+
eventType = EventType.ROOM_KEY_REPLY,
511+
encryptedPayload.toContent()
512+
))
513+
Timber.tag(loggerTag.value)
514+
.i("successfully direct shared session for ${validRequest.shortDbgString()}")
515+
cryptoStore.saveForwardKeyAuditTrail(
516+
validRequest.roomId,
517+
validRequest.sessionId,
518+
validRequest.senderKey,
519+
validRequest.algorithm,
520+
validRequest.requestingUserId,
521+
validRequest.requestingDeviceId,
522+
0,
523+
)
524+
cryptoStore.markedSessionAsShared(
525+
validRequest.roomId,
526+
validRequest.sessionId,
527+
validRequest.requestingUserId,
528+
validRequest.requestingDeviceId,
529+
validRequest.requestingDeviceKey,
530+
0)
531+
true
532+
} catch (failure: Throwable) {
533+
Timber.tag(loggerTag.value)
534+
.e(failure, "fail to direct share session for ${validRequest.shortDbgString()}")
535+
false
536+
}
537+
}
538+
449539
fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
450540
synchronized(gossipingRequestListeners) {
451541
gossipingRequestListeners.add(listener)

sdn-sdk-android/src/main/java/org/sdn/android/sdk/internal/crypto/MXOlmDevice.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,41 @@ internal class MXOlmDevice @Inject constructor(
461461
return payloadString
462462
}
463463

464+
suspend fun olmEncryptMessage(theirDeviceIdentityKey: String, theirOneTimeKey: String, payloadString: String): Map<String, Any>? {
465+
return try {
466+
val olmSession = OlmSession()
467+
store.doWithOlmAccount { olmAccount ->
468+
olmSession.initOutboundSession(olmAccount, theirDeviceIdentityKey, theirOneTimeKey)
469+
}
470+
val olmMessage = olmSession.encryptMessage(payloadString)
471+
mapOf(
472+
"body" to olmMessage.mCipherText,
473+
"type" to olmMessage.mType,
474+
)
475+
} catch (e: Throwable) {
476+
Timber.tag(loggerTag.value).e(e, "## encryptMessage() : failed to encrypt olm with device:$theirDeviceIdentityKey")
477+
null
478+
}
479+
}
480+
481+
suspend fun olmDecryptMessage(ciphertext: String, messageType: Int): String? {
482+
return try {
483+
val olmSession = OlmSession()
484+
store.doWithOlmAccount { olmAccount ->
485+
olmSession.initInboundSession(olmAccount, ciphertext)
486+
}
487+
488+
val olmMessage = OlmMessage()
489+
olmMessage.mCipherText = ciphertext
490+
olmMessage.mType = messageType.toLong()
491+
492+
olmSession.decryptMessage(olmMessage)
493+
} catch (e: Exception) {
494+
Timber.tag(loggerTag.value).e(e, "## createInboundSession() : the session creation failed")
495+
null
496+
}
497+
}
498+
464499
/**
465500
* Determine if an incoming messages is a prekey message matching an existing session.
466501
*

0 commit comments

Comments
 (0)